当另一个建议已经处理时如何忽略默认的异常处理建议

How to ignore default exception handling advice when it is already handled by another advice

我目前有一个 ExceptionAdvice class,它可以处理所有基本(400、405、404 和其他)异常。例如,我有一个默认建议,它处理所有 MethodArgumentNotValidExceptions 和 returns 400 Bad Request Error。例如

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public Error handleBadRequestException(Exception exception) {
    return buildError(extractTriggerElement(exception), exception);
}

我还有一个针对处理 MethodArgumentNotValidException 的控制器方法之一的不同切入点建议,因为我需要为这种情况创建自定义错误消息。像这样

@AfterThrowing(pointcut = "execution(* 
package.controller.MyController*.updateSomething(*))", throwing = "ex")
private Error handleCustomError(MethodArgumentNotValidException ex) {
    return buildCustomError(ex);
}

问题是控制器建议首先被调用,但随后它被默认建议覆盖,所以我得到了默认错误消息。 有没有办法在其他建议已经处理时忽略默认建议中的 @ExceptionHandler,以便我可以取回 customError 消息?

如何使用 annotations 属性 of ControllerAdvice class。

假设您有一个控制器 c1,其方法为 m1、m2 和 m3。

@RestController
class c1 {
        m1() {.. }
        m2() {.. }
        m3() {.. } //let's assume m3 is the one you don't want to be handled by controller advice
}

你可以像这样把它分成两个控制器

@RestController
@SomeCustomAnnotation
    class c1 {
            m1() {.. }
            m2() {.. }
    }

@RestController
    class c2 {
            m3() {.. } 
    }

现在在建议中 class 你可以这样做

 @ControllerAdvice(annotations = {SomeCustomAnnotation.class})  
   class YourControllerAdviceClass {
    .....
   }  

您必须像这样创建自定义注释

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SomeCustomAnnotation {
  public String myValue();
}  

这样,您的 controlleradvice 将仅处理来自具有 SomeCustomAnnotation 的控制器的异常。

代码有点多。但是我觉得这样干净多了。

你误会了@AfterThrowing:

  • 您只能使用那种建议,以便在方法因异常退出后以及在抛出并可能由另一段代码处理之前执行某些操作。 您不能更改申请流程,例如捕获异常或操作方法结果。

  • 此外,@AfterThrowing建议必须returnvoid因为我刚才解释的原因。你的建议甚至不应该编译,但编译器应该产生错误“This advice must return void”。至少这是我的 AspectJ 编译器所做的(我使用的是完整的 AspectJ,而不是称为 Spring AOP 的“精简版”,但结果应该是相同的。)

  • 有关 @AfterThrowing 的更多信息,请参阅 Spring AOP manual

解释完了,你能做什么?我将在一个纯 Java + AspectJ 示例中向您展示,以便从等式中得到 Spring。您可以自己轻松地将知识转移到 Spring AOP 和 Spring MVC 错误处理程序:

要更改申请流程,您需要的是 @Around 建议。如果你让那种建议在你的特殊方法中捕获错误并且 return 一个错误对象,默认的 Spring 异常处理程序甚至不会看到有一个异常,因为它已经被捕获方面。 IE。默认处理程序将只处理方面未处理的所有其他错误。

下面是一些完全自洽且可编译的示例代码:

回复类:

我们在示例应用程序中使用它们来模拟 Spring 中的正常和错误响应。

package de.scrum_master.app;

public interface Response {
  String getMessage();
}
package de.scrum_master.app;

public class NormalResponse implements Response {
  private String message = "OK";

  @Override
  public String getMessage() {
    return message;
  }

  @Override
  public String toString() {
    return "NormalResponse [message=" + message + "]";
  }
}
package de.scrum_master.app;

public class ErrorResponse implements Response {
  private String message;
  private Exception exeception;

  public ErrorResponse(String message, Exception exeception) {
    this.message = message;
    this.exeception = exeception;
  }

  @Override
  public String getMessage() {
    return message;
  }

  public Exception getExeception() {
    return exeception;
  }

  @Override
  public String toString() {
    return "ErrorResponse [message=" + message + ", exeception=" + exeception + "]";
  }
}

驱动申请:

该应用程序有两种方法,这两种方法都会随机产生正常或错误响应。方法 produceSpecialException() 是我们稍后要由方面处理的方法。

我们通过 try-catch 块模拟默认处理程序,然后调用辅助方法 defaultHandler(Exception e)

package de.scrum_master.app;

import java.util.Random;

public class Application {
  private final static Random RANDOM = new Random();

  public Response produceException() throws Exception {
    if (RANDOM.nextBoolean())
      throw new Exception("normal error");
    return new NormalResponse();
  }

  public Response produceSpecialException() throws Exception {
    if (RANDOM.nextBoolean())
      throw new Exception("special error");
    return new NormalResponse();
  }

  public static ErrorResponse defaultHandler(Exception e) {
    return new ErrorResponse("default handler", e);
  }

  public static void main(String[] args) {
    Application application = new Application();
    for (int i = 0; i < 5; i++) {
      try {
        System.out.println(application.produceException());
      } catch (Exception e) {
        System.out.println(defaultHandler(e));
      }
      try {
        System.out.println(application.produceSpecialException());
      } catch (Exception e) {
        System.out.println(defaultHandler(e));
      }
    }
  }

}

没有方面的控制台日志:

ErrorResponse [message=default handler, exeception=java.lang.Exception: normal error]
NormalResponse [message=OK]
ErrorResponse [message=default handler, exeception=java.lang.Exception: normal error]
ErrorResponse [message=default handler, exeception=java.lang.Exception: special error]
NormalResponse [message=OK]
NormalResponse [message=OK]
ErrorResponse [message=default handler, exeception=java.lang.Exception: normal error]
ErrorResponse [message=default handler, exeception=java.lang.Exception: special error]
NormalResponse [message=OK]
NormalResponse [message=OK]

正如您在上面看到的,所有错误都由默认处理程序处理。不足为奇。

看点:

方面只处理“特殊”错误,忽略其他错误。

package de.scrum_master.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

import de.scrum_master.app.ErrorResponse;
import de.scrum_master.app.Response;

@Aspect
public class ErrorHandler {
  @Around("execution(de.scrum_master.app.Response produceSpecialException(..))")
  public Response handleError(ProceedingJoinPoint thisJoinPoint) throws Throwable {
    try {
      return (Response) thisJoinPoint.proceed();
    }
    catch (Exception e) {
      return new ErrorResponse("aspect handler", e);
    }
  }
}

具有方面的控制台日志:

ErrorResponse [message=default handler, exeception=java.lang.Exception: normal error]
ErrorResponse [message=aspect handler, exeception=java.lang.Exception: special error]
NormalResponse [message=OK]
ErrorResponse [message=aspect handler, exeception=java.lang.Exception: special error]
ErrorResponse [message=default handler, exeception=java.lang.Exception: normal error]
NormalResponse [message=OK]
ErrorResponse [message=default handler, exeception=java.lang.Exception: normal error]
ErrorResponse [message=aspect handler, exeception=java.lang.Exception: special error]
NormalResponse [message=OK]
NormalResponse [message=OK]

正如您在上面看到的,现在一些错误由 aspect handler(“特殊错误”)处理,而所有其他错误仍由 default handler(“正常错误”)处理。