使用@ResponseBody 自定义 HttpMessageConverter 来做 Json 的事情
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/5019162/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Custom HttpMessageConverter with @ResponseBody to do Json things
提问by Iogui
I don't like Hymanson.
我不喜欢Hyman逊。
I want to use ajax but with Google Gson.
我想使用 ajax,但要使用 Google Gson。
So I'm trying to figure out how to implement my own HttpMessageConverter to use it with @ResponseBody annotation. Can someone take a time to show me the way I should go? What configurations should I turn on? Also I'm wondering if I can do this and still use <mvc:annotation-driven />?
所以我试图弄清楚如何实现我自己的 HttpMessageConverter 以将它与 @ResponseBody 注释一起使用。有人可以花点时间向我展示我应该走的路吗?我应该开启哪些配置?另外我想知道我是否可以做到这一点并且仍然使用 <mvc:annotation-driven />?
Thanks in advance.
提前致谢。
I've already asked it in Spring Community Foruns about 3 days ago with no answer so I'm asking here to see if I get a better chance. Spring Community Forums link to my question
大约 3 天前,我已经在 Spring Community Foruns 上问过这个问题,但没有答案,所以我在这里问我是否有更好的机会。 Spring 社区论坛链接到我的问题
I've also made an exhaustive search on the web and found something interesting on this subject but it seems they're thinking to put it in Spring 3.1 and I'm still using spring 3.0.5: Jira's Spring Improvement ask
我还在网上进行了详尽的搜索,发现了一些关于这个主题的有趣内容,但似乎他们正在考虑将它放在 Spring 3.1 中,而我仍在使用 spring 3.0.5: Jira 的 Spring Improvement ask
Well... now I'm trying to debug Spring code to find out myself how to do this, but I'm having some problems like I've said here: Spring Framework Build Error
嗯...现在我正在尝试调试 Spring 代码以找出自己如何执行此操作,但是我遇到了一些问题,就像我在这里所说的那样: Spring Framework Build Error
If there is another way to do this and I'm missing it, please let me know.
如果有其他方法可以做到这一点而我错过了它,请告诉我。
回答by Iogui
Well... it was so hard to find the answer and I had to follow so many clues to incomplete information that I think it will be good to post the complete answer here. So it will be easier for the next one searching for this.
嗯......找到答案太难了,我不得不按照很多不完整信息的线索,我认为在这里发布完整的答案会很好。所以下一个搜索这个会更容易。
First I had to implement the custom HttpMessageConverter:
首先,我必须实现自定义 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 "";
}
}
}
Then I had to strip off the annnotaion-driven tag and configure all by my own hands on the spring-mvc configuration file:
然后我不得不剥离 annnotaion-driven 标签,并在 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>
See that, to make the Formaterand Validatorto work, we have to build a custom webBindingInitializertoo:
看到,为了使Formater和Validator工作,我们也必须构建一个自定义的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);
}
}
An Interesting thing to see is that In order to make the configuration work without the annotaion-driventag, we have to manually configure a AnnotationMethodHandlerAdapterand a DefaultAnnotationHandlerMapping. And in order to make the AnnotationMethodHandlerAdaptercapable of handling formatting and validation, we had to configure a validator, a conversionServiceand to build a custom webBindingInitializer.
一个有趣的事情是,为了使配置在没有annotaion-driven标签的情况下工作,我们必须手动配置一个AnnotationMethodHandlerAdapter和一个DefaultAnnotationHandlerMapping。为了使AnnotationMethodHandlerAdapter能够处理格式化和验证,我们必须配置一个验证器、一个转换服务并构建一个自定义的webBindingInitializer。
I hope all this helps someone else besides me.
我希望这一切能帮助除我之外的其他人。
On my desperate search, this@Bozho post was extremely util. I am also grateful to @GaryF couse his answer took me to the @Bozho post. To you that are trying to do this in Spring 3.1, see @Robby Pond answer.. A lot easier, isn't it?
在我绝望的搜索中,这个@Bozho 帖子非常有用。我也很感谢@GaryF 因为他的回答把我带到了@Bozho 帖子。对于在 Spring 3.1 中尝试执行此操作的您,请参阅 @Robby Pond 的答案.. 容易多了,不是吗?
回答by Robby Pond
You need to create a GsonMessageConverter that extends AbstractHttpMessageConverterand use the mvc-message-converterstag to register your message converter. That tag will let your converter take precedence over the Hymanson one.
您需要创建一个扩展AbstractHttpMessageConverter的 GsonMessageConverter并使用 m vc-message-converters标签来注册您的消息转换器。该标签将使您的转换器优先于 Hymanson 转换器。
回答by ravinukala
I had situation where usage of Hymanson would require me to alter other group's (in the same company) code. Didn't like that. So I chose to use Gson and register TypeAdapters as needed.
我遇到过使用 Hymanson 需要我更改其他组(在同一家公司)的代码的情况。不喜欢那样。所以我选择使用Gson,根据需要注册TypeAdapters。
Hooked up a converter and wrote a few integration tests using spring-test (used to be spring-mvc-test). No matter what variation I tried (using mvc:annotation-driven OR manual definition of the bean). None of them worked. Any combination of these always used the Hymanson Converter which kept on failing.
连接了一个转换器并使用 spring-test(以前是 spring-mvc-test)编写了一些集成测试。无论我尝试过什么变化(使用 mvc:annotation-driven OR 手动定义 bean)。他们都没有工作。这些的任何组合总是使用不断失败的Hyman逊转换器。
Answer> Turns out that MockMvcBuilders' standaloneSetup method "hard" coded the message converters to default versions and ignored all my changes above. Here is what worked:
答案> 结果是 MockMvcBuilders 的 standaloneSetup 方法将消息转换器“硬”编码为默认版本,并忽略了我上面的所有更改。这是有效的:
@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();
.
.
}
Hope this helps someone, in the end I used annotation-driven and re-purposing android's converter
希望这对某人有所帮助,最后我使用了注释驱动并重新利用了 android 的转换器
回答by Erik Martino
If you want to add a message converter without messing with xml here is a simple example
如果你想在不搞乱 xml 的情况下添加消息转换器,这里是一个简单的例子
@Autowired
private RequestMappingHandlerAdapter adapter;
@PostConstruct
public void initStuff() {
List<HttpMessageConverter<?>> messageConverters = adapter.getMessageConverters();
BufferedImageHttpMessageConverter imageConverter = new BufferedImageHttpMessageConverter();;
messageConverters.add(0,imageConverter);
}
回答by maxb3k
Notice that GsonHttpMessageConverterwas added recently to Spring (4.1)
注意GsonHttpMessageConverter最近被添加到 Spring (4.1)
回答by GaryF
Robby Pond is basically correct, but note that his suggestion to use the mvc:message-converters tag requires that you use 3.1. Since 3.1 is currently only a milestone release (M1), I'd suggest registering your converter this way after creating it:
Robby Pond 基本上是正确的,但请注意,他建议使用 mvc:message-converters 标签要求您使用 3.1。由于 3.1 目前只是一个里程碑版本 (M1),我建议您在创建转换器后以这种方式注册它:
<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>
回答by NevinJ
Or as mentioned in Jira's Spring Improvement ask, write a BeanPostProcessor that adds your HttpMessageConvertorto the AnnotationMethodHandlerAdapter
或者如Jira's Spring Improvement ask 中所述,编写一个 BeanPostProcessor 将您HttpMessageConvertor的添加到AnnotationMethodHandlerAdapter
回答by PRABHU P.S
You can do this by writing the WebConfig file as a Java File. Extend your config file with WebMvcConfigurerAdapter and override extendMessageConverters method to add your intented Message Convertor. This method will retain the default converters added by Spring and will add your convertor at the end. Apparently you have full control with the list and you can add where ever you want in the list.
您可以通过将 WebConfig 文件编写为 Java 文件来完成此操作。使用 WebMvcConfigurerAdapter 扩展您的配置文件并覆盖 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 "";
}
}

