Supposons que j'ai un contrôleur qui sert la requête GET
et renvoie le bean à sérialiser en JSON et fournit également un gestionnaire d'exceptions pour IllegalArgumentException
qui peut être déclenché en service:
@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);
}
Les convertisseurs de messages sont:
<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, lorsque je demande l'URL donnée dans le navigateur, je vois la bonne réponse JSON. Cependant, si une exception est déclenchée, l'exception stringifiée est également convertie en JSON, mais j'aimerais qu'elle soit traitée par StringHttpMessageConverter
(résultant text/plain
type mime). Comment puis-je y aller?
Pour rendre l'image plus complète (et compliquée), supposons que j'ai également le gestionnaire suivant:
@RequestMapping(value = "/version", method = RequestMethod.GET)
@ResponseBody
public String getApplicationVersion() {
return "1.0.12";
}
Ce gestionnaire permet de sérialiser la chaîne de retour par les deux MappingJackson2HttpMessageConverter
et StringHttpMessageConverter
en fonction du passé Accept-type
par le client. Les types et valeurs de retour doivent être les suivants:
+ ---- + --------------------- + ----------------- ------ + ------------------ + ------------------------ ------------- + | NN | URL | Type d'acceptation | Type de contenu | Convertisseur de messages | | | | en-tête de demande | en-tête de réponse | | + ---- + --------------------- + ---------------- ------- + ------------------ + ----------------------- -------------- + | 1. |/version | text/html; */* | texte/clair | 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; */* | texte/clair | StringHttpMessageConverter | | 6. |/meta/0 (exception) | application/json; */* | texte/clair | StringHttpMessageConverter | + ---- + --------------------- + --------------- -------- + ------------------ + ---------------------- --------------- +
Je pense que la suppression du 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 pas le scénario 3,4, voici une solution fonctionnant avec 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);
}
Il y a plusieurs aspects liés au problème:
StringHttpMessageConverter
ajoute le type mime fourre-tout */*
à la liste des types de supports pris en charge, tandis que MappingJackson2HttpMessageConverter
est lié à application/json
uniquement.@RequestMapping
Fournit produces = ...
, Cette valeur est stockée dans HttpServletRequest
(voir RequestMappingInfoHandlerMapping.handleMatch()
) et lorsque le gestionnaire d'erreurs est appelé, ce type MIME est automatiquement hérité et utilisé.La solution dans un cas simple serait de mettre StringHttpMessageConverter
en premier 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 supprimez également produces
de l'annotation @RequestMapping
:
@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.), ce qui permet de les passer plus loin.StringHttpMessageConverter
aura la priorité.Jusqu'ici tout va bien, mais malheureusement, les choses se compliquent avec ObjectToStringHttpMessageConverter
. Pour le type de retour du 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 que la méthode AbstractHttpMessageConverter.canWrite(Class<?> clazz, MediaType mediaType)
obtient la classe Java.util.Collection<?>
Pour vérification, cependant quand il s'agit de l'étape de conversion ObjectToStringHttpMessageConverter
échoue. Pour résoudre le problème, nous conservons produces
pour l'annotation @RequestMapping
Où le convertisseur JSON doit être utilisé, mais pour forcer le type de contenu correct pour le gestionnaire d'exceptions, nous effacerons l'attribut HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE
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();
}
Le 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>
Désormais, les scénarios (1, 2, 3, 4) sont traités correctement en raison de la négociation du type de contenu, et les scénarios (5, 6) sont traités dans le gestionnaire d'exceptions.
Alternativement, on peut remplacer le type de retour de collection par des tableaux, puis la solution # 1 est à nouveau applicable:
@RequestMapping(value = "/meta", method = RequestMethod.GET)
@ResponseBody
public MetaInformation[] getMetaInformations() {
return myService.getMetaInformations().toArray();
}
Pour discuter:
Je pense que AbstractMessageConverterMethodProcessor.writeWithMessageConverters()
ne devrait pas hériter la classe de la valeur, mais plutôt de la signature de la méthode:
Type returnValueType = returnType.getGenericParameterType();
et HttpMessageConverter.canWrite(Class<?> clazz, MediaType mediaType)
doit être changé en:
canWrite(Type returnType, MediaType mediaType)
ou (dans le cas où cela limite trop les convertisseurs potentiels basés sur la classe) à
canWrite(Class<?> valueClazz, Type returnType, MediaType mediaType)
Les types paramétrés pourraient alors être traités correctement et la solution # 1 serait à nouveau applicable.