2011-02-16 61 views
30

我不喜欢杰克逊。用@ResponseBody自定义HttpMessageConverter做Json事情

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

所以我想弄清楚如何实现我自己的HttpMessageConverter使用它与@ResponseBody注释。 有人可以抽出时间向我展示我应该走的路吗?我应该打开哪些配置? 另外我想知道如果我可以做到这一点,仍然使用< mvc:注释驱动/ >?

在此先感谢。

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

,我也做了网络上的穷举搜索,发现一些有趣的关于这个问题,但似乎他们正在考虑把它放在春季3.1,我仍然使用Spring 3.0.5: Jira's Spring Improvement ask

嗯......现在我试图调试春代码,找出自己如何做到这一点,但我有像我在这里说的一些问题: Spring Framework Build Error

如果有另一个如何做到这一点,我错过了,请让我知道。

回答

35

嗯...这是很难找到答案,我只好跟着这样很多关于不完整信息的线索,我认为在这里发布完整的答案会很好。所以下一个搜索这个会更容易。

首先,我不得不实现自定义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驱动的标签,并通过我自己的弹簧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> 
 

看到的是,使格式化器验证工作,我们必须建立一个自定义的的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驱动标签的配置工作,我们必须手动配置AnnotationMethodHandlerAdapter上DefaultAnnotationHandlerMapping。而为了使AnnotationMethodHandlerAdapter上能够处理的格式和验证,我们不得不配置一个验证,一个conversionService并建立一个自定义的WebBindingInitializer

我希望这一切可以帮助别人,除了我。

在我的拼命搜索,this @Bozho职位是非常UTIL。我也很感谢@GaryF他的回答让我回到了@Bozho post。 要你正在尝试做这在Spring 3.1,看到@Robby池塘回答..轻松了很多,是不是?

16

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

+0

请注意,我指定了:“我也做了网络上的exaustive搜索,发现一些有趣的关于这个问题,但似乎他们正在考虑把它在春天3.1和我仍然使用弹簧3.0.5“。查看Jira的改进请求链接 – Iogui 2011-02-16 22:00:59

+0

同样在这里...一个例子的答案会很好。 – 4gus71n 2013-10-06 06:41:24

3

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

好,我仍然可以使用标记?所有通过这些标签自动注册的适配器都将被注册?或者我将不得不剥离标注驱动标签并亲自完成所有配置? – Iogui 2011-02-16 22:07:05

5

我不得不情况杰克逊的使用要求我改变其他组(在同一家公司)的代码。不喜欢那样。所以我选择使用Gson并根据需要注册TypeAdapter。

迷上了一个转换器和写使用弹簧测试(以前是弹簧MVC测试)的几个集成测试。无论我尝试了什么样的变体(使用mvc:注释驱动的或手动定义的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的转换器

3

如果你想添加一个消息变换而不这里与XML搞乱是简单的例子

@Autowired 
private RequestMappingHandlerAdapter adapter; 

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

您可以通过编写WebConfig FIL做到这一点e作为Java文件。使用WebMvcConfigurerAdapter扩展您的配置文件并覆盖extendMessageConverters方法以添加您的Intented消息转换器。此方法将保留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 ""; 
    } 
}