2012-10-19 15 views
21

Supponiamo che io sono un controller che serve GET richiesta e restituisce fagioli essere serializzato per JSON e fornisce anche un gestore di eccezioni per IllegalArgumentException che può essere sollevato in servizio:Come cambiare il tipo di contenuto nel gestore di eccezioni

@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); 
} 

convertitori messaggi sono:

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

Ora, quando chiedo il dato URL nel navigatore vedo la risposta JSON corretta. Tuttavia, se viene sollevata un'eccezione, l'eccezione stringificata viene convertita anche in JSON, ma mi piacerebbe che venisse elaborata da StringHttpMessageConverter (tipo risultante text/plain mime). Come posso andare?

per rendere l'immagine più completa (e complicato), supponiamo di avere anche il seguente gestore:

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

Questo gestore consente la stringa di ritorno da serializzare sia MappingJackson2HttpMessageConverter e StringHttpMessageConverter seconda in passato Accept-type dal cliente. I tipi e valori di ritorno devono essere il seguente:

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

risposta

17

Penso che la rimozione del produces = MediaType.APPLICATION_JSON_VALUE dal @RequestMapping del getMetaInformation vi darà il risultato desiderato.

Il tipo di risposta verrà negoziato in base al valore del tipo di contenuto nell'intestazione Accept.


modificare

Dato che questo non copre scenario 3,4 Ecco una soluzione di lavoro con ResponseEntity.class direttamente:

@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

Suona bene. Ma come verranno gestiti gli scenari (3,4)? –

+0

Grazie per il suggerimento con 'ResponseEntity'!Per quanto riguarda l'impostazione della proprietà 'supportedMediaTypes' per' StringHttpMessageConverter' (vedere [risposta mia] (http://stackoverflow.com/a/12979543/267197))? Potrebbe anche essere una soluzione. –

+2

Ho appena controllato la tua soluzione con 'ResponseEntity': non funziona. Il tipo di contenuto è sovrascritto dal convertitore di messaggi e il convertitore di messaggi viene scelto (algoritmi approssimativi) intersecando 'Accept-type' e convertitori 'supportedMediaTypes'. –

8

Ci sono diversi aspetti relativi al problema:

  • StringHttpMessageConverter aggiunge gatto ch-all mime type */* all'elenco dei tipi di media supportati, mentre MappingJackson2HttpMessageConverter è limitato a application/json.
  • Quando @RequestMapping sta fornendo produces = ..., questo valore viene memorizzato in HttpServletRequest (vedi RequestMappingInfoHandlerMapping.handleMatch()) e quando il gestore di errori viene chiamato, questo tipo MIME è ereditato e utilizzato automaticamente.

La soluzione in caso semplice sarebbe quella di mettere StringHttpMessageConverter primo della lista:

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

e anche rimuovere produces da @RequestMapping annotazione:

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

Ora:

  • StringHttpMessageConverter eliminerà tutti i tipi, che possono gestire solo MappingJackson2HttpMessageConverter (MetaInformation, java.util.Collection, ecc.) Consentendo loro di passare oltre.
  • In caso di eccezione nello scenario (5, 6) StringHttpMessageConverter avrà la precedenza.

Fin qui tutto bene, ma sfortunatamente le cose si complicano con ObjectToStringHttpMessageConverter. Per il tipo di risposta gestore java.util.Collection<MetaInformation> questo convertitore di messaggi segnalerà che può convertire questo tipo in java.lang.String. La limitazione deriva dal fatto che i tipi di elementi di raccolta vengono cancellati e il metodo AbstractHttpMessageConverter.canWrite(Class<?> clazz, MediaType mediaType) riceve la classe java.util.Collection<?> per il controllo, tuttavia quando si verifica il passaggio di conversione ObjectToStringHttpMessageConverter fallisce. Per risolvere il problema teniamo produces per @RequestMapping l'annotazione in cui deve essere utilizzato JSON convertitore, ma per forzare corretto tipo di contenuto per gestore delle eccezioni, ci sarà cancellare HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE attributo dal 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(); 
} 

contesto rimane lo stesso come era in origine :

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

ora scenari (1,2,3,4) sono gestite correttamente a causa del tipo di contenuto negoziale, e gli scenari (5,6) sono trattati in gestore di eccezioni.

alternativa si può sostituire la raccolta tipo restituito con gli array, allora la soluzione # 1 è di nuovo applicabile:

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

Per la discussione:

Penso che AbstractMessageConverterMethodProcessor.writeWithMessageConverters() non dovrebbe ereditare la classe da valore, ma piuttosto dalla firma del metodo:

Type returnValueType = returnType.getGenericParameterType();

e HttpMessageConverter.canWrite(Class<?> clazz, MediaType mediaType) dovrebbe essere cambiata a:

canWrite(Type returnType, MediaType mediaType)

o (nel caso sia troppo limitante potenziali convertitori base di classe) per

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

Poi parametrizzata tipi potrebbero essere gestiti correttamente e la soluzione n. 1 sarebbe di nuovo applicabile.

+0

Grazie! Questa è la risposta che ha funzionato per me. In particolare la riga 'request.removeAttribute (HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);' o per estensione 'request.setAttribute (HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, Collections.singleton (MediaType.APPLICATION_JSON));' –

Problemi correlati