2011-02-16 62 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 ""; 
    } 
}