2012-10-19 91 views
21

假設我有一個控制器,供應GET請求,並返回豆被序列化到JSON並且還提供了IllegalArgumentException異常處理程序可以在服務方式籌集:如何更改異常處理程序的內容類型

@RequestMapping(value = "/meta/{itemId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) 
@ResponseBody 
public MetaInformation getMetaInformation(@PathVariable int itemId) { 
    return myService.getMetaInformation(itemId); 
} 

@ExceptionHandler(IllegalArgumentException.class) 
@ResponseStatus(value = HttpStatus.BAD_REQUEST) 
@ResponseBody 
public String handleIllegalArgumentException(IllegalArgumentException ex) { 
    return ExceptionUtils.getStackTrace(ex); 
} 

信息轉換器有:

<mvc:annotation-driven> 
    <mvc:message-converters> 
     <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" /> 
     <bean class="org.springframework.http.converter.StringHttpMessageConverter" /> 
    </mvc:message-converters> 
</mvc:annotation-driven> 

現在,當我要求給定的URL在瀏覽器中我看到了正確的JSON回覆。但是,如果引發異常,字符串化的異常也會轉換爲JSON,但是我希望它能夠被StringHttpMessageConverter(導致text/plain mime類型)處理。我怎麼去呢?

爲了使畫面更完整的(複雜),假設我也有以下處理:

@RequestMapping(value = "/version", method = RequestMethod.GET) 
@ResponseBody 
public String getApplicationVersion() { 
    return "1.0.12"; 
} 

這個處理器允許返回串,既MappingJackson2HttpMessageConverterStringHttpMessageConverter根據傳入Accept-type被序列化客戶。返回類型和值應爲:

 
+----+---------------------+-----------------------+------------------+-------------------------------------+ 
| NN | URL     | Accept-type   | Content-type  | Message converter     | 
| |      | request header  | response header |          | 
+----+---------------------+-----------------------+------------------+-------------------------------------+ 
| 1. | /version   | text/html; */*  | text/plain  | StringHttpMessageConverter   | 
| 2. | /version   | application/json; */* | application/json | MappingJackson2HttpMessageConverter | 
| 3. | /meta/1    | text/html; */*  | application/json | MappingJackson2HttpMessageConverter | 
| 4. | /meta/1    | application/json; */* | application/json | MappingJackson2HttpMessageConverter | 
| 5. | /meta/0 (exception) | text/html; */*  | text/plain  | StringHttpMessageConverter   | 
| 6. | /meta/0 (exception) | application/json; */* | text/plain  | StringHttpMessageConverter   | 
+----+---------------------+-----------------------+------------------+-------------------------------------+ 

回答

17

我覺得從getMetaInformation@RequestMapping去除produces = MediaType.APPLICATION_JSON_VALUE會給你想要的結果。

響應類型將根據Accept頭中的內容類型值進行協商。


編輯

因爲這並不能掩蓋方案3,4這裏與ResponseEntity.class工作直接的解決方案:

@ExceptionHandler(Exception.class) 
public ResponseEntity<String> handleIllegalArgumentException(Exception ex) { 
    HttpHeaders headers = new HttpHeaders(); 
    headers.setContentType(MediaType.TEXT_PLAIN); 
    return new ResponseEntity<String>(ex.getMessage(), headers, HttpStatus.BAD_REQUEST); 
} 
+0

聽起來不錯。但是如何處理場景(3,4)? –

+0

感謝提供'ResponseEntity'的提示!怎麼樣爲'StringHttpMessageConverter'設置'supportedMediaTypes'屬性(見[我的答案](http://stackoverflow.com/a/12979543/267197))?也可能是一個解決方案。 –

+2

我剛剛用'ResponseEntity'檢查了你的解決方案:它不起作用。內容類型被消息轉換器覆蓋,並且通過相交'Accept-type'和轉換器'supportedMediaTypes'選擇消息轉換器(粗略算法)。 –

8

有與問題相關的幾個方面:

  • StringHttpMessageConverter加貓ch-all MIME類型*/*到支持的媒體類型列表,而MappingJackson2HttpMessageConverter僅綁定到application/json
  • @RequestMapping正在提供produces = ...,該值被存儲在HttpServletRequest(見RequestMappingInfoHandlerMapping.handleMatch()),並且當錯誤處理程序被調用時,這個mime類型被自動繼承和使用。

在簡單的情況下,該解決方案將是把StringHttpMessageConverter在列表的首位:

@RequestMapping(value = "/meta/{itemId}", method = RequestMethod.GET) 
@ResponseBody 
public MetaInformation getMetaInformation(@PathVariable int itemId) { 
    return myService.getMetaInformation(itemId); 
} 

現在:

    <mvc:annotation-driven> 
        <mvc:message-converters> 
         <bean class="org.springframework.http.converter.StringHttpMessageConverter"> 
          <property name="supportedMediaTypes"> 
           <array> 
            <util:constant static-field="org.springframework.http.MediaType.TEXT_PLAIN" /> 
           </array> 
          </property> 
         </bean> 
         <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" /> 
        </mvc:message-converters> 
    </mvc:annotation-driven> 
    

    ,也從@RequestMapping註釋去掉produces

  • StringHttpMessageConverter將丟棄所有類型,只有MappingJackson2HttpMessageConverter可以處理(MetaInformation,java.util.Collection等),允許它們進一步傳遞。
  • 如果場景(5,6)有異常,則優先使用StringHttpMessageConverter

到目前爲止好,但不幸的是,事情變得更復雜與ObjectToStringHttpMessageConverter。對於處理程序返回類型java.util.Collection<MetaInformation>,此消息轉換器將報告它可以將此類型轉換爲java.lang.String。限制來自收集元素類型被擦除並且AbstractHttpMessageConverter.canWrite(Class<?> clazz, MediaType mediaType)方法獲得java.util.Collection<?>類用於檢查的事實,但是當涉及到轉換步驟ObjectToStringHttpMessageConverter失敗時。爲了解決我們保持produces@RequestMapping註釋應當使用JSON轉換器的問題,而是要強制進行異常處理程序正確的內容類型,我們將刪除HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE屬性從HttpServletRequest

@ExceptionHandler(IllegalArgumentException.class) 
@ResponseStatus(value = HttpStatus.BAD_REQUEST) 
@ResponseBody 
public String handleIllegalArgumentException(HttpServletRequest request, IllegalArgumentException ex) { 
    request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE); 
    return ExceptionUtils.getStackTrace(ex); 
} 

@RequestMapping(value = "/meta", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) 
@ResponseBody 
public Collection<MetaInformation> getMetaInformations() { 
    return myService.getMetaInformations(); 
} 

語境停留,因爲它本來就是相同的:

<mvc:annotation-driven> 
    <mvc:message-converters> 
     <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" /> 
     <bean class="org.springframework.http.converter.ObjectToStringHttpMessageConverter"> 
      <property name="conversionService"> 
       <bean class="org.springframework.context.support.ConversionServiceFactoryBean" /> 
      </property> 
      <property name="supportedMediaTypes"> 
       <array> 
        <util:constant static-field="org.springframework.http.MediaType.TEXT_PLAIN" /> 
       </array> 
      </property> 
     </bean> 
    </mvc:message-converters> 
</mvc:annotation-driven> 

現在的情況(1,2,3,4)被正確處理,因爲內容類型協商的,和場景(5,6)在異常處理程序進行處理。

或者,可以用數組代替集合返回類型,然後解決方案#1同樣是適用的:

@RequestMapping(value = "/meta", method = RequestMethod.GET) 
@ResponseBody 
public MetaInformation[] getMetaInformations() { 
    return myService.getMetaInformations().toArray(); 
} 

爲了討論:

我認爲AbstractMessageConverterMethodProcessor.writeWithMessageConverters()不應該從價值繼承類,而是來自方法簽名:

Type returnValueType = returnType.getGenericParameterType();

HttpMessageConverter.canWrite(Class<?> clazz, MediaType mediaType)應改爲:

canWrite(Type returnType, MediaType mediaType)

或(在情況下,它也限制了潛在的基於類的轉換器),以

canWrite(Class<?> valueClazz, Type returnType, MediaType mediaType)

然後參數化類型可以正確處理和解決方案#1將再次適用。

+0

謝謝!這是爲我工作的答案。特別是'request.removeAttribute'(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);'或者擴展名'request.setAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE,Collections.singleton(MediaType.APPLICATION_JSON));' –

相關問題