我开始从事使用AOP(面向方面编程(的项目,特别是基于实现自定义Spring AOP注释,例如:授权.java
@Retention(RUNTIME)
@Target(METHOD)
public @interface Authorized {
public String[] value() default "" ;
}
和AuthorizedSpecpect.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));
}
}
我的问题是,是否可以为handel异常创建自定义注释,并避免更干净的代码的try-and-catch。例如,如果我们有这种方法私有静态无效日志,我们如何使用@CustomException替换try-and-catch。
//--------@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);
}
}
首先,我注意到您使用反射来获取方法注释。相反,您可以简单地将其绑定到一个建议方法参数:
@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)
);
}
关于你的问题,我建议你阅读一些文件。您可以使用@Around
建议并在那里处理您的异常。切入点是否只针对具有特定注释的方法,或者更全局地匹配方法或类,取决于您自己。除非绝对必要,否则我不建议使用基于匹配注释的AOP。注释污染了源代码,使与方面相关的信息分散在代码库中。相反,如果您可以根据包名、类名、父类、实现的接口、方法名、方法返回值类型、方法参数类型或应用程序中的任何类型来匹配切入点,那么该方面更容易维护,而且您不能轻易忘记在每一个需要异常处理的地方添加注释。
此外,Spring已经具有异常处理机制,例如用于控制器的@ExceptionHandler
和用于全局异常处理的@ControllerAdvice
。
当然,您仍然可以使用SpringAOP为Springbean实现异常处理,就像我所说的那样。或者,如果您使用本机AspectJ,因为您想处理非Spring类中的异常,那么@Around
建议会很方便地出现。
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();
}
}
这将打印如下内容:
2022-08-27T07:38:46.644235600Z [INFO ] (jane_doe) public static void de.scrum_master.app.Application.main(java.lang.String[]):4 - Oops!
请注意,如果您只想记录异常而不是处理异常,则建议使用@AfterThrowing
建议类型。如果您真的想处理(捕获并吞咽(异常,并且您的目标方法返回void
以外的值,请确保返回一个有意义的值。在我的小示例中,我只是返回null
(对于相应的基元类型,AspectJ会自动将其映射到0
或false
(。通常,这不是你想要的。
还请注意,方面如何直接从连接点的元数据中获取类和方法名称信息以及源行,而不是在堆栈跟踪上使用反射。