Spring 4.1's JsonView and Hibernate4Module - spring

I have a project which liberally uses lazy loading via the org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter view filter.
If I disable this, I have to add a new message converter in my rest servlet configuration:
public MappingJackson2HttpMessageConverter jacksonMessageConverter() {
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper mapper = new ObjectMapper();
// Registering Hibernate4Module to support lazy objects
mapper.registerModule(new Hibernate4Module());
messageConverter.setObjectMapper(mapper);
return messageConverter;
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// Add the custom-configured HttpMessageConverter
converters.add(jacksonMessageConverter());
super.configureMessageConverters(converters);
}
However, doing so seems to break the behavior of #JsonView: the existence of the #JsonView annotation no longer seems to filter in/out any properties.
My guess is Hibernate4Module doesn't support #JsonView, but if that's the case, how can I use Spring 4.1's #JsonView support and make Jackson cognizant of lazy-loaded Hibernate entities? The only solution so far seems to be avoiding Hibernate4Module and relying on the OpenEntityManagerInViewFilter filter.
Thanks for any insight you can offer.

Whenever you are using spring 4.1.*, make sure you are changing
import org.codehaus.jackson.annotate.*;
to
import org.fasterxml.jackson.annotate.*;
in your case its
import org.fasterxml.jackson.annotate.JsonView;

I think you need to configure the DEFAULT_VIEW_INCLUSION feature to be false. By default all properties without explicit view definition are included in serialization. Out of box Spring will disable this default for you. So if you want to use your own custom mapper you will probably want to disable it or all the properties with out #JsonView annotations will get added to you JSON.
mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false);

Related

Spring Cloud Stream #StreamListener Custom MappingJackson2MesageConverter

I have a custom ObjectMapper configured within my application which has custom modules for deserializing immutable types provided by Guava in Jackson.
My problem is that I cannot seem to override the objectMapper use by #StreamListener such that it deserializes my objects correctly.
Here is what I have tried:
#Bean
public MessageConverter messageConverter() {
final MappingJackson2MessageConverter mappingJackson2MessageConverter = new MappingJackson2MessageConverter();
mappingJackson2MessageConverter.setObjectMapper(objectMapper);
return mappingJackson2MessageConverter;
}
The above snippet is in a class annotated with #Configuration and the objectMapper is defined in the class:
public static final ObjectMapper objectMapper = new ObjectMapper()
.registerModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES))
.registerModule(new GuavaModule());
Any ideas on what I may be missing?
Spring Boot: 1.5.10.RELEASE
Spring Rabbit: 1.7.6.RELEASE
Spring Cloud Stream: 1.2.2.RELEASE
Spring Messaging: 4.3.14.RELEASE
So, it turns out there was no issue with my configuration. My custom ObjectMapper was being selected by the MappingJackson2MessageConverter. However, the message converter was not being selected, because the messages were being sent with the wrong content-type header.
Moral of the story, be careful with the Content-Type and make sure it matches the converter.

Spring return image from controller while using Jackson Hibernate5Module

I am using Spring 4.3.1 and Hibernate 5.1.0 for my webapp.
For Jackson to be able serializing lazy objects I have to add the Hibernate5Module to my default ObjectMapper. This I have done via
#EnableWebMvc
#Configuration
#ComponentScan({ "xxx.controller" })
public class SpringWebConfig extends WebMvcConfigurerAdapter {
#Autowired
SessionFactory sf;
...
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Hibernate5Module module = new Hibernate5Module(sf);
module.disable(Feature.USE_TRANSIENT_ANNOTATION);
module.enable(Feature.FORCE_LAZY_LOADING);
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.modulesToInstall(module);
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
super.configureMessageConverters(converters);
}
}
This is working but if it is enabled serializing a byte[] does not work anymore and fails with HTTP Status 500 - Could not write content: No serializer found for class java.io.BufferedInputStream
So my question is how to extend the default ObjectMapper while preserving the default ones?
I have seen somthing preserving the defaults using Spring Boot but I do not use Spring Boot. Any ideas?
As specified in the WebMvcConfigurer.configureMessageConverters javadoc, "If no converters are added, a default list of converters is registered", i.e. you will have to manually add all the default converters if you are using WebMvcConfigurer. Calling 'super.configureMessageConverters(converters)' does nothing if you extend WebMvcConfigurer. Take a look in 'WebMvcConfigurationSupport.addDefaultHttpMessageConverters(...)' to see all the default message converters, you can also extend this class instead of WebMvcConfigurer, with which you get slightly more clarity what happens.

Spring Boot with Two MVC Configurations

