导航
📖 本讲目标
- 理解 AOP 的核心概念
- 理解切面、切点、通知的概念
- 学习 JDK 动态代理原理
- 了解 AOP 的应用场景
🎯 什么是 AOP?
AOP 的定义
AOP(Aspect-Oriented Programming,面向切面编程) 是一种编程范式,它允许将横切关注点(cross-cutting concerns)从业务逻辑中分离出来。
横切关注点
横切关注点是指那些在多个模块中重复出现的功能,例如:
- 日志记录:在多个方法中都需要记录日志
- 事务管理:多个方法都需要事务控制
- 性能监控:需要统计方法执行时间
- 安全检查:需要验证权限
- 异常处理:统一的异常处理逻辑
传统方式的问题
传统方式(代码耦合):
public class UserService {
public void saveUser(User user) {
// 日志记录
System.out.println("开始保存用户: " + user.getName());
try {
// 业务逻辑
userRepository.save(user);
// 日志记录
System.out.println("用户保存成功");
} catch (Exception e) {
// 异常处理
System.err.println("保存用户失败: " + e.getMessage());
throw e;
}
}
}
问题:
- 业务逻辑和横切关注点混合在一起
- 代码重复,难以维护
- 修改横切关注点需要修改所有相关方法
AOP 方式
AOP 方式(关注点分离):
// 业务代码
public class UserService {
public void saveUser(User user) {
userRepository.save(user); // 只关注业务逻辑
}
}
// 切面代码
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore() {
System.out.println("方法执行前...");
}
}
优势:
- 业务逻辑清晰
- 横切关注点集中管理
- 易于维护和扩展
🎭 AOP 核心概念
1. 切面(Aspect)
切面是横切关注点的模块化。一个切面可以包含多个通知和切点。
@Aspect
@Component
public class LoggingAspect {
// 切面包含多个通知
}
2. 切点(Pointcut)
切点是匹配连接点的表达式,用于确定在哪些方法上应用通知。
@Before("com.example.service.UserService.*") // 切点表达式
public void logBefore() {
// ...
}
3. 通知(Advice)
通知是在切点上执行的动作。常见的通知类型:
- @Before:前置通知,在方法执行前执行
- @After:后置通知,在方法执行后执行(无论是否抛出异常)
- @AfterReturning:返回通知,在方法正常返回后执行
- @AfterThrowing:异常通知,在方法抛出异常后执行
- @Around:环绕通知,可以控制方法的执行
4. 连接点(Join Point)
连接点是程序执行过程中的一个点,例如方法调用、异常抛出等。
5. 目标对象(Target)
目标对象是被代理的原始对象。
6. 代理对象(Proxy)
代理对象是 AOP 框架创建的对象,用于拦截方法调用。
🔄 AOP 工作原理
代理模式
AOP 基于代理模式实现:
客户端调用
↓
代理对象(Proxy)
↓
前置通知(@Before)
↓
目标对象(Target)
↓
后置通知(@After)
↓
返回结果
JDK 动态代理
JDK 动态代理是 Java 提供的代理机制,它要求目标对象必须实现接口。
工作原理
- 创建代理类:在运行时动态创建代理类
- 实现接口:代理类实现目标对象的所有接口
- 拦截调用:通过
InvocationHandler拦截方法调用 - 执行通知:在方法调用前后执行通知
代码示例
// 接口
public interface IUserService {
void saveUser(User user);
}
// 实现类
public class UserService implements IUserService {
public void saveUser(User user) {
System.out.println("保存用户: " + user.getName());
}
}
// 代理处理器
public class LoggingHandler implements InvocationHandler {
private Object target;
public LoggingHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置通知
System.out.println("方法执行前: " + method.getName());
// 调用目标方法
Object result = method.invoke(target, args);
// 后置通知
System.out.println("方法执行后: " + method.getName());
return result;
}
}
// 创建代理对象
IUserService proxy = (IUserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class[]{IUserService.class},
new LoggingHandler(new UserService())
);
// 使用代理对象
proxy.saveUser(new User("张三"));
CGLIB 代理
CGLIB 代理通过继承目标类创建子类来实现代理,不需要接口。
注意:本教程只实现 JDK 动态代理,不实现 CGLIB。
📋 AOP 应用场景
1. 日志记录
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("调用方法: " + joinPoint.getSignature().getName());
}
}
2. 性能监控
@Aspect
@Component
public class PerformanceAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object monitor(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long end = System.currentTimeMillis();
System.out.println("方法执行时间: " + (end - start) + "ms");
return result;
}
}
3. 事务管理
@Aspect
@Component
public class TransactionAspect {
@Around("@annotation(Transactional)")
public Object manageTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
// 开启事务
beginTransaction();
try {
Object result = joinPoint.proceed();
// 提交事务
commitTransaction();
return result;
} catch (Exception e) {
// 回滚事务
rollbackTransaction();
throw e;
}
}
}
4. 安全检查
@Aspect
@Component
public class SecurityAspect {
@Before("@annotation(RequiresPermission)")
public void checkPermission(JoinPoint joinPoint) {
// 检查权限
if (!hasPermission()) {
throw new SecurityException("没有权限");
}
}
}
🎯 AOP 的优势
1. 关注点分离
业务逻辑和横切关注点分离,代码更清晰。
2. 代码复用
横切关注点可以在多个地方复用,避免代码重复。
3. 易于维护
修改横切关注点只需要修改切面代码。
4. 灵活配置
可以通过配置决定哪些方法应用切面。
⚠️ AOP 的局限性
1. 只能代理接口方法
JDK 动态代理只能代理接口方法,不能代理具体类的方法。
2. 性能开销
代理对象的方法调用有一定的性能开销。
3. 调试困难
代理对象的存在可能使调试变得困难。
📝 本讲总结
本讲我们学习了:
- ✅ AOP 的核心概念和定义
- ✅ 切面、切点、通知的概念
- ✅ JDK 动态代理的工作原理
- ✅ AOP 的应用场景
- ✅ AOP 的优势和局限性
🎯 关键点回顾
- AOP:面向切面编程,分离横切关注点
- 切面:横切关注点的模块化
- 切点:匹配连接点的表达式
- 通知:在切点上执行的动作
- 代理模式:AOP 的实现基础
🚀 下一讲预告
在下一讲中,我们将实现 AOP 功能,包括:
- @Aspect 切面类
- @Before/@After 通知
- 切点表达式匹配
- 代理对象创建
准备好了吗?让我们继续 第 10 讲:AOP 实现!
思考题:
- AOP 和 OOP 有什么区别和联系?
- 为什么 JDK 动态代理需要接口?
- 如何选择合适的通知类型?
