AspectJ编译期织入实战

JDK动态代理对final类/方法增强无效,CGLIB因继承机制无法代理final类/方法。当业务场景中必须使用final类(如工具类、第三方依赖类)或final方法时,Spring AOP(动态代理)已无法满足需求,此时需使用 AspectJ编译期织入 实现AOP增强。

本文核心:用规范的开发示例,讲解AspectJ编译期织入的配置、编码、编译运行全流程,解决final类/方法的AOP增强痛点,贴合实际开发规范。

一、核心前提:AspectJ编译期织入原理

与Spring AOP(运行时动态代理)不同,AspectJ编译期织入是 在代码编译阶段,将切面逻辑(通知)直接织入目标类的字节码中,生成包含增强逻辑的class文件。

核心优势:不依赖动态代理,不受final类/方法的限制------无论目标类/方法是否为final,只要匹配切点规则,就能实现增强,且性能优于运行时代理(无反射开销)。

关键区别:Spring AOP是"运行时增强",AspectJ编译期织入是"编译时增强",直接修改目标类字节码,无需代理对象。

二、规范开发示例(SpringBoot + AspectJ 编译期织入)

以"final工具类的方法增强(日志记录)"为实战场景,完整演示从依赖配置、切面编写、编译配置到测试运行的全流程,符合企业开发规范。

2.1 场景定义

假设存在一个final工具类(业务要求必须为final,防止被继承篡改),需对其内部的final方法添加日志增强,记录方法调用参数、返回值和执行耗时。

java 复制代码
/**
 * 业务要求:必须为final类(工具类,禁止继承)
 * 内部方法为final,禁止重写
 */
public final class FinalToolUtil {

    /**
     * final方法:业务核心工具方法,需添加日志增强
     * @param param 入参
     * @return 处理结果
     */
    public final String process(String param) {
        // 模拟业务逻辑:参数处理
        try {
            Thread.sleep(100); // 模拟处理耗时
        } catch (InterruptedException e) {
            throw new RuntimeException("处理失败", e);
        }
        return "处理结果:" + param.toUpperCase();
    }
}

2.2 第一步:添加依赖(SpringBoot项目)

需添加AspectJ核心依赖和编译期织入插件,确保编译时能将切面逻辑织入目标类。

pom.xml 配置(规范依赖版本,贴合SpringBoot版本):

xml 复制代码
<!-- SpringBoot 基础依赖(省略,根据自身版本引入) -->

<!-- 1. AspectJ 核心依赖 -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.9.19</version>
</dependency>

<!-- 2. AspectJ 编译期织入插件(关键) -->
<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>aspectj-maven-plugin</artifactId>
            <version>1.14.0</version>
            <configuration>
               <!-- 指定Java版本,与项目一致 -->
                <source>1.8</source>
                <target>1.8</target>
                <complianceLevel>1.8</complianceLevel>
                <!-- 指定切面类所在包(扫描切面) -->
                <aspectLibraries>
                    <aspectLibrary>
                        <groupId>org.aspectj</groupId>
                        <artifactId>aspectjrt</artifactId>
                    </aspectLibrary>
                </aspectLibraries>
            </configuration>
            <executions>
                <execution>
                    <goals><!-- 编译时织入 -->
                        <goal>compile</goal>
                        <goal>test-compile</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

说明:aspectj-maven-plugin 是编译期织入的核心,负责在maven编译阶段,将切面逻辑织入目标类字节码。

2.3 第二步:编写AspectJ切面(规范编写)

使用AspectJ注解编写切面,定义切点(匹配final类的final方法)和通知(日志增强逻辑),遵循AOP开发规范。

java 复制代码
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
 * AspectJ 切面类(编译期织入)
 * 用于增强 FinalToolUtil 的 final 方法
 */
@Aspect // 标识为AspectJ切面
@Component // 交给Spring管理(可选,若不依赖Spring,可省略)
public class FinalMethodAspect {

    private static final Logger log = LoggerFactory.getLogger(FinalMethodAspect.class);

    /**
     * 切点:匹配 FinalToolUtil 类的所有final方法
     * 切点表达式规范:execution(修饰符 返回值 全类名.方法名(参数))
     */
    @Pointcut("execution(public final String com.example.demo.util.FinalToolUtil.process(..))")
    public void finalMethodPointcut() {}

    /**
     * 环绕通知:记录方法调用日志、执行耗时
     * 环绕通知可控制方法执行,适合记录耗时、异常处理
     */
    @Around("finalMethodPointcut()")
    public Object aroundFinalMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        // 1. 前置增强:记录方法调用信息
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        log.info("【Final方法增强】开始调用方法:{},入参:{}", methodName, args[0]);

        // 2. 记录开始时间,执行目标方法(final方法)
        long startTime = System.currentTimeMillis();
        Object result = joinPoint.proceed(); // 执行目标final方法

        // 3. 后置增强:记录执行耗时和返回值
        long costTime = System.currentTimeMillis() - startTime;
        log.info("【Final方法增强】方法{}执行完毕,耗时:{}ms,返回值:{}", methodName, costTime, result);

        // 4. 返回目标方法结果
        return result;
    }
}