I have a Spring Boot app with a REST API, using Jackson for the JSON view configuration. It works great and I can get all the Spring Boot goodness.
However, I need to add an additional REST API that is similar but with different settings. For example, among other things, it needs a different Jackson object mapper configuration because the JSON will look quite a bit different (e.g. no JSON arrays). That is just one example but there are quite a few differences. Each API has a different context (e.g. /api/current and /api/legacy).
Ideally I'd like two MVC configs mapped to these different contexts, and not have to give up any of the automatic wiring of things in boot.
So far all I've been able to get close on is using two dispatcher servlets each with its own MVC config, but that results in Boot dropping a whole bunch of things I get automatically and basically defeats the reason for using boot.
I cannot break the app up into multiple apps.
The answer "you cannot do this with Boot and still get all its magic" is an acceptable answer. Seems like it should be able to handle this though.
There's several ways to achieve this. Based on your requirement , Id say this is a case of managing REST API versions.
There's several ways to version the REST API, some the popular ones being version urls and other techniques mentioned in the links of the comments.
The URL Based approach is more driven towards having multiple versions of the address:
For example
For V1 :
/path/v1/resource
and V2 :
/path/v2/resource
These will resolve to 2 different methods in the Spring MVC Controller bean, to which the calls get delegated.
The other option to resolve the versions of the API is to use the headers, this way there is only URL, multiple methods based on the version.
For example:
/path/resource
HEADER:
X-API-Version: 1.0
HEADER:
X-API-Version: 2.0
This will also resolve in two separate operations on the controller.
Now these are the strategies based on which multiple rest versions can be handled.
The above approaches are explained well in the following: git example
Note: The above is a spring boot application.
The commonality in both these approaches is that there will need to be different POJOS based on which Jackson JSON library to automatically marshal instances of the specified type into JSON.
I.e. Assuming that the code uses the #RestController [org.springframework.web.bind.annotation.RestController]
Now if your requirement is to have different JSON Mapper i.e. different JSON mapper configurations, then irrespective of the Spring contexts you'll need a different strategy for the serialization/De-Serialization.
In this case, you will need to implement a Custom De-Serializer {CustomDeSerializer} that will extend JsonDeserializer<T> [com.fasterxml.jackson.databind.JsonDeserializer] and in the deserialize() implement your custom startegy.
Use the #JsonDeserialize(using = CustomDeSerializer.class) annotation on the target POJO.
This way multiple JSON schemes can be managed with different De-Serializers.
By Combining Rest Versioning + Custom Serialization Strategy , each API can be managed in it's own context without having to wire multiple dispatcher Servlet configurations.
Expanding on my comment of yesterday and #Ashoka Header idea i would propose to register 2 MessageConverters (legacy and current) for custom media types. You can do this like that:
#Bean
MappingJackson2HttpMessageConverter currentMappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
// set features
jsonConverter.setObjectMapper(objectMapper);
jsonConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("json", "v2")));
return jsonConverter;
}
#Bean
MappingJackson2HttpMessageConverter legacyMappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
// set features
jsonConverter.setObjectMapper(objectMapper);
return jsonConverter;
}
Pay attention to the custom media-type for one of the converters.
If you like , you can use an Interceptor to rewrite the Version-Headers proposed by #Ashoka to a custom Media-Type like so:
public class ApiVersionMediaTypeMappingInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
try {
if(request.getHeader("X-API-Version") == "2") {
request.setAttribute("Accept:","json/v2");
}
.....
}
}
This might not be the exact answer you were looking for, but maybe it can provide some inspiration. An interceptor is registered like so.
If you can live with a different port for each context, then you only have to overwrite the DispatcherServletAutoConfiguration beans. All the rest of the magic works, multpart, Jackson etc. You can configure the Servlet and Jackson/Multipart etc. for each child-context separately and inject bean of the parent context.
package test;
import static org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME;
import static org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
#Configuration
#EnableAutoConfiguration(exclude = {
Application.Context1.class,
Application.Context2.class
})
public class Application extends WebMvcConfigurerAdapter {
#Bean
public TestBean testBean() {
return new TestBean();
}
public static void main(String[] args) {
final SpringApplicationBuilder builder = new SpringApplicationBuilder().parent(Application.class);
builder.child(Context1.class).run();
builder.child(Context2.class).run();
}
public static class TestBean {
}
#Configuration
#EnableAutoConfiguration(exclude = {Application.class, Context2.class})
#PropertySource("classpath:context1.properties")
public static class Context1 {
#Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
DispatcherServlet dispatcherServlet() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
// custom config here
return dispatcherServlet;
}
#Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
ServletRegistrationBean dispatcherServletRegistration() {
ServletRegistrationBean registration = new ServletRegistrationBean(dispatcherServlet(), "/test1");
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
// custom config here
return registration;
}
#Bean
Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder(TestBean testBean) {
System.out.println(testBean);
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
// custom config here
return builder;
}
}
#Configuration
#EnableAutoConfiguration(exclude = {Application.class, Context1.class})
#PropertySource("classpath:context2.properties")
public static class Context2 {
#Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
DispatcherServlet dispatcherServlet() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
// custom config here
return dispatcherServlet;
}
#Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
ServletRegistrationBean dispatcherServletRegistration() {
ServletRegistrationBean registration = new ServletRegistrationBean(dispatcherServlet(), "/test2");
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
// custom config here
return registration;
}
#Bean
Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder(TestBean testBean) {
System.out.println(testBean);
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
// custom config here
return builder;
}
}
}
The context1/2.properties files currently only contain a server.port=8080/8081 but you can set all the other spring properties for the child contexts there.
In Spring-boot ypu can use different profiles (like dev and test).
Start application with
-Dspring.profiles.active=dev
or -Dspring.profiles.active=test
and use different properties files named application-dev.properties or application-test.properties inside your properties directory.
That could do the problem.

