Rest表现(@ResponseBody)
在web层(其实为Spring MVC
),对于Rest接口,默认会使用Jackson
进行消息的序列化。那么它在不同版本的表现也会存在差异:
公用代码:
@RestController @RequestMapping("/demo") public class DemoController { @GetMapping("/get") public Object get() { Map<String, Object> map = new LinkedHashMap<>(); map.put("date", new Date()); map.put("timestamp", new Timestamp(System.currentTimeMillis())); map.put("localDateTime", LocalDateTime.now()); map.put("localDate", LocalDate.now()); map.put("localTime", LocalTime.now()); map.put("instant", Instant.now()); return map; } }
1.x版本:
{ "date":1580897613003, "timestamp":1580897613003, "localDateTime":{ "dayOfMonth":5, "dayOfWeek":"WEDNESDAY", "month":"FEBRUARY", "year":2020, "hour":18, "minute":13, "nano":9000000, "second":33, "dayOfYear":36, "monthValue":2, "chronology":{ "id":"ISO", "calendarType":"iso8601" } }, "localDate":{ "year":2020, "month":"FEBRUARY", "dayOfMonth":5, "dayOfWeek":"WEDNESDAY", "era":"CE", "chronology":{ "id":"ISO", "calendarType":"iso8601" }, "dayOfYear":36, "leapYear":true, "monthValue":2 }, "localTime":{ "hour":18, "minute":13, "second":33, "nano":9000000 }, "instant":{ "epochSecond":1580897613, "nano":9000000 } }
2.x版本:
{ "date":"2020-02-02T13:26:07.116+0000", "timestamp":"2020-02-02T13:26:07.116+0000", "localDateTime":"2020-02-02T21:26:07.12", "localDate":"2020-02-02", "localTime":"21:26:07.12", "instant":"2020-02-02T13:26:07.120Z" }
小总结
Rest表现处理啊的差异,完全同容器内的ObjectMapper的差异。
根据前面掌握的知识:Spring MVC消息转换器使用的ObjectMapper实例是自己新构建的,和容器内的无关,但为何Spring Boot里的表现是如此呢?详细缘由,接下来会做出解答。
Spring Boot消息转换器配置与Jackson
从现象上看,Spring Boot使用的ObjectMapper是从容器中拿的,而传统Spring MVC使用的是自己新构建的。此处存在差异,需要一探究竟。同样的逆推法,一切还是从MappingJackson2HttpMessageConverter出发,Spring Boot使用了一个JacksonHttpMessageConvertersConfiguration配置类来配置Jackson的消息转换器。
JacksonHttpMessageConvertersConfiguration
Configuration for HTTP message converters that use Jackson.
@Configuration class JacksonHttpMessageConvertersConfiguration { // 目的:向容器内扔一个MappingJackson2HttpMessageConverter实例,但是有很多约束条件 @Configuration @ConditionalOnClass(ObjectMapper.class) @ConditionalOnBean(ObjectMapper.class) @ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY, havingValue = "jackson", matchIfMissing = true) protected static class MappingJackson2HttpMessageConverterConfiguration { @Bean @ConditionalOnMissingBean(value = MappingJackson2HttpMessageConverter.class, ignoredType = { "org.springframework.hateoas.mvc.TypeConstrainedMappingJackson2HttpMessageConverter", "org.springframework.data.rest.webmvc.alps.AlpsJsonHttpMessageConverter" }) public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) { return new MappingJackson2HttpMessageConverter(objectMapper); } } ... // 支持xml,略 }
该配置的目的是向Spring容器内放置一个Jackson消息转换器实例,不过它有很多前提条件:
- 导入了Jackson核心包,并且容器内存在ObjectMapper这个Bean
- spring.http.converters.preferred-json-mapper这个key对应的值不能是false(缺少此key默认也是true)
- 你自己木有定义MappingJackson2HttpMessageConverter这个Bean,这个内置的会生效
这些条件在Spring Boot下只要导入了Jackson核心包就自然而然的成立了。从源码处很清楚了:MappingJackson2HttpMessageConverter它使用的是Spring容器内的ObjectMapper完成的构建。
那么JacksonHttpMessageConvertersConfiguration此配置类如何被最终使用的呢?这个很关键,因此这里大体倒退一下,列出如下:
@Configuration @ConditionalOnClass(HttpMessageConverter.class) @AutoConfigureAfter({ GsonAutoConfiguration.class, JacksonAutoConfiguration.class }) @Import({ JacksonHttpMessageConvertersConfiguration.class, GsonHttpMessageConvertersConfiguration.class }) public class HttpMessageConvertersAutoConfiguration { // 把容器内所有的消息转换器注入、管理起来 private final List<HttpMessageConverter<?>> converters; public HttpMessageConvertersAutoConfiguration(ObjectProvider<List<HttpMessageConverter<?>>> convertersProvider) { this.converters = convertersProvider.getIfAvailable(); } // 向容器内扔一个`HttpMessageConverters`实例,管理所有的HttpMessageConverter // HttpMessageConverters它实现了接口:Iterable // 本处:converters的值有两个(size为2):`MappingJackson2HttpMessageConverter`和`StringHttpMessageConverter` @Bean @ConditionalOnMissingBean public HttpMessageConverters messageConverters() { return new HttpMessageConverters((this.converters != null) ? this.converters : Collections.<HttpMessageConverter<?>>emptyList()); } ... // 略。 -> 向容器内定义一个StringHttpMessageConverter,用于处理字符串消息 }
由以上源码可知:
- EnableAutoConfiguration驱动HttpMessageConvertersAutoConfiguration生效,它通过@Import让JacksonHttpMessageConvertersConfiguration配置生效。
- 默认情况下容器内通过@Bean方式配置了两个消息转换器:MappingJackson2HttpMessageConverter和StringHttpMessageConverter,最后都封装进HttpMessageConverters实例里,此实例也放进了容器。
所以,其它组件若要使用消息转换器,只需要“引入”HttpMessageConverters这个Bean来使用即可。有两个地方使用到了它:WebMvcAutoConfiguration和WebClientAutoConfiguration,分别对应Servlet和Reactive模式。