Problème Spring Web est un ensemble de bibliothèques qui facilite la production de réponses application/problem+json à partir d'une application Spring. Il remplit un créneau, en ce qu'il connecte la bibliothèque de problèmes et la gestion des exceptions de Spring Web MVC ou la gestion des exceptions de Spring Webflux afin qu'elles fonctionnent parfaitement ensemble, tout en nécessitant un minimum d'effort de développeur supplémentaire. Ce faisant, il vise à effectuer une tâche petite mais répétitive - une fois pour toutes.
Le fonctionnement de cette bibliothèque est basé sur ce que nous appelons les traits de conseil . Un trait de conseil est une petite @ExceptionHandler réutilisable implémentée en tant que méthode par défaut placée dans une seule interface de méthode. Ces traits de conseil peuvent être combinés librement et ne nécessitent pas d'utiliser une classe de base commune pour votre @ControllerAdvice .
? Veuillez consulter Baeldung: un guide de la bibliothèque Web Spring Problem pour une introduction détaillée!
Le processus de gestion des problèmes fournis par AdviceTrait est construit de manière à permettre la personnalisation chaque fois que le besoin s'en fait sentir. Tous les aspects suivants (et plus) peuvent être personnalisés en mettant en œuvre l'interface de trait de conseil appropriée:
| Aspect | Méthode (s) | Défaut |
|---|---|---|
| Création | AdviceTrait.create(..) | |
| Enregistrement | AdviceTrait.log(..) | 4xx comme WARN , 5xx comme ERROR y compris la trace de pile |
| Négociation de contenu | AdviceTrait.negotiate(..) | application/json , application/*+json , application/problem+json et application/x.problem+json |
| Retomber | AdviceTrait.fallback(..) | application/problem+json |
| Post-traitement | AdviceTrait.process(..) | n / A |
L'exemple suivant personnalise le MissingServletRequestParameterAdviceTrait en ajoutant un champ d'extension de parameter au Problem :
@ ControllerAdvice
public class MissingRequestParameterExceptionHandler implements MissingServletRequestParameterAdviceTrait {
@ Override
public ProblemBuilder prepare ( Throwable throwable , StatusType status , URI type ) {
var exception = ( MissingServletRequestParameterException ) throwable ;
return Problem . builder ()
. withTitle ( status . getReasonPhrase ())
. withStatus ( status )
. withDetail ( exception . getMessage ())
. with ( "parameter" , exception . getParameterName ());
}
}En supposant qu'il y a un contrôleur comme celui-ci:
@ RestController
@ RequestMapping ( "/products" )
class ProductsResource {
@ RequestMapping ( method = GET , value = "/{productId}" , produces = APPLICATION_JSON_VALUE )
public Product getProduct ( String productId ) {
// TODO implement
return null ;
}
@ RequestMapping ( method = PUT , value = "/{productId}" , consumes = APPLICATION_JSON_VALUE )
public Product updateProduct ( String productId , Product product ) {
// TODO implement
throw new UnsupportedOperationException ();
}
}Les demandes HTTP suivantes produiront respectivement la réponse correspondante:
GET /products/123 HTTP/1.1
Accept: application/xml HTTP/1.1 406 Not Acceptable
Content-Type: application/problem+json
{
"title" : " Not Acceptable " ,
"status" : 406 ,
"detail" : " Could not find acceptable representation "
} POST /products/123 HTTP/1.1
Content-Type: application/json
{} HTTP/1.1 405 Method Not Allowed
Allow: GET
Content-Type: application/problem+json
{
"title" : " Method Not Allowed " ,
"status" : 405 ,
"detail" : " POST not supported "
}Avant de continuer , veuillez lire la section sur les traces de pile et les chaînes causales dans Zalando / Problem.
Dans le cas où vous souhaitez activer les traces de pile, veuillez configurer votre ProblemModule comme suit:
ObjectMapper mapper = new ObjectMapper ()
. registerModule ( new ProblemModule (). withStackTraces ());Les chaînes causales des problèmes sont désactivées par défaut , mais peuvent être remplacées si vous le souhaitez:
@ ControllerAdvice
class ExceptionHandling implements ProblemHandling {
@ Override
public boolean isCausalChainsEnabled () {
return true ;
}
} Remarque Étant donné que vous avez un accès complet au contexte de l'application à ce stade, vous pouvez externaliser la configuration de votre application.yml et même décider de réutiliser la propriété Spring's server.error.include-stacktrace .
L'activation des deux fonctionnalités, des chaînes causales et des stacktraces produira:
{
" title " : " Internal Server Error " ,
" status " : 500,
" detail " : " Illegal State " ,
" stacktrace " : [
" org.example.ExampleRestController.newIllegalState(ExampleRestController.java:96) " ,
" org.example.ExampleRestController.nestedThrowable(ExampleRestController.java:91) "
],
" cause " : {
" title " : " Internal Server Error " ,
" status " : 500,
" detail " : " Illegal Argument " ,
" stacktrace " : [
" org.example.ExampleRestController.newIllegalArgument(ExampleRestController.java:100) " ,
" org.example.ExampleRestController.nestedThrowable(ExampleRestController.java:88) "
],
" cause " : {
" title " : " Internal Server Error " ,
" status " : 500,
" detail " : " Null Pointer " ,
" stacktrace " : [
" org.example.ExampleRestController.newNullPointer(ExampleRestController.java:104) " ,
" org.example.ExampleRestController.nestedThrowable(ExampleRestController.java:86) " ,
" sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) " ,
" sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) " ,
" sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) " ,
" java.lang.reflect.Method.invoke(Method.java:483) " ,
" org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) " ,
" org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) " ,
" org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) " ,
" org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) " ,
" org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) " ,
" org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) " ,
" org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) " ,
" org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) " ,
" org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) " ,
" org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) " ,
" org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) " ,
" org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) " ,
" org.junit.runners.ParentRunner.run(ParentRunner.java:363) " ,
" org.junit.runner.JUnitCore.run(JUnitCore.java:137) " ,
" com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:117) " ,
" com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234) " ,
" com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74) "
]
}
}
} Le ressort permet de restreindre la portée d'un @ControllerAdvice à un certain sous-ensemble de contrôleurs:
@ ControllerAdvice ( assignableTypes = ExampleController . class )
public final class ExceptionHandling implements ProblemHandlingEn faisant cela, vous perdez la capacité de gérer certains types d'exceptions à savoir:
HttpRequestMethodNotSupportedExceptionHttpMediaTypeNotAcceptableExceptionHttpMediaTypeNotSupportedExceptionNoHandlerFoundException Nous héritons de cette restriction du printemps et recommandons donc d'utiliser un @ControllerAdvice sans restriction.
Si vous avez des questions, des préoccupations, des rapports de bogues, etc., veuillez déposer un problème dans le tracker de problèmes de ce référentiel.
Pour contribuer, faites simplement une demande de traction et ajoutez une brève description (1-2 phrases) de votre ajout ou de votre modification. Pour plus de détails, vérifiez les directives de contribution.