How to customize the Jackson serializer for Spring SQS

How do you customize the Jackson JSON serializer for SQS? I've googled around, but so far everything I've found is related to the Spring web stuff, and there doesn't seem to be any way to get a hold of a reference to the serializer that Spring SQS uses, so that I can add my custom types (Java 8 Date stuff)
I just had a look at the source code of spring-cloud-aws to see how the Jackson object mapper is being instantiated, see here: QueueMessagingTemplate.java.
It turns out that it has a constructor that takes in a MessageConverter, so you could do this:
#Configuration
public class SpringAwsMessagingConfig {
#Bean
public QueueMessagingTemplate myMessagingTemplate(AmazonSQS amazonSqs, ResourceIdResolver resolver) {
ObjectMapper mapper = new ObjectMapper();
// configure the Jackson mapper as needed
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setSerializedPayloadClass(String.class);
converter.setObjectMapper(mapper);
return new QueueMessagingTemplate(amazonSqs, resolver, converter);
}
}

How to avoid lazy fetch in JSON serialization using Spring Data JPA + Spring Web MVC?

I have a solution using Spring Data JPA and a REST Controller in Spring Web MVC. The persistence provider is Hibernate.
The persistence layer is built using Spring Repositories and between de REST Controller and the repository exists a Service Layer:
Entity <--> Repository <--> Service <--> Controller
At entity level, I have #OneToMany fields with FetchType = LAZY.
When the REST Controller make the serialization, the fetching is made, but this is undesirable in some cases.
I already tried with #JSONInclude Jackson annotation and the serialization still occurs.
Can somebody help me with a proved solution?
If I understood you properly, you want to serialize only when the lazy loaded collection is fetched, but you don't want the serialization to trigger the fetching.
If that is the case you should use the jackson-datatype-hibernate, and added as their docs already explains
public class HibernateAwareObjectMapper extends ObjectMapper {
public HibernateAwareObjectMapper() {
registerModule(new Hibernate4Module());
}
}
than register
<mvc:annotation-driven>
<mvc:message-converters>
<!-- Use the HibernateAware mapper instead of the default -->
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="path.to.your.HibernateAwareObjectMapper" />
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
The module has a Feature.FORCE_LAZY_LOADING settings, that tells whether the object should be forced to be loaded and then serialized, which is by default set to false which I believe is the behaviour you need
Simple architectural solution is not to use model Entity as Data Transfer Object. Make Simple POJO as Data transfer Object. In the conversion logic you could easily put try and catch block for LazyInitializationException. And thus your POJO is always serializable and You can use it on your controller.
Use the #JsonIgnore annotation if you ALWAYS want to skip this particular field. use #JsonView if you want to dynamically determine which field(s) to skip. Note that #JsonView is a Jackson specific annotation, but since you're already using Jackson, things should be fine.
We can do this by adding Hibernate4Module (which support Lazy objects) to the default MappingJackson2HttpMessageConverter that Spring already provide and by adding it to HttpMessageConverters.
So we need to extend our spring config class from WebMvcConfigurerAdapter and override the method configureMessageConverters and add MappingJackson2HttpMessageConverter with the Hibernate4Module registered in it.
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
//Here we add our custom-configured HttpMessageConverter
converters.add(jacksonMessageConverter());
super.configureMessageConverters(converters);
}
// Here we register the Hibernate4Module into an ObjectMapper,
// then use this custom ObjectMapper
// to the MessageConverter
public MappingJackson2HttpMessageConverter jacksonMessageConverter(){
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper mapper = new ObjectMapper();
//Registering Hibernate4Module to support lazy objects
mapper.registerModule(new Hibernate4Module());
messageConverter.setObjectMapper(mapper);
return messageConverter;
}
You can solve this problem with wit 2 steps with jackson-datatype-hibernate:
kotlin example
Add In build.gradle.kts:
implementation("com.fasterxml.jackson.datatype:jackson-datatype-hibernate5:$jacksonHibernate")
Create #Bean
#Bean
fun hibernate5Module(): Module = Hibernate5Module()
Notice that Module is com.fasterxml.jackson.databind.Module, not java.util.Module
Also good practice is to add #JsonBackReference & #JsonManagedReference to #OneToMany & #ManyToOne relationships.

Resources