Spring MVC Configuration + Jackson + Guava Multiplayer

I am struggling with this:

We have a Table class with a Guava multimap (simplified code, basically 1 member, 2 constructors, getter and setter for a multimap):

public class Table { private LinkedHashMultimap<String,Field> fields; public Table(){ this.fields = LinkedHashMultimap.create(); }; public Table (LinkedHashMultimap<String, Field> fields){ this.fields= fields; } public LinkedHashMultimap<String, Field> getFields() { return fields; } public void setFields(LinkedHashMultimap<String, Field> fields) { this.fields = fields; } } 

And I want to serialize this using Spring MVC 3.2.11 using jackson 2.4.3.

Corresponding POM dependencies:

 <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.4.3</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.4.3</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-guava</artifactId> <version>2.4.3</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>3.2.11.RELEASE</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>3.2.11.RELEASE</version> <scope>compile</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> 

My spring.xml NOW looks like this (after example )

 <bean id="abstractJacksonObjectMapper" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean" p:targetMethod="registerModule"> <property name="targetObject"> <bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean" p:indentOutput="true"> <!--<property name="featuresToDisable">--> <!--<util:constant static-field="com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES" />--> <!--</property>--> </bean> </property> <property name="arguments"> <list> <bean class="com.fasterxml.jackson.datatype.guava.GuavaModule" /> </list> </property> </bean> <bean id="abstractMappingJacksonHttpMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" abstract="true"/> <bean id="abstractMappingJacksonJsonView" class="org.springframework.web.servlet.view.json.MappingJackson2JsonView" abstract="true" p:extractValueFromSingleKeyModel="true"/> <bean id="jacksonObjectMapper" parent="abstractJacksonObjectMapper" /> <bean id="mappingJacksonHttpMessageConverter" parent="abstractMappingJacksonHttpMessageConverter" p:objectMapper-ref="jacksonObjectMapper" p:supportedMediaTypes="application/json" /> <bean id="mappingJacksonJsonView" parent="abstractMappingJacksonJsonView" p:objectMapper-ref="jacksonObjectMapper" p:contentType="application/json" /> 

I also tried this other approach using extended Jackson2ObjectMapperFactoryBean:

 <!-- Json rendering configuration--> <bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"> <property name="order" value="1" /> <property name="mediaTypes"> <map> <entry key="json" value="application/json" /> <entry key="xml" value="application/xml" /> </map> </property> <property name="defaultViews"> <list> <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"> <property name="objectMapper"> <bean class="my.package.Jackson2ObjectMapperFactoryBean"/> </property> </bean> </list> </property> <property name="ignoreAcceptHeader" value="true" /> </bean> 

And then FactoryBean looks like this:

 package my.package; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.guava.GuavaModule; public class Jackson2ObjectMapperFactoryBean extends org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean{ public ObjectMapper getObject(){ ObjectMapper objectMapper =super.getObject(); objectMapper.registerModule(new GuavaModule()); return objectMapper; } 

}

I have a Test class that works fine and doesn't use Spring at all (just testing Table.class + Jackson + guava) Simplified:

 Table study = getTable(); ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new GuavaModule()); String tableString = mapper.writeValueAsString(table); 

It serializes it correctly:

 { "fields":{ "Field1":[ { "index":0, "header":"Field1", "fieldType":"fieldtype", "description":null, "cleanHeader":null } ], "Field2":[ { "index":1, "header":"Field2", "fieldType":"fieldtype", "description":null, "cleanHeader":null } ] } } 

Using Spring (either of the two approaches), I get:

 { "fields":{ "empty": false } } 

My controller has the @ResponseBody annotation and returns a table.

EDITED: I am debugging the deep classes Spring (firs time, ;-)) and org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor processes the request. This is due to my problem ... Is my Spring xml somehow contradicting @ResponseBody annotations?

Any ideas what I'm doing wrong?

NOTE. I need a multimap, cannot be a standard Java build.

+5
source share
3 answers

In the end, I found out that the @ResponseBody annotation forced me to use another "viewResolver" that Jackson used, but without the Guava module.

So, to fix this, I removed the @ResponseBody annotation in my controller method:

 <!-- language: java --> @ResponseBody @RequestMapping("table") public Table getTable() { 

Unfortunately, this returned: {table: {...}} and was an artifact introduced by ValueHandlder (ModelAttributeMethodProcessor).

In the end, it now works:

1.- Restore @ResponseBody 2. Register the Guava module in MappingJackson2HttpMessageConverter, which is used by @ResponseBody handler by default.

Here's what spring xml looks like: much cleaner and simpler:

 <!-- JSON parser configuration--> <bean id="guavaObjectMapper" class="com.fasterxml.jackson.databind.ObjectMapper"/> <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="targetObject"><ref local="guavaObjectMapper" /></property> <property name="targetMethod"><value>registerModule</value></property> <property name="arguments"> <list> <bean id="guavaModule" class="com.fasterxml.jackson.datatype.guava.GuavaModule"/> </list> </property> </bean> <mvc:annotation-driven> <mvc:message-converters register-defaults="true"> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="objectMapper"> <ref local="guavaObjectMapper"/> </property> </bean> </mvc:message-converters> </mvc:annotation-driven> 
0
source

It is wrong if the solution below exactly matches your problem, but just in case you want to configure SpringBootApplication to serialize guava, you can improve your @Configuration class with @Bean as follows:

 @Configuration public class Application extends SpringBootServletInitializer { @Bean ObjectMapper customizeJacksonConfiguration() { ObjectMapper om = new ObjectMapper(); om.registerModule(new GuavaModule()); return om; } } 
+3
source

I think you need to add the mvc: messsage-converters section to your spring.xml. For instance,

  <mvc:annotation-driven ignoreDefaultModelOnRedirect="true" > <mvc:message-converters> <bean class="org.springframework.http.converter.ResourceHttpMessageConverter"/> <bean class="org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter"/> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/> </mvc:message-converters> </mvc:annotation-driven> 
+1
source

Source: https://habr.com/ru/post/1207057/


All Articles