基于 AspectJ 实现 AOP 完整指南
概述
AspectJ 是一个功能强大的 AOP 框架,它扩展了 Java 语言,提供了更灵活的切入点表达式和更丰富的通知类型。Spring AOP 集成了 AspectJ 的注解和切入点表达式语言,使得 AOP 编程更加便捷。
💡 核心优势:
- 精确控制:通过切入点表达式精确控制切面的织入位置
- 多样化通知:支持前置、后置、环绕、异常、最终通知
- 注解驱动:支持使用注解定义切面,简化配置
- 性能优异:编译时织入,运行时性能更好
知识回顾
代理模式回顾
JDK 动态代理 vs CGLIB 代理
| 特性 | JDK 动态代理 | CGLIB 代理 |
|---|---|---|
| 被代理对象 | 必须实现接口 | 可以是普通类 |
| 代理生成方式 | 基于接口,使用反射 | 基于继承,使用字节码生成 |
| 性能 | JDK 8+ 性能优化后较好 | 创建代理对象较快 |
| 使用场景 | Spring 默认(有接口时) | Spring 默认(无接口时) |
Spring AOP 实现步骤
-
依赖引入:
- Spring 核心包:spring-context, spring-beans, spring-core, spring-expression
- AOP 支持包:spring-aop, aopalliance
-
核心组件:
- 被代理对象(目标对象)
- 切面类(包含通知方法)
- 代理对象(由 Spring 容器生成)
AOP 核心概念
| 术语 | 说明 | 示例 |
|---|---|---|
| 切面(Aspect) | 横切关注点的模块化 | 日志切面、事务切面 |
| 连接点(Join Point) | 程序执行的特定点 | 方法调用、异常抛出 |
| 切入点(Pointcut) | 匹配连接点的表达式 | execution(* com.service..(..)) |
| 通知(Advice) | 切面在特定连接点执行的动作 | 前置通知、后置通知 |
| 目标对象(Target) | 被代理的对象 | UserService 实例 |
| 代理(Proxy) | 创建的代理对象 | 包含增强逻辑的对象 |
| 织入(Weaving) | 将切面应用到目标对象的过程 | 编译时、加载时、运行时 |
传统 AOP 的局限性
❗ 问题:传统 Spring AOP 会对目标对象的所有方法进行拦截,缺乏精细化控制。
✅ 解决方案:使用 AspectJ 框架,通过切入点表达式精确控制切面的应用范围。
——————————————————————————————————————————————————
基于AspectJ实现AOP(实现代理)
一、基于xml
(1)导包:5个核心包:commons-logging spring-context spring-core spring-beans spring-expression 2个包:spring-aspects aspectjweaver (2) 创建被代理对象 (3) 创建切面类 (4) 创建代理对象:xml文件使用aop编程 (5) 测试
代码实现如下:
package com.aspectj.xml;
public interface UserDao {
void addUser();
void deleteUser();
}
package com.aspectj.xml;
public class UserDaoImpl implements UserDao {
@Override
public void addUser() {
System.out.println("添加用户");
// 模拟异常
// System.out.println(1/0);
}
@Override
public void deleteUser() {
System.out.println("删除用户");
}
}
package com.aspectj.xml;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAspect {
//前置通知
public void myBefore(JoinPoint joinPoint){
System.out.print("前置通知:模拟执行权限检查...,");
System.out.print("目标类是:"+joinPoint.getTarget());
System.out.println(",被植入增强处理的目标方法为:"+joinPoint.getSignature().getName());
}
//后置通知
public void myAfterRturning(JoinPoint joinPoint) {
System.out.print("后置通知:模拟记录日志...");
System.out.println("被植入增强处理的目标方法为:" + joinPoint.getSignature().getName());
}
// 环绕通知
// ProceedingJoinPoint 是 JoinPoint 子接口,表示可以执行目标方法
// 1.必须是 Object 类型的返回值
// 2.必须接收一个参数,类型为 ProceeddingJoinPoint
// 3.必须 throws Throwable
public Object myAround(ProceedingJoinPoint proceedingJoinPoint)throws Throwable{
//开始
System.out.println("环绕开始:执行目标方法之前,模拟开始事务...");
// 执行当前目标方法
Object obj = proceedingJoinPoint.proceed();
//结束
System.out.println("环绕结束:执行目标方法之后,模拟关闭事物...");
return obj;
}
//异常通知
public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
System.out.println("异常通知:"+"出错了"+e.getMessage());
}
//最终通知
public void myAfter(){
System.out.println("最终通知:模拟方法结束后的释放资源...");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 1.目标对象:被代理对象-->
<bean id="userDao" class="com.aspectj.xml.UserDaoImpl"/>
<!-- 2.切面类对象-->
<bean id="myAspect" class="com.aspectj.xml.MyAspect"/>
<!-- 3.产生代理对象的aop编程-->
<aop:config>
<!-- 配置切面 -->
<aop:aspect ref="myAspect">
<!-- 1. *目标方法返回值-->
<!-- 2. com.aspectj.xml 包-->
<!-- 3. * 包下所有类-->
<!-- 4. * 类中所有方法-->
<!-- 5. (..)类中所有方法-->
<!-- 配置切入点-->
<aop:pointcut id="myPointCut" expression="execution(* com.aspectj.xml.*.*(..))"/>
<!-- 配置前置通知-->
<aop:before method="myBefore" pointcut-ref="myPointCut"/>
<!-- 配置后置通知:目标方法被成功执行后,才调用的方法-->
<aop:after-returning method="myAfterRturning" pointcut-ref="myPointCut"/>
<!-- 配置环绕通知:目标方法调用前会被调用,目标方法被成功执行后,还会被调用-->
<aop:around method="myAround" pointcut-ref="myPointCut"/>
<!-- 配置异常通知:调用目标方法时发生异常才会被调用-->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
<!-- 配置最终通知:目标方法被调用后,不管是否被成功调用,此方法都会被调用-->
</aop:aspect>
</aop:config>
</beans>
package com.aspectj.xml;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class XmlTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("a.xml");
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
userDao.addUser();
System.out.println("_______________");
userDao.deleteUser();
}
}
正常运行结果:

