我正在使用 Java 开发一个 Spring Boot Gradle 项目,其中在每个需要记录的函数中手动完成日志记录。在每个需要日志记录的函数中,自定义记录器的一个新实例在 Lombok 的 @Cleanup 注释的帮助下被初始化。自定义记录器实现了 AutoCloseable 接口,因此 @Cleanup 注释会在函数结束时自动关闭资源。它是这样工作的:
public void someFuction(String str) {
@Cleanup var logging = new CustomLogger(log, "someFuction", str); //NOSONAR
logging.startLogging();
....
Some logic
.....
}
@Cleanup 注释本质上是将代码放在 try/finally 块中,因此它在幕后的工作方式如下:
public void someFuction(String str) {
try{
CustomLogger logging = new CustomLogger(log, "someFuction", str); //NOSONAR
logging.startLogging();
....
Some logic
.....
} finally(){
logging.close();
}
}
这种方法工作正常,但是必须将两行复制到每个需要记录的函数中,并且每次都手动传递函数名称、记录器和函数的参数,这似乎不是一个“干净”的解决方案,它引入了关键声纳问题也是如此。
所以,我认为我可以使用自定义注释并借助 Spring AOP,更具体地说是 AspectJ 库,轻松实现相同的效果。
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
@Slf4j
public class LoggingAspect {
private ThreadLocal<CustomLogger> businessLoggingThreadLocal = new ThreadLocal<>();
@Before("@annotation(Loggable)")
public void logMethodStart(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
CustomLogger businessLogging = new CustomLogger(log, methodName, args);
businessLoggingThreadLocal.set(businessLogging);
businessLogging.logMethodStart();
}
@AfterReturning(pointcut = "@annotation(Loggable)", returning = "result")
public void logMethodEnd(JoinPoint joinPoint, Object result) {
CustomLogger businessLogging = businessLoggingThreadLocal.get();
if (businessLogging != null) {
businessLogging.close();
businessLoggingThreadLocal.remove();
}
}
@AfterThrowing(pointcut = "@annotation(Loggable)", throwing = "exception")
public void logMethodException(JoinPoint joinPoint, Throwable exception) {
CustomLogger businessLogging = businessLoggingThreadLocal.get();
if (businessLogging != null) {
businessLogging.close();
businessLoggingThreadLocal.remove();
}
}
}
这是我的自定义注释:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Loggable {}
这种围绕现有日志记录机制的“包装器”乍一看似乎很好,但它不能在同一个 Spring 组件内执行嵌套日志记录。如果一个方法调用另一个组件中的方法,日志记录工作正常,但如果它调用同一个组件中的方法,它根本不会记录另一个方法。
例如:
@Service
public class ExampleService {
....
@Loggable
public void someMethod(){
....
some logic
....
anOtherMethod();
}
@Loggable
public void anOtherMethod(){
....
some logic
....
}
}
预期的日志记录将是:
Method started: someMethod()
Method started: anOtherMethod()
Method ended: anOtherMethod()
Method ended: someMethod()
但是当两个函数都在同一个组件中时,实际的日志记录是:
(如果 anOtherMethod() 在不同的组件中,它工作正常,上面提到的方式)
Method started: someMethod()
Method ended: someMethod()
我正在使用这个 gradle 插件:
id("io.freefair.aspectj") version "8.0.1"
和这种依赖:
implementation("org.aspectj:aspectjrt:1.9.7")
可以使用 AspectJ 在同一个组件中进行嵌套日志记录吗?如果不行或者不推荐,是否有更好的方法来解决这个问题?
就像 Spring AOP 一样工作。当您在同一个类中调用方法时,不会调用切面,而只会从类外部调用切面。但是,如果你想强制在同一个类中调用 Aspect,你可以这样做:
@Resource
private ExampleService self = this;
public void someMethod() {
self.method2();
}
public void anOtherMethod() {
}