为啥 Spring Boot 不在休息控制器上使用 @Primary Jackson ObjectMapper 进行 JSON 序列化?
Posted
技术标签:
【中文标题】为啥 Spring Boot 不在休息控制器上使用 @Primary Jackson ObjectMapper 进行 JSON 序列化?【英文标题】:Why is Spring Boot not using a @Primary Jackson ObjectMapper for JSON serialization on a rest controller?为什么 Spring Boot 不在休息控制器上使用 @Primary Jackson ObjectMapper 进行 JSON 序列化? 【发布时间】:2018-08-29 15:55:56 【问题描述】:我设置了一个类来返回自定义的 ObjectMapper。据我所知,让 Spring Boot 使用此 ObjectMapper 的正确方法是将其声明为 @Primary,它就是。
@Configuration
public class MyJacksonConfiguration
@Bean
@Primary
public ObjectMapper objectMapper()
return Jackson2ObjectMapperBuilder
.json()
.findModulesViaServiceLoader(true)
.mixIn(Throwable.class, ThrowableMixin.class)
.featuresToDisable(
WRITE_DATES_AS_TIMESTAMPS)
.serializationInclusion(
Include.NON_ABSENT)
.build();
但是,当我从控制器方法返回对象时,它会使用默认的 Jackson ObjectMapper 配置进行序列化。
如果我将一个显式 ObjectMapper 添加到我的控制器并在其上调用 writeValueAsString,我可以看到这个 ObjectMapper 是我希望 Spring Boot 使用的自定义对象。
@RestController
public class TestController
@Autowired
private TestService service;
@Autowired
private ObjectMapper mapper;
@GetMapping(value = "/test", produces = "application/json")
public TestResult getResult()
final TestResult ret = service.getResult();
String test = "";
try
test = mapper.writeValueAsString(ret);
// test now contains the value I'd like returned by the controller!
catch (final JsonProcessingException e)
e.printStackTrace();
return ret;
当我在控制器上运行测试时,测试类也使用自动装配的 ObjectMapper。同样,提供给测试的 ObjectMapper 是自定义的。
所以 Spring 在一定程度上知道自定义的 ObjectMapper,但我的 rest 控制器类没有使用它。
我已尝试为 Spring 启用调试日志记录,但在日志中看不到任何有用的信息。
知道可能会发生什么,或者我应该在哪里寻找问题?
编辑:似乎有多种方法可以做到这一点,但是我尝试这样做的方式似乎是一种推荐的方法,我想让它以这种方式工作 -请参阅https://docs.spring.io/spring-boot/docs/1.4.7.RELEASE/reference/html/howto-spring-mvc.html#howto-customize-the-jackson-objectmapper 中的 71.3 - 我是否误解了那里的内容?
【问题讨论】:
【参考方案1】:不会破坏字符串序列化的正确方法(保持顺序):
public void configureMessageConverters(List<HttpMessageConverter<?>> converters)
addDefaultHttpMessageConverters(converters);
HttpMessageConverter<?> jacksonConverter =
converters.stream()
.filter(converter -> converter.getClass().equals(MappingJackson2HttpMessageConverter.class))
.findFirst().orElseThrow(RuntimeException::new);
converters.add(converters.indexOf(jacksonConverter), new MappingJackson2HttpMessageConverter(objectMapper));
【讨论】:
【参考方案2】:虽然其他答案显示了实现相同结果的替代方法,但这个问题的实际答案是我定义了一个扩展 WebMvcConfigurationSupport
的单独类。通过这样做,WebMvcAutoConfiguration
bean 已被禁用,因此 Spring 没有拾取 @Primary ObjectMapper。 (在WebMvcAutoConfiguration
source 中查找@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
。)
暂时删除扩展 WebMvcConfigurationSupport
的类允许 @Primary
ObjectMapper
被 Spring 拾取并按预期使用。
由于我无法永久删除 WebMvcConfigurationSupport
扩展类,因此我添加了以下内容:
@Autowired
private ObjectMapper mapper;
@Override
public void configureMessageConverters(final List<HttpMessageConverter<?>> converters)
converters.add(new MappingJackson2HttpMessageConverter(mapper));
addDefaultHttpMessageConverters(converters);
super.configureMessageConverters(converters);
【讨论】:
仅供参考,这篇博文(自定义 Jackson ObjectMapper 部分)对此提供了非常好的信息,并指出了使用 @EnableWebMvc 禁用默认 Spring Boot 自动配置的场景,并举例说明在这种情况下如何配置:spring.io/blog/2014/12/02/… 为了正确的顺序,并且为了不破坏字符串序列化,我们必须保持相同的顺序。 与此类似,我发现一个流氓@EnableWebMvc
注释导致我的 ObjectMapper 无法使用。
感谢您提供有关它为什么会中断的信息!帮助我解决了我的问题,直到今天!【参考方案3】:
因为您所做的是创建一个 bean,您可以 Autowired
供以后使用。 (就像你在代码中写的那样)Spring boot 使用自动配置的Jackson2ObjectMapperBuilder
来获取默认的 ObjectMapper 进行序列化。
所以如果你想在你的rest控制器上自定义一个ObjectMapper
进行序列化,你需要找到一种方法来配置Jackson2ObjectMapperBuilder
。有两种方法可以做到这一点:
为 spring 声明你自己的 Jackson2ObjectMapperBuilder
@Bean
public Jackson2ObjectMapperBuilder jacksonBuilder()
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.serializationInclusion(JsonInclude.Include.NON_NULL);
// other settings you want.
return builder;
通过属性设置
您可以通过application.properties
进行设置。例如,如果要启用漂亮打印,请设置spring.jackson.serialization.indent_output=true
。
更多详细信息,请在此处查看: Customize the Jackson ObjectMapper
【讨论】:
@wang 我认为 spring 使用 HttpMessageConverter 而不是反序列化器。检查这个docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/… 确实,Spring 使用 HttpMessageConverters 来渲染 ResponseBody。但是自定义 ResponseBody 渲染不同于自定义 ObjectMapper。根据帖子,我认为他想做的是自定义ObjectMapper而不是MessageConverter。 虽然根据文档,这里的两个建议似乎都是正确的,但我相信根据您在答案中链接到的文档,我正在尝试的也是正确的方法。我是不是误会了什么? @Dan 根据***.com/a/28327309/3599310(安迪威尔金森的评论)。您所做的方式将不适用于 Spring Boot 配置的任何ObjectMapper
。这就是为什么你的ObjectMapper
没有被休息控制器使用的原因。它只是告诉 spring '如果我自动装配 ObjectMapper,请使用我的版本!'
@S.Y.Wang 谢谢,鉴于 Andy Wilkinson 似乎是一名 Spring 开发人员,我想他应该知道他在说什么。在那种情况下,我真的不理解这些文档——也许它们只是错误的,或者我仍然遗漏了一些东西。【参考方案4】:
Spring 使用 HttpMessageConverters 来呈现 @ResponseBody(或来自 @RestController 的响应)。我认为您需要覆盖 HttpMessageConverter。
您可以通过扩展 WebMvcConfigurerAdapter
并覆盖关注来做到这一点。
@Override
public void configureMessageConverters(
List<HttpMessageConverter<?>> converters)
messageConverters.add(new MappingJackson2HttpMessageConverter());
super.configureMessageConverters(converters);
Spring documentation
【讨论】:
这仍然对我不起作用,它不尊重对象上的 LocalDateDeserializer 之类的注释以上是关于为啥 Spring Boot 不在休息控制器上使用 @Primary Jackson ObjectMapper 进行 JSON 序列化?的主要内容,如果未能解决你的问题,请参考以下文章
使用 spring boot、kotlin 和 junit 进行休息控制器单元测试