web-dev-qa-db-fra.com

Convertisseur HttpMessageConverter personnalisé avec @ResponseBody pour effectuer des opérations Json

Je n'aime pas Jackson.

Je veux utiliser ajax mais avec Google Gson.

J'essaie donc de comprendre comment implémenter mon propre HttpMessageConverter pour l'utiliser avec l'annotation @ResponseBody .Quelqu'un peut-il prendre un peu de temps pour me montrer la voie à suivre? Quelles configurations dois-je activer? Je me demande également si je peux le faire tout en utilisant <mvc: annotation-driven />?

Merci d'avance.

Je l'ai déjà posée aux forums communautaires du printemps il y a environ 3 jours sans réponse, alors je demande ici si ma chance est meilleure . Les forums des communautés de printemps pointent vers ma question

J'ai également fait une recherche exhaustive sur le Web et trouvé quelque chose d'intéressant à ce sujet, mais il semble qu'ils envisagent de le mettre dans Spring 3.1 et j'utilise toujours le printemps 3.0.5: Jira's Spring Improvement demander

Eh bien ... maintenant, j'essaye de déboguer le code Spring pour trouver moi-même comment faire cela, mais j'ai des problèmes, comme je l'ai dit ici: Erreur de compilation de framework Spring

S'il y a un autre moyen de le faire et que je le manque, faites le moi savoir.

30
Iogui

Eh bien ... il était si difficile de trouver la réponse et j'ai dû suivre tellement d'indices d'informations incomplètes que je pense que ce sera bien de poster la réponse complète ici. Il sera donc plus facile pour le prochain de chercher cela.

J'ai d'abord dû implémenter le HttpMessageConverter personnalisé:


package net.iogui.web.spring.converter;

import Java.io.BufferedReader;
import Java.io.IOException;
import Java.io.InputStream;
import Java.io.InputStreamReader;
import Java.io.Reader;
import Java.io.StringWriter;
import Java.io.Writer;
import Java.nio.charset.Charset;

import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;

import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;

public class GsonHttpMessageConverter extends AbstractHttpMessageConverter<Object> {

    private Gson gson = new Gson();

    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    public GsonHttpMessageConverter(){
        super(new MediaType("application", "json", DEFAULT_CHARSET));
    }

