前一段时间,我学习了 Spring IOC 容器方面的源码,并写了数篇文章对此进行讲解。在写完 Spring IOC 容器源码分析系列文章中的最后一篇后,没敢懈怠,趁热打铁,花了3天时间阅读了 AOP 方面的源码。开始觉得 AOP 部分的源码也会比较复杂,因此原计划投入一周的时间用于阅读源码。但在我大体理清 AOP 源码逻辑后,发现没想的那么复杂,因此目前进度算是超前了。从今天(5.15)开始,我将对 AOP 部分的源码分析系列文章进行更新。包括本篇文章在内,本系列大概会有4篇文章,我将会在接下来一周时间内陆续进行更新。在本系列文章中,我将会分析 Spring AOP 是如何为 bean 筛选合适的通知器(Advisor),以及代理对象生成的过程。除此以外,还会对拦截器的调用过程进行分析。与前面的文章同样,本系列文章不会对 AOP 的 XML 配置解析过程进行分析。html
下面来说讲本篇文章的内容,在本篇文章中,我将会向你们介绍一下 AOP 的原理,以及 AOP 中的一些术语及其对应的源码。我以为,你们在阅读 AOP 源码时,必定要弄懂这些术语和源码。否则,在阅读 AOP 源码的过程当中,可能会有点晕。好了,其余的就很少说了,下面进入正题吧。java
关于 AOP 的原理,想必你们都知道了。无非是经过代理模式为目标对象生产代理对象,并将横切逻辑插入到目标方法执行的先后。这样一说,本章确实没什么好说的了,毕竟原理就是这么简单。不过原理归原理,在具体的实现上,不少事情并没想象的那么简单。好比,咱们须要肯定是否应该为某个 bean 生成代理,若是应该的话,还要进一步肯定将横切逻辑插入到哪些方法上。说到横切逻辑,这里简单介绍一下。横切逻辑其实就是通知(Advice),Spring 提供了5种通知,Spring 须要为每种通知提供相应的实现类。除了以上说的这些,在具体的实现过程当中,还要考虑如何将 AOP 和 IOC 整合在一块儿,毕竟 IOC 是 Spring 框架的根基。除此以外,还有其余一些须要考虑的地方,这里就不一一列举了。总之 AOP 原理提及来容易,但作起来却不简单,尤为是实现一个业界承认的,久经考验的框架。因此,在随后的文章中,让咱们带着对代码的敬畏之心,去学习 Spring AOP 模块的源码吧。spring
本章我来向你们介绍一下 AOP 中的一些术语,并会把这些术语对应的代码也贴出来。在介绍这些术语以前,咱们先来了解一下 AOP 吧。AOP 全称是 Aspect Oriented Programming,即面向切面的编程,AOP 是一种开发理念。经过 AOP,咱们能够把一些非业务逻辑的代码,好比安全检查,监控等代码从业务方法中抽取出来,以非侵入的方式与原方法进行协同。这样可使原方法更专一于业务逻辑,代码结构会更加清晰,便于维护。express
这里特别说明一下,AOP 并不是是 Spring 首创,AOP 有本身的标准,也有机构在维护这个标准。Spring AOP 目前也遵循相关标准,因此别认为 AOP 是 Spring 首创的。编程
链接点是指程序执行过程当中的一些点,好比方法调用,异常处理等。在 Spring AOP 中,仅支持方法级别的链接点。上面是比较官方的说明,下面举个例子说明一下。如今咱们有一个用户服务 UserService 接口,该接口定义以下:安全
public interface UserService { void save(User user); void update(User user); void delete(String userId); User findOne(String userId); List<User> findAll(); boolean exists(String userId); }
该接口的实现类是 UserServiceImpl,假设该类的方法调用以下:框架
如上所示,每一个方法调用都是一个链接点。接下来,咱们来看看链接点的定义:源码分析
public interface Joinpoint { /** 用于执行拦截器链中的下一个拦截器逻辑 */ Object proceed() throws Throwable; Object getThis(); AccessibleObject getStaticPart(); }
这个 Joinpoint 接口中,proceed 方法是核心,该方法用于执行拦截器逻辑。关于拦截器这里简单说一下吧,以前置通知拦截器
为例。在执行目标方法前,该拦截器首先会执行前置通知逻辑,若是拦截器链中还有其余的拦截器,则继续调用下一个拦截器逻辑。直到拦截器链中没有其余的拦截器后,再去调用目标方法。关于拦截器这里先说这么多,在后续文章中,我会进行更为详细的说明。性能
上面说到一个方法调用就是一个链接点,那下面咱们不妨看一下方法调用
这个接口的定义。以下:学习
public interface Invocation extends Joinpoint { Object[] getArguments(); } public interface MethodInvocation extends Invocation { Method getMethod(); }
如上所示,方法调用接口 MethodInvocation 继承自 Invocation,Invocation 接口又继承自 Joinpoint。看了上面的代码,我想你们如今对链接点应该有更多的一些认识了。接下面,咱们来继续看一下 Joinpoint 接口的一个实现类 ReflectiveMethodInvocation。固然不是看源码,而是看它的继承体系图。以下:
关于链接点的相关知识,咱们先了解到这里。有了这些链接点,接下来要作的事情是对咱们感兴趣链接点进行一些横切操做。在操做以前,咱们首先要把咱们所感兴趣的链接点选中,怎么选中的呢?这就是切点 Pointcut 要作的事情了,继续往下看。
刚刚说到切点是用于选择链接点的,那么应该怎么选呢?在回答这个问题前,咱们不妨先去看看 Pointcut 接口的定义。以下:
public interface Pointcut { /** 返回一个类型过滤器 */ ClassFilter getClassFilter(); /** 返回一个方法匹配器 */ MethodMatcher getMethodMatcher(); Pointcut TRUE = TruePointcut.INSTANCE; }
Pointcut 接口中定义了两个接口,分别用于返回类型过滤器和方法匹配器。下面咱们再来看一下类型过滤器和方法匹配器接口的定义:
public interface ClassFilter { boolean matches(Class<?> clazz); ClassFilter TRUE = TrueClassFilter.INSTANCE; } public interface MethodMatcher { boolean matches(Method method, Class<?> targetClass); boolean matches(Method method, Class<?> targetClass, Object... args); boolean isRuntime(); MethodMatcher TRUE = TrueMethodMatcher.INSTANCE; }
上面的两个接口均定义了 matches 方法,用户只要实现了 matches 方法,便可对链接点进行选择。在平常使用中,你们一般是用 AspectJ 表达式对链接点进行选择。Spring 中提供了一个 AspectJ 表达式切点类 - AspectJExpressionPointcut,下面咱们来看一下这个类的继承体系图:
如上所示,这个类最终实现了 Pointcut、ClassFilter 和 MethodMatcher 接口,所以该类具有了经过 AspectJ 表达式对链接点进行选择的能力。那下面咱们不妨写一个表达式对上一节的链接点进行选择,好比下面这个表达式:
execution(* *.find*(..))
该表达式用于选择以 find 的开头的方法,选择结果以下:
经过上面的表达式,咱们能够就能够选中 findOne 和 findAll 两个方法了。那选中方法以后呢?固然是要搞点事情。so,接下来通知(Advice)
就该上场了。
通知 Advice 即咱们定义的横切逻辑,好比咱们能够定义一个用于监控方法性能的通知,也能够定义一个安全检查的通知等。若是说切点解决了通知在哪里调用的问题,那么如今还须要考虑了一个问题,即通知在什么时候被调用?是在目标方法前被调用,仍是在目标方法返回后被调用,还在二者兼备呢?Spring 帮咱们解答了这个问题,Spring 中定义了如下几种通知类型:
上面是对通知的一些介绍,下面咱们来看一下通知的源码吧。以下:
public interface Advice { }
如上,通知接口里好像什么都没定义。不过别慌,咱们再去到它的子类接口中一探究竟。
/** BeforeAdvice */ public interface BeforeAdvice extends Advice { } public interface MethodBeforeAdvice extends BeforeAdvice { void before(Method method, Object[] args, Object target) throws Throwable; } /** AfterAdvice */ public interface AfterAdvice extends Advice { } public interface AfterReturningAdvice extends AfterAdvice { void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable; }
从上面的代码中能够看出,Advice 接口的子类接口里仍是定义了一些东西的。下面咱们再来看看 Advice 接口的具体实现类 AspectJMethodBeforeAdvice 的继承体系图,以下:
如今咱们有了切点 Pointcut 和通知 Advice,因为这两个模块目前仍是分离的,咱们须要把它们整合在一块儿。这样切点就能够为通知进行导航,而后由通知逻辑实施精确打击。那怎么整合两个模块呢?答案是,切面
。好的,是时候来介绍切面 Aspect 这个概念了。
切面 Aspect 整合了切点和通知两个模块,切点解决了 where 问题,通知解决了 when 和 how 问题。切面把二者整合起来,就能够解决 对什么方法(where)在什么时候(when - 前置仍是后置,或者环绕)执行什么样的横切逻辑(how)的三连发问题。在 AOP 中,切面只是一个概念,并无一个具体的接口或类与此对应。不过 Spring 中却是有一个接口的用途和切面很像,咱们不妨了解一下,这个接口就是切点通知器 PointcutAdvisor。咱们先来看看这个接口的定义,以下:
public interface Advisor { Advice getAdvice(); boolean isPerInstance(); } public interface PointcutAdvisor extends Advisor { Pointcut getPointcut(); }
简单来讲一下 PointcutAdvisor 及其父接口 Advisor,Advisor 中有一个 getAdvice 方法,用于返回通知。PointcutAdvisor 在 Advisor 基础上,新增了 getPointcut 方法,用于返回切点对象。所以 PointcutAdvisor 的实现类便可以返回切点,也能够返回通知,因此说 PointcutAdvisor 和切面的功能类似。不过他们之间仍是有一些差别的,好比看下面的配置:
<bean id="aopCode" class="xyz.coolblog.aop.AopCode"/> <aop:config expose-proxy="true"> <aop:aspect ref="aopCode"> <!-- pointcut --> <aop:pointcut id="helloPointcut" expression="execution(* xyz.coolblog.aop.*.hello*(..))" /> <!-- advoce --> <aop:before method="before" pointcut-ref="helloPointcut"/> <aop:after method="after" pointcut-ref="helloPointcut"/> </aop:aspect> </aop:config>
如上,一个切面中配置了一个切点和两个通知,两个通知均引用了同一个切点,即 pointcut-ref="helloPointcut"。这里在一个切面中,一个切点对应多个通知,是一对多的关系(能够配置多个 pointcut,造成多对多的关系)。而在 PointcutAdvisor 的实现类中,切点和通知是一一对应的关系。上面的通知最终会被转换成两个 PointcutAdvisor,这里我把源码调试的结果贴在下面:
在本节的最后,咱们再来看看 PointcutAdvisor 的实现类 AspectJPointcutAdvisor 的继承体系图。以下:
如今咱们有了链接点、切点、通知,以及切面等,可谓万事俱备,可是还差了一股东风。这股东风是什么呢?没错,就是织入。所谓织入就是在切点的引导下,将通知逻辑插入到方法调用上,使得咱们的通知逻辑在方法调用时得以执行。说完织入的概念,如今来讲说 Spring 是经过何种方式将通知织入到目标方法上的。先来讲说以何种方式进行织入,这个方式就是经过实现后置处理器 BeanPostProcessor 接口。该接口是 Spring 提供的一个拓展接口,经过实现该接口,用户可在 bean 初始化先后作一些自定义操做。那 Spring 是在什么时候进行织入操做的呢?答案是在 bean 初始化完成后,即 bean 执行完初始化方法(init-method)。Spring经过切点对 bean 类中的方法进行匹配。若匹配成功,则会为该 bean 生成代理对象,并将代理对象返回给容器。容器向后置处理器输入 bean 对象,获得 bean 对象的代理,这样就完成了织入过程。关于后置处理器的细节,这里就很少说了.你们如有兴趣,能够参考我以前写的Spring IOC 容器源码分析系列文章。
本篇文章做为 AOP 源码分析系列文章的导读,简单介绍了 AOP 中的一些术语,及其对应的源码。总的来讲,没有什么特别之处。毕竟对于 AOP,你们都有所了解。所以,若文中有不妥错误之处,还请你们指明。固然,也但愿多多指教。
好了,本篇文章先到这里。感谢你们的阅读。
本文在知识共享许可协议 4.0 下发布,转载需在明显位置处注明出处
做者:coolblog.xyz
本文同步发布在个人我的博客: http://www.coolblog.xyz
更新时间 | 标题 |
---|---|
2018-05-30 | Spring IOC 容器源码分析系列文章导读 |
2018-06-01 | Spring IOC 容器源码分析 - 获取单例 bean |
2018-06-04 | Spring IOC 容器源码分析 - 建立单例 bean 的过程 |
2018-06-06 | Spring IOC 容器源码分析 - 建立原始 bean 对象 |
2018-06-08 | Spring IOC 容器源码分析 - 循环依赖的解决办法 |
2018-06-11 | Spring IOC 容器源码分析 - 填充属性到 bean 原始对象 |
2018-06-11 | Spring IOC 容器源码分析 - 余下的初始化工做 |
更新时间 | 标题 |
---|---|
2018-06-17 | Spring AOP 源码分析系列文章导读 |
2018-06-20 | Spring AOP 源码分析 - 筛选合适的通知器 |
2018-06-20 | Spring AOP 源码分析 - 建立代理对象 |
2018-06-22 | Spring AOP 源码分析 - 拦截器链的执行过程 |
更新时间 | 标题 |
---|---|
2018-06-29 | Spring MVC 原理探秘 - 一个请求的旅行过程 |
2018-06-30 | Spring MVC 原理探秘 - 容器的建立过程 |
本做品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。