【问题标题】: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 - 我是否误解了那里的内容?

【问题讨论】:

    标签: spring-boot jackson2


    【解决方案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 根据stackoverflow.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 之类的注释
      猜你喜欢
      • 2020-07-09
      • 2014-06-03
      • 2013-03-09
      • 1970-01-01
      • 2019-02-01
      • 1970-01-01
      • 2020-12-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多