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

时光毁灭记忆、已成空白 提交于 2019-12-11 01:36:47

问题


I currently have a ExceptionAdvice class where it handles all the basic (400, 405, 404 and Other) Exceptions. For example I have a default advice where it handles all MethodArgumentNotValidExceptions and returns 400 Bad Request Error. For example

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

I also have a different point cut advice targeting one of the controller methods that handles MethodArgumentNotValidException because I need to create a custom error message for this case. Something like this

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

The problem is that the controller advice gets called first but then it gets overwritten by the default advice so I get the default error message back. Is there a way to ignore the @ExceptionHandler from default advice when other advices have already handled it so I could get the customError message back?


回答1:


You are misunderstanding @AfterThrowing:

  • You can only use that kind of advice in order to do something after a method exits with an exception and before it is thrown and maybe handled by another piece of code. You cannot change the application flow, e.g. catch the exception or manipulate the method result.

  • Furthermore, @AfterThrowing advices must return void for the reason I just explained. Your advice should not even compile but the compiler should yield the error "This advice must return void". At least this is what my AspectJ compiler does (I am using full AspectJ, not the "lite" version called Spring AOP, but the result should be the same.)

  • See the Spring AOP manual for more info about @AfterThrowing.

Having explained that, what can you do? I will show you in a pure Java + AspectJ example so as to get Spring out of the equation. You can easily transfer the knowledge to Spring AOP and Spring MVC error handlers by yourself:

What you need in order to change the application flow is an @Around advice. If you make that kind of advice catch the error in your special method(s) and return an error object, the default Spring exception handler will not even see that there was an exception because it was already caught by the aspect. I.e. the default handler will only handle all other errors not handled by the aspect.

Here is some fully self-consistent and compileable sample code:

Response classes:

We use these in the sample application in order to emulate normal and error responses like in 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 + "]";
  }
}

Driver application:

The application has two methods, both of which randomly produce normal or error responses. The method produceSpecialException() is the one we want handled by the aspect later.

We emulate the default handler by try-catch blocks and then calling helper method 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));
      }
    }
  }

}

Console log without aspect:

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]

As you can see above, all errors are handled by the default handler. No surprise here.

Aspect:

The aspect handles the "special" errors only, ignores the others.

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

Console log with aspect:

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]

As you can see above, now some errors are handled by the aspect handler ("special error") while all the others are still handled by the default handler ("normal error").

I hope this helps. Enjoy!




回答2:


How about using annotations property of ControllerAdvice class.

Let's assume you have a controller c1 with methods m1, m2 and m3.

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

You can split this into two controllers like this

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

and

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

Now in the advice class you can do something like this

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

You have to create a custom annotation like this

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

This way, your controlleradvice will handle only exceptions from the controller which has SomeCustomAnnotation.

This is a bit more code. But I feel this is much cleaner.



来源:https://stackoverflow.com/questions/48221838/how-to-ignore-default-exception-handling-advice-when-it-is-already-handled-by-an

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!