Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring MVC configuration + Jackson + Guava multimap

I'm struggling with this:

We have a Table class with a Guava multimap (simplified code, basically 1 member, 2 constructors, getter and setter for the 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 serialise this using Spring MVC 3.2.11 using jackson 2.4.3.

POM relevant dependencies are:

<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 (following this 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 an 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 the 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 is not using 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 serialises it properly:

{
  "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 (any of the 2 approaches) I'm getting:

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

My controller has a @ResponseBody annotation and it's returning a Table.

EDITED: I'm debugging deep into spring classes (firs time, ;-)) and org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor is handling the request. Is this related to my problem...Is my spring xml, somehow in contradiction with the @ResponseBody annotation?

Any ideas what I'm doing wrong?

NOTE: I need the Multimap, can't be a standard Java collection.

like image 702
Pablo Avatar asked Nov 17 '14 18:11

Pablo


People also ask

How to configure all beans in Spring MVC using Java?

It uses only java annotation to config all beans. 2. Create The Example Project. Click File —> New —> Spring Legacy Project menu item to open the New Spring Legacy Project window, then select Spring MVC Project template in the Spring Legacy Project wizard dialog.

What is guava multimap implementation?

Multimap Implementation In the case of Guava Multimap, if we add two values for the same key, the second value will not override the first value. Instead, we will have two values in the resulting map.

How to configure viewresolver in Spring MVC using Servlet?

Treat as the configuration file for Spring MVC-enabled applications. Also, we use the @Bean tag to register ViewResolver. We use InternalResourceViewResolver. Create another class, which will replace our traditional web.xml. We use Servlet 3.0 and extend the org.springframework.web.WebApplicationInitializer class.

How to mark a class as a controller in Spring MVC?

@Controller annotation is used to mark that class as controller. Front Controller: It remains responsible for managing the flow of the web application. DispatcherServelet acts as a front controller in spring MVC. Eclipse (EE version). Tomcat Apache latest version. Step 1: Go to File menu and click on New -> Maven Project.


2 Answers

Dunno if the solution below fits your problem exactly, but just in case you want to configure your SpringBootApplication to also serialize guava, you could enhance your @Configuration class using a @Bean like this:

@Configuration
public class Application extends SpringBootServletInitializer {

    @Bean
    ObjectMapper customizeJacksonConfiguration() {
        ObjectMapper om = new ObjectMapper();
        om.registerModule(new GuavaModule());
        return om;
    }
}
like image 93
helt Avatar answered Sep 30 '22 00:09

helt


I think you need to add an 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>
like image 28
Katerina A. Avatar answered Sep 30 '22 00:09

Katerina A.