Problem Spring Web es un conjunto de bibliotecas que facilita la producción de respuestas application/problem+json desde una aplicación de resorte. Llena un nicho, ya que conecta la biblioteca de problemas y el manejo de excepciones de Spring Web MVC o el manejo de excepciones de Spring WebFlux para que funcionen sin problemas, al tiempo que requieren un esfuerzo adicional de desarrollador mínimo. Al hacerlo, tiene como objetivo realizar una tarea pequeña pero repetitiva, de una vez por todas.
La forma en que funciona esta biblioteca se basa en lo que llamamos rasgos de asesoramiento . Un rasgo de asesoramiento es un pequeño y reutilizable @ExceptionHandler implementado como un método predeterminado colocado en una sola interfaz de método. Esos rasgos de consejos se pueden combinar libremente y no requieren usar una clase base común para su @ControllerAdvice .
? ¡Consulte Baeldung: una guía de la biblioteca web Spring Spring para una introducción detallada!
El proceso de manejo de problemas proporcionado por AdviceTrait se construye de una manera que permita la personalización siempre que surja la necesidad. Todos los siguientes aspectos (y más) se pueden personalizar implementando la interfaz de rasgo de asesoramiento apropiado:
| Aspecto | Método (s) | Por defecto |
|---|---|---|
| Creación | AdviceTrait.create(..) | |
| Explotación florestal | AdviceTrait.log(..) | 4xx como WARN , 5xx como ERROR incluido Stack Trace |
| Negociación de contenido | AdviceTrait.negotiate(..) | application/json , application/*+json , application/problem+json y application/x.problem+json |
| Retroceder | AdviceTrait.fallback(..) | application/problem+json |
| Postprocesamiento | AdviceTrait.process(..) | n / A |
El siguiente ejemplo personaliza el MissingServletRequestParameterAdviceTrait agregando un campo de extensión parameter al 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 ());
}
}Suponiendo que hay un controlador como este:
@ 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 ();
}
}Las siguientes solicitudes HTTP producirán la respuesta correspondiente respectivamente:
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 "
}Antes de continuar , lea la sección sobre trazas de pila y cadenas causales en Zalando/problema.
En caso de que desee habilitar las trazas de pila, configure su ProblemModule de la siguiente manera:
ObjectMapper mapper = new ObjectMapper ()
. registerModule ( new ProblemModule (). withStackTraces ());Las cadenas causales de problemas están deshabilitadas de forma predeterminada , pero se pueden anular si lo desea:
@ ControllerAdvice
class ExceptionHandling implements ProblemHandling {
@ Override
public boolean isCausalChainsEnabled () {
return true ;
}
} Nota Dado que tiene acceso completo al contexto de la aplicación en ese momento, puede externalizar la configuración de su application.yml e incluso decidir reutilizar la propiedad de Spring.error.include server.error.include-stacktrace .
Habilitar ambas características, cadenas causales y pilas producirá:
{
" 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) "
]
}
}
} Spring permite restringir el alcance de un @ControllerAdvice a un determinado subconjunto de controladores:
@ ControllerAdvice ( assignableTypes = ExampleController . class )
public final class ExceptionHandling implements ProblemHandlingAl hacer esto, perderá la capacidad de manejar ciertos tipos de excepciones, a saber:
HttpRequestMethodNotSupportedExceptionHttpMediaTypeNotAcceptableExceptionHttpMediaTypeNotSupportedExceptionNoHandlerFoundException Heredamos esta restricción de Spring y, por lo tanto, recomendamos usar un @ControllerAdvice sin restricciones.
Si tiene preguntas, inquietudes, informes de errores, etc., presente un problema en el rastreador de problemas de este repositorio.
Para contribuir, simplemente haga una solicitud de extracción y agregue una breve descripción (1-2 oraciones) de su adición o cambio. Para obtener más detalles, consulte las pautas de contribución.