整理 AOP 相关知识点, 一次性搞懂
在搞懂 AOP 之前, 我想在说AOP, 大部分人都会把它与 spring AOP 划等号, 实际上 spring AOP 是对 aop 的一种扩展和实现
AOP 实际上是一组规范, 规定了一套如何解决面相切面编程的规范, 也就是 AOPALLIANCE http://aopalliance.sourceforge.net/
spring 的作者 Rod Johnson 也是 aopalliance 的主要贡献者, 这也就不难看出, 为什么 spring 的 aop 做的如此出色, 因为在最底层的设计都是同一个人操刀, 思想肯定是统一的
1. 浅谈 AOPALLIANCE
最初版本的 aop 联盟包仅有两个子包: aop 和 intercept, 下面分别介绍下各个包和接口类
aop 包: 抽象了最高等级的 AOP 行为
Advice: 实现该接口的类被标记为通知类, 一般来说, 实现通知类的方式都是拦截器
通知可以理解为, 以某个逻辑点作为参照, 当 逻辑点 执行之前, 会有通知, 执行之后会有通知, 而通知的实现就是实现通知接口的类
AspectException: 切面异常
intercept 包: advice 的一种实现包
这个包下有几个比较重要的接口: Interceptor Joinpoint
Interceptor: 继承了 advice, 是通知的一种实现方式, 通过拦截实现, 它还有几个子类, 分别是方法拦截器和构造方法拦截器
Joinpoint: 运行时的集合点, 相当于一个上述的逻辑点, 通知就是围绕Joinpoint来进行工作的, 它还有几个子类, Invocation相当程序调用, 也是某个逻辑点, 还有更具体的方法调用点, 和构造方法调用点
2. spring aop
术语:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
AOP的术语很多,虽然不清楚术语我们也能很熟练地使用AOP,但是要理解分析源码,术语就需要深刻体会其含义。
通知(Advice) 增强的具体实现
目标对象(Target) 被代理|织入增强逻辑的类
连接点(JoinPoint) 程序执行到某个特定的逻辑点
切点(Pointcut) 一组逻辑点称为切点, 通过切点, 可以匹配多个连接点
引介(Introduction) 引介是一种特殊的增强, 它为类添加一些属性和方法. 即使一个类原本没有实现某个接口, 通过AOP的引介功能, 我们可以动态地为该类添加接口的实现逻辑, 让类成为这个接口的实现类
织入(Weaving) 即如何将增强添加到目标对象的连接点上,有动态(运行期生成代理)、静态(编译期、类加载时期)两种方式。
代理(Proxy) 目标对象被增强后,就会产生一个代理对象,该对象可能是和原对象实现了同样的一个接口(JDK),也可能是原对象的子类(CGLIB)。
切面(Aspect、Advisor) 切面由切点和增强组成
spring aop 的实现很有意思, 它造了一半的轮子, 然后借了一半的轮子, 也就是代理类生成使用 jdk proxy cglib, 切面处理规范使用了 aspectj 的接口, 很有趣, 这让很多时候, 让我一直以为 aspectj 就是 spring aop 的全部
实际上 aspectj 是个很大很全的工具包, 它的官网如下: https://www.eclipse.org/aspectj/
1
2
3
4
5
lib/
├── aspectjrt.jar 运行时
├── aspectjtools.jar 工具包
├── aspectjweaver.jar 织入规范
└── org.aspectj.matcher.jar
spring 依赖的是 weaver 包, 至于工具包, 它并没有用, 里面有牛逼哄哄的 ajc 编译器, 可以在编译期将代码织入目标类, spring aop 使用的是代理方式增强类, 与织入的方式不同(反射), 而且效率也没有织入的高
我个人认为通过动态代理可以更好的更换运行时代理对象, 虽然在业务稳定环境下, 没有织入效率高, 但是个人认为织入有局限性, 具体哪里存在局限性, 还有待找资料学习补充
spring aop 扩展
spring aop 扩展了 advice 和 intercept, 而且自定义了一种包含通知的容器类: Advisor, 这个抽象类规定了, 实现该接口的类, 必须内部要保持一种通知实现
这个类我个人认为主要是为了聚合一些行为: 比如获取通知类, 获取集合点信息等操作, 可以通过其子类 PointcutAdvisor看出来
同时 spring 扩展了 advice, 把其细分为 前置通知BeforeAdvice, 后置通知AfterAdvice, 使通知的具体细节更加完善, 更细化的就是 java 目前的最小颗粒度通知: 方法的生命周期, MethodBeforeAdvice, AfterReturningAdvice
spring 上下文刷新方法中 通过 beanFactory 后置处理器, 对实例化的 bean 进行了增强处理, BeanFactoryPostProcessor, 具体的代码简直看的脑仁疼, 感兴趣的可以看看AbstractAutoProxyCreator这个类, 是增强处理的具体实现
通过后置处理器, 可以使 interceptor 可以介入到实际的 pointcut 中
aspectj 的 weaver 包使用
定义一个类, 实现AbstractPointcutAdvisor, 在内部维护一个拦截器: MethodInterceptor, 自己实现其方法, 在 getPointcut() 中, 我们有如下方式去定义集合点:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Pointcut pointcut = new AnnotationMatchingPointcut(CkDemo.class);
//AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
// 指定注解拦截
//pointcut.setExpression("@annotation(com.gomyck.demo.CkDemo)");
// 表达式拦截 规则为: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
// 参数匹配((param-pattern))可以指定具体的参数类型,多个参数间用“,”隔开,各个参数也可以用“”来表示匹配任意类型的参数,如(String)表示匹配一个String参数的方法
// (,String) 表示匹配有两个参数的方法,第一个参数可以是任意类型,而第二个参数是String类型;可以用(…)表示零个或多个任意参数
//pointcut.setExpression("execution(* com.gomyck.demo.CkDemoBean.*(..))");
// 指定类型, 方法会被拦截, 但是父类子类不会被拦截
//pointcut.setExpression("within(com.gomyck.demo.CkDemoBean)");
// 为可以逆变为此类型的匹配类型拦截(当前类和其子类-支持多重继承) this(代理对象) -> 表达式的转变
// this 和 target 的区别是使用 cglib 和 jdk proxy 时 会有点区别, 其他情况下一样
//pointcut.setExpression("this(com.gomyck.demo.CkDemoBeanParent)");
// 为可以逆变为此类型的匹配类型拦截(当前类和其子类-支持多重继承) target(被代理对象) -> 表达式的转变
// this 和 target 的区别是使用 cglib 和 jdk proxy 时 会有点区别, 其他情况下一样
//pointcut.setExpression("target(com.gomyck.demo.CkDemoBean)");
// 根据参数类型拦截 必须要和其他表达式一起使用, 否则会把框架本身的方法也拦截到, 很不友好
//pointcut.setExpression("args(java.lang.String) && execution(* com.gomyck.demo.CkDemoBean.*(..))");
需要源代码的小伙伴可以加我微信 如果帮助到你, 请我喝杯星巴克吧~