2011-02-16 20 views
30

No me gusta Jackson.HttpMessageConverter personalizado con @ResponseBody para hacer cosas Json

Quiero usar ajax pero con Google Gson.

Así que estoy tratando de descubrir cómo implementar mi propio HttpMessageConverter para usarlo con la anotación @ResponseBody. ¿Puede alguien tomarse un tiempo para mostrarme el camino por el que debería ir? ¿Qué configuraciones debería activar? También me pregunto si puedo hacer esto y seguir usando < mvc: annotation-driven/>?

Gracias de antemano.

Ya lo he preguntado en Spring Community Foruns hace 3 días sin respuesta, así que estoy preguntando aquí para ver si tengo una mejor oportunidad. Spring Community Forums link to my question

También he hecho una búsqueda exhaustiva en la web y encontrar algo interesante sobre este tema, pero parece que están pensando ponerlo en la primavera de 3,1 y todavía estoy usando la primavera 3.0.5: Jira's Spring Improvement ask

Bueno ... ahora estoy tratando de código de primavera de depuración para encontrar a mí mismo cómo hacer esto, pero estoy teniendo algunos problemas como he dicho aquí: Spring Framework Build Error

Si hay otro forma de hacer esto y me falta, por favor, hágamelo saber.

Respuesta

35

Bueno ... era tan difícil de encontrar la respuesta y tuve que seguir para muchas pistas sobre información incompleta que creo que será bueno publicar la respuesta completa aquí. Por lo tanto, será más fácil para el siguiente buscar esto.

primero tenía que poner en práctica el HttpMessageConverter personalizado:

 

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 ""; 
     } 
    } 

} 
 

Luego tuve que quitarse la etiqueta annnotaion impulsada y configurar todo por mis propias manos en el fichero de configuración de primavera-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> 
 

Ver que, para hacer el Formater y Validador funcione, tenemos que construir una costumbre webBindingInitializer también:

 

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); 
    } 

} 
 

una cosa interesante que vemos es que el fin de hacer el trabajo de configuración sin la etiqueta de annotaion impulsada , tenemos que configurar manualmente un AnnotationMethodHandlerAdapter y una DefaultAnnotationHandlerMapping.Y con el fin de hacer que el AnnotationMethodHandlerAdapter capaz de manejar el formato y validación, hemos tenido que configurar un validador , un conversionService y construir una costumbre webBindingInitializer.

Espero que todo esto ayude a alguien más aparte de mí.

En mi búsqueda desesperada, this @Bozho publicación fue extremadamente útil. También estoy agradecido a @GaryF por su respuesta que me llevó al @Bozho post. Para ustedes que están tratando de hacer esto en la primavera 3.1, ver la respuesta de @Robby Pond .. Mucho más fácil, ¿no?

+0

Gracias, yo Estoy de acuerdo con Jackson, aunque estaba pensando en cómo haría lo mismo con Gson. Por cierto, podrías haber usado Apache Commons IOUtils para sacar la cadena del InputStream. Normalmente prefiero usar bibliotecas de terceros donde sea posible, ya que no necesito mejorar el código cuando aparece una opción más rápida/mejor, solo cambio la versión en maven :). – Stef

+0

Dado que 'AnnotationMethodHandlerAdapter' ahora está en desuso, ¿funciona esta misma configuración con' RequestMappingHandlerAdapter'? – Stewart

16

Necesita crear un GsonMessageConverter que extienda AbstractHttpMessageConverter y use la etiqueta m vc-message-converters para registrar su convertidor de mensajes. Esa etiqueta permitirá que tu convertidor tenga prioridad sobre el de Jackson.

+0

Tenga en cuenta que he especificado: "También he hecho una búsqueda exaustive en la web y encontrar algo interesante en este tema, pero parece que están pensando ponerlo en la primavera de 3,1 y todavía estoy usando la primavera 3.0.5. " Vea el enlace en la Solicitud de Mejora de Jira – Iogui

+0

Lo mismo aquí ... una respuesta con un ejemplo sería agradable. – 4gus71n

3

Robby Pond es básicamente correcto, pero tenga en cuenta que su sugerencia de utilizar la etiqueta mvc: message-converters requiere que utilice 3.1. Dado que en la actualidad sólo 3.1 es un lanzamiento hito (M1), sugeriría el registro de su convertidor de esta manera después de crearlo:

<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> 
+0

Ok, y aún puedo usar la etiqueta ? Todos los adaptadores registrados automáticamente por estas etiquetas serán registrados? ¿O tendré que quitarme la etiqueta impulsada por la anotación y hacer toda la configuración con mis propias manos? – Iogui

+0

Tiene razón, la solución es configurar manualmente AnnotationMethodHandlerAdapter y sus messageConverters, pero significa que no puedo usar Annotaion-driven y tengo que hacer toda la configuración manualmente. Fue tan difícil y me tomó tanto tiempo llegar a mi objetivo que lo publicaré todo aquí, así que si alguien más viene a buscarlo, será una respuesta realmente útil. – Iogui

+0

@logui Me alegra que haya descubierto el resto.Parece que esto será menos doloroso en Spring 3.1: http://blog.springsource.com/2011/02/21/spring-3-1-m1-mvc-namespace-enhancements-and-configuration/ – GaryF

5

tuve situación en la que el uso de Jackson me requeriría para alterar el código de otro grupo (en la misma empresa). No me gustó eso. Así que elegí usar Gson y registrar TypeAdapters según sea necesario.

Enganchó un convertidor y escribió algunas pruebas de integración con spring-test (que solía ser spring-mvc-test). No importa qué variación intenté (usando mvc: anotación o definición manual del bean). Ninguno de ellos funcionó. Cualquier combinación de estos siempre usó el convertidor Jackson que siguió fallando.

Respuesta> Resulta que el método standaloneSetup de MockMvcBuilders "duro" codificó los convertidores de mensajes en versiones predeterminadas e ignoró todos mis cambios anteriores. Esto es lo que funcionó:

@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(); 
    . 
    . 
} 

Espero que esto ayude a alguien, al final he usado anotación impulsada y de nuevo propósito convertidor de androide

3

Si desea añadir un convertidor de mensajes sin jugar con XML que aquí hay una ejemplo sencillo

@Autowired 
private RequestMappingHandlerAdapter adapter; 

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

puede hacer esto escribiendo el fil WebConfig e como un archivo de Java. Extienda su archivo de configuración con WebMvcConfigurerAdapter y anule el método extendMessageConverters para agregar su Convertidor de mensajes intentado. Este método retendrá los convertidores predeterminados agregados por Spring y agregará su convertidor al final. Aparentemente tienes el control total de la lista y puedes agregar donde quieras en la lista.

@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 ""; 
    } 
} 
Cuestiones relacionadas