HttpMessageConverter personalizado con @ResponseBody para hacer cosas Json

No me gusta Jackson.

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 que debería tomar? ¿Qué configuraciones debería activar? También me pregunto si puedo hacer esto y seguir usando ?

Gracias por adelantado.

Ya lo solicité en Spring Community Foruns hace 3 días sin respuesta, así que pregunto aquí para ver si tengo una mejor oportunidad. Los foros de la comunidad de Spring enlazan a mi pregunta

También hice una búsqueda exhaustiva en la web y encontré algo interesante sobre este tema, pero parece que están pensando en ponerlo en Spring 3.1 y todavía estoy usando la spring 3.0.5: Mejora de spring de Jira pregunte

Bueno … ahora estoy tratando de depurar el código de Spring para averiguar cómo hacerlo, pero tengo algunos problemas, como he dicho aquí: Spring Framework Build Error

Si hay otra manera de hacer esto y me falta, por favor avíseme.

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

Primero tuve que implementar 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 { 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 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 quitarme la etiqueta impulsada por annnotaion y configurar todo con mis propias manos en el archivo de configuración spring-mvc:

                                
                                 

Mira, para que el Formateador y el Validador funcionen, también tenemos que crear un webBindingInitializer personalizado:

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); } }
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 ver es que para que la configuración funcione sin la etiqueta accionada por anotación , tenemos que configurar manualmente un AnnotationMethodHandlerAdapter y un DefaultAnnotationHandlerMapping . Y para hacer que AnnotationMethodHandlerAdapter sea capaz de manejar el formateo y la validación, tuvimos que configurar un validador , un servicio de conversión y crear un webBindingInitializer personalizado.

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

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

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.

Tuve una situación en la que el uso de Jackson requeriría que modificara 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 usando 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 de instalación autónoma de MockMvcBuilders codificó “duro” los convertidores de mensajes en versiones predeterminadas e ignoró todos mis cambios anteriores. Aquí está lo que funcionó:

 @Autowired private RequestMappingHandlerAdapter adapter; public void someOperation() { StandaloneMockMvcBuilder smmb = MockMvcBuilders.standaloneSetup(controllerToTest); List> 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 utilicé el conversor de android impulsado por anotaciones y reorientación

Si desea agregar un convertidor de mensajes sin jugar con xml aquí hay un ejemplo simple

 @Autowired private RequestMappingHandlerAdapter adapter; @PostConstruct public void initStuff() { List> messageConverters = adapter.getMessageConverters(); BufferedImageHttpMessageConverter imageConverter = new BufferedImageHttpMessageConverter();; messageConverters.add(0,imageConverter); } 

Robby Pond es básicamente correcto, pero tenga en cuenta que su sugerencia de usar la etiqueta mvc: message-converters requiere que use 3.1. Dado que 3.1 actualmente es solo un lanzamiento de un hito (M1), le sugiero que registre su convertidor de esta manera después de crearlo:

         

O como se menciona en Jira’s Spring Improvement pregunte , escriba BeanPostProcessor que agregue su HttpMessageConvertor al AnnotationMethodHandlerAdapter

Observe que GsonHttpMessageConverter se agregó recientemente a Spring (4.1)

Puede hacerlo escribiendo el archivo WebConfig como un archivo 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> 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 { 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 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 ""; } }