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 切面数量 每个切面增加匹配检查
相关推荐
猪猪拆迁队13 小时前
虚拟工厂仿真引擎的架构设计:让一条产线可编程、可观测、可干预
后端·ai编程
字节跳动数据库13 小时前
文章分享——相似函数处理方法
人工智能·后端·程序员
云技纵横13 小时前
@Transactional 失效的 7 种场景:第 5 种最难排查
后端
用户67570498850213 小时前
你知道 Go 结构体和结构体指针调用的区别吗?一文带你彻底搞懂!
后端·go
程序员cxuan14 小时前
读懂 Claude Code 架构分析系列,第一篇,开始!
人工智能·后端·架构
用户67570498850214 小时前
面试官问“装饰器模式”,这样回答薪资多要 3000!
后端
tntxia14 小时前
Geo Scene域名修改引起的一些问题
后端
用户2986985301414 小时前
Java 实现 Word 文档加密与权限解除
java·后端
vanuan14 小时前
给你的A2A-Agent加把锁-认证鉴权实战指南
后端
Yeats_Liao14 小时前
14:Servlet中的页面跳转-Java Web
java·后端·架构