How to configure HttpMessageConverter for specified controller in SpringMvc - spring

As we know, we can configure the global HttpMessageConverter by configureMessageConverters method in WebMvcConfigurer.
see https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-config-message-converters
But I want to configure a HttpMessageConverter for specified Controller to override the global configuration to implement the function different from the global.
How should I configure it? Can any friends give me pointers?

You can put below code in any of your configuration classes. and you have to autowire this specific objectmapper in that class where you need.Even you can create multiple objectmappers to serve different purposes.
#Bean
#Qualifier("customForController")
public ObjectMapper getObjectMapper() {
ObjectMapper mapper=new ObjectMapper();
return mapper;
}
#Bean
#Qualifier("customMessageConverter")
public MappingJackson2HttpMessageConverter converter() {
MappingJackson2HttpMessageConverter httConverter = new MappingJackson2HttpMessageConverter();
httConverter.setObjectMapper(getObjectMapper());
//others configuration goes here
return httConverter;
}

Related

Spring Jackson custom Deserializer does not called

I have a Spring project, and I try to add a custom deserializer to deserialize Date properties depend on their format.
If I use it as annotation on Date property, it works fine.
But if I add the deserializer to my object mapper, it does not called when Jackson deserialize a date.
I try to apply my custom deserializer like this:
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Date.class, new DateDeserializer());
mapper.registerModule(module);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false);
mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);
mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);
mapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false);
return mapper;
}
I don't want to apply an annotation on Date properties every time, I want to use this deserializer by default.
What I do wrong?
Thanks the help for everyone.
Finally I found the answer at spring.io.
#Configuration
#EnableWebMvc
public class WebConfiguration extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.deserializerByType(Date.class, new DateDeserializer());
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
}
}
Given that if you use the deserializer in an annotation on Date property then I would say that this ObjectMapper is not being used for deserialization. Try the following:
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
jsonConverter.setObjectMapper(objectMapper);
return jsonConverter;
}
I have doubt how you try to use this ObjectMapper bean inside your application.
I trust you already know that this bean is need to created inside a Configuration class. If not your bean will not register in the context. Like this for example,
#Configuration
public class MapperConfig {
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Date.class, new DateDeserializer());
mapper.registerModule(module);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false);
mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);
mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);
mapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false);
return mapper;
}
}
The second problem might be how you try use this ObjectMapper bean. If your create new instance of ObjectMapper like this ObjectMapper objectMapper = new ObjectMapper();, that instance will not have your custom deserializer and stuff. What you can do is #Autowire the ObjectMapper instance that you have already created.

How to configure spring controller to use different objectMapper for the response (serialization)

I configured my own ObjectMapper for my SpringBoot application, Let say the object mapper is something like below (simplified):
#Bean
#Primary
public ObjectMapper objectMapper() {
return new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
Since the ObjectMapper is a singleton, all my REST Controller use that same ObjectMapper.
But, there is some controller that I want to ignore the null value, but I want to keep the rest of my controller to send the null value response.
Is there a way to configure it? So the best result is I can configure something like this:
Controller A,B,C -> ObjectMapper X (ignore null value)
Controller D -> ObjectMapper Y (ignore empty value)
Default (all other controller) -> ObjectMapper Z (return null value)
Requirement Note:
I can't change the POJO since it's autogenerated, and I don't want to update the codegen lib or the mustache template for this.
Specific content negotiation is not an option
In addition to your ObjectMapper annotated with #Primary
you can configure more ObjectMappers qualified with bean names of your choice.
#Bean
#Primary
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return objectMapper;
}
#Bean("mySpecialObjectMapper")
public ObjectMapper anotherObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper()
/* do some other configuration */;
return objectMapper;
}
Then you can refer to them in your controllers like this:
Just giving #Autowired will inject the primary ObjectMapper.
Giving #Autowired together with #Qualifier("anyName") will inject
the ObjectMapper configured with #Bean("anyName")
#Autowired
private ObjectMapper objectMapper;
#Autowired
#Qualifier("mySpecialObjectMapper")
private ObjectMapper otherObjectMapper;
Maybe you just need to use #JsonInclude(JsonInclude.Include.NON_NULL). Try to fix it in the response model level.

