如何在不覆盖 Spring Boot 使用的情况下定义自定义 ObjectMapper bean

Posted

技术标签:

【中文标题】如何在不覆盖 Spring Boot 使用的情况下定义自定义 ObjectMapper bean【英文标题】:How can I define a custom ObjectMapper bean without overriding the one used by Spring Boot 【发布时间】:2018-08-14 18:56:21 【问题描述】:

我有一个带有多个 @RestController 类的 Spring Boot Web 应用程序。 我喜欢我的 REST 控制器返回的默认 json 格式。

为了在我的 DAO bean(进行 json 序列化和反序列化)中使用,我创建了一个自定义 ObjectMapper

@Configuration
public class Config

  @Bean
  public ObjectMapper getCustomObjectMapper() 
    final ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    objectMapper.setPropertyNamingStrategy(new PropertyNamingStrategy.SnakeCaseStrategy());
    return objectMapper;
  

在我的每个 DAO 类中,我都会自动装配我的自定义 ObjectMapper

@Repository
@Transactional
public class MyDaoImpl implements MyDao 

@Autowired
ObjectMapper objectMapper

//Dao implementation...


这一切都很好。问题是我的自定义 ObjectMapper 被 Spring 自动拾取并用于序列化 REST 响应。 这是不可取的。对于 REST 控制器,我想保留 Spring 默认创建的 ObjectMapper

我如何告诉 Spring Boot 检测和 使用我的自定义 ObjectMapper bean 进行自己的内部工作?

【问题讨论】:

【参考方案1】:

您可以提供一个标准的ObjectMapper和您自定义的对象映射器,并将标准设置为@Primary

然后为您的自定义 ObjectMapper 命名,并将其与 @Qualifier 注释一起使用。

@Configuration
public class Config

  //This bean will be selected for rest
  @Bean
  @Primary
  public ObjectMapper stdMapper()
     return new ObjectMapper();
  

  //You can explicitly refer this bean later
  @Bean("customObjectMapper")
  public ObjectMapper getCustomObjectMapper() 
    final ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    objectMapper.setPropertyNamingStrategy(new PropertyNamingStrategy.SnakeCaseStrategy());
    return objectMapper;
  

现在您可以引用您的自定义映射器

@Repository
@Transactional
public class MyDaoImpl implements MyDao 

@Autowired
@Qualifier("customObjectMapper")
ObjectMapper objectMapper

//Dao implementation...


@Resource("custonmObjectMapper") 将与 @Autowired 和 @Qualifier 一起做同样的事情

【讨论】:

【参考方案2】:

因为我不想触及 Spring 的默认 ObjectMapper,所以创建一个 @Primary ObjectMapper 来影响 Spring 的默认 ObjectMapper 是不可能的。

相反,我最终做的是创建一个 BeanFactoryPostProcessor,它在 Spring 的上下文中注册一个自定义的非主要 ObjectMapper

@Component
public class ObjectMapperPostProcessor implements BeanFactoryPostProcessor 

public static final String OBJECT_MAPPER_BEAN_NAME = "persistenceObjectMapper";

@Override
public void postProcessBeanFactory(final ConfigurableListableBeanFactory beanFactory) 
    final AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
        .genericBeanDefinition(ObjectMapper.class, this::getCustomObjectMapper)
        .getBeanDefinition();
    // Leave Spring's default ObjectMapper (configured by JacksonAutoConfiguration)
    // as primary
    beanDefinition.setPrimary(false);
    final AutowireCandidateQualifier mapperQualifier = new AutowireCandidateQualifier(PersistenceObjectMapper.class);
    beanDefinition.addQualifier(mapperQualifier);
    ((DefaultListableBeanFactory) beanFactory).registerBeanDefinition(OBJECT_MAPPER_BEAN_NAME, beanDefinition);


private ObjectMapper getCustomObjectMapper() 
    final ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    objectMapper.setPropertyNamingStrategy(new PropertyNamingStrategy.SnakeCaseStrategy());
    return objectMapper;


从上面的代码中可以看出,我还为我的自定义 ObjectMapper bean 分配了一个限定符。 我的限定符是一个用@Qualifier 注释的注释:

@Target( ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE )
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface PersistenceObjectMapper 

然后我可以使用我的自定义注释自动连接我的自定义 ObjectMapper,如下所示:

@Repository
public class MyDao 
@Autowired
public MyDao(DataSource dataSource, @PersistenceObjectMapper ObjectMapper objectMapper) 
// constructor code

【讨论】:

【参考方案3】:

Simone Pontiggia 的答案是正确的。您应该创建一个 @Primary bean,Spring 将在其内部使用它,然后创建您自己的 ObjectMapper bean 并使用 @Qualifier 自动装配它们。

这里的问题是,创建默认 bean 如下:

@Bean
@Primary
public ObjectMapper objectMapper() 
    return new ObjectMapper();

实际上不会按预期工作,因为 Spring 默认的 ObjectMapper 有额外的配置。 创建spring将使用的默认ObjectMapper的正确方法是:

@Bean
@Primary
public ObjectMapper objectMapper() 
    return Jackson2ObjectMapperBuilder.json().build();

您可以在此处找到有关 Spring 默认 ObjectMapper 的更多信息:https://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-mvc.html under 79.3 Customize the Jackson ObjectMapper

【讨论】:

【参考方案4】:

您可以创建:

public class MapperUtils 

    private static final ObjectMapper mapper = new ObjectMapper();

    public static <T> T parseResponse(byte[] byteArrray, Class<T> parseType) throws JsonParseException, JsonMappingException, IOException 
        return mapper.readValue(byteArrray, parseType);
    

ObjectMapper 是线程安全的。但是,由于性能问题,有些人不鼓励使用单实例 (Should I declare Jackson's ObjectMapper as a static field?)。

【讨论】:

以上是关于如何在不覆盖 Spring Boot 使用的情况下定义自定义 ObjectMapper bean的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot - 如何在不使用 spring 注释的情况下在运行时获取端口

如何在不从 spring-boot-starter-web 继承的情况下在 Spring Boot 中获取 ObjectMapper 实例?

如何在不使用 Spring Boot 的情况下注入 Feign Client 并调用 REST Endpoint

如何在不依赖 MongoDB 的情况下启动 spring-boot 应用程序?

如何在不维护 jsessionid 的情况下在 Spring Boot 中保护 RESTful API

如何在不启动 HikariPool 关闭的情况下使用 Hikari 数据源运行 Spring Boot 应用程序