Spring 中主要有四种使用 LocalDateTime 的方式需要格式化,如下:

  1. LocalDateTime 作为 Controller 的参数
  2. LocalDateTime 是某实体类的字段,实体类作为 Controller 的参数
  3. LocalDateTime 作为 Controller 的返回值
  4. LocalDateTime 是某实体类的字段,实体类作为 Controller 的返回值
@RestController
public class TestController {
    @GetMapping("/t1")
    public LocalDateTime t1(LocalDateTime time) {
        return time;
    @PostMapping("/t2")
    public Obj t2(@RequestBody Obj o) {
        return o;
@Data
public class Obj {
    private LocalDateTime time;

LocalDateTime 直接作为 Controller 的参数

若直接请求 http://127.0.0.1:8080/t1?time=2021-12-06 21:15:24会抛出异常 MethodArgumentTypeMismatchException,最内层的异常是 DateTimeParseException: Text '2021-12-06 21:15:24' could not be parsed at index 2

org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.time.LocalDateTime'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.time.LocalDateTime] for value '2021-12-06 21:15:24'; nested exception is java.lang.IllegalArgumentException: Parse attempt failed for value [2021-12-06 21:15:24]
......
Caused by: java.time.format.DateTimeParseException: Text '2021-12-06 21:15:24' could not be parsed at index 2
	at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1949) ~[na:1.8.0_311]
	at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851) ~[na:1.8.0_311]
	at java.time.LocalDateTime.parse(LocalDateTime.java:492) ~[na:1.8.0_311]
	at org.springframework.format.datetime.standard.TemporalAccessorParser.doParse(TemporalAccessorParser.java:120) ~[spring-context-5.3.13.jar:5.3.13]
	at org.springframework.format.datetime.standard.TemporalAccessorParser.parse(TemporalAccessorParser.java:85) ~[spring-context-5.3.13.jar:5.3.13]
	at org.springframework.format.datetime.standard.TemporalAccessorParser.parse(TemporalAccessorParser.java:50) ~[spring-context-5.3.13.jar:5.3.13]
	at org.springframework.format.support.FormattingConversionService$ParserConverter.convert(FormattingConversionService.java:217) ~[spring-context-5.3.13.jar:5.3.13]
	... 54 common frames omitted

解决方案1:添加 @DateTimeFormat 注解

使用 spring 的注解 org.springframework.format.annotation.DateTimeFormat,这种方式是局部的,不会影响整个项目

@GetMapping("/t1")
public LocalDateTime t1(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime time) {
    return time;

解决方案2:自定义 Converter

自定义 Converter 是全局设置,会覆盖 @DateTimeFormat

@Configuration(proxyBeanMethods = false)
public class ConverterConfig {
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    @Bean
    public Converter<String, LocalDateTime> localDateTimeConverter() {
        // 使用 lambda 启动会报错
        return new Converter<String, LocalDateTime>() {
            @Override
            public LocalDateTime convert(String source) {
                return LocalDateTime.parse(source, FORMATTER);

解决方案3:@InitBinder 自定义参数绑定

此方式可使用 @RestControllerAdvice 进行全局设置,也可以只在需要的 Controller 中进行局部设置

// 全局设置
@RestControllerAdvice
public class ExceptionProcessor {
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    @InitBinder
    public void a(WebDataBinder binder) {
        binder.registerCustomEditor(LocalDateTime.class, new PropertyEditorSupport() {
            @Override
            public void setAsText(String text) throws IllegalArgumentException {
                setValue(LocalDateTime.parse(text, FORMATTER));
        });

LocalDateTime 是实体类的字段,实体类作为 Controller 的参数

SpringBoot 默认使用 Jackson 进行 json 的序列化和反序列化的。但是 Jackson 默认是不支持 JDK8 新增的日期、时间类型的,会抛出异常如下,且在异常中已经给出了解决办法:引入新的模块 com.fasterxml.jackson.datatype:jackson-datatype-jsr310

本文会假设读者已经熟悉 Jackson,不会介绍其的使用方式,有兴趣的同学可以看一下我的 CNDS 专栏 Jackson

Jackson Java 8 模块:

  • jackson-module-parameter-names::添加了对使用 JDK8 新特性的支持,能够访问构造函数和方法参数的名称,以允许省略 @JsonProperty
  • jackson-datatype-jsr310:用于支持 JDK8 新增的日期、时间类型
  • jackson-datatype-jdk8: 支持日期/时间之外的其他新 Java 8 数据类型,比如 Optional
public static void main(String[] args) throws JsonProcessingException {
    String json = "{\"time\": \"2021-12-06T21:15:24.237\"}";
    ObjectMapper mapper = new ObjectMapper();
    Obj obj = mapper.readValue(json, Obj.class);
    System.out.println(obj);
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.LocalDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling
 at [Source: (String)"{"time": "2021-12-06T21:15:24.237"}"; line: 1, column: 10] (through reference chain: com.example.springxx.Obj["time"])
	at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
	at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1764)
	at com.fasterxml.jackson.databind.deser.impl.UnsupportedTypeDeserializer.deserialize(UnsupportedTypeDeserializer.java:36)
	at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:129)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:324)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:187)
	at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:322)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4593)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3548)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3516)
	at com.example.springxx.Obj.main(Obj.java:25)

引入 jackson-datatype-jsr310

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
public static void main(String[] args) throws JsonProcessingException {
    String json = "{\"time\": \"2021-12-06T21:15:24.237\"}";
    ObjectMapper mapper = JsonMapper.builder()
        // 新模块
        .addModule(new JavaTimeModule())
        .build();
    Obj obj = mapper.readValue(json, Obj.class);
    System.out.println(obj);
// 输出
// Obj(time=2021-12-06T21:15:24.237)

Spring 测试

// 调用 http://127.0.0.1:8080/t2
// 参数
    "time": "2021-12-06T21:15:24.237"
// 返回值
    "time": "2021-12-06T21:15:24.237"
// 参数 yyyy-MM-dd HH:mm:ss
    "time": "2021-12-06 21:15:24"
// 返回值
// 抛异常

Spring 可以处理默认格式的 LocalDateTime,是因为在 JacksonAutoConfiguration 中默认引入了 jackson-datatype-jsr310

JacksonAutoConfiguration 部分源码

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
static class JacksonObjectMapperBuilderConfiguration {
	// 注册了一个Bean Jackson2ObjectMapperBuilder,用于创建 ObjectMapper
    @Bean
    @Scope("prototype")
    @ConditionalOnMissingBean
    Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder(ApplicationContext applicationContext,
                                                           List<Jackson2ObjectMapperBuilderCustomizer> customizers) {
        // 实例化 Jackson2ObjectMapperBuilder
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
        builder.applicationContext(applicationContext);
        // 对 Jackson2ObjectMapperBuilder 定制化,即可对 ObjectMapper 定制化
        // 我们也可以通过 Jackson2ObjectMapperBuilderCustomizer 的功能实现 LocalDateTime 格式化的全局配置
        customize(builder, customizers);
        return builder;
    private void customize(Jackson2ObjectMapperBuilder builder,
                           List<Jackson2ObjectMapperBuilderCustomizer> customizers) {
        for (Jackson2ObjectMapperBuilderCustomizer customizer : customizers) {
            customizer.customize(builder);
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
static class JacksonObjectMapperConfiguration {
	// 使用 Jackson2ObjectMapperBuilder 创建 ObjectMapper 并注册 Bean
    @Bean
    @Primary
    @ConditionalOnMissingBean
    ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
        return builder.createXmlMapper(false).build();

Jackson2ObjectMapperBuilder#configure

configure 方法会调用 registerWellKnownModulesIfAvailable 查找可用的 Module,包括 com.fasterxml.jackson.datatype.jsr310.JavaTimeModule,然后注册所有的 Module。

public void configure(ObjectMapper objectMapper) {
    Assert.notNull(objectMapper, "ObjectMapper must not be null");
    MultiValueMap<Object, Module> modulesToRegister = new LinkedMultiValueMap<>();
    if (this.findModulesViaServiceLoader) {
        ObjectMapper.findModules(this.moduleClassLoader).forEach(module -> registerModule(module, modulesToRegister));
    else if (this.findWellKnownModules) {
        // 查找 Jdk8Module、JavaTimeModule 模块
        registerWellKnownModulesIfAvailable(modulesToRegister);
	...
    List<Module> modules = new ArrayList<>();
    for (List<Module> nestedModules : modulesToRegister.values()) {
        modules.addAll(nestedModules);
    // 注册 Module
    objectMapper.registerModules(modules);
private void registerWellKnownModulesIfAvailable(MultiValueMap<Object, Module> modulesToRegister) {
    try {
        // com.fasterxml.jackson.datatype.jdk8.Jdk8Module
        Class<? extends Module> jdk8ModuleClass = (Class<? extends Module>)
            ClassUtils.forName("com.fasterxml.jackson.datatype.jdk8.Jdk8Module", this.moduleClassLoader);
        Module jdk8Module = BeanUtils.instantiateClass(jdk8ModuleClass);
        modulesToRegister.set(jdk8Module.getTypeId(), jdk8Module);
    catch (ClassNotFoundException ex) {
        // jackson-datatype-jdk8 not available
    try {
        // 日期、时间模块 com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
        Class<? extends Module> javaTimeModuleClass = (Class<? extends Module>)
            ClassUtils.forName("com.fasterxml.jackson.datatype.jsr310.JavaTimeModule", this.moduleClassLoader);
        Module javaTimeModule = BeanUtils.instantiateClass(javaTimeModuleClass);
        modulesToRegister.set(javaTimeModule.getTypeId(), javaTimeModule);
    catch (ClassNotFoundException ex) {
        // jackson-datatype-jsr310 not available
    ...

知道了 Spring 与 Jackson 的原理,问题就好解决了。

解决方案1:添加 Jackson 的 @JsonFormat 注解

这是个局部配置

@Data
public class Obj {
	@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime time;
// 参数
    "time": "2021-12-06 21:15:24"
// 返回值
    "time": "2021-12-06 21:15:24"

可以看到返回值也被格式化成了 yyyy-MM-dd HH:mm:ss

解决方案2:@JsonSerialize、@JsonDeserialize 自定义序列化器和反序列化器

@JsonSerialize、@JsonDeserialize 可分别指定序列化、反序列化时的格式:

public class MyLocalDateTimeSerializer extends com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer {
    public MyLocalDateTimeSerializer() {
        super(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
public class MyLocalDateTimeDeserializer extends com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer {
    public MyLocalDateTimeDeserializer() {
        super(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
@Data
public class Obj {
    @JsonSerialize(using = MyLocalDateTimeSerializer.class)
    @JsonDeserialize(using = MyLocalDateTimeDeserializer.class)
    private LocalDateTime time;
// 参数
    "time": "2021-12-06 21:15:24"
// 返回值
    "time": "2021-12-06 21:15:24"

解决方案3:使用 Jackson2ObjectMapperBuilderCustomizer 定制化功能

使用 Jackson2ObjectMapperBuilderCustomizer定制化 Jackson2ObjectMapperBuilder,进而定制化 ObjectMapper

@Configuration
public class LocalDateTimeCustomizer implements Jackson2ObjectMapperBuilderCustomizer {
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    @Override
    public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
        // 反序列化(参数)
        jacksonObjectMapperBuilder.deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(FORMATTER));
        // 序列化(返回值)
        //jacksonObjectMapperBuilder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(FORMATTER));
// 参数
    "time": "2021-12-06 21:15:24"
// 返回值:注释掉 serializerByType
    "time": "2021-12-06T21:15:24"
// 返回值:不注释 serializerByType
    "time": "2021-12-06 21:15:24"

LocalDateTime 作为返回值

不管是 LocalDateTime 直接作为返回值,还是实体类作为返回值,都可以用 Jackson2ObjectMapperBuilderCustomizer 解决。

Spring 中主要有两种使用 LocalDateTime 的方式需要格式化,如下:LocalDateTime 作为 Controller 的参数LocalDateTime 是某实体类的字段,实体类作为 Controller 的参数LocalDateTime 作为 Controller 的返回值LocalDateTime 是某实体类的字段,实体类作为 Controller 的返回值@RestControllerpublic class TestController { @GetMap
首先第一点需要知道的是springboot默认依赖的json框架是jackson。当使用@ResponseBody注解返回json格式数据时就是该框架在起作用。 如果字段属性是Date而非LocalDateTime时,通常我们会在application.properties里面配置如下: spring.mvc.date-format=yyyy-MM-dd HH:mm:ss spring.jackson.date-format=yyyy-MM-dd HH:mm:ss spring.jackson.time-
在项目日期格式化是最常见的问题,之前涉及的 java.util.Date 和 java.util.Calendar 类易用性差,不支持时区,非线程安全,对日期的计算方式繁琐,而且容易出错,因为月份是从0开始的,从 Calendar 获取的月份需要加一才能表示当前月份。 在 JDK8 ,一个新的重要特性就是引入了全新的时间和日期API,它被收录在 java.time 包,借助新的时间和日期API可以以更简洁的方法处理时间和日期。
在项目日期格式化是最常见的问题,之前涉及的java.util.Date 和 java.util.Calendar 类易用性差,不支持时区,非线程安全,对日期的计算方式繁琐,而且容易出错,因为月份是从0开始的,从 Calendar 获取的月份需要加一才能表示当前月份。 在 JDK8 ,一个新的重要特性就是引入了全新的时间和日期API,它被收录在 java.time 包,借助新的时间和日期API可以以更简洁的方法处理时间和日期。 (1)@JsonFormat和@Date...
1. 说明 对于@RequestBody注解,Springboot默认使用Jackson实现json字符串和java对象之间的转换(序列化和反序列化);也可进行修改使用阿里的fastjson(本文不介绍),Jackson不支持LocalDateTime与格式yyyy-MM-dd HH:mm:ss之间的转换,需要自定义转换。 对于@RequestParam注解,需要自定义转换器。 2. 格式化...
public class LocalDateTimeSerializerConfig { @Value("${spring.jackson.date-format:yyyy-MM-dd HH:mm:ss}") private String pattern;
默认情况下,springboot 返回的 Date和LocalDateTime 格式是这样的 2021-11-05T11:17:09也就是带有T的,但是我们很多时候不想要这个T 这种情况网上有一种解决方法说的是配置JsonFormat @JsonFormat( pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8") private Date createdDate; @JsonFormat( pattern = "yyyy-MM-dd
用Mybatis Plus生成的entity是LocalDateTime,Spring MVC的接口返回直接返回了生成的Entity, 结果序列化为json的时候出问题了。 一般处理是有两种方式,在entity的property上加上JsonFormat,如 @JsonFormat(pattern = "yyyy-MM-dd") private LocalDateTime createTime;...
好的,非常感谢您的提问。关于您的问题,我可以回答。在SpringBoot,我们可以通过注解和切面的方式来使用AOP。具体实现方法可以参考以下步骤: 1. 在SpringBoot主应用程序类上添加@EnableAspectJAutoProxy注解,启用AspectJ自动代理。 2. 创建一个切面类并添加@Aspect注解,标记这个类为一个切面类。 3. 在切面类定义一个或多个切点,即程序需要拦截的方法或位置。 4. 定义一个或多个通知,即在方法执行前、后或发生异常时需要执行的操作。 5. 将切面类注册到Spring容器,这样Spring就会将其应用到符合切点的方法上。 通过以上步骤,我们就可以在SpringBoot成功使用AOP了。希望这个回答能够帮助到您。
-- before serialization -- Employee(name=Amy, dept=Department(deptName=Admin, location=NY, leader=Leader(leaderName=Tom))) -- after serialization -- {"name":"Amy","deptName":"Admin","location":"NY","leaderName":"Tom"} -- after deserialization -- Employee(name=Amy, dept=Department(deptName=Admin, location=NY, leader=Leader(leaderName=Tom)))