I have a Spring Boot web app with several @RestController
classes.
I like the default json format returned by my REST controllers.
For use in my DAO beans (which do json serialization and deserialization), I have created a custom 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;
}
}
And in each of my DAO classes I autowire my custom ObjectMapper
:
@Repository
@Transactional
public class MyDaoImpl implements MyDao {
@Autowired
ObjectMapper objectMapper
//Dao implementation...
}
This all works fine. The problem is that my custom ObjectMapper
gets automatically picked up by Spring and is used for serializing REST responses.
This is undesirable. For REST controllers I want to keep the ObjectMapper
that Spring creates by default.
How can I tell Spring Boot to not detect and not use my custom ObjectMapper
bean for its own internal workings?
Since I didn't want to touch Spring's default ObjectMapper
, creating a @Primary
ObjectMapper
to shadow Spring's default ObjectMapper
was out of the question.
Instead, what I ended up doing is creating a BeanFactoryPostProcessor
which registers in Spring's context a custom, non primary 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;
}
}
As can be seen in the code above, I also assigned a qualifier to my custom ObjectMapper
bean.
My qualifier is an annotation which is annotated with @Qualifier
:
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface PersistenceObjectMapper {
}
I can then autowire my custom ObjectMapper
using my custom annotation, like this:
@Repository
public class MyDao {
@Autowired
public MyDao(DataSource dataSource, @PersistenceObjectMapper ObjectMapper objectMapper) {
// constructor code
}