2012-10-19 7 views
21

Supposons que j'ai un contrôleur qui sert demande GET et retourne haricots à sérialisé JSON et fournit également un gestionnaire d'exception pour IllegalArgumentException qui peut être soulevée en service:Comment changer le type de contenu dans le gestionnaire d'exception

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

de message sont des convertisseurs:

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

maintenant, quand je demande l'URL donnée dans le navigateur, je vois la réponse JSON correcte. Cependant, si une exception est levée, l'exception stringifiée est également convertie en JSON, mais j'aimerais qu'elle soit traitée par StringHttpMessageConverter (résultat: text/plain mime type). Comment puis-je y aller?

Pour rendre l'image plus complète (et complexe), supposons que j'ai aussi le gestionnaire suivant:

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

Ce gestionnaire permet à la chaîne de retour à sérialisé par les deux MappingJackson2HttpMessageConverter et StringHttpMessageConverter selon au passé Accept-type par la client. Les types de retour et les valeurs doivent être les suivantes:

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

Répondre

17

Je pense que la suppression produces = MediaType.APPLICATION_JSON_VALUE de @RequestMapping du getMetaInformation vous donnera le résultat souhaité.

Le type de réponse sera négocié en fonction de la valeur du type de contenu dans l'en-tête Accept.


modifier

Comme cela ne couvre le scénario 3,4 est une solution de travail ici pas ResponseEntity.class directement:

@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

Ça a l'air bien. Mais comment les scénarios (3,4) seront-ils traités? –

+0

Merci pour l'indice avec ResponseEntity!Qu'en est-il de la définition de la propriété 'supportedMediaTypes' pour' StringHttpMessageConverter' (voir [ma réponse] (http://stackoverflow.com/a/12979543/267197))? Cela pourrait aussi être une solution. –

+2

Je viens de vérifier votre solution avec ResponseEntity: cela ne fonctionne pas. Le type de contenu est remplacé par un convertisseur de message, et un convertisseur de message est choisi (algorithmes approximatifs) en croisant 'Accept-type' et' supportedMediaTypes' du convertisseur. –

8

Il y a plusieurs aspects liés au problème:

  • StringHttpMessageConverter ajoute chat ch-all mime tapez */* à la liste des types de média pris en charge, tandis que MappingJackson2HttpMessageConverter est lié à application/json uniquement.
  • Lorsque @RequestMapping fournit produces = ..., cette valeur est stockée dans HttpServletRequest (voir RequestMappingInfoHandlerMapping.handleMatch()) et lorsque le gestionnaire d'erreur est appelé, ce type mime est automatiquement hérité et utilisé.

La solution en cas simple serait de mettre StringHttpMessageConverter d'abord dans la liste:

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

et également supprimer produces de @RequestMapping annotation:

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

maintenant:

  • StringHttpMessageConverter supprimera tous les types, que seuls MappingJackson2HttpMessageConverter peuvent gérer (MetaInformation, java.util.Collection, etc) permettant leur passage ultérieur. En cas d'exception dans le scénario (5, 6) StringHttpMessageConverter aura la priorité.

Jusqu'ici tout va bien, mais malheureusement, les choses se compliquent avec ObjectToStringHttpMessageConverter. Pour le type de retour de gestionnaire java.util.Collection<MetaInformation>, ce convertisseur de message indiquera qu'il peut convertir ce type en java.lang.String. La limitation vient du fait que les types d'éléments de collection sont effacés et la méthode AbstractHttpMessageConverter.canWrite(Class<?> clazz, MediaType mediaType) obtient la classe java.util.Collection<?> pour vérification, mais en cas d'échec de l'étape de conversion ObjectToStringHttpMessageConverter. Pour résoudre le problème, nous gardons produces pour @RequestMapping annotation où convertisseur JSON doit être utilisé, mais pour forcer le type de contenu correct pour gestionnaire d'exceptions, nous effacerons HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE attribut de 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(); 
} 

contexte reste le même qu'à l'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> 

maintenant scénarios (1,2,3,4) sont traités correctement en raison de la négociation de type de contenu, et les scénarios (5,6) sont traités dans gestionnaire d'exceptions.

En variante, on peut remplacer le type de retour de collection avec des tableaux, puis la solution n ° 1 est applicable à nouveau:

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

Pour discussion:

Je pense que AbstractMessageConverterMethodProcessor.writeWithMessageConverters() ne devrait pas hériter la classe de valeur, mais plutôt à partir de la signature de la méthode:

Type returnValueType = returnType.getGenericParameterType();

et HttpMessageConverter.canWrite(Class<?> clazz, MediaType mediaType) devrait être remplacé:

canWrite(Type returnType, MediaType mediaType)

ou (dans le cas où il est trop restrictive des convertisseurs à base de classe potentiel) pour

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

Ensuite types paramétrés peuvent être manipulés correctement et la solution n ° 1 serait applicable à nouveau.

+0

Merci! C'est la réponse qui a fonctionné pour moi. Spécifiquement la ligne 'request.removeAttribute (HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);' ou par extension 'request.setAttribute (HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, Collections.singleton (MediaType.APPLICATION_JSON));' –

Questions connexes