运行发生异常截图:出现结果为零异常

二、基于注解(Annotation)-------减少xml中的代码量
(1) 导包 5个核心包:commons-logging spring-context spring-core spring-beans spring-expression 2个包:spring-aspects aspectjweaver (2) 创建被代理对象 (3) 创建切面类 注意:切面类中的注解相当于a.xml文件中的哪一句代码 (4) 创建xml文件:扫描所有的注解和开启AspectJ实现AOP
package com.aspectj.annotation;
public interface UserDao {
void addUser();
void deleteUser();
}
package com.aspectj.annotation;
import org.springframework.stereotype.Repository;
//注解实现
//@Repository("userDao")
public class UserDaoImpl implements UserDao {
@Override
public void addUser() {
System.out.println("添加用户");
// 模拟异常
// System.out.println(1/0);
}
@Override
public void deleteUser() {
System.out.println("删除用户");
}
}
package com.aspectj.annotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
//切面类,在此类中编写通知
@Aspect
@Component
public class MyAspect {
// 定义切入点表达式
@Pointcut("execution(* com.aspectj.annotation.*.*(..))")
// 使用一个返回值为 void 、方法体为空的方法来命名切入点
private void myPointCut(){}
// 前置通知
@Before("myPointCut()")
public void myBefore(JoinPoint joinPoint){
System.out.println("前置通知:模拟执行权限检查...,");
System.out.print("目标类是:"+joinPoint.getTarget());
System.out.println(",被植入增强处理的目标方法为:"+joinPoint.getSignature().getName());
}
//后置通知
@AfterReturning("myPointCut()")
public void myAfterReturning(JoinPoint joinPoint){
System.out.println("后置通知:模拟记录日志...");
System.out.println("被植入增强处理的目标方法为:" + joinPoint.getSignature().getName());
}
//环绕通知
@Around("myPointCut()")
public Object myAround(ProceedingJoinPoint proceedingJoinPoint)throws Throwable{
//开始
System.out.println("环绕开始:执行目标方法之前,模拟开始事务...");
//执行当前目标方法
Object obj = proceedingJoinPoint.proceed();
//结束
System.out.println("环绕结束:执行目标方法之后,模拟关闭事物...");
return obj ;
}
//异常通知
@AfterThrowing(value = "myPointCut()",throwing = "e")
public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
System.out.println("异常通知:"+"出错了"+e.getMessage());
}
//最终通知
@After("myPointCut()")
public void myAfter(){
System.out.println("最终通知:模拟方法结束后的释放资源...");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 通过Bean实现-->
<bean id="userDao" class="com.aspectj.annotation.UserDaoImpl"/>
<!--扫描所有注解-->
<context:component-scan base-package="com.aspectj.annotation"/>
<!-- 开始Aspectj实现AOP-->
<aop:aspectj-autoproxy/>
</beans>
package com.aspectj.annotation;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AnnotationTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("b.xml");
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
userDao.deleteUser();
userDao.addUser();
}
}
运行截图:

