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 在处理横切关注点时的不足。
