Spring框架之AOP
Spring框架之AOP
一、什么是AOP
1.1 定义
- AOP,即面向切面编程。
- 它是一种编程范式,旨在将程序中横切关注点与业务逻辑分离。
1.2 横切关注点
- 指在整个应用程序中都会涉及的功能,但是又与核心业务无关,例如:
- 日志记录
- 权限管理
- 异常处理
- 这些功能往往散布在代码的各个角落,如果直接写在业务代码中,会导致代码冗余、难以维护。
1.3 AOP核心思想
- 将横切关注点模块化:通过AOP,可以将这些横切关注点封装成模块,成为切面。
- 动态的将切面应用到目标对象:不需要修改原有代码,就能为其添加新的功能。
二、AOP有什么用
2.1 代码解耦
- 降低耦合度:业务逻辑与横切关注点分离,代码更清晰,职责更明确。
- 提高可维护性:修改横切关注点的实现时,只需要修改对应的切面,无需修改业务代码。
2.2 代码复用
- 统一管理:将通用的功能(如日志、事务)集中管理,避免重复代码。
- 便于扩展:添加新的横切关注点时,不需要修改原有业务代码。
三、Spring AOP简介
3.1 Spring AOP是什么?
- Spring框架提供的面向切面编程的模块,允许在程序运行时将切面织入到Spring管理的bean中。
- 织入:将切面应用到目标对象的过程。
3.2 AOP的相关概念
3.2.1 通知(Advice)
- 定义:切面要执行的动作,即具体要做什么。
- 类型:
- 前置通知(Before Advice):在方法执行前执行。
- 后置通知(After Advice):在方法执行后执行。
- 返回后通知(After Returning Advice):在方法成功返回后执行。
- 异常后通知(After Throwing Advice):在方法抛出异常后执行。
- 环绕通知(Around Advice):包裹目标方法,能在方法执行前后做处理。
3.2.1 连接点(Joinpoint)
- 定义:程序执行的某个点,如方法的调用或异常的抛出。Spring AOP 只支持方法级别的连接点。
3.2.3 切入点(Pointcut)
- 定义:匹配连接点的表达式,用于筛选出哪些连接点应用通知。
- 作用:决定了通知应用于哪些方法。
3.2.4 切面(Aspect)
- 定义:通知和切入点的结合,即切面的定义包括了要做什么(通知)以及在哪里做(切入点)。
3.2.5 目标对象(Target Object)
- 定义:被通知的对象,即被代理的业务对象。
3.2.6 代理(Proxy)
- 定义:AOP 织入切面后,创建的增强对象,包含了原有对象的功能和切面的功能。
四、Spring AOP的实现
4.1 动态代理
- Spring AOP 基于 动态代理 实现,即在运行时创建目标对象的代理对象。
- 两种代理方式:
- JDK 动态代理:基于接口的代理,仅能代理实现了接口的类。
- CGLIB 动态代理:基于继承的代理,对目标类生成子类,覆盖其方法。适用于没有实现接口的类。
4.2 AOP在Spring中的工作流程
- 定义切面:编写一个包含通知和切入点的类。
- 配置切面:通过注解或 XML,将切面声明为 Spring 管理的 Bean。
- 织入切面:Spring 容器在启动时,扫描并织入切面,生成代理对象。
- 调用代理对象:应用程序获取的是代理对象,调用方法时,AOP 框架会根据切入点和通知的配置,执行相应的切面逻辑。
五、实战讲解
5.1 引入依赖
若使用 Spring Boot,在 pom.xml
中:
<!-- AOP 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
5.2 编写业务代码
假设有一个用户服务:
public interface UserService {
void createUser(String name);
}
@Service
public class UserServiceImpl implements UserService {
@Override
public void createUser(String name) {
// 模拟创建用户的业务逻辑
System.out.println("创建用户:" + name);
}
}
5.3 编写切面
我们想在用户创建前后记录日志。
@Aspect // 标记为切面
@Component // 由 Spring 管理
public class LoggingAspect {
// 定义切入点,匹配 UserService 的所有方法
@Pointcut("execution(* com.example.service.UserService.*(..))")
public void userServiceMethods() {}
// 前置通知
@Before("userServiceMethods()")
public void beforeAdvice(JoinPoint joinPoint) {
System.out.println("方法执行前:" + joinPoint.getSignature().getName());
}
// 后置通知
@After("userServiceMethods()")
public void afterAdvice(JoinPoint joinPoint) {
System.out.println("方法执行后:" + joinPoint.getSignature().getName());
}
// 环绕通知
@Around("userServiceMethods()")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕通知开始");
Object result = pjp.proceed(); // 执行目标方法
System.out.println("环绕通知结束");
return result;
}
}
5.4 说明
@Aspect
:标记该类为切面。@Component
:将该类交由 Spring 容器管理。@Pointcut
:定义切入点,execution(* com.example.service.UserService.*(..))
表示匹配UserService
接口下的所有方法。@Before
、@After
、@Around
:分别定义不同类型的通知,关联到指定的切入点。
六、深入原理
6.1 动态代理原理
6.1.1 JDK 动态代理
- 适用条件:目标对象实现了接口。
- 实现方式:
- 通过反射机制,在运行时动态创建接口的实现类。
- 代理对象实现了与目标对象相同的接口。
- 方法调用时,代理对象负责拦截,并在适当的时机执行通知逻辑。
6.1.2 CGLIB 动态代理
- 适用条件:目标对象未实现接口。
- 实现方式:
- 通过继承目标类,创建子类作为代理对象。
- 利用字节码操作,在运行时生成代理类的字节码。
- 方法调用时,代理对象覆盖父类的方法,加入通知逻辑。
6.2 Spring AOP 的代理选择
- 默认策略:
- 优先使用 JDK 动态代理:如果目标对象实现了接口。
- 使用 CGLIB 动态代理:如果目标对象没有实现接口。
- 可以强制使用 CGLIB:
- 在配置中设置
spring.aop.proxy-target-class=true
。
- 在配置中设置
6.3 AOP 的执行流程
- 容器启动:Spring 容器启动时,加载所有的 Bean 定义。
- 扫描切面:检测所有标记了
@Aspect
的类。 - 解析通知和切入点:建立通知方法与切入点之间的关系。
- 代理对象创建:对于匹配了切入点的目标 Bean,生成对应的代理对象。
- 方法调用:应用程序调用目标方法时,实际上调用的是代理对象的方法。
- 通知执行:代理对象根据通知类型,在合适的时机执行切面逻辑。
七、AOP 的应用场景
7.1 日志记录
- 需求:在方法调用前后记录日志。
- 实现:使用前置通知和后置通知,打印方法名、参数、执行时间等信息。
7.2 事务管理
- 需求:在方法执行时保证事务的一致性,出现异常时回滚。
- 实现:使用环绕通知,在方法执行前开启事务,成功后提交事务,异常时回滚。
7.3 安全检查
- 需求:在方法执行前检查用户权限。
- 实现:使用前置通知,在方法执行前验证权限,不通过则阻止方法执行。
7.4 缓存管理
- 需求:在方法调用时,先检查缓存中是否有结果,有则返回缓存,没有则调用方法并缓存结果。
- 实现:使用环绕通知,先从缓存获取数据,控制方法执行与否。
八、注意事项
8.1 AOP 只对 Spring 管理的 Bean 生效
- 解释:只有被 Spring 容器托管的对象,才能被 AOP 代理或增强。
8.2 自调用问题
- 现象:同一个类内部的方法调用,AOP 可能失效。
- 原因:自调用时,调用的是对象自身的方法,而不是代理对象的方法。
- 解决方案:
- 将自调用的方法提取到另一个类中。
- 使用 AOP 的
@Configurable
注解(较为复杂)。
8.3 使用 CGLIB 的限制
- final 方法无法代理:由于 CGLIB 基于继承,
final
方法无法被重写,因此无法被代理。 - 接口优先:如果目标类实现了接口,默认会使用 JDK 动态代理。
九、总结
- AOP 的核心:通过将横切关注点从业务逻辑中分离出来,达到解耦和复用的目的。
- Spring AOP:利用动态代理机制,实现了在运行时织入切面的功能,简化了 AOP 的使用。
- 实践建议:
- 从简单开始:先尝试编写简单的切面,理解基本概念。
- 逐步深入:了解底层的代理机制,有助于理解 AOP 的执行流程。
- 注意细节:留意自调用问题、代理方式的选择等。
十、延伸阅读
- AspectJ:Spring AOP 是基于代理的,只能在运行时织入,而 AspectJ 是完整的 AOP 框架,支持编译时、类加载时织入,功能更强大。
- AOP 与 OOP 的关系:
- OOP(面向对象编程):关注的是模块化,封装数据和行为。
- AOP:关注的是横切关注点,弥补了 OOP 在处理横切关注点时的不足。
版权申明
本文系作者 @卸了磨的驴 原创发布在Spring框架之AOP。未经许可,禁止转载。
全部评论