#Bean vs #Autowired cyclic dependency

I came across a strange behaviour with respect to #Bean and #Autowired in my #Configuration class. My web project structure is like
Controller → Service → Repository
In my Service I have a dependency on ObjectMapper
#Autowired
public ServiceClass(final ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
Since I want to use a Java 8 optional class while deserializing, I wanted to register Jdk8Module. So I created a configuration class like this:
#Configuration
public class JacksonConfig {
#Bean
public ObjectMapper objectMapper(final ObjectMapper objectMapper) {
objectMapper.registerModule(new Jdk8Module());
return objectMapper;
}
}
I initially thought that Spring will inject the objectMapper instance which it has, which I can manipulate and return it, so that when I autowire it in my service class, I get the updated instance of ObjectMapper.
But I get a cyclic dependency error. This is understandable because, my bean configuration depends on objectmapper and returns an objectmapper.
But it is surprising, if I change the method to have #Autowired instead of #Bean, Spring doesn't complain and works as expected.
#Autowired
public ObjectMapper objectMapper(final ObjectMapper objectMapper) {
objectMapper.registerModule(new Jdk8Module());
return objectMapper;
}
Why is that?
The #Bean annotation goal is to provide beans from the Spring BeanFactory. When a method is annotated #Bean, it is supposed to return a new instance of an object. It can use paramaters if Spring is able to instantiate them too.
In your example, when you declare
#Bean
public ObjectMapper objectMapper(final ObjectMapper objectMapper) {
objectMapper.registerModule(new Jdk8Module());
return objectMapper;
}
It means your ObjectMapper is a bean (of course) that takes a parameter which is an instance of ObjectMapper and Spring knows ObjectMapper so it can instantiate it using... the very same method. Here is your cyclic dependency.
In the case of #Autowired, Spring uses an ObjectMapper as a parameter of your method that is already in the BeanFactory. That's why it removes the cyclic dependency.
I hope I'm clear enough.
Little reference here : https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Bean.html
In
#Autowired
public ObjectMapper objectMapper(final ObjectMapper objectMapper) {
objectMapper.registerModule(new Jdk8Module());
return objectMapper;
}
Someone else is injecting an ObjectMapper (maybe Spring) to this method. I think the Autowired annotation is not necessary in this case, Spring knows you want to inject a bean here.
You aren't creating new beans in this case so you will not have a cyclic dependency error here.
If you want to create a new one, maybe you should play with #Qualifier.
More info: https://dzone.com/articles/spring-configuration-and

Configure a Jackson's DeserializationProblemHandler in Spring environment [duplicate]

This question already has answers here:
Can't set ProblemHandler to ObjectMapper in Spring Boot
(2 answers)
Closed 4 years ago.
As I understood, Spring is already providing a bean for Jackson ObjectMapper. Therefore, instead of creating a new bean, I'm trying to customize this bean.
From this blog post, and then this Github project I used Jackson2ObjectMapperBuilder bean to achieve this customization.
#Bean
public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder(ApplicationContext context) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.findModulesViaServiceLoader(true);
return builder;
}
Then, I was trying to customize the deserializer in order to make it lenient: if an exception is raised when deserializing a property, I want the result object's property to be null and let the deserialization continue (default is to fail on first property that cannot be deserialized).
I've been able to achieve that with a class NullableFieldsDeserializationProblemHandler that extends DeserializationProblemHandler (I do not think the code is relevant but if needed, I can share it).
The simplest way to register this handler is to use the .addHandler() method of ObjectMapper. But of course, doing like this, I would need to set that every time I inject and use the ObjectMapper. I'd like to be able to configure handler so that every time the ObjectMapper is auto-wired, the handler is already present.
The best solution I came up with so far is to use a #PostConstruct annotation only to register the problem handler.
#Configuration
public class JacksonConfiguration implements InitializingBean {
#Autowired private ObjectMapper objectMapper;
#Bean
public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder(ApplicationContext context) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.findModulesViaServiceLoader(true);
return builder;
}
#Override
public void afterPropertiesSet() {
objectMapper.addHandler(new NullableFieldsDeserializationProblemHandler());
}
}
But the problem of this solution is that it seems I can still access an autowired ObjectMapper that doesn't have yet registered the problem handler (I can see it happening after when I need it in debug mode).
Any idea how I should register this handler? I've noticed Jackson2ObjectMapperBuilder has a .handlerInstantiator() but I couldn't figure out how to use it.
Note I've also tried with Jackson2ObjectMapperBuilderCustomizer since I'm using Spring Boot but had no better results.
It's not possible to directly add a DeserializationProblemHandler to the ObjectMapper via a Jackson2ObjectMapperBuilder or Jackson2ObjectMapperBuilderCustomizer. The handlerInstanciator() method is for something else.
However, it's possible to do so by registering a Jackson module:
the builder has a modules() method
the module has access via setupModule() to a SetupContext instance, which has a addDeserializationProblemHandler() method
This works:
#Bean
public Jackson2ObjectMapperBuilderCustomizer customizer() {
return new Jackson2ObjectMapperBuilderCustomizer() {
#Override
public void customize(Jackson2ObjectMapperBuilder builder) {
builder.modules(new MyModule());
}
};
}
private static class MyModule extends SimpleModule {
#Override
public void setupModule(SetupContext context) {
// Required, as documented in the Javadoc of SimpleModule
super.setupModule(context);
context.addDeserializationProblemHandler(new NullableFieldsDeserializationProblemHandler());
}
}
What about writing a bean like this:
#Configuration
public class ObjectMapperConfiguration {
#Bean
ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
// jackson 1.9 and before
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// or jackson 2.0
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return objectMapper;
}
}
This is for global configuration. If, instead, what you want to do is to configure the feature for specific a class, use this annotation above the class definition:
#JsonIgnoreProperties(ignoreUnknown = true)

