Home > Blockchain >  use custom annotation to handle exception (AOP)
use custom annotation to handle exception (AOP)

Time:09-02

I started working on project that uses AOP(Aspect Oriented Programming) and based specially on Implementing a Custom Spring AOP Annotation for example: Authorized.java

@Retention(RUNTIME)
@Target(METHOD)
public @interface Authorized {

public String[] value() default "" ;
}

and AuthorizedAspect.java

@Component
@Aspect
public class AuthorizedAspect {

@Before("@annotation(com.eng.paper.common.Authorized)")
public void authorize(JoinPoint p) {

    Authorized annotation = ((MethodSignature) p.getSignature()).getMethod().getAnnotation(Authorized.class);
    String[] roles = annotation.value();
    if (! userHasRoles(roles)) {
        throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "You must have one of the following permission: "  Arrays.asList(roles));
    }
}

private boolean userHasRoles(String[] roles) {
    User user = User.user();
    return  Arrays.stream(roles)
     .anyMatch(e -> user.roles.contains(e));
}

}

my question is it possible to create custom annotation to handel exceptions and avoid the try and catch for much cleaner code for example if we have this method private static void log how can we replace the try and catch using @CustomException.

//--------@CustomException
private static void log(String level, Object message) {
    try {
        throw new RuntimeException();
    } catch (Exception e) {
        String loggedUser = user() == null ? "" : user().name;
        System.out.printf("%s [%5s] (%s) %s.%s:%d - %s \n", Instant.now(), level, loggedUser,
                e.getStackTrace()[2].getClassName(), e.getStackTrace()[2].getMethodName(),
                e.getStackTrace()[2].getLineNumber(), message);
    }
}

CodePudding user response:

Firstly, I noticed that you use reflection in order to fetch the method annotation. Instead, you can simply bind it to an advice method parameter:

@Before("@annotation(authorized)")
public void authorize(JoinPoint joinPoint, Authorized authorized) {
  String[] roles = authorized.value();
  if (!userHasRoles(roles))
    throw new ResponseStatusException(
      HttpStatus.UNAUTHORIZED,
      "You must have one of the following permission: "   Arrays.asList(roles)
    );
}

Concerning your question, I recommend to read some documentation. You can use an @Around advice and handle your exceptions there. Whether your pointcut only targets methods with a specific annotation or matches methods or classes more globally, is up to you. I do not recommend to use AOP based on matching annotations, unless absolutely necessary. The annotations pollute your source code, scattering aspect-related information across your code base. If instead, you can match pointcuts based on package names, class names, parent classes, implemented interfaces, method names, method return value types, method parameter types or whatever you have in your application anyway, the aspect is easier to maintain, and you cannot so easily forget to add an annotation in every single place where you e.g. need exception handling.

Besides, Spring already has mechanisms for exception handling, e.g. @ExceptionHandler for controllers and @ControllerAdvice for global exception handling.

Of course, you can still implement exception handling for Spring beans using Spring AOP, like I said. Or if you use native AspectJ, because you want to handle exceptions in non-Spring classes, an @Around advice comes in handily.

package de.scrum_master.aspect;

import java.time.Instant;

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

@Aspect
public class ExceptionHandlerAspect {
  String level = "INFO";
  String message = "Oops!";  

  @Around("execution(* *(..))")
  public Object catchAndLogException(ProceedingJoinPoint joinPoint) throws Throwable {
    try {
      return joinPoint.proceed();
    }
    catch (Exception e) {
      String loggedUser = user() == null ? "" : user().name;
      MethodSignature signature = (MethodSignature) joinPoint.getSignature();
      System.out.printf("%s [%-5s] (%s) %s:%d - %s%n",
        Instant.now(), level, loggedUser,
        signature.getMethod(), joinPoint.getSourceLocation().getLine(),
        message
      );
      return null;
    }
  }

  private User user() {
    return new User();
  }
}

This shall print something like:

2022-08-27T07:38:46.644235600Z [INFO ] (jane_doe) public static void de.scrum_master.app.Application.main(java.lang.String[]):4 - Oops!

Please note that if you only want to log the exception instead of handling it, a @AfterThrowing advice type is preferable. If you really want to handle (catch and swallow) the exception and your target method returns something other than void, please make sure to return a meaningful value. In my little example, I am simply returning null (which AspectJ automatically maps to 0 or false for the corresponding primitive types). Usually, this is not what you want.

Please also note how the aspect fetches class and method name information as well as the source line directly from the joinpoint's meta data instead of using reflection on the stack trace.

  • Related