AspectJ 框架介绍
AspectJ 是一个面向切面的编程框架,它扩展了 Java 语言,提供了强大的 AOP 功能。
AspectJ 的优势
- 精细化控制:通过切入点表达式精确指定切面应用位置
- 丰富的通知类型:支持前置、后置、环绕、异常、最终通知
- 注解驱动:简化配置,提高开发效率
- 编译时织入:更高的执行效率
Spring 集成 AspectJ
Spring AOP 使用了 AspectJ 的部分功能:
- 使用 AspectJ 的切入点表达式语言
- 使用 AspectJ 的注解来定义切面
- 但仍然使用 Spring 的代理机制(运行时动态代理)
AspectJ 切入点表达式
表达式格式
execution([modifiers-pattern] return-type-pattern [declaring-type-pattern] method-name-pattern(param-pattern) [throws-pattern])
常用表达式示例
| 表达式 | 说明 |
|---|---|
execution(* com.service.*.*(..)) | 匹配 com.service 包下所有类的所有方法 |
execution(public * *(..)) | 匹配所有 public 方法 |
execution(* save*(..)) | 匹配所有以 save 开头的方法 |
execution(* com.service.UserService.*(..)) | 匹配 UserService 类的所有方法 |
execution(* com.service..*.*(..)) | 匹配 com.service 包及其子包下的所有方法 |
其他切入点指示符
within():指定类型内的方法@annotation():匹配带有特定注解的方法args():匹配参数类型@within():匹配带有特定注解的类
AspectJ 通知类型
| 通知类型 | 注解 | 执行时机 | 应用场景 |
|---|---|---|---|
| 前置通知 | @Before | 目标方法执行前 | 权限验证、参数校验 |
| 后置通知 | @After | 目标方法执行后(无论成功或异常) | 资源清理 |
| 返回通知 | @AfterReturning | 目标方法正常返回后 | 结果处理、日志记录 |
| 异常通知 | @AfterThrowing | 目标方法抛出异常后 | 异常处理、错误日志 |
| 环绕通知 | @Around | 目标方法执行前后 | 性能监控、事务管理 |
实战示例
示例1:日志记录切面
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("执行方法:" + joinPoint.getSignature().getName());
}
@AfterReturning(value = "execution(* com.service.*.*(..))",
returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
System.out.println("方法返回:" + result);
}
}
示例2:性能监控切面
@Aspect
@Component
public class PerformanceAspect {
@Around("@annotation(com.annotation.Monitor)")
public Object measurePerformance(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
try {
return pjp.proceed();
} finally {
long executionTime = System.currentTimeMillis() - start;
System.out.println(pjp.getSignature() + " 执行时间:" + executionTime + "ms");
}
}
}
高级特性
1. 切入点复用
@Aspect
public class CommonPointcuts {
@Pointcut("execution(* com.service.*.*(..))")
public void serviceLayer() {}
@Pointcut("execution(* com.dao.*.*(..))")
public void daoLayer() {}
@Pointcut("serviceLayer() || daoLayer()")
public void applicationLayer() {}
}
2. 参数传递
@Aspect
public class ValidationAspect {
@Before("execution(* com.service.*.save(..)) && args(entity,..)")
public void validateEntity(Object entity) {
// 验证实体参数
if (entity == null) {
throw new IllegalArgumentException("实体不能为空");
}
}
}
3. 注解驱动的 AOP
// 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Cacheable {
String value() default "";
int timeout() default 3600;
}
// 切面实现
@Aspect
public class CacheAspect {
@Around("@annotation(cacheable)")
public Object cache(ProceedingJoinPoint pjp, Cacheable cacheable) throws Throwable {
String key = generateKey(pjp, cacheable);
Object cached = getFromCache(key);
if (cached != null) {
return cached;
}
Object result = pjp.proceed();
putToCache(key, result, cacheable.timeout());
return result;
}
}
性能优化
1. 切入点优化
- 使用更精确的切入点表达式减少不必要的拦截
- 避免在频繁调用的方法上使用复杂的切面逻辑
- 使用
@Pointcut复用切入点定义
2. 通知优化
- 优先使用特定通知类型(如
@Before)而非@Around - 在通知方法中避免耗时操作
- 合理使用异步处理
3. 代理选择
# application.yml
spring:
aop:
proxy-target-class: true # 强制使用 CGLIB 代理
auto: true # 自动创建代理
常见问题
问题 1:AOP 不生效
原因:
- 切入点表达式错误
- 目标类没有被 Spring 管理
- 内部方法调用绕过代理
解决方案:
// 错误示例:内部调用
public class UserService {
public void method1() {
method2(); // AOP 不生效
}
public void method2() {
// ...
}
}
// 正确做法:通过代理对象调用
@Service
public class UserService {
@Autowired
private UserService self;
public void method1() {
self.method2(); // AOP 生效
}
}
问题 2:切面执行顺序
解决方案:使用 @Order 注解
@Aspect
@Order(1) // 数值越小,优先级越高
public class SecurityAspect { }
@Aspect
@Order(2)
public class LoggingAspect { }
问题 3:获取目标对象信息
@Before("execution(* com.service.*.*(..))")
public void beforeAdvice(JoinPoint joinPoint) {
// 获取目标对象
Object target = joinPoint.getTarget();
// 获取方法名
String methodName = joinPoint.getSignature().getName();
// 获取参数
Object[] args = joinPoint.getArgs();
}
问题 4:异常处理
@Aspect
public class ExceptionHandlingAspect {
@AfterThrowing(pointcut = "execution(* com.service.*.*(..))",
throwing = "ex")
public void handleException(JoinPoint joinPoint, Exception ex) {
// 记录错误日志
logger.error("方法 {} 执行失败:{}",
joinPoint.getSignature().getName(),
ex.getMessage());
}
}
总结
AspectJ 为 Spring AOP 提供了强大的功能支持:
🎯 核心优势
- 灵活的切入点表达式:精确控制切面作用范围
- 丰富的通知类型:满足各种 AOP 需求
- 注解支持:简化配置,提高开发效率
- 与 Spring 无缝集成:充分利用 Spring 容器功能
📊 适用场景
- 日志记录:统一记录系统操作日志
- 权限控制:方法级的安全控制
- 事务管理:声明式事务处理
- 性能监控:方法执行时间统计
- 缓存管理:方法级缓存实现
🚀 最佳实践
- 合理使用切入点表达式,避免过度拦截
- 选择合适的通知类型,避免滥用 @Around
- 注意 AOP 的性能影响,在关键路径慎用
- 利用注解驱动,提高代码可维护性
相关文章
前后章节导航
相关主题
- Spring Bean 管理完整指南 - IoC容器基础和Bean生命周期管理