AspectJ 详解

AspectJ 是 Java 生态中最强大、最成熟的 AOP 框架,Spring AOP 的底层就依赖 AspectJ 的注解和切点表达式语法。

Spring AOP vs AspectJ

对比维度 Spring AOP AspectJ
实现方式 动态代理(JDK/CGLIB) 字节码织入(编译期/类加载期/运行期)
织入时机 运行时 编译期、类加载期、运行期
性能 一般(代理调用开销) 更好(直接执行增强代码)
支持粒度 仅方法级别的连接点 字段、构造器、静态方法、异常处理等
目标对象限制 需要 Spring 容器管理 任意 Java 对象
private/protected 方法 ❌ 不支持 ✅ 支持(通过 Load-Time Weaving)
IDE 支持 一般 Eclipse/AJDT 插件支持

AspectJ 五种织入时机

织入类型 时机 说明
编译期织入 (CTW) 源代码编译时 需要 AspectJ 编译器(ajc)
编译后织入 (BTW) 编译后处理 class 文件 修改已有 class
加载期织入 (LTW) 类加载到 JVM 时 通过 Java Agent 实现,最灵活
运行时织入 运行时 Spring AOP 采用的方式(非原生 AspectJ)
混合织入 组合使用 部分代码编译期织入 + LTW

核心语法

1. 切点表达式完整语法
复制代码
execution([权限修饰符] 返回值类型 [类全限定名.]方法名(参数列表) [throws 异常类型])

示例:

bash 复制代码
// 任意 public 方法
execution(public * *(..))

// 指定包下所有方法
execution(* com.example.service..*.*(..))  // .. 表示子包

// 参数匹配
execution(* *.*(String, int))              // 两个特定参数
execution(* *.*(..))                       // 任意参数
execution(* *.*(String, ..))               // 第一个是 String,后面任意

// 返回值匹配
execution(* com.example.User+.*(..))       // User 及其子类的方法
2. 常用切点指示器
指示器 说明 示例
execution 匹配方法执行 execution(* com..*.*(..))
within 匹配类型内所有方法 within(com.example.service.*)
this 匹配代理对象类型 this(com.example.UserService)
target 匹配目标对象类型 target(com.example.UserService)
args 匹配参数类型 args(String, int)
@within 匹配有某注解的类型 @within(org.springframework.stereotype.Service)
@target 匹配目标对象有注解 @target(com.example.annotation.MyAnnotation)
@args 匹配参数有注解 @args(com.example.annotation.Valid)
@annotation 匹配方法有注解 @annotation(com.example.annotation.Log)
bean 匹配 Spring Bean 名称 bean(userService)

LTW(加载期织入)配置示例

1. 创建切面

java 复制代码
@Aspect
public class PerformanceAspect {
    
    @Around("execution(* com.example.service.*.*(..))")
    public Object measureTime(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.nanoTime();
        Object result = pjp.proceed();
        long end = System.nanoTime();
        System.out.println(pjp.getSignature() + " 耗时: " + (end - start) + "ns");
        return result;
    }
    
    // 拦截字段访问
    @Before("get(String com.example.User.name)")
    public void beforeGetName() {
        System.out.println("即将读取 name 字段");
    }
}

2. META-INF/aop.xml

XML 复制代码
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
    <weaver options="-verbose -showWeaveInfo">
        <!-- 指定需要织入的包 -->
        <include within="com.example.service..*"/>
        <include within="com.example.entity..*"/>
    </weaver>
    
    <aspects>
        <aspect name="com.example.aspect.PerformanceAspect"/>
    </aspects>
</aspectj>

3. JVM 启动参数

bash 复制代码
-javaagent:path/to/aspectjweaver.jar

Spring 集成 AspectJ

方式一:使用注解(最常用)
java 复制代码
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)  // 启用 AspectJ 注解支持
@ComponentScan("com.example")
public class AppConfig {
}
方式二:XML 配置
XML 复制代码
<aop:aspectj-autoproxy proxy-target-class="true"/>

<bean id="logAspect" class="com.example.aspect.LogAspect"/>

高级特性示例

1. 拦截字段赋值
java 复制代码
@Aspect
public class FieldMonitorAspect {
    
    @Before("set(* com.example.User.age)")
    public void beforeSetAge(JoinPoint jp) {
        int newAge = (int) jp.getArgs()[0];
        if (newAge < 0 || newAge > 150) {
            throw new IllegalArgumentException("年龄非法: " + newAge);
        }
    }
}
2. 拦截异常处理
java 复制代码
@Aspect
public class ExceptionHandlerAspect {
    
    @AfterThrowing(pointcut = "execution(* *.*(..))", throwing = "ex")
    public void handleException(Exception ex) {
        System.out.println("捕获异常: " + ex.getMessage());
        // 自定义异常处理逻辑
    }
    
    // 拦截 Handler 方法本身
    @Before("handler(java.lang.Exception+)")
    public void beforeExceptionHandler() {
        System.out.println("即将处理异常");
    }
}
3. 多重切点组合
java 复制代码
@Aspect
public class ComplexPointcutAspect {
    
    // 定义复用切点
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceLayer() {}
    
    @Pointcut("@annotation(com.example.annotation.Auditable)")
    public void auditableMethod() {}
    
    @Pointcut("bean(*Service)")
    public void serviceBean() {}
    
    // 组合使用
    @Before("serviceLayer() && auditableMethod() && !serviceBean()")
    public void beforeCombined() {
        System.out.println("满足多个条件的切点");
    }
}
4. 软升级(Softening)异常
java 复制代码
// 将受检异常转换为非受检异常
@DeclareSoft("execution(* *.*(..))")
public static SoftException softException;

性能优化建议

优化项 说明
窄化切点 切点表达式越精确,匹配速度越快
避免使用 * 通配符过多 减少模式匹配开销
使用 within 替代 execution within 匹配更快(基于类型)
优先使用编译期织入 运行时开销最小
减少 AspectJ 切面数量 每个切面增加匹配检查
相关推荐
程序员海军7 小时前
用了两年多 GPT,我最近开始换模型了,还折腾了个求职避坑 Agent
前端·人工智能·后端
码士集团小青7 小时前
SpringBoot 集成缓存性能之王 Caffeine
spring boot·后端·缓存
凤凰院凶涛QAQ8 小时前
《Java版数据结构 & 集合类剖析》Map & Set & 反射:“从哈希碰撞到红黑树转换,反射帮你扒开 HashMap 的桶数组看个究竟”
java·数据结构·算法
H.莓飛8 小时前
【数据结构】排序_OJ题(一):计数、桶与排序后的处理
java·开发语言·数据结构
wangchunyu1149 小时前
JAVA AQS与Lock实现原理(含CAS)
java·开发语言·word
excel9 小时前
IndexNow:让搜索引擎更快发现网站更新的主动通知协议
前端·后端·架构
Json____9 小时前
用 FastAPI + Vue3 打造一套完整的在线考试系统:从组卷到阅卷的业务闭环实践
后端·node.js·fastapi·it学习·wwwoop.com
w_zero_one10 小时前
算法--模拟
java·数据结构·算法
小灰灰搞电子10 小时前
Rust+Slint 实现FTP客户端调试助手源码分享
开发语言·后端·rust
Javatutouhouduan10 小时前
阿里1658页Java面试题总结(含答案)
java·高并发·java面试·java面试题·后端开发·java程序员·java八股文