【问题标题】:Java 8 date time types serialized as object with Spring Boot使用 Spring Boot 序列化为对象的 Java 8 日期时间类型
【发布时间】:2017-11-05 10:11:29
【问题描述】:

我有一个包含 Java 8 日期时间类型字段的实体。问题是这些字段被序列化为对象。我添加了 jackson-datatype-jsr310 依赖项,因此 Spring Boot 1.5.7 将自动配置处理 Java 8 日期时间类型的 JavaTimeModule。似乎该模块未注册(我在 JavaTimeModule 构造函数中放置了一个断点)。我知道我不需要自定义 ObjectMapper。我花了几个小时阅读该问题,解决方案始终是添加 jackson-datatype-jsr310 依赖项,但在我的情况下它不起作用。

实体:

@Entity
public class DateTimeEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private LocalDate localDate;

    private LocalDateTime localDateTime;

    private Instant instant;

    private OffsetDateTime offsetDateTime;

    private ZonedDateTime zonedDateTime;

}

RestController 方法:

@GetMapping("/datetimes/{id}")
public ResponseEntity<DateTimeEntity> getById(@PathVariable Long id) {
    DateTimeEntity dateTimeEntity = dateTimeRepository.findOne(id);
    return new ResponseEntity<DateTimeEntity>(dateTimeEntity, HttpStatus.OK);

}

返回的 JSON 对象:

    {
    "id": 1,
    "localDate": null,
    "localDateTime": null,
    "instant": {
        "epochSecond": 1508772600,
        "nano": 0
    },
    "offsetDateTime": {
        "offset": {
            "totalSeconds": 0,
            "id": "Z",
            "rules": {
                "fixedOffset": true,
                "transitionRules": [],
                "transitions": []
            }
        },
        "dayOfMonth": 23,
        "dayOfWeek": "MONDAY",
        "dayOfYear": 296,
        "month": "OCTOBER",
        "monthValue": 10,
        "year": 2017,
        "hour": 15,
        "minute": 30,
        "nano": 0,
        "second": 0
    },
    "zonedDateTime": {
        "offset": {
            "totalSeconds": 0,
            "id": "Z",
            "rules": {
                "fixedOffset": true,
                "transitionRules": [],
                "transitions": []
            }
        },
        "zone": {
            "totalSeconds": 0,
            "id": "Z",
            "rules": {
                "fixedOffset": true,
                "transitionRules": [],
                "transitions": []
            }
        },
        "dayOfMonth": 23,
        "dayOfWeek": "MONDAY",
        "dayOfYear": 296,
        "month": "OCTOBER",
        "monthValue": 10,
        "year": 2017,
        "hour": 15,
        "minute": 30,
        "nano": 0,
        "second": 0,
        "chronology": {
            "id": "ISO",
            "calendarType": "iso8601"
        }
    }
}