关键说明:

  • 切点表达式必须精准匹配final方法:`execution(public final String com.example.demo.util.FinalToolUtil.process(..))`,明确方法的修饰符(final)、返回值、全类名、方法名和参数。

  • @Around通知:AspectJ的环绕通知与Spring AOP用法一致,可完整控制目标方法的执行流程,适合日志、耗时统计等场景。

  • 切面类可交给Spring管理(@Component),也可独立使用(不依赖Spring),本文演示SpringBoot集成场景。

2.4 第三步:编译验证(核心步骤)

AspectJ编译期织入的核心是"编译阶段织入",需通过maven编译,确保切面逻辑被织入FinalToolUtil的字节码中。

执行maven编译命令:

bash 复制代码
mvn clean compile

编译成功后,可通过反编译工具(如JD-GUI)查看FinalToolUtil.class文件,会发现:切面的日志逻辑已被直接织入process()方法中,而非通过代理实现。

反编译核心片段(示意):

java 复制代码
public final class FinalToolUtil {
    public final String process(String param) {
        // 织入的切面逻辑(前置日志)
        Logger log = LoggerFactory.getLogger(FinalMethodAspect.class);
        String methodName = "process";
        Object[] args = new Object[]{param};
        log.info("【Final方法增强】开始调用方法:{},入参:{}", methodName, args[0]);
        long startTime = System.currentTimeMillis();

        // 原业务逻辑
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            throw new RuntimeException("处理失败", e);
        }
        String result = "处理结果:" + param.toUpperCase();

        // 织入的切面逻辑(后置日志)
        long costTime = System.currentTimeMillis() - startTime;
        log.info("【Final方法增强】方法{}执行完毕,耗时:{}ms,返回值:{}", methodName, costTime, result);
        return result;
    }
}

可见:编译后,切面逻辑已与目标final方法的业务逻辑融合,无需代理,直接执行。

2.5 第四步:测试运行(实战验证)

编写测试类,调用FinalToolUtil的process()方法,验证AOP增强是否生效(日志是否打印)。

java 复制代码
import com.example.demo.util.FinalToolUtil;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class FinalToolUtilTest {

    // 直接注入final类实例(无需代理,编译后已包含增强逻辑)
    @Autowired
    private FinalToolUtil finalToolUtil;

    @Test
    public void testProcess() {
        // 调用final方法
        String result = finalToolUtil.process("aspectj-test");
        System.out.println("测试结果:" + result);
    }
}

2.6 运行结果(符合预期)

text 复制代码
【Final方法增强】开始调用方法:process,入参:aspectj-test
【Final方法增强】方法process执行完毕,耗时:102ms,返回值:处理结果:ASPECTJ-TEST
测试结果:处理结果:ASPECTJ-TEST

结论:final类的final方法成功被增强,日志正常打印,证明AspectJ编译期织入生效。

三、关键注意事项(规范开发必看)

  1. 依赖版本一致:aspectjrt、aspectj-maven-plugin的版本需匹配,避免编译报错(本文使用稳定版本组合,可直接复用)。

  2. 切点表达式精准:必须明确匹配final方法的修饰符(final),否则无法织入(AspectJ支持精准匹配修饰符)。

  3. 编译方式:必须使用maven编译(mvn compile),IDE直接编译可能无法触发AspectJ织入(需配置IDE的AspectJ插件,如IntelliJ IDEA的AspectJ Support)。

  4. 与Spring AOP区分:AspectJ编译期织入无需依赖Spring AOP,可独立使用;若集成SpringBoot,只需添加@Component将切面交给Spring管理即可。

  5. 第三方final类增强:若目标final类是第三方依赖(无法修改源码),只需在切面中精准配置切点表达式,编译时同样能织入增强逻辑(核心优势)。

四、总结(开发实战结论)

当遇到final类/方法需要AOP增强时,Spring AOP(JDK/CGLIB)无法解决,此时 AspectJ编译期织入 是最优方案:

  • 优势:不受final限制,性能优于动态代理(无反射开销),支持第三方final类增强。

  • 实战流程:添加依赖 → 编写切面 → maven编译 → 测试运行,符合企业开发规范。

  • 适用场景:工具类、第三方依赖类、业务要求必须为final的类/方法的AOP增强(日志、权限、耗时统计等)。

本文示例可直接复制到项目中复用,只需修改包名、类名和切点表达式,即可快速实现final类/方法的AOP增强。

相关推荐
鲸渔2 小时前
【C++ 跳转语句】break、continue、goto 与 return
开发语言·c++·算法
贺小涛2 小时前
python和golang进程、线程、协程区别
java·python·golang
Seven972 小时前
Tomcat的架构设计和启动过程详解
java
Mr-Wanter2 小时前
踩坑记录:IDEA 启动服务连续三次 OOM 内存溢出完整解决
java·ide·intellij-idea·oom
阿巴斯甜2 小时前
User::getName含义?
java
喜欢吃燃面2 小时前
Linux 进程信号深度解析:从概念到产生机制
linux·开发语言·学习
2601_949818092 小时前
SpringBoot项目集成ONLYOFFICE
java·spring boot·后端
AI玫瑰助手2 小时前
Python基础:字符串的常用内置方法(查找替换分割)
android·开发语言·python
阿巴斯甜2 小时前
int sum = list.stream().reduce(0, Integer::sum); 含义?
java