为啥 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 进行休息控制器单元测试

用于 Spring Boot 休息控制器的 Junit

kotlin + Spring boot 中的单元测试休息控制器

Spring Boot 休息 API

在spring boot微服务中,休息调用的调度不工作。

为啥 Spring Boot 中的 REST 控制器返回 HTTP 状态 404 – 未找到