版本说明:
- 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
是通过Jackson2ObjectMapperBuilder
的build
方法来生成,部分代码如下,重点在于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