json - gson maven
HttpMessageConverter personalizado con @ResponseBody para hacer cosas Json (8)
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<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 quitarme la etiqueta impulsada por annnotaion y configurar todo con mis propias manos en el archivo de configuración 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>
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);
}
}
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, this publicación de @Bozho fue extremadamente útil. También estoy agradecido con @GaryF porque su respuesta me llevó a la this . Para ustedes que están tratando de hacer esto en la primavera 3.1, ver @Robby Pond responder ... Mucho más fácil, ¿no?
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 <mvc: annotation-driven />?
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 primavera 3.0.5: Mejora de primavera 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.
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.
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<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 "";
}
}
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:
<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>
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<HttpMessageConverter<?>> messageConverters = adapter.getMessageConverters();
BufferedImageHttpMessageConverter imageConverter = new BufferedImageHttpMessageConverter();;
messageConverters.add(0,imageConverter);
}
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<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 utilicé el conversor de android impulsado por anotaciones y reorientación