用@ResponseBody自定义HttpMessageConverter来做Json的事情

我不喜欢jackson。

我想用阿贾克斯,但与谷歌Gson。

所以我想弄清楚如何实现我自己的HttpMessageConverter与@ResponseBody注释使用它。 有人可以花一些时间让我看看我应该走的路吗? 我应该打开哪些configuration? 另外我想知道如果我可以做到这一点,仍然使用<mvc:annotation-driven />?

提前致谢。

我已经在3天前在Spring Community Foruns上提出了这个问题,但没有回答,所以我在这里问我是否有更好的机会。 春季社区论坛链接到我的问题

我也在网上进行了详尽的search,发现了一些有趣的东西,但似乎他们正在考虑把它放在Spring 3.1中,而我仍然使用Spring 3.0.5: Jira的Spring Improvement问

那么…现在我试图debuggingSpring代码来找出自己如何做到这一点,但我有一些像我在这里说过的问题: Spring框架生成错误

如果还有其他方法可以做到这一点,我错过了,请让我知道。

那么…很难find答案,我不得不遵循这么多的线索,不完整的信息,我认为这是完整的答案。 所以下一个search这个会更容易。

首先我必须实现自定义的HttpMessageConverter:

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

然后,我不得不剥离annnotaion驱动标签,并在spring-mvcconfiguration文件中亲自configuration:

<?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>
<?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> 

看到这个,为了使FormaterValidator正常工作,我们也必须build立一个自定义webBindingInitializer

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

有趣的是,为了使configuration在没有annotion-driven标签的情况下工作,我们必须手动configurationAnnotationMethodHandlerAdapterDefaultAnnotationHandlerMapping 。 为了使AnnotationMethodHandlerAdapter能够处理格式和validation,我们必须configuration一个validation器 ,一个conversionService和构build一个自定义webBindingInitializer

我希望这一切都能帮助除我之外的其他人。

在我绝望的search, 这个 @Bozhopost是非常util。 我也感谢@GaryF他的回答把我带到了@Bozho的post 。 对于你正在尝试在Spring 3.1中做到这一点,请参阅@Robby Pond答案..更容易,不是吗?

您需要创build一个扩展AbstractHttpMessageConverter的GsonMessageConverter,并使用m vc-message-converters标签注册您的消息转换器。 这个标签会让你的转换器优先于jackson。

我有这样的情况,即jackson的使用要求我改变其他组织(在同一个公司)的代码。 不喜欢那个 所以我select使用Gson并根据需要注册TypeAdapter。

挂钩了一个转换器,并使用弹簧testing(以前是spring-mvc-test)写了一些集成testing。 无论我尝试了什么样的变体(使用mvc:注释驱动的或手动定义的bean)。 他们都没有工作。 这些任何组合总是使用jackson转换器不断失败。

答案 > MockMvcBuilders的standaloneSetup方法“hard”将消息转换器编码为默认版本,并忽略了上面的所有更改。 这是什么工作:

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

希望这可以帮助别人,最后我使用注释驱动和重新利用Android的转换器

Robby Pond基本上是正确的,但请注意,他build议使用mvc:message-converters标签,要求您使用3.1。 由于3.1目前只是一个里程碑版本(M1),我build议在创build它之后以这种方式注册你的转换器:

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

或者如Jira的Spring改进请求中所述 ,编写一个BeanPostProcessor,将您的HttpMessageConvertor添加到AnnotationMethodHandlerAdapter

如果你想添加一个消息转换器而不用弄乱xml,这里是一个简单的例子

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

请注意, GsonHttpMessageConverter最近被添加到Spring(4.1)

您可以通过将WebConfig文件编写为Java文件来完成此操作。 使用WebMvcConfigurerAdapter扩展您的configuration文件并覆盖extendMessageConverters方法来添加您的消息转换器。 此方法将保留由Spring添加的默认转换器,并将在最后添加您的转换器。 显然你完全可以控制列表,你可以添加列表中的任何地方。

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