【问题标题】:Custom HttpMessageConverter with @ResponseBody to do Json things使用 @ResponseBody 自定义 HttpMessageConverter 来做 Json 事情
【发布时间】:2011-06-28 11:55:34
【问题描述】:

我不喜欢杰克逊。

我想使用 ajax,但要使用 Google Gson。

所以我试图弄清楚如何实现我自己的 HttpMessageConverter 以将其与 @ResponseBody 注释一起使用。 有人可以花点时间告诉我我应该走的路吗?我应该打开哪些配置? 另外我想知道我是否可以这样做并且仍然使用

提前致谢。

大约 3 天前,我已经在 Spring Community Foruns 中询问过,但没有得到答复,所以我在这里询问是否有更好的机会。 Spring Community Forums link to my question

我还在网上进行了详尽的搜索,发现了一些关于这个主题的有趣内容,但似乎他们正在考虑将其放在 Spring 3.1 中,而我仍在使用 spring 3.0.5: Jira's Spring Improvement ask

嗯...现在我正在尝试调试 Spring 代码以了解自己如何做到这一点,但我遇到了一些问题,就像我在这里所说的那样: Spring Framework Build Error

如果有其他方法可以做到这一点而我错过了,请告诉我。

【问题讨论】:

    标签: json spring spring-mvc gson


    【解决方案1】:

    嗯...很难找到答案,我必须遵循很多线索来获取不完整的信息,所以我认为在这里发布完整的答案会很好。所以下一个搜索这个会更容易。

    首先我必须实现自定义的 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-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>

    看到,为了使 FormaterValidator 工作,我们也必须构建一个自定义的 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); } }

    有趣的是,为了使配置在没有 annotaion-driven 标签的情况下工作,我们必须手动配置一个 AnnotationMethodHandlerAdapter 和一个 DefaultAnnotationHandlerMapping。为了使 AnnotationMethodHandlerAdapter 能够处理格式化和验证,我们必须配置一个 validator、一个 conversionService 并构建一个自定义 em>webBindingInitializer.

    我希望这一切对我以外的其他人有所帮助。

    在我绝望的搜索中,this@Bozho 的帖子非常有用。我也很感谢@GaryF,因为他的回答把我带到了@Bozho post。 对于尝试在 Spring 3.1 中执行此操作的您,请参阅@Robby Pond 的答案。要容易得多,不是吗?

    【讨论】:

    • 谢谢,我对 jackson 没问题,虽然我在想我会如何对 Gson 做同样的事情。顺便说一句,您可以使用 Apache Commons IOUtils 将字符串从 InputStream 中取出。我通常更喜欢尽可能使用 3rd 方库,因为当出现更快/更好的选项时我不需要改进代码,我只需在 maven 中更改版本:)。
    • 鉴于 AnnotationMethodHandlerAdapter 现在已弃用,同样的配置是否适用于 RequestMappingHandlerAdapter
    【解决方案2】:

    您需要创建一个扩展 AbstractHttpMessageConverter 的 GsonMessageConverter 并使用 mvc-message-converters 标记来注册您的消息转换器。该标签将使您的转换器优先于杰克逊的。

    【讨论】:

    • 请注意,我已经指定:“我还在网络上进行了详尽的搜索,发现了一些关于这个主题的有趣内容,但似乎他们正在考虑将它放在 Spring 3.1 中,我”我仍在使用 spring 3.0.5。”请参阅 Jira 改进请求上的链接
    • 这里也一样...有一个例子的答案会很好。
    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

      我遇到过使用 Jackson 需要我更改其他组(在同一公司中)的代码的情况。不喜欢那样。所以我选择使用Gson,根据需要注册TypeAdapters。

      连接了一个转换器并使用 spring-test(以前是 spring-mvc-test)编写了一些集成测试。无论我尝试了何种变体(使用 mvc:annotation-driven OR 手动定义 bean)。他们都没有工作。这些的任何组合总是使用不断失败的杰克逊转换器。

      答案> 原来 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();
         .
         .
      }
      

      希望这对某人有所帮助,最后我使用了注释驱动和重新利用 android 的转换器

      【讨论】:

        【解决方案5】:

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

        【讨论】:

          【解决方案6】:

          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>
          

          【讨论】:

          • 好的,我仍然可以使用 标签吗?这些标签自动注册的所有适配器都会被注册吗?或者我将不得不剥离注释驱动的标签并亲手完成所有配置?
          • 你是对的,解决方案是手动设置 AnnotationMethodHandlerAdapter 和 messageConverters 但这意味着我不能使用注释驱动,我必须手动进行所有设置。这太难了,我花了很多时间来完成我的目标,所以我会把它都贴在这里,所以如果其他人来搜索这个,这将是一个非常有用的答案。
          • @logui 很高兴你想出了其余的。看起来这在 Spring 3.1 中不会那么痛苦:blog.springsource.com/2011/02/21/…
          【解决方案7】:

          或如Jira's Spring Improvement ask 中所述,编写一个 BeanPostProcessor 将您的 HttpMessageConvertor 添加到 AnnotationMethodHandlerAdapter

          【讨论】:

            【解决方案8】:

            您可以通过将 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 "";
                }
            }
            

            【讨论】:

              猜你喜欢
              • 2018-04-12
              • 2018-10-30
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2015-01-14
              • 1970-01-01
              • 2021-04-10
              • 2018-04-09
              相关资源
              最近更新 更多