同事合了代码到开发分支,并没有涉及到改动的类却报错。错误信息如下:
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed;
nested exception is org.springframework.http.converter.HttpMessageConversionException:
Type definition error: [simple type, class com.tongweb.demo.bean.Registration];
nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
Cannot construct instance of `com.tongweb.demo.bean.Registration` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
这个错误的大概意思就是jackson解析Registration类时出现了错误,说Registration类缺少默认构造函数。这个类是Controller中一个方法的入参。示例代码如下:
@PostMapping(path = "/instances", consumes = MediaType.APPLICATION_JSON_VALUE)
public String register(@RequestBody Registration registration) {
Registration withSource = Registration.copyOf(registration).source("http-api").build();
return withSource.toString();
}
相关代码并不涉及到改动,自从我同事代码合过来就开始报错,很不应该。起初我猜测是某些jar冲突引起的。但是查看了他合并过来的代码,也并未涉及到jar依赖的升级变更等。对于这个错误网上是有很多解决方案的,比如加个构造函数。但是不排查到导致问题的原因,这总归是个雷。于是我开启了漫长的排除过程。
首先考虑是jar冲突导致。我重新写了一个新项目,沿用了我目前的所有jar。访问instances接口,jackson解析并不会报错。那只有跟踪源码了。
spring提供了一个Jackson的HTTP消息转换器的配置JacksonHttpMessageConvertersConfiguration
,会创建一个MappingJackson2HttpMessageConverter
。MappingJackson2HttpMessageConverter
会负责解析Registration
类。特别说明Registration
类只有有参构造函数,没有无参构造函数。
HttpMessageConverters
负责管理Spring Boot应用程序中使用的HttpMessageConverters。提供了一种向web应用程序添加和合并其他HttpMessageConverter的方便方法。
如果需要,可以向特定的附加转换器注册此bean的实例,否则将使用默认转换器。说白了就是Spring Boot应用程序中的各个转换就是在HttpMessageConverters中。
按理来说MappingJackson2HttpMessageConverter
也是HttpMessageConverters
中的一员。但是我跟踪源码发现MappingJackson2HttpMessageConverter
并未出现在HttpMessageConverters
中。跟踪源码WebMvcConfigurationSupport
类,源码如下:
protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(new StringHttpMessageConverter());
messageConverters.add(new ResourceHttpMessageConverter());
messageConverters.add(new ResourceRegionHttpMessageConverter());
...
}
这一段关键代码是把HttpMessageConverters
添加到messageConverters。后续解析@RequestBody
修饰的类就会从中选择对应的Converter。当源码进行到这一步的时候。我发现MappingJackson2HttpMessageConverter
并不存在。它的加载顺序发生在了addDefaultHttpMessageConverters
方法执行之后。这就导致messageConverters中并没有MappingJackson2HttpMessageConverter
。所以当@RequestBody修饰的类没有无参构造函数的时候解析就报错了。一个正常的应用程序MappingJackson2HttpMessageConverter
会优先被加载。找到问题的原因就方便多了。
为什么JacksonHttpMessageConvertersConfiguration
的加载顺序会晚于WebMvcConfigurationSupport
类呢?换而言之有哪些因素会影响WebMvcConfigurationSupport
类的加载。答案就是@EnableWebMvc注解
!!
如果在代码中使用了@EnableWebMvc注解
,那么WebMvcConfigurationSupport就会比JacksonHttpMessageConvertersConfiguration优先加载,因为@EnableWebMvc注解会导入DelegatingWebMvcConfiguration类,而这个类继承了WebMvcConfigurationSupport类,并且有@Order注解,指定了加载顺序。
这样的话,Spring Boot的默认MVC配置就会被关闭,你需要自己配置Spring MVC的功能,例如拦截器、视图解析器、消息转换器等。
最后附录一下导致问题发生的代码: