How do I declare another Jackson ObjectMapper without affecting the "clients" of the original bean?

I have a spring-boot application that provides json REST API. For mapping objects to json, the built-in jackson ObjectMapper is used, configured with spring-boot.

Now I need to read some data from the yaml file and I found that an easy way to do this is to use Jackson - for that I need to declare another ObjectMapper to convert the yaml to objects. I have declared this new mapper bean with a specific name to be able to inject it into my service related to reading from a yaml file:

@Bean(YAML_OBJECT_MAPPER_BEAN_ID)
public ObjectMapper yamlObjectMapper() {
    return new ObjectMapper(new YAMLFactory());
}

      

But I need a way to tell all other "clients" of the original json ObjectMapper to keep using this bean. So I basically need the @Primary annotation on the original bean. Is there a way to achieve this without having to override the original ObjectMapper in my own config (I'll have to dig through the spring-boot code to find and copy its config)?

One solution I found is to declare a FactoryBean for the ObjectMapper and return it to the already declared bean as suggested in this answer . I found out while debugging that my original bean is called "_halObjectMapper", so my factoryBean will look for that bean and return it:

public class ObjectMapperFactory implements FactoryBean<ObjectMapper> {

    ListableBeanFactory beanFactory;

    public ObjectMapper getObject() {
        return beanFactory.getBean("_halObjectMapper", ObjectMapper.class);
    }
    ...
}

      

Then in my config class I declare it as @Primary bean to make sure it is the first choice for auto-provisioning:

@Primary
@Bean
public ObjectMapperFactory objectMapperFactory(ListableBeanFactory beanFactory) {
    return new ObjectMapperFactory(beanFactory);
}

      

However, I am not 100% happy with this solution because it relies on a bean name that is out of my control and it also looks like a hack. Is there a cleaner solution?

Thank!

+3


source to share


4 answers


You can define two ObjectMapper

beans and declare them primary, for example:

@Bean("Your_id")
public ObjectMapper yamlObjectMapper() {
    return new ObjectMapper(new YAMLFactory());
}

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

      

Once done, you can use your objectmapper bean with annotation @Qualifier

like:

@Autowired
@Qualifier("Your_id")
private ObjectMapper yamlMapper;

      

Update

You can dynamically add ObjectMapper

to Spring bean factory at runtime like:



@Configuration
public class ObjectMapperConfig {

    @Autowired
    private ConfigurableApplicationContext  context;

    @PostConstruct
    private void init(){
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ObjectMapper.class);
        builder.addConstructorArgValue(new JsonFactory());
        DefaultListableBeanFactory factory = (DefaultListableBeanFactory) context.getBeanFactory();
        factory.registerBeanDefinition("yamlMapper", builder.getBeanDefinition());
        Map<String, ObjectMapper> beans = context.getBeansOfType(ObjectMapper.class);
        beans.entrySet().forEach(System.out::println);
    }
}

      

The above code adds a new bean to context

without modifying the existing bean ( sysout

prints two beans at the end of the method init

). Then you can use "yamlMapper" as a qualifier for its auto-mapper anywhere.

Update 2 (from the author of the question):

The solution suggested in Update works and here's a simplified version:

@Autowired
private DefaultListableBeanFactory beanFactory;

@PostConstruct
private void init(){
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(YAMLMapper.class);
    beanFactory.registerBeanDefinition("yamlMapper", builder.getBeanDefinition());
}

      

+1


source


Another option is to wrap the custom mapper in a custom object:

@Component
public class YamlObjectMapper {
    private final ObjectMapper objectMapper;

    public YamlObjectMapper() {
        objectMapper = new ObjectMapper(new YAMLFactory());
    }

    public ObjectMapper getMapper() {
        return objectMapper;
    }
}

      



Unfortunately, this approach requires a call getMapper

after you type YamlObjectMapper

.

+1


source


I believe that defining an explicit primitive object transformer for the MVC layer should work like this:

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

      

All beans that autowire object mapper via type will use above bean. Your Yaml logic can auto-connect via YAML_OBJECT_MAPPER_BEAN_ID

.

0


source


I just realized that I don't need to use FactoryBean, I could also declare a normal bean as @Primary and return it to the original bean, like so:

@Bean
@Primary
public ObjectMapper objectMapper(@Qualifier("_halObjectMapper") ObjectMapper objectMapper) {
    return objectMapper;
}

      

This makes the configuration a little cleaner, but still requires the exact name of the original ObjectMapper. I guess I'll stay with this solution.

0


source







All Articles