如何使用 AspectJ 在 Spring 组件中实现嵌套日志记录?

问题描述 投票:0回答:1

我正在使用 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 在同一个组件中进行嵌套日志记录吗?如果不行或者不推荐,是否有更好的方法来解决这个问题?

java spring logging aspectj
1个回答
0
投票

就像 Spring AOP 一样工作。当您在同一个类中调用方法时,不会调用切面,而只会从类外部调用切面。但是,如果你想强制在同一个类中调用 Aspect,你可以这样做:

@Resource
private ExampleService self = this;

public void someMethod() {
    self.method2();
}

public void anOtherMethod() {

}

查看更多信息:https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop-understanding-aop-proxies

© www.soinside.com 2019 - 2024. All rights reserved.