之前《零基础带你看 Spring 源码 --IoC 控制反转》 http://zackku.com/spring-ioc 详细讲了 Spring 容器的初始化和加载的原理, 后面《你真的完全了解 Java 动态代理吗? 看这篇就够了》 http://zackku.com/java-dynamic-proxy 介绍了下 JDK 的动态代理.
基于这两者的实现上, 这次来探索下 Spring 的 AOP 原理. 虽然 AOP 是基于 Spring 容器和动态代理, 但不了解这两者原理也丝毫不影响理解 AOP 的原理实现, 因为大家起码都会用.
AOP,Aspect Oriented Programming, 面向切面编程. 在很多时候我们写一些功能的时候, 不需要用到继承这么重的方法, 例如对每个方法在执行前打 log, 在没有 AOP 的情况下, 我们只能对每个方法都写一句打 log 的语句. 如果是一个复杂点的功能, 那么将会产生许多重复的代码, 而且会对模块之间有更多的耦合.
然而, 在 AOP 下, 我们只需要通过特定的方法, 就能直接切入代码, 添加自定义的功能(后续再讲 AOP 里面的概念点).
下面将从一个简单的示例入手, 拆解示例的内容, 通过源码分析, 一步步带大家读懂 AOP 的原理实现.
使用示例
以下代码不以文字形式展示, 若需要代码, 可以到 GitHub 查看完整 Demo.
Demo:https://github.com/Zack-Ku/spring-aop-demo
Spring 项目依然是用 xml 最原始的配置方式, 为了只是能简单地阅读原理, 否则会多很多自动配置的内容在里面. 而 AOP 的配置用的是注解形式, 因为毕竟看起来毕竟清晰, 容易理解逻辑.
创建一个 Gradle 项目, 添加对应的 Spring 与 AOP 的依赖.
(Gradle 和 Maven 类似, 都是自动化构建的工具. 但与 Maven 相比, Gradle 是基于 groovy, 采用 DSL 格式, 具有更强的灵活性, 简洁性, 拓展性. 现在连 Spring 的官方源码都是用 Gradle 的, 可以说是一款面向未来的工具, 后续也值得我们深入学习.)
创建一个 Bean,TestBean.
创建 AOP 的 Aspect.
然后写一个启动类, 测试以上配置
运行结果:
com.zack.demo.TestBean.getStr()开始执行...
getStr():Testing!
com.zack.demo.TestBean.getStr()方法结束...
Demo:https://github.com/Zack-Ku/spring-aop-demo
示例解析与 AOP 术语概念
看到上面的结果, 很容易猜想到, LogAspect 作用了在 TestBean 上, 使得每次执行 TestBean 上的方法时, 都会执行对应的方法(before/after).
LogAspect 中带注解 @Pointcut 的 allMethod(), 是用来扫描程序中的连接点. 当执行一个方法时, 命中了连接点, 则会根据不同的通知, 执行对应的织入代码. 在上面例子中, 执行 getStr()前会执行 LogAspect 中的 before(), 执行 getStr()后会执行 LogAspect 中的 after().
具体的通知包含
@Before, 前置通知, 执行方法前执行
@AfterReturn, 返回通知, 正常返回方法后执行
@After, 后置通知, 方法最终结束后执行, 相当于 finaly
@Around, 环绕通知, 围绕整个方法
@AfterThrowing, 异常通知, 抛出异常后执行
开发者在命中连接点时, 可以通过以上不同的通知, 执行对应方法. 这就是 AOP 中的 Advisor.
以上的内容其实已经把 AOP 核心的概念都已经点出来了, 我们再深入具体的认识下其中的术语,
Aspect, 切面, 一个关注点的模块.
例子中, LogAspect 就是切面.
JoinPoint, 连接点, 程序执行中的某个点, 某个位置.
例子中, testBean.getStr()是连接点.
PointCut, 切点, 切面匹配连接点的点, 一般与切点表达式相关, 就是切面如何切点.
例子中,@PointCut 注解就是切点表达式, 匹配对应的连接点
Advice, 通知, 指在切面的某个特定的连接点上执行的动作.
例子中, before()与 after()方法中的代码.
TargetObject, 目标对象, 指被切入的对象.
例子中, 从 ctx 中取出的 testBean 则是目标对象.
Weave, 织入, 将 Advice 作用在 JoinPoint 的过程.
以上概念看起来可以还比较难懂, 可以通过以下一图 (来源于网络) 来理解
请各位读者和各位程序员, 在阅读源码的时候, 一定要先搞清楚基本概念, 和一定一定要知道对应概念的英文, 否则在看源码的时候, 根本对不上号, 使理解难度大大提高. 因为源码都是英文写的.
至此 AOP 的基本使用和概念相信大家都有一定的了解, 下面开始从源码入手, 去探索整个 Spring AOP 的实现.
源码分析
上面的例子之所以能完成 AOP 的代理, 只因为 Spring 的 xml 配置里面加了这一句
<aop : aspectj-autoproxy />
加上了这一个配置, 使得整个 Spring 项目拥有了 AOP 的功能. 全局搜索下 aspectj-autoproxy 这个字段, 可以发现, 是这个类 AspectJAutoProxyBeanDefinitionParser 解析了这个元素.
其中的 parse 方法调用的是 AopNamespaceUtils 类中的 registerAspectJAnnotationAutoProxyCreatorIfNecessary. 这个方法作用是初始化一个 AOP 专用的 Bean, 并且注册到 Spring 容器中.
解析这三个操作,
第一句, 注册一个 AnnotationAwareAspectJAutoProxyCreator(称它为自动代理器), 这个 Creator 是 AOP 的操作核心, 也是扫描 Bean, 代理 Bean 的操作所在.
第二句, 解析配置元素, 决定代理的模式. 其中有 JDK 动态代理, 还有 CGLIB 代理, 这部分后续会再细讲.
第三句, 作为系统组件, 把 Creator 这个 Bean, 放到 Spring 容器中. 让 Spring 实例化, 启动这个 Creator.
自动代理器
下面我们来细看 AnnotationAwareAspectJAutoProxyCreator 是怎么对 Bean 做 AOP 的.
AnnotationAwareAspectJAutoProxyCreator 的父类 AbstractAutoProxyCreator, 里面实现了 BeanPostProceesor 接口的 postProcessAfterInitialization 方法(该方法在一个 Bean 加载到 Spring 后会执行).
关联注释描述可知, 当一个 bean 加载完后, 执行了该方法, 会生成一个新的代理对象, 返回 context 中加载.
下面重点看其中的 wrapIfNecessary 方法. 讲述了整个 AOP 的核心流程, 是 Spring AOP 最最最核心的代码所在.
看到红框的两个核心方法, 可以知道, 先从刚加载的 Bean 中扫描出所有的 advice 和 advisor, 然后用它来创建一个代理对象.
获取 Advisor
先看如何扫描出 advice 和 advisor.
一步步 Debug getAdvicesAndAdvisorsForBean(), 找到 BeanFactoryAspectJAdvisorsBuilder 中的 buildAspectJAdvisors 方法.
该方法就是找出 Spring 容器中存在的 AspectBean, 然后返回所有 AspectBean 中的 Advisor.
示例中, LogAspect 就是 AspectBean, 然后 LogAspect 中的 before 和 after 方法就是 Advisor.
所以最终返回了 LogAspect 中的 Advisor(before 和 after).
创建代理
拿到了所有的 Advisor 后, 就进入了创建代理的流程了 createProxy().
这些入参, 对比上一篇讲过的动态代理, 其实非常相似.
beanClass, 加载到 Spring, 触发 AOP 的 bean 类
targetSource, 目标对象, 示例中则是从 ctx 中取出的 testBean
specificInterceptors, 指定 Advisor, 示例中则是 before 和 after 的方法.
下面来具体看下代理的过程
代码可以概括为, 创建一个 proxyFactory 对象, 然后把上面的参数都丢到这个这个工厂里, 最后从 proxyFactory 获取一个代理对象.
来看看 ProxyFactory 的 getProxy 方法是怎么生成代理对象的.
Debug 该方法, 可以在 DefaultAopProxyFactory 中 createAopProxy 看到
工厂会根据配置与目标对象的类型, 选择用 JDK 动态代理 (参考《你真的完全了解 Java 动态代理吗? 看这篇就够了》) 还是 CGLIB 的代理(CGLIB 具体在后续讲).
代理后的对象放回 ctx 中, 然后当程序执行的时候, 会直接调用这个代理类.
至此整个 AOP 的代理流程就结束了. 下面来了解下 CGLIG 代理与 JDK 代理的不同
CGLIB 与 JDK 代理区别
CGLIB(Code Generation Library)是一个强大的, 高性能, 高质量的 Code 生成类库. 它可以在运行期扩展 Java 类与实现 Java 接口. Hibernate 支持它来实现 PO(Persistent Object 持久化对象)字节码的动态生成.
回顾下 JDK 代理, JDK 代理需要一组需要实现的接口, 然后通过这些接口获取构造方法, 用这个构造方法和 InvocationHandler, 实例化一个对象出来. 所以 JDK 的方式是基于接口的.
而 CGLIB 的代理是基于类的, 用目标类生成一个子类, 子类重写父类的方法, 从而达到动态代理的效果. CGLIB 的使用和实现等后面有机会再详细介绍. 目前暂时只要理解两者不同的使用场景就足够了.
总结
回顾下 Spring AOP 的流程
Spring 加载自动代理器 AnnotationAwareAspectJAutoProxyCreator, 当作一个系统组件.
当一个 bean 加载到 Spring 中时, 会触发自动代理器中的 bean 后置处理
bean 后置处理, 会先扫描 bean 中所有的 Advisor
然后用这些 Adviosr 和其他参数构建 ProxyFactory
ProxyFactory 会根据配置和目标对象的类型寻找代理的方式(JDK 动态代理或 CGLIG 代理)
然后代理出来的对象放回 context 中, 完成 Spring AOP 代理
相信大家通过阅读本文, 对 Spring 的 AOP 处理有一定的认识. 想更深入地了解, 探索每一步, 每一行代码的实现, 可以下载 Demo 源码, 一步步地调试
Demo:https://github.com/Zack-Ku/spring-aop-demo
来源: https://www.cnblogs.com/zackku/p/10032663.html