How can you extend behavior of the spring boot autoconfiguration?

I am looking to extend JacksonAutoConfiguration specifically when the builder is created, i would like to set that ObjectMapper to a Util class which has static setter for the ObjectMapper. Look at the line before returning builder where I would like to set ObjectMpper to a static class.
#Bean
#ConditionalOnMissingBean(Jackson2ObjectMapperBuilder.class)
public Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.applicationContext(this.applicationContext);
if (this.jacksonProperties.getSerializationInclusion() != null) {
builder.serializationInclusion(this.jacksonProperties
.getSerializationInclusion());
}
configureFeatures(builder, this.jacksonProperties.getDeserialization());
configureFeatures(builder, this.jacksonProperties.getSerialization());
configureFeatures(builder, this.jacksonProperties.getMapper());
configureFeatures(builder, this.jacksonProperties.getParser());
configureFeatures(builder, this.jacksonProperties.getGenerator());
configureDateFormat(builder);
configurePropertyNamingStrategy(builder);
configureModules(builder);
**ObjectMapperUtils.setObjectMapper( builder.build() );**
return builder;
}
The ObjectMapper created from the auto-configured Jackson2ObjectMapperBuilder is exposed as a bean in the JacksonAutoConfiguration. You could simply create another #Configuration class, get a reference to the ObjectMapper (via auto-wiring) and use an #PostConstruct method to set the ObjectMapper in your ObjectMapperUtils class.
Another suggestion would be to refactor ObjectMapperUtils so that it is created as a Spring bean itself, then you can auto-wire ObjectMapper directly into it.

Resources