    @Override
    protected Object readInternal(Class<? extends Object> clazz,
                                  HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {

        try{
            return gson.fromJson(convertStreamToString(inputMessage.getBody()), clazz);
        }catch(JsonSyntaxException e){
            throw new HttpMessageNotReadableException("Could not read JSON: " + e.getMessage(), e);
        }

    }

    @Override
    protected boolean supports(Class<?> clazz) {
        return true;
    }

    @Override
    protected void writeInternal(Object t, 
                                 HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {

        //TODO: adapt this to be able to receive a list of json objects too

        String json = gson.toJson(t);

        outputMessage.getBody().write(json.getBytes());
    }

    //TODO: move this to a more appropriated utils class
    public String convertStreamToString(InputStream is) throws IOException {
        /*
         * To convert the InputStream to String we use the Reader.read(char[]
         * buffer) method. We iterate until the Reader return -1 which means
         * there's no more data to read. We use the StringWriter class to
         * produce the string.
         */
        if (is != null) {
            Writer writer = new StringWriter();

            char[] buffer = new char[1024];
            try {
                Reader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
                int n;
                while ((n = reader.read(buffer)) != -1) {
                    writer.write(buffer, 0, n);
                }
            } finally {
                is.close();
            }
            return writer.toString();
        } else {
            return "";
        }
    }

}

Ensuite, j'ai dû déshabiller la balise gérée par l'annotation et configurer tout seul le fichier de configuration spring-mvc:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <!-- Configures the @Controller programming model -->

    <!-- To use just with a JSR-303 provider in the classpath 
    <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
    -->

    <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean" />

    <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
        <property name="webBindingInitializer">
            <bean class="net.iogui.web.spring.util.CommonWebBindingInitializer" />
        </property>
        <property name="messageConverters">
            <list>
                <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter" />
                <bean class="org.springframework.http.converter.StringHttpMessageConverter" />
                <bean class="org.springframework.http.converter.ResourceHttpMessageConverter" />
                <bean class="net.iogui.web.spring.converter.GsonHttpMessageConverter" />
                <bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter" />
                <bean class="org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter" />
                <!-- bean class="org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter" /-->
            </list>
        </property>
    </bean>
    <bean id="handlerMapping" class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />


    <context:component-scan base-package="net.iogui.teste.web.controller"/>

    <!-- Forwards requests to the "/" resource to the "login" view -->
    <mvc:view-controller path="/" view-name="home"/>

    <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources/ directory -->
    <mvc:resources mapping="/resources/**" location="/resources/" />

    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
        <property name="prefix" value="/WEB-INF/view/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

</beans>

Voir cela, pour que Formater et Validator fonctionnent, nous devons aussi créer un webBindingInitializer personnalisé:


package net.iogui.web.spring.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.ConversionService;
import org.springframework.validation.Validator;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.context.request.WebRequest;

public class CommonWebBindingInitializer implements WebBindingInitializer {

    @Autowired(required=false)
    private Validator validator;

    @Autowired
    private ConversionService conversionService;

    @Override
    public void initBinder(WebDataBinder binder, WebRequest request) {
        binder.setValidator(validator);
        binder.setConversionService(conversionService);
    }

}

Il est intéressant de constater que, pour que la configuration fonctionne sans la balise annotaion-driven, nous devons configurer manuellement un AnnotationMethodHandlerAdapter et un DefaultAnnotationHandlerMapping. Et pour rendre AnnotationMethodHandlerAdapter capable de gérer le formatage et la validation, nous avons dû configurer un validator, un conversionService et créer un webBindingInitializer personnalisé.

J'espère que tout cela aide quelqu'un d'autre que moi.

Lors de ma recherche désespérée, ce @Bozho post était extrêmement utile. Je suis également reconnaissant à @GaryF couse que sa réponse m’a amenée au @Bozho post . Pour vous qui tentez de le faire au printemps 3.1, voir la réponse de @Robby Pond .. Beaucoup plus facile, je vous en prie. t-il?

37
Iogui

Vous devez créer un convertisseur GsonMessageConverter qui s'étend AbstractHttpMessageConverter et utiliser le tag m vc-message-converters pour enregistrer votre convertisseur de message. Cette balise permettra à votre convertisseur de prévaloir sur celui de Jackson.

16
Robby Pond

Si vous souhaitez ajouter un convertisseur de message sans vous occuper de XML, voici un exemple simple

@Autowired
private RequestMappingHandlerAdapter adapter;

@PostConstruct
public void initStuff() {
    List<HttpMessageConverter<?>> messageConverters = adapter.getMessageConverters();
    BufferedImageHttpMessageConverter imageConverter = new BufferedImageHttpMessageConverter();;
    messageConverters.add(0,imageConverter);
}
5
Erik Martino

J'étais dans une situation où l'utilisation de Jackson m'obligerait à modifier le code d'un autre groupe (dans la même société). Je n'ai pas aimé ça. J'ai donc choisi d'utiliser Gson et d'enregistrer TypeAdapters au besoin. 

Connecté un convertisseur et écrit quelques tests d'intégration en utilisant spring-test (test de printemps-mvc-test). Quelle que soit la variante que j'ai essayée (avec mvc: définition manuelle du bean pilotée par annotation OR). Aucun d'entre eux a travaillé. Toute combinaison de ceux-ci a toujours utilisé le convertisseur de Jackson qui échouait.

Answer > Il s'avère que la méthode standaloneSetup de MockMvcBuilders "hard" a codé les convertisseurs de messages aux versions par défaut et a ignoré toutes mes modifications ci-dessus. Voici ce qui a fonctionné:

@Autowired
private RequestMappingHandlerAdapter adapter;

public void someOperation() {
  StandaloneMockMvcBuilder smmb = MockMvcBuilders.standaloneSetup(controllerToTest);
  List<HttpMessageConverter<?>> converters = adapter.getMessageConverters();
  HttpMessageConverter<?> ary[] = new HttpMessageConverter[converters.size()];
  smmb.setMessageConverters(conveters.toArray(ary));
  mockMvc = smmb.build();
   .
   .
}

J'espère que cela va aider quelqu'un. Au final, j'ai utilisé le convertisseur Android basé sur des annotations et une nouvelle utilisation.

5
ravinukala

Notez que GsonHttpMessageConverter a été ajouté récemment à Spring (4.1)

3
maxb3k

Ou, comme indiqué dans Jira's Spring Improvement, demandez , écrivez un BeanPostProcessor qui ajoute votre HttpMessageConvertor à la AnnotationMethodHandlerAdapter

3
NevinJ

Robby Pond est fondamentalement correct, mais notez que sa suggestion d'utiliser la balise mvc: message-converters nécessite l'utilisation de la version 3.1. Étant donné que la version 3.1 n’est actuellement qu’une version jalon (M1), je vous suggère d’enregistrer votre convertisseur de cette manière après sa création:

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="messageConverters">
      <util:list id="beanList">
        <ref bean="someMessageConverter"/>
        <ref bean="someOtherMessageConverter"/>
      </util:list>
    </property>
</bean>
3
GaryF

Vous pouvez le faire en écrivant le fichier WebConfig en tant que fichier Java. Étendez votre fichier de configuration avec WebMvcConfigurerAdapter et substituez la méthode extendMessageConverters pour ajouter votre convertisseur de messages voulu. Cette méthode conservera les convertisseurs par défaut ajoutés par Spring et ajoutera votre convertisseur à la fin. Apparemment, vous avez le contrôle total sur la liste et vous pouvez ajouter où vous voulez dans la liste. 

@Configuration
@EnableWebMvc
@ComponentScan(basePackageClasses={WebConfig.class})
public class WebConfig extends WebMvcConfigurerAdapter {
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
      converters.add(new GsonHttpMessageConverter());
   }
}

package net.iogui.web.spring.converter;

import Java.io.BufferedReader;
import Java.io.IOException;
import Java.io.InputStream;
import Java.io.InputStreamReader;
import Java.io.Reader;
import Java.io.StringWriter;
import Java.io.Writer;
import Java.nio.charset.Charset;

import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;

import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;

public class GsonHttpMessageConverter extends AbstractHttpMessageConverter<Object> {

private Gson gson = new Gson();

public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

public GsonHttpMessageConverter(){
    super(new MediaType("application", "json", DEFAULT_CHARSET));
}

@Override
protected Object readInternal(Class<? extends Object> clazz,
                              HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {

    try{
        return gson.fromJson(convertStreamToString(inputMessage.getBody()), clazz);
    }catch(JsonSyntaxException e){
        throw new HttpMessageNotReadableException("Could not read JSON: " + e.getMessage(), e);
    }

}

@Override
protected boolean supports(Class<?> clazz) {
    return true;
}

@Override
protected void writeInternal(Object t, 
                             HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {

    //TODO: adapt this to be able to receive a list of json objects too

    String json = gson.toJson(t);

    outputMessage.getBody().write(json.getBytes());
}

//TODO: move this to a more appropriated utils class
public String convertStreamToString(InputStream is) throws IOException {
    /*
     * To convert the InputStream to String we use the Reader.read(char[]
     * buffer) method. We iterate until the Reader return -1 which means
     * there's no more data to read. We use the StringWriter class to
     * produce the string.
     */
    if (is != null) {
        Writer writer = new StringWriter();

        char[] buffer = new char[1024];
        try {
            Reader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
            int n;
            while ((n = reader.read(buffer)) != -1) {
                writer.write(buffer, 0, n);
            }
        } finally {
            is.close();
        }
        return writer.toString();
    } else {
        return "";
    }
}
0
PRABHU P.S