AOP(Aspect Oriented Programming),即面向切面编程,它是Spring两大核心特性之一:
- 它是一种编程范式,除了AOP,面向过程编程、面向对象编程、函数式编程、事件驱动编程等都是编程范式
- 它用于解决一些特定的问题:
- DRY(Don’t Repeat Yourself)
- Soc(Separation of Concerns)
1. 水平分离:展示层->服务层->持久层
2. 垂直分离:模块划分(订单、库存等)
3. 切面分离:分离功能性需求与非功能性需求
- 它是对OOP的补充和完善,OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合(纵向),而当我们需要为分散的对象引入公共行为的时候,则需要使用AOP来实现(横向)
使用AOP的好处
- 集中处理某一关注点/横切逻辑
- 可以很方便的添加/删除关注点
- 侵入性少,增强代码可读性及可维护性
AOP的应用场景
- 权限控制
- 缓存控制
- 事务控制
- 审计日志
- 性能监控
- 分布式追踪
- 异常处理
AOP核心概念
切面(Aspect)
AOP将封装好的对象剖开,找出其中对多个对象产生影响的公共行为,并将其封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),切面将那些与业务无关,却被业务模块共同调用的逻辑提取并封装起来,减少了系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。连接点(Joinpoint)
程序执行过程中能够插入切面(被拦截)的点,如方法的调用,特定异常的抛出,甚至一个字段的修改。切面代码可以利用这些点插入到应用的正常流程中,并添加新的行为。通知(Advice)
在特定的连接点,AOP框架执行的动作,它定义了切面是什么以及何时使用。通知分为前置(@Before)、后置(@After)、异常(@AfterThrowing)、返回(@AfterReturning)、环绕(@Around)五类。- 前置通知:在连接点执行之前执行
- 后置通知:在连接点完成之后执行
- 异常通知:在连接点抛出异常之后执行
- 返回通知:在连接点返回结果之后执行
- 环绕通知:围绕着连接点执行,能够全面地控制连接点,甚至可以控制是否执行连接点
AOP代理(AOP Proxy)
AOP框架创建的对象,包含通知。在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。目标对象(Target Object)
包含连接点的对象。也被称作被通知或被代理对象。织入(Weaving)
将切面应用到目标对象并导致代理对象创建的过程。引入(Introduction)
引入可以在无需修改现有类的情况下为被通知的类动态地添加一些方法或字段,从而让它们具有新的行为和状态。例如,你可以使用一个引入使任何对象实现 IsModified接口,来简化缓存。在Spring中要使用Introduction, 可以通过DelegatingIntroductionInterceptor来实现通知,通过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口。切点(Pointcut)
指定一个通知将被引发的一系列连接点的集合。切点会匹配通知所要织入的一个或多个连接点,一般常用正则表达式定义所匹配的类和方法名称(Spring定义了Pointcut接口,用来组合MethodMatcher和ClassFilter, MethodMatcher是用来检查目标类的方法是否可以被应用此通知,而ClassFilter是用来检查Pointcut是否应该应用到目标类上)来指定这些切点。
Pointcut expression
designators(指示器)
- 匹配方法:execution()
- 匹配注解:@target(),@args(),@within(),@annotation()
- 匹配包/类型:within()
- 匹配对象:this(),bean(),target()
- 匹配参数:args()
wildcards(通配符)
- *:匹配任意数量的字符
- +:匹配指定的类及其子类
- ..:用于匹配任意数量的子包或参数
operators(运算符)
- &&:与操作符
- ||:或操作符
- !:非操作符
Spring AOP实现原理
概述
织入的时机: 1.编译器(AspectJ);2.类加载时(AspectJ 5+);3.运行时(Spring AOP)。运行时织入通过从静态代理(具有代理的方法越多,重复的逻辑越多的缺陷)到动态代理(基于接口代理和基于继承代理)来实现。设计
(1) 代理模式
(2) 责任链模式实现
(1) JDK实现(基于接口)- 类: java.lang.reflect.Proxy
- 接口: InvocationHandle
- 只能基于接口进行动态代理
- JDK代理源码: Proxy.newProxyInstance;getProxyClass0、ProxyClassFactory、ProxyGenerator;newInstance
(2) Cglib实现(基于继承)
- net.sf.cglib.proxy.Enhancer;setSuperclass;setCallback
- MethodInterceptor
- intercept: methodProxy.invokeSuper
(3) JDK代理与Cglib代理的对比
- JDK只能针对有接口的类的接口方法进行动态代理
- Cglib基于继承来实现代理,无法对static、final类进行代理
- Cglib基于继承来实现代理,无法对private、static类进行代理
- JDK基于接口来实现代理,无法对private方法进行代理
(4) Spring对两种实现的选择
- 如果目标对象实现了接口,则默认采用JDK动态代理
- 如果目标对象没有实现接口,则采用Cglib动态代理
- 如果目标对象实现了接口,且强制Cglib代理(@EnableAspectJAutoProxy(proxyTargetClass = true)),则使用Cglib代理
Spring AOP经典代码解读
- 事务控制(@Transactional)
- 安全控制(@PreAuthorize)
(1) MethodSecurityInterceptor
(2) PreInvocationAuthorizationAdviceVoter
(3) ExpressionBasedPreInvocationAdvice - 缓存控制(@Cacheable)
(1) AnnotationCacheAspect
(2) CacheInterceptor
(3) CacheAspectSupport
Spring AOP案例
- 实战案例背景/目标
(1) 商家产品管理系统
(2) 记录产品修改的操作记录
(3) 什么人在什么时间修改了哪些产品的哪些字段,修改为什么值 - 实现思路
(1) 利用aspect去拦截增删改方法
(2) 利用反射获取对象的新旧值
(3) 利用@Around的advice去记录操作记录