目录
一、AOP基础概述
1.1 为什么需要AOP?
在传统OOP(面向对象编程)开发中,当我们需要为多个模块添加相同功能(如日志记录、事务管理、权限校验)时,会出现大量重复代码。例如:
public void transferMoney() {
// 记录日志开始
log.info("转账开始");
try {
// 核心业务逻辑
accountService.transfer();
// 记录成功日志
log.info("转账成功");
} catch (Exception e) {
// 记录异常日志
log.error("转账失败");
}
}
AOP(面向切面编程) 的出现就是为了解决这类横切关注点问题,通过将通用功能从业务逻辑中剥离,实现代码复用和解耦。
1.2 AOP核心价值
-
解耦性:业务代码与通用功能分离
-
可维护性:修改公共功能只需改动一处
-
可扩展性:新增功能不影响原有代码
-
代码简洁性:消除重复代码
二、AOP快速入门(Spring AOP示例)
2.1 环境准备
在pom.xml中添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.2 第一个切面实现
@Aspect
@Component
public class LoggingAspect {
// 定义切入点:所有Service层的public方法
@Pointcut("execution(public * com.example.service.*.*(..))")
public void serviceLayer() {}
// 前置通知
@Before("serviceLayer()")
public void logMethodStart(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("方法 " + methodName + " 开始执行");
}
}
2.3 测试验证
@Service
public class UserService {
public void createUser(String username) {
System.out.println("创建用户:" + username);
}
}
// 调用输出:
// 方法 createUser 开始执行
// 创建用户:testUser
三、AOP核心概念详解
3.1 核心概念图解
┌──────────────┐ ┌───────────┐
│ Aspect │ │ Advice │
│ (日志模块) │─────▶│(增强逻辑) │
└──────────────┘ └───────────┘
▲ │
│ Pointcut ▼
└─────────────┐ ┌──────────────┐
│ │ JoinPoint │
└─▶│(方法执行点) │
└──────────────┘
3.2 七大核心概念
-
连接点(JoinPoint)
-
程序执行过程中的特定点:方法调用、异常抛出等
-
示例:
UserService.createUser()
方法执行时
-
-
切入点(Pointcut)
-
匹配连接点的表达式
-
示例:
execution(* com.example.service.*.*(..))
-
-
通知(Advice)
-
在切入点执行的增强逻辑
-
类型:前置、后置、环绕等
-
-
切面(Aspect)
-
包含切入点与通知的模块化单元
-
示例:日志切面、事务切面
-
-
目标对象(Target)
-
被代理的原始对象
-
示例:
UserService
实例
-
-
代理(Proxy)
-
增强后的目标对象
-
实现方式:JDK动态代理/CGLIB
-
-
织入(Weaving)
-
将切面应用到目标对象的过程
-
时机:编译期/类加载期/运行期
-
四、AOP进阶知识
4.1 五种通知类型对比
通知类型 | 注解 | 执行时机 | 特点 |
---|---|---|---|
前置通知 | @Before | 目标方法执行前 | 无法阻止方法执行 |
后置通知 | @After | 方法执行后(无论是否异常) | 类似finally块 |
返回通知 | @AfterReturning | 方法正常返回后 | 可获取返回值 |
异常通知 | @AfterThrowing | 方法抛出异常时 | 可捕获特定异常类型 |
环绕通知 | @Around | 方法执行前后 | 最强大的通知类型 |
环绕通知示例:
@Around("serviceLayer()")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
Object result = pjp.proceed(); // 执行目标方法
long duration = System.currentTimeMillis() - start;
System.out.println("方法执行耗时:" + duration + "ms");
return result;
}
4.2 通知执行顺序
当存在多个切面时,执行顺序遵循:
-
同一切面内:按照通知类型自然顺序
-
@Around -> @Before -> 方法执行 -> @Around -> @After -> @AfterReturning/Throwing
-
-
不同切面间:
-
实现
Ordered
接口或使用@Order
注解 -
值越小优先级越高
-
配置示例:
@Aspect
@Order(1) // 数字越小优先级越高
public class SecurityAspect {
// 安全校验切面
}
@Aspect
@Order(2)
public class LoggingAspect {
// 日志记录切面
}
4.3 切入点表达式精讲
4.3.1 execution表达式
execution(
[修饰符] 返回类型 [类路径].方法名(参数类型列表)
[throws 异常类型]
)
常用匹配模式:
-
匹配所有public方法:
execution(public * *(..))
-
匹配service包下方法:
execution(* com.example.service.*.*(..))
-
匹配以Service结尾的类:
execution(* *..*Service.*(..))
-
匹配第一个参数为String的方法:
execution(* *(String, ..))
4.3.2 其他指示符
指示符 | 作用 | 示例 |
---|---|---|
within | 匹配类或包 | within(com.example.service.*) |
this | 代理对象类型匹配 | this(com.example.service.UserService) |
target | 目标对象类型匹配 | target(com.example.dao.UserDao) |
args | 参数类型匹配 | args(java.io.Serializable) |
@annotation | 方法带有指定注解 | @annotation(com.example.Loggable) |
组合使用示例:
@Pointcut("execution(* com.example.service.*.*(..)) && @annotation(org.springframework.transaction.annotation.Transactional)")
public void transactionalServiceMethods() {}
4.4 连接点(JoinPoint)详解
通过JoinPoint对象可以获取:
-
方法签名:
getSignature()
-
方法参数:
getArgs()
-
目标对象:
getTarget()
-
代理对象:
getThis()
实战示例:
@Before("serviceLayer()")
public void logMethodInfo(JoinPoint jp) {
MethodSignature signature = (MethodSignature) jp.getSignature();
String className = signature.getDeclaringType().getSimpleName();
String methodName = signature.getName();
Object[] args = jp.getArgs();
System.out.printf("执行 %s.%s(),参数:%s%n",
className, methodName, Arrays.toString(args));
}
五、总结与最佳实践
5.1 AOP适用场景
-
日志记录
-
性能统计(方法耗时)
-
事务管理
-
权限校验
-
异常处理
-
数据校验
-
缓存管理
5.2 核心要点回顾
-
切面 = 切入点 + 通知
-
优先使用环绕通知处理复杂逻辑
-
切入点表达式要尽量精确匹配
-
多个切面时注意执行顺序
-
生产环境推荐使用编译时织入(AspectJ)
5.3 开发建议
-
保持切面单一职责
-
避免在切面中处理业务逻辑
-
谨慎使用around通知
-
为切入点表达式添加注释说明
-
使用自定义注解增强可读性
通过本文的系统学习,你已经掌握了AOP的核心概念和实际应用方法。建议结合具体项目实践,从简单的日志切面开始,逐步深入理解AOP的强大能力。