POM 文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>framework-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.7.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
        <mockito.version>2.11.0</mockito.version>
        <org.mapstruct.version>1.2.0.Final</org.mapstruct.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-jdk8</artifactId>
            <version>${org.mapstruct.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>${jackson.version}</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.5.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>${org.mapstruct.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.asciidoctor</groupId>
                <artifactId>asciidoctor-maven-plugin</artifactId>
                <version>1.5.5</version>
                <executions>
                    <execution>
                        <id>output-html</id>
                        <phase>generate-resources</phase>
                        <goals>
                            <goal>process-asciidoc</goal>
                        </goals>
                        <configuration>
                            <backend>html</backend>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

【问题讨论】:

标签: spring spring-boot


【解决方案1】:

我在将 spring boot 从 2.3.7 升级到 2.5.1 时开始遇到这个问题。

如果您定义了 ObjectMapper @Bean,那么您需要向它注册时间模块。

@Bean
public ObjectMapper defaultMapper() {
    ObjectMapper objectMapper = new ObjectMapper(); 
    objectMapper.registerModule(new JavaTimeModule()); 
    return objectMapper;
}

很多时候,编码人员在使用 jackson 序列化时只会创建一个“new ObjectMapper()”,因此请注意使用 vanilla 映射器,而不是自动装配一个已注册时间模块的预配置默认值。

如前所述,您将需要 jackson-datatype-jsr310,但它作为托管版本包含在 Spring Boot 中。

如果你不手动定义一个对象映射器bean,那么spring boot应该会自动提供一个注册的时间模块。

【讨论】:

  • 我通过在 localdatetime 字段之前添加 @JsonSerialize(using = LocalDateTimeSerializer.class) 解决了这个问题。我认为这是最新版本的jsr310中的一个错误
【解决方案2】:

根据 How to customize ObjectMapper

com.fasterxml.jackson.databind.Module 类型的任何 bean 都将是 使用自动配置自动注册 Jackson2ObjectMapperBuilder 并应用于任何 ObjectMapper 实例 它创造的。这提供了一个全球性的贡献机制 向应用程序添加新功能时的自定义模块。

仅仅添加依赖是不够的,你必须声明一个@Bean你的模块,如下所示:

@Bean
public Module dateTimeModule(){
    return new JavaTimeModule();
}

加上jackson-datatype-jsr310 模块已弃用,您应该改用JavaTimeModule

【讨论】:

  • 你不需要声明一个bean,因为spring boot auto configure会自动注册它,如果它在我的问题的类路径中找到。 jar 不在 IDE 类路径中
  • Wrell 感谢分享,我有一个类似的问题,我通过声明 Beans 解决了它,也可能是由于类路径。很高兴知道!
【解决方案3】:

解决方案是将依赖项添加到类路径。由于某种原因,它不在 IDE 中。

即使不推荐使用依赖项,spring-boot-autoconfigure 模块仍然使用它。见Spring Boot Code

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>

当它在类路径中时,Java 8 日期和时间对象被序列化为时间戳。

【讨论】:

    【解决方案4】:

    如果您目前正在使用 String Boot 2.5.4 + Apache CXF (Java config) 进行 JAX-RS,下面的配置为我解决了问题

    dependencies{
        constraints{
                implementation("org.apache.cxf:cxf-spring-boot-starter-jaxrs:3.4.4")
                implementation("com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:2.12.5")
                implementation("com.fasterxml.jackson.module:jackson-modules-java8:2.12.5")
        }
    }
    
    
    dependencies{
        implementation("org.apache.cxf:cxf-spring-boot-starter-jaxrs")
        implementation("com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider")
        implementation("com.fasterxml.jackson.module:jackson-modules-java8")
    }
    

    Spring Boot (org.springframework.http.converter.json.Jackson2ObjectMapperBuilder),如果在类路径中检测到以下知名模块,则会自动注册它们:

    • jackson-datatype-jdk8 : 支持其他 Java 8 类型,例如 java.util.Optional
    • jackson-datatype-jsr310:支持 Java 8 日期和时间 API 类型
    • jackson-datatype-joda:支持 Joda-Time 类型
    • jackson-module-kotlin : 支持 Kotlin 类和数据类 如果需要的类在类路径上,则会自动配置

    但要使用 CXF,我们似乎需要使用正确的序列化提供程序对其进行初始化。如果我们没有将 Spring Boot 配置的 ObjectMapper 传递给 JacksonJsonProvider,我们将捕获如下错误:

    com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type 'java.time.Instant' not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling
    

    要修复它,我们必须使用 Spring 配置的 ObjectMapper 来初始化 JacksonJsonProvider,如下面的代码

        @Autowired
        DataService dataService;
        @Autowired
        EventService eventService;
        @Autowired
        SessionService sessionService;
        @Autowired
        StatusService statusService;
    
        @Autowired
        private Bus bus;
        @Autowired
        private ObjectMapper objectMapper;
    
        @Bean
        public JacksonJsonProvider jsonProvider() {
            JacksonJsonProvider provider = new JacksonJsonProvider();
            provider.setMapper(objectMapper);
            return provider;
        }
    
    
        @Bean
        public Server rsServer() {
            JAXRSServerFactoryBean server = new JAXRSServerFactoryBean();
            server.setProviders(Stream.of(jsonProvider()).collect(Collectors.toList()));
            server.setBus(bus);
            server.setServiceBeans(Stream
                    .of(dataService, eventService, sessionService, statusService)
                    .collect(Collectors.toList()));
            return server.create();
        }
    

    【讨论】:

      【解决方案5】:

      Spring Data Couchbase 在 AbstractCouchbaseConfiguration 类中引入了 @Bean 工厂方法,该方法生成 ObjectMapper bean,因此 Spring 不会创建自己的正确的 bean(自动包含 java-time 模块的东西)。

      这是有问题的源文件: https://github.com/spring-projects/spring-data-couchbase/blame/4.2.x/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java#L309

      错误报告在这里: https://github.com/spring-projects/spring-data-couchbase/issues/1209

      这已在 Spring-Data-Couchbase 4.3 中修复

      【讨论】:

        猜你喜欢
        • 2017-07-29
        • 2022-12-07
        • 1970-01-01
        • 2021-08-24
        • 2019-06-25
        • 2019-12-08
        • 1970-01-01
        • 2018-08-05
        • 1970-01-01
        相关资源
        最近更新 更多