Reputation: 1017
I have a Spring REST web service which populates a generic object based on data we have in a database, the goal is to have the users pass a parameter to the web service to to indicate the format they want the output to be in. Based on their input we will use the correct JSONSerializer to give them what they want.
I have set up my webservice as follows, in my spring-ws-servlet.xml I have set our company ObjectMapper to be used by the mvc:message-converters, I have also set it on the RestController so that it can adjust the ObjectMapper to register the serializer. It looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="jacksonObjectMapper" />
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<bean id="endpoint" class="org.company.Controller">
<property name="objectMapper" ref="jacksonObjectMapper" />
</bean>
<bean id="jacksonObjectMapper" class="org.company.CompanyObjectMapper" />
</beans>
The controller looks like this:
@RestController
public class Controller {
private ObjectMapper objectMapper;
@RequestMapping(...)
public GenericObject getObject(@PathVariables ...) {
//Get Object from database, just creating an object for example
GenericObject object = new GenericObject();
//Based on the user input we will pick out
//a Serializer that extends JsonSerializer<GenericObject>
BaseSerializer serializer = getSerializer();
//Create a simpleModule and use it to register our serializer
SimpleModule module = new SimpleModule();
module.addSerializer(GenericObject.class, serializer);
//get module and register the serializer
ObjectMapper mapper = getObjectMapper();
mapper.registerModule(module);
return object;
}
public ObjectMapper getObjectMapper() {
return objectMapper;
}
public void setObjectMapper(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
}
The issue is that when I publish my webapp, the first query works correctly, if I specify format=format1, I will get the output in format1. However, after that I can only receive format1. I may specify format=format2, but still get the output in format1. I believe the issue is that the ObjectMapper still has the module registered to it from the first query. I have read that I can avoid this problem by creating a new ObjectMapper every time, but I am not sure how to set that to be used by Spring when it outputs the JSON.
Could someone help me come up with a solution to either create a new ObjectMapper every time I run the code and set that ObjectMapper to the Spring rest service, or help me figure out how I can "unregister" any modules that are registered on the object mapper before setting the latest desired serializer?
Upvotes: 2
Views: 1627
Reputation: 1332
An idea could be to create and configure all the mappers you need at startup time as a spring beans.
Then create the default object mapper that will work as a dispatcher for other object mappers (or as the fallback one), and it may be aware of the current http request. You can register all the mappers in this object mapper, register this mapper to be used as the default one in spring.
Something like this maybe :
public class RequestAwareObjectMapper extends ObjectMapper{
private Map<String, ObjectMapper > mappers = new HashMap<>();
@Override
public String writeValueAsString(Object value) throws JsonProcessingException{
HttpServletRequest req = null;//get request from spring context, if any, this is a managed spring bean it wont be a prorblem
String param = null; // read the param from the query
ObjectMapper mapper = mappers.get(param);
if(mapper == null){
mapper = this;
}
return mapper.writeValueAsString(value);
}
public void registerMapper(String key, ObjectMapper mapper){...}
}
in this way you are not going to pollute your controller with references to the object mapper and you can carry on using @ResponseBody (thanks to @RestController)..
I am sure there's a cleaner way to achieve the same result integrating a similar solution in the spring flow, can't look on something better right now.
Upvotes: 2
Reputation: 3465
Create your customObjectMapper class and auto wire it to your controller using @Autowire annotation. You can then create different methods to create different formatted objects.
You can also send serialiser as parameters.
public class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper() {
super();
super.setSerializationInclusion(JsonInclude.Include.ALWAYS);
super.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
..... etc.....
super.setDateFormat(df);
}
public byte[] generateJsonFormat1(Object value, BaseSerializer serializer) throws IOException, JsonGenerationException, JsonMappingException {
Hibernate4Module hm = new Hibernate4Module();
hm.configure(Hibernate4Module.Feature.USE_TRANSIENT_ANNOTATION, false);
hm.configure(Hibernate4Module.Feature.FORCE_LAZY_LOADING, false);
.....
.....
hm.addSerializer(Object.class, serializer);
return super.registerModule(hm).writeValueAsBytes(value);
}
public byte[] generateJsonFormat2(Object value, BaseSerializer serialiser) throws IOException, JsonGe nerationException, JsonMappingException {
SimpleModule sm = new SimpleModule();
sm.addSerializer(Object.class, serialiser);
return super.registerModule(hm).writeValueAsBytes(value);
}
}
Above code is a snippet from my own application. I hope it gives the idea.
Upvotes: 2