You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用AspectJ在Spring组件中实现嵌套日志功能?

Spring AOP无法实现同组件内嵌套日志的解决方法

问题场景

在基于Java的Spring Boot Gradle项目中,原本通过Lombok的@Cleanup手动初始化实现AutoCloseable的CustomLogger实例处理日志,但这种方式存在代码重复且触发Sonar问题。于是改用自定义@Loggable注解结合Spring AOP实现日志切面,代码如下:

日志切面代码

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(){ 
        // 业务逻辑
        anOtherMethod(); 
    }

    @Loggable
    public void anOtherMethod(){
        // 业务逻辑
    }
}

预期日志输出:

Method started: someMethod()
Method started: anOtherMethod()
Method ended: anOtherMethod()
Method ended: someMethod()

实际日志输出:

Method started: someMethod()
Method ended: someMethod()

使用的Gradle插件为id("io.freefair.aspectj") version "8.0.1",依赖为implementation("org.aspectj:aspectjrt:1.9.7")。


原因分析

Spring AOP默认基于动态代理实现:调用Spring组件方法时,实际调用的是代理对象的方法,代理会触发切面逻辑。但组件内部方法调用是直接调用目标对象的方法,不走代理,因此切面不会生效。


解决方案

方案1:使用AspectJ编译时织入(CTW)实现全方法拦截

AspectJ是功能更强大的AOP框架,通过直接修改字节码实现切面织入,不受Spring动态代理限制,能拦截组件内部方法调用,完美解决嵌套日志问题。

Gradle配置调整

修改build.gradle,启用AspectJ编译时织入:

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.0'
    id 'io.spring.dependency-management' version '1.1.4'
    id "io.freefair.aspectj" version "8.0.1"
}

aspectj {
    compileWeave = true // 开启编译时织入
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-aop'
    implementation 'org.aspectj:aspectjrt:1.9.7'
    aspect 'org.aspectj:aspectjweaver:1.9.7' // 编译时织入必须依赖
    // 其他项目依赖...
}

代码无需修改

原有的LoggingAspect和@Loggable注解代码保持不变,AspectJ会在编译阶段将切面逻辑织入所有标注@Loggable的方法中,包括组件内部的方法调用。

方案2:自注入代理对象(临时 workaround)

如果不想引入AspectJ,可以在组件内注入自身的代理实例,调用内部方法时通过代理对象触发切面:

@Service 
public class ExampleService { 
    @Autowired
    private ExampleService self; // 注入自身的代理对象

    @Loggable 
    public void someMethod(){ 
        // 业务逻辑
        self.anOtherMethod(); // 通过代理调用内部方法
    }

    @Loggable
    public void anOtherMethod(){
        // 业务逻辑
    }
}

这种方式有代码侵入性,仅适合临时解决个别场景,不推荐大规模使用。

方案3:重构代码拆分组件

将需要日志的方法拆分到不同的Spring组件中,通过跨组件调用触发Spring AOP切面。但该方案仅适用于业务逻辑适合拆分的场景,通用性不强。


总结

  • 最优方案:使用AspectJ编译时织入,彻底解决同组件内嵌套日志问题,无代码侵入性,适配所有场景。
  • 临时方案:自注入代理对象,适合小范围临时解决,但代码侵入性高。

内容的提问来源于stack exchange,提问作者Norbert Maszlag

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 16:40:03