springboot中LocalDate请求参数处理

  • Post author:
  • Post category:其他

版本说明:

  • springboot:2.6.7
  • jdk8

在项目中,有几个地方使用到了LocalDate作为请求参数,有的是作为URL参数进行传递,也有的作为请求体中的一个属性进行传递,即下面两种情况

@GetMapping("/xxx")
public List<String> listByBeginDate(@RequestParam("beginDate") LocalDate beginDate) {
    // ...
}
@PostMapping("/xxx")
public User save(@ResponseBody User user) {
    // ...
}

@Data
static class User {
    private String name;
    private LocalDate birthday;
    // ...
}

在后台接收参数的时候,需要分别对二者进行处理,否则无法正常接收到参数。

1、LocalDate作为URL参数

添加如下的配置类即可,一般情况下,都是通过实现WebMvcConfigurer接口来处理跨域、静态资源映射或者添加拦截器等。而要处理LocalDate请求参数,最简单的做法就是添加类型转换器。

import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.FormatterRegistry;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(localDateConvert());
    }

    public Converter<String, LocalDate> localDateConvert() {
        // 不能替换为lambda表达式
        return new Converter<String, LocalDate>() {
            @Override
            public LocalDate convert(String source) {
                if (StringUtils.hasText(source)) {
                    return LocalDate.parse(source, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
                }
                return null;
            }
        };
    }

}

2、LocalDate作为请求体中的一个属性

一般情况下是不会有问题的,如果真的出现了如下的错误,那多半是手动修改了MappingJackson2HttpMessageConverter中的ObjectMapper

org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class java.time.LocalDate]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.LocalDate` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling

实际上,项目中是有jackson-datatype-jsr310的,因为项目中依赖了spring-boot-starter-web,而其中又依赖了spring-boot-starter-json,这里面是有jackson-datatype-jsr310的,所以不需要额外添加依赖。

MappingJackson2HttpMessageConverter是springboot中的一个消息转换器,其部分Bean定义代码如下

// JacksonHttpMessageConvertersConfiguration.java
// ...
@Bean
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
    return new MappingJackson2HttpMessageConverter(objectMapper);
}

ObjectMapper的定义如下

// JacksonAutoConfiguration.java
// ...
@Bean
@Primary
@ConditionalOnMissingBean
ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
    return builder.createXmlMapper(false).build();
}

通过上面的简要分析,可以知道,ObjectMapper是通过Jackson2ObjectMapperBuilderbuild方法来生成,部分代码如下,重点在于configure方法

public <T extends ObjectMapper> T build() {
    // ...
    configure(mapper);
    return (T) mapper;
}

public void configure(ObjectMapper objectMapper) {
    Assert.notNull(objectMapper, "ObjectMapper must not be null");

    MultiValueMap<Object, Module> modulesToRegister = new LinkedMultiValueMap<>();
    // 是否让 Jackson 通过 JDK ServiceLoader 查找可用模块,默认false
    if (this.findModulesViaServiceLoader) {
        ObjectMapper.findModules(this.moduleClassLoader).forEach(module -> registerModule(module, modulesToRegister));
    }
    // 注册知名模块,默认为true,其中包括 Jdk8Module、JavaTimeModule
    else if (this.findWellKnownModules) {
        registerWellKnownModulesIfAvailable(modulesToRegister);
    }

    if (this.modules != null) {
        this.modules.forEach(module -> registerModule(module, modulesToRegister));
    }
    if (this.moduleClasses != null) {
        for (Class<? extends Module> moduleClass : this.moduleClasses) {
            registerModule(BeanUtils.instantiateClass(moduleClass), modulesToRegister);
        }
    }
    List<Module> modules = new ArrayList<>();
    for (List<Module> nestedModules : modulesToRegister.values()) {
        modules.addAll(nestedModules);
    }
    objectMapper.registerModules(modules);
    // ......
}

所以,一般出现了请求体中的LocalDate报错,那多半是手动覆盖了ObjectMapper的Bean定义。

但有些时候,我们会自定义Jaskson工具类,这个时候,加一行代码即可解决,这个与Jackson2ObjectMapperBuilder中的findModulesViaServiceLoader = true基本上是一致的

objectMapper.findAndRegisterModules();

参考:https://github.com/spring-projects/spring-boot/issues/26859


版权声明:本文为Remember_Z原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。