1.简介

从本篇文章开始,我将会对 Spring AOP 部分的源码进行分析。本文是 Spring AOP 源码分析系列文章的第二篇,本文主要分析 Spring AOP 是如何为目标 bean 筛选出合适的通知器(Advisor)。在上一篇AOP 源码分析导读一文中,我简单介绍了 AOP 中的一些术语及其对应的源码,部分术语和源码将会在本篇文章中出现。如果大家不熟悉这些术语和源码,不妨去看看。
关于 Spring AOP,我个人在日常开发中用过一些,也参照过 tiny-spring 过写过一个玩具版的 AOP 框架,并写成了文章。正因为前面做了一些准备工作,最近再看 Spring AOP 源码时,觉得也没那么难了。所以如果大家打算看 AOP 源码的话,这里建议大家多做一些准备工作。比如熟悉 AOP 的中的术语,亦或是实现一个简单的 IOC 和 AOP,并将两者整合在一起。经过如此准备,相信大家会对 AOP 会有更多的认识。

好了,其他的就不多说了,下面进入源码分析阶段。

2.源码分析

2.1 AOP 入口分析

在导读一文中,我已经说过 Spring AOP 是在何处向目标 bean 中织入通知(Advice)的。也说过 Spring 是如何将 AOP 和 IOC 模块整合到一起的,即通过拓展点 BeanPostProcessor 接口。Spring AOP 抽象代理创建器实现了 BeanPostProcessor 接口,并在 bean 初始化后置处理过程中向 bean 中织入通知。下面我们就来看看相关源码,如下:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupportimplements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {@Override/** bean 初始化后置处理方法 */public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {if (bean != null) {Object cacheKey = getCacheKey(bean.getClass(), beanName);if (!this.earlyProxyReferences.contains(cacheKey)) {// 如果需要,为 bean 生成代理对象return wrapIfNecessary(bean, beanName, cacheKey);}}return bean;}protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {if (beanName != null && this.targetSourcedBeans.contains(beanName)) {return bean;}if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {return bean;}/** 如果是基础设施类(Pointcut、Advice、Advisor 等接口的实现类),或是应该跳过的类,* 则不应该生成代理,此时直接返回 bean*/ if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {// 将 <cacheKey, FALSE> 键值对放入缓存中,供上面的 if 分支使用this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}// 为目标 bean 查找合适的通知器Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);/** 若 specificInterceptors != null,即 specificInterceptors != DO_NOT_PROXY,* 则为 bean 生成代理对象,否则直接返回 bean*/ if (specificInterceptors != DO_NOT_PROXY) {this.advisedBeans.put(cacheKey, Boolean.TRUE);// 创建代理Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));this.proxyTypes.put(cacheKey, proxy.getClass());/** 返回代理对象,此时 IOC 容器输入 bean,得到 proxy。此时,* beanName 对应的 bean 是代理对象,而非原始的 bean*/ return proxy;}this.advisedBeans.put(cacheKey, Boolean.FALSE);// specificInterceptors = null,直接返回 beanreturn bean;}
}

以上就是 Spring AOP 创建代理对象的入口方法分析,过程比较简单,这里简单总结一下:

  1. 若 bean 是 AOP 基础设施类型,则直接返回
  2. 为 bean 查找合适的通知器
  3. 如果通知器数组不为空,则为 bean 生成代理对象,并返回该对象
  4. 若数组为空,则返回原始 bean

上面的流程看起来并不复杂,不过不要被表象所迷糊,以上流程不过是冰山一角。


图片来源:无版权图片网站 pixabay.com

在本文,以及后续的文章中,我将会对步骤2和步骤3对应的源码进行分析。在本篇文章先来分析步骤2对应的源码。

2.2 筛选合适的通知器

在向目标 bean 中织入通知之前,我们先要为 bean 筛选出合适的通知器(通知器持有通知)。如何筛选呢?方式由很多,比如我们可以通过正则表达式匹配方法名,当然更多的时候用的是 AspectJ 表达式进行匹配。那下面我们就来看一下使用 AspectJ 表达式筛选通知器的过程,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource targetSource) {// 查找合适的通知器List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);if (advisors.isEmpty()) {return DO_NOT_PROXY;}return advisors.toArray();
}protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {// 查找所有的通知器List<Advisor> candidateAdvisors = findCandidateAdvisors();/** 筛选可应用在 beanClass 上的 Advisor,通过 ClassFilter 和 MethodMatcher* 对目标类和方法进行匹配*/List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);// 拓展操作extendAdvisors(eligibleAdvisors);if (!eligibleAdvisors.isEmpty()) {eligibleAdvisors = sortAdvisors(eligibleAdvisors);}return eligibleAdvisors;
}

如上,Spring 先查询出所有的通知器,然后再调用 findAdvisorsThatCanApply 对通知器进行筛选。在下面几节中,我将分别对 findCandidateAdvisors 和 findAdvisorsThatCanApply 两个方法进行分析,继续往下看吧。

2.2.1 查找通知器

Spring 提供了两种配置 AOP 的方式,一种是通过 XML 进行配置,另一种是注解。对于两种配置方式,Spring 的处理逻辑是不同的。对于 XML 类型的配置,比如下面的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 目标 bean -->
<bean id="hello" class="xyz.coolblog.aop.Hello"/><aop:aspectj-autoproxy/><!-- 普通 bean,包含 AOP 切面逻辑 -->
<bean id="aopCode" class="xyz.coolblog.aop.AopCode"/>
<!-- 由 @Aspect 注解修饰的切面类 -->
<bean id="annotationAopCode" class="xyz.coolblog.aop.AnnotationAopCode"/><aop:config><aop:aspect ref="aopCode"><aop:pointcut id="helloPointcut" expression="execution(* xyz.coolblog.aop.*.hello*(..))" /><aop:before method="before" pointcut-ref="helloPointcut"/><aop:after method="after" pointcut-ref="helloPointcut"/></aop:aspect>
</aop:config>

Spring 会将上的配置解析为下面的结果:

如上图所示,红框中对应的是普通的 bean 定义,比如 <bean id="hello" .../>、<bean id="annotationAopCode" .../>、<bean id="appCode" .../> 等配置。黄色框中的则是切点的定义,类型为 AspectJExpressionPointcut,对应 <aop:pointcut id="helloPointcut" .../> 配置。那绿色框中的结果对应的是什么配置呢?目前仅剩下两个配置没说,所以对应 <aop:before .../> 和 <aop:after .../> 配置,类型为 AspectJPointcutAdvisor。这里请大家注意,由 @Aspect 注解修饰的 AnnotationAopCode 也是普通类型的 bean,该 bean 会在查找通知器的过程中被解析,并被构建为一个或多个 Advisor。

上面讲解了 Spring AOP 两种配置的处理方式,算是为下面的源码分析做铺垫。现在铺垫完毕,我们就来分析一下源码吧。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class AnnotationAwareAspectJAutoProxyCreator extends AspectJAwareAdvisorAutoProxyCreator {//...@Overrideprotected List<Advisor> findCandidateAdvisors() {// 调用父类方法从容器中查找所有的通知器List<Advisor> advisors = super.findCandidateAdvisors();// 解析 @Aspect 注解,并构建通知器advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());return advisors;}//...
}

AnnotationAwareAspectJAutoProxyCreator 覆写了父类的方法 findCandidateAdvisors,并增加了一步操作,即解析 @Aspect 注解,并构建成通知器。下面我先来分析一下父类中的 findCandidateAdvisors 方法的逻辑,然后再来分析 buildAspectJAdvisors 方法逻的辑。

2.2.1.1 findCandidateAdvisors 方法分析

我们先来看一下 AbstractAdvisorAutoProxyCreator 中 findCandidateAdvisors 方法的定义,如下:

1
2
3
4
5
6
7
8
9
10
11
12
public abstract class AbstractAdvisorAutoProxyCreator extends AbstractAutoProxyCreator {private BeanFactoryAdvisorRetrievalHelper advisorRetrievalHelper;//...protected List<Advisor> findCandidateAdvisors() {return this.advisorRetrievalHelper.findAdvisorBeans();}//...
}

从上面的源码中可以看出,AbstractAdvisorAutoProxyCreator 中的 findCandidateAdvisors 是个空壳方法,所有逻辑封装在了一个 BeanFactoryAdvisorRetrievalHelper 的 findAdvisorBeans 方法中。这里大家可以仔细看一下类名 BeanFactoryAdvisorRetrievalHelper 和方法 findAdvisorBeans,两个名字其实已经描述出他们的职责了。BeanFactoryAdvisorRetrievalHelper 可以理解为从 bean 容器中获取 Advisor 的帮助类,findAdvisorBeans 则可理解为查找 Advisor 类型的 bean。所以即使不看 findAdvisorBeans 方法的源码,我们也可从方法名上推断出它要做什么,即从 bean 容器中将 Advisor 类型的 bean 查找出来。下面我来分析一下这个方法的源码,如下:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public List<Advisor> findAdvisorBeans() {String[] advisorNames = null;synchronized (this) {// cachedAdvisorBeanNames 是 advisor 名称的缓存advisorNames = this.cachedAdvisorBeanNames;/** 如果 cachedAdvisorBeanNames 为空,这里到容器中查找,* 并设置缓存,后续直接使用缓存即可*/ if (advisorNames == null) {// 从容器中查找 Advisor 类型 bean 的名称advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Advisor.class, true, false);// 设置缓存this.cachedAdvisorBeanNames = advisorNames;}}if (advisorNames.length == 0) {return new LinkedList<Advisor>();}List<Advisor> advisors = new LinkedList<Advisor>();// 遍历 advisorNamesfor (String name : advisorNames) {if (isEligibleBean(name)) {// 忽略正在创建中的 advisor beanif (this.beanFactory.isCurrentlyInCreation(name)) {if (logger.isDebugEnabled()) {logger.debug("Skipping currently created advisor '" + name + "'");}}else {try {/** 调用 getBean 方法从容器中获取名称为 name 的 bean,* 并将 bean 添加到 advisors 中*/ advisors.add(this.beanFactory.getBean(name, Advisor.class));}catch (BeanCreationException ex) {Throwable rootCause = ex.getMostSpecificCause();if (rootCause instanceof BeanCurrentlyInCreationException) {BeanCreationException bce = (BeanCreationException) rootCause;if (this.beanFactory.isCurrentlyInCreation(bce.getBeanName())) {if (logger.isDebugEnabled()) {logger.debug("Skipping advisor '" + name +"' with dependency on currently created bean: " + ex.getMessage());}continue;}}throw ex;}}}}return advisors;
}

以上就是从容器中查找 Advisor 类型的 bean 所有的逻辑,代码虽然有点长,但并不复杂。主要做了两件事情:

  1. 从容器中查找所有类型为 Advisor 的 bean 对应的名称
  2. 遍历 advisorNames,并从容器中获取对应的 bean

看完上面的分析,我们继续来分析一下 @Aspect 注解的解析过程。

2.2.1.2 buildAspectJAdvisors 方法分析

与上一节的内容相比,解析 @Aspect 注解的过程还是比较复杂的,需要一些耐心去看。下面我们开始分析 buildAspectJAdvisors 方法的源码,如下:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
public List<Advisor> buildAspectJAdvisors() {List<String> aspectNames = this.aspectBeanNames;if (aspectNames == null) {synchronized (this) {aspectNames = this.aspectBeanNames;if (aspectNames == null) {List<Advisor> advisors = new LinkedList<Advisor>();aspectNames = new LinkedList<String>();// 从容器中获取所有 bean 的名称String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class, true, false);// 遍历 beanNamesfor (String beanName : beanNames) {if (!isEligibleBean(beanName)) {continue;}// 根据 beanName 获取 bean 的类型Class<?> beanType = this.beanFactory.getType(beanName);if (beanType == null) {continue;}// 检测 beanType 是否包含 Aspect 注解if (this.advisorFactory.isAspect(beanType)) {aspectNames.add(beanName);AspectMetadata amd = new AspectMetadata(beanType, beanName);if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {MetadataAwareAspectInstanceFactory factory =new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);// 获取通知器List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);if (this.beanFactory.isSingleton(beanName)) {this.advisorsCache.put(beanName, classAdvisors);}else {this.aspectFactoryCache.put(beanName, factory);}advisors.addAll(classAdvisors);}else {if (this.beanFactory.isSingleton(beanName)) {throw new IllegalArgumentException("Bean with name '" + beanName +"' is a singleton, but aspect instantiation model is not singleton");}MetadataAwareAspectInstanceFactory factory =new PrototypeAspectInstanceFactory(this.beanFactory, beanName);this.aspectFactoryCache.put(beanName, factory);advisors.addAll(this.advisorFactory.getAdvisors(factory));}}}this.aspectBeanNames = aspectNames;return advisors;}}}if (aspectNames.isEmpty()) {return Collections.emptyList();}List<Advisor> advisors = new LinkedList<Advisor>();for (String aspectName : aspectNames) {List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);if (cachedAdvisors != null) {advisors.addAll(cachedAdvisors);}else {MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);advisors.addAll(this.advisorFactory.getAdvisors(factory));}}return advisors;
}

上面就是 buildAspectJAdvisors 的代码,看起来比较长。代码比较多,我们关注重点的方法调用即可。在进行后续的分析前,这里先对 buildAspectJAdvisors 方法的执行流程做个总结。如下:

  1. 获取容器中所有 bean 的名称(beanName)
  2. 遍历上一步获取到的 bean 名称数组,并获取当前 beanName 对应的 bean 类型(beanType)
  3. 根据 beanType 判断当前 bean 是否是一个的 Aspect 注解类,若不是则不做任何处理
  4. 调用 advisorFactory.getAdvisors 获取通知器

下面我们来重点分析advisorFactory.getAdvisors(factory)这个调用,如下:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {// 获取 aspectClass 和 aspectNameClass<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();validate(aspectClass);MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);List<Advisor> advisors = new LinkedList<Advisor>();// getAdvisorMethods 用于返回不包含 @Pointcut 注解的方法for (Method method : getAdvisorMethods(aspectClass)) {// 为每个方法分别调用 getAdvisor 方法Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);if (advisor != null) {advisors.add(advisor);}}// If it's a per target aspect, emit the dummy instantiating aspect.if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);advisors.add(0, instantiationAdvisor);}// Find introduction fields.for (Field field : aspectClass.getDeclaredFields()) {Advisor advisor = getDeclareParentsAdvisor(field);if (advisor != null) {advisors.add(advisor);}}return advisors;
}public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,int declarationOrderInAspect, String aspectName) {validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());// 获取切点实现类AspectJExpressionPointcut expressionPointcut = getPointcut(candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());if (expressionPointcut == null) {return null;}// 创建 Advisor 实现类return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}

如上,getAdvisor 方法包含两个主要步骤,一个是获取 AspectJ 表达式切点,另一个是创建 Advisor 实现类。在第二个步骤中,包含一个隐藏步骤 – 创建 Advice。下面我将按顺序依次分析这两个步骤,先看获取 AspectJ 表达式切点的过程,如下:

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
27
28
29
30
private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {// 获取方法上的 AspectJ 相关注解,包括 @Before,@After 等AspectJAnnotation<?> aspectJAnnotation =AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);if (aspectJAnnotation == null) {return null;}// 创建一个 AspectJExpressionPointcut 对象AspectJExpressionPointcut ajexp =new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class<?>[0]);// 设置切点表达式ajexp.setExpression(aspectJAnnotation.getPointcutExpression());ajexp.setBeanFactory(this.beanFactory);return ajexp;
}protected static AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) {// classesToLookFor 中的元素是大家熟悉的Class<?>[] classesToLookFor = new Class<?>[] {Before.class, Around.class, After.class, AfterReturning.class, AfterThrowing.class, Pointcut.class};for (Class<?> c : classesToLookFor) {// 查找注解AspectJAnnotation<?> foundAnnotation = findAnnotation(method, (Class<Annotation>) c);if (foundAnnotation != null) {return foundAnnotation;}}return null;
}

获取切点的过程并不复杂,不过需要注意的是,目前获取到的切点可能还只是个半成品,需要再次处理一下才行。比如下面的代码:

1
2
3
4
5
6
7
8
9
10
11
@Aspect
public class AnnotationAopCode {@Pointcut("execution(* xyz.coolblog.aop.*.world*(..))")public void pointcut() {}@Before("pointcut()")public void before() {System.out.println("AnnotationAopCode`s before");}
}

@Before 注解中的表达式是pointcut(),也就是说 ajexp 设置的表达式只是一个中间值,不是最终值,即execution(* xyz.coolblog.aop.*.world*(..))。所以后续还需要将 ajexp 中的表达式进行转换,关于这个转换的过程,我就不说了。有点复杂,我暂时没怎么看懂。

说完切点的获取过程,下面再来看看 Advisor 实现类的创建过程。如下:

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
27
28
29
30
public InstantiationModelAwarePointcutAdvisorImpl(AspectJExpressionPointcut declaredPointcut,Method aspectJAdviceMethod, AspectJAdvisorFactory aspectJAdvisorFactory,MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {this.declaredPointcut = declaredPointcut;this.declaringClass = aspectJAdviceMethod.getDeclaringClass();this.methodName = aspectJAdviceMethod.getName();this.parameterTypes = aspectJAdviceMethod.getParameterTypes();this.aspectJAdviceMethod = aspectJAdviceMethod;this.aspectJAdvisorFactory = aspectJAdvisorFactory;this.aspectInstanceFactory = aspectInstanceFactory;this.declarationOrder = declarationOrder;this.aspectName = aspectName;if (aspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {Pointcut preInstantiationPointcut = Pointcuts.union(aspectInstanceFactory.getAspectMetadata().getPerClausePointcut(), this.declaredPointcut);this.pointcut = new PerTargetInstantiationModelPointcut(this.declaredPointcut, preInstantiationPointcut, aspectInstanceFactory);this.lazy = true;}else {this.pointcut = this.declaredPointcut;this.lazy = false;// 按照注解解析 Advicethis.instantiatedAdvice = instantiateAdvice(this.declaredPointcut);}
}

上面是 InstantiationModelAwarePointcutAdvisorImpl 的构造方法,不过我们无需太关心这个方法中的一些初始化逻辑。我们把目光移到构造方法的最后一行代码中,即 instantiateAdvice(this.declaredPointcut),这个方法用于创建通知 Advice。在上一篇文章中我已经说过,通知器 Advisor 是通知 Advice 的持有者,所以在 Advisor 实现类的构造方法中创建通知也是合适的。那下面我们就来看看构建通知的过程是怎样的,如下:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
private Advice instantiateAdvice(AspectJExpressionPointcut pcut) {return this.aspectJAdvisorFactory.getAdvice(this.aspectJAdviceMethod, pcut,this.aspectInstanceFactory, this.declarationOrder, this.aspectName);
}public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut,MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();validate(candidateAspectClass);// 获取 Advice 注解AspectJAnnotation<?> aspectJAnnotation =AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);if (aspectJAnnotation == null) {return null;}if (!isAspect(candidateAspectClass)) {throw new AopConfigException("Advice must be declared inside an aspect type: " +"Offending method '" + candidateAdviceMethod + "' in class [" +candidateAspectClass.getName() + "]");}if (logger.isDebugEnabled()) {logger.debug("Found AspectJ method: " + candidateAdviceMethod);}AbstractAspectJAdvice springAdvice;// 按照注解类型生成相应的 Advice 实现类switch (aspectJAnnotation.getAnnotationType()) {case AtBefore:    // @Before -> AspectJMethodBeforeAdvicespringAdvice = new AspectJMethodBeforeAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);break;case AtAfter:    // @After -> AspectJAfterAdvicespringAdvice = new AspectJAfterAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);break;case AtAfterReturning:    // @AfterReturning -> AspectJAfterAdvicespringAdvice = new AspectJAfterReturningAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();if (StringUtils.hasText(afterReturningAnnotation.returning())) {springAdvice.setReturningName(afterReturningAnnotation.returning());}break;case AtAfterThrowing:    // @AfterThrowing -> AspectJAfterThrowingAdvicespringAdvice = new AspectJAfterThrowingAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {springAdvice.setThrowingName(afterThrowingAnnotation.throwing());}break;case AtAround:    // @Around -> AspectJAroundAdvicespringAdvice = new AspectJAroundAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);break;/** 什么都不做,直接返回 null。从整个方法的调用栈来看,* 并不会出现注解类型为 AtPointcut 的情况*/ case AtPointcut:    if (logger.isDebugEnabled()) {logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'");}return null;default:throw new UnsupportedOperationException("Unsupported advice type on method: " + candidateAdviceMethod);}springAdvice.setAspectName(aspectName);springAdvice.setDeclarationOrder(declarationOrder);/** 获取方法的参数列表名称,比如方法 int sum(int numX, int numY), * getParameterNames(sum) 得到 argNames = [numX, numY]*/String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);if (argNames != null) {// 设置参数名springAdvice.setArgumentNamesFromStringArray(argNames);}springAdvice.calculateArgumentBindings();return springAdvice;
}

上面的代码逻辑不是很复杂,主要的逻辑就是根据注解类型生成与之对应的通知对象。下面来总结一下获取通知器(getAdvisors)整个过程的逻辑,如下:

  1. 从目标 bean 中获取不包含 Pointcut 注解的方法列表
  2. 遍历上一步获取的方法列表,并调用 getAdvisor 获取当前方法对应的 Advisor
  3. 创建 AspectJExpressionPointcut 对象,并从方法中的注解中获取表达式,最后设置到切点对象中
  4. 创建 Advisor 实现类对象 InstantiationModelAwarePointcutAdvisorImpl
  5. 调用 instantiateAdvice 方法构建通知
  6. 调用 getAdvice 方法,并根据注解类型创建相应的通知

如上所示,上面的步骤做了一定的简化。总的来说,获取通知器的过程还是比较复杂的,并不是很容易看懂。大家在阅读的过程中,还要写一些测试代码进行调试才行。调试的过程中,一些不关心的调用就别跟进去了,不然会陷入很深的调用栈中,影响对源码主流程的理解。

现在,大家知道了通知是怎么创建的。那我们难道不要去看看这些通知的实现源码吗?显然,我们应该看一下。那接下里,我们一起来分析一下 AspectJMethodBeforeAdvice,也就是 @Before 注解对应的通知实现类。看看它的逻辑是什么样的。

2.2.1.3 AspectJMethodBeforeAdvice 分析

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class AspectJMethodBeforeAdvice extends AbstractAspectJAdvice implements MethodBeforeAdvice {public AspectJMethodBeforeAdvice(Method aspectJBeforeAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) {super(aspectJBeforeAdviceMethod, pointcut, aif);}@Overridepublic void before(Method method, Object[] args, Object target) throws Throwable {// 调用通知方法invokeAdviceMethod(getJoinPointMatch(), null, null);}@Overridepublic boolean isBeforeAdvice() {return true;}@Overridepublic boolean isAfterAdvice() {return false;}}protected Object invokeAdviceMethod(JoinPointMatch jpMatch, Object returnValue, Throwable ex) throws Throwable {// 调用通知方法,并向其传递参数return invokeAdviceMethodWithGivenArgs(argBinding(getJoinPoint(), jpMatch, returnValue, ex));
}protected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable {Object[] actualArgs = args;if (this.aspectJAdviceMethod.getParameterTypes().length == 0) {actualArgs = null;}try {ReflectionUtils.makeAccessible(this.aspectJAdviceMethod);// 通过反射调用通知方法return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs);}catch (IllegalArgumentException ex) {throw new AopInvocationException("Mismatch on arguments to advice method [" +this.aspectJAdviceMethod + "]; pointcut expression [" +this.pointcut.getPointcutExpression() + "]", ex);}catch (InvocationTargetException ex) {throw ex.getTargetException();}
}

如上,AspectJMethodBeforeAdvice 的源码比较简单,这里我们仅关注 before 方法。这个方法调用了父类中的 invokeAdviceMethod,然后 invokeAdviceMethod 在调用 invokeAdviceMethodWithGivenArgs,最后在 invokeAdviceMethodWithGivenArgs 通过反射执行通知方法。是不是很简单?

关于 AspectJMethodBeforeAdvice 就简单介绍到这里吧,至于剩下的几种实现,大家可以自己去看看。好了,关于 AspectJMethodBeforeAdvice 的源码分析,就分析到这里了。我们继续往下看吧。

2.2.2 筛选合适的通知器

查找出所有的通知器,整个流程还没算完,接下来我们还要对这些通知器进行筛选。适合应用在当前 bean 上的通知器留下,不适合的就让它自生自灭吧。那下面我们来分析一下通知器筛选的过程,如下:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
protected List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {ProxyCreationContext.setCurrentProxiedBeanName(beanName);try {// 调用重载方法return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);}finally {ProxyCreationContext.setCurrentProxiedBeanName(null);}
}public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {if (candidateAdvisors.isEmpty()) {return candidateAdvisors;}List<Advisor> eligibleAdvisors = new LinkedList<Advisor>();for (Advisor candidate : candidateAdvisors) {// 筛选 IntroductionAdvisor 类型的通知器if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {eligibleAdvisors.add(candidate);}}boolean hasIntroductions = !eligibleAdvisors.isEmpty();for (Advisor candidate : candidateAdvisors) {if (candidate instanceof IntroductionAdvisor) {continue;}// 筛选普通类型的通知器if (canApply(candidate, clazz, hasIntroductions)) {eligibleAdvisors.add(candidate);}}return eligibleAdvisors;
}public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {if (advisor instanceof IntroductionAdvisor) {/** 从通知器中获取类型过滤器 ClassFilter,并调用 matchers 方法进行匹配。* ClassFilter 接口的实现类 AspectJExpressionPointcut 为例,该类的* 匹配工作由 AspectJ 表达式解析器负责,具体匹配细节这个就没法分析了,我* AspectJ 表达式的工作流程不是很熟*/return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);}else if (advisor instanceof PointcutAdvisor) {PointcutAdvisor pca = (PointcutAdvisor) advisor;// 对于普通类型的通知器,这里继续调用重载方法进行筛选return canApply(pca.getPointcut(), targetClass, hasIntroductions);}else {return true;}
}public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {Assert.notNull(pc, "Pointcut must not be null");// 使用 ClassFilter 匹配 classif (!pc.getClassFilter().matches(targetClass)) {return false;}MethodMatcher methodMatcher = pc.getMethodMatcher();if (methodMatcher == MethodMatcher.TRUE) {return true;}IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;if (methodMatcher instanceof IntroductionAwareMethodMatcher) {introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;}/** 查找当前类及其父类(以及父类的父类等等)所实现的接口,由于接口中的方法是 public,* 所以当前类可以继承其父类,和父类的父类中所有的接口方法*/ Set<Class<?>> classes = new LinkedHashSet<Class<?>>(ClassUtils.getAllInterfacesForClassAsSet(targetClass));classes.add(targetClass);for (Class<?> clazz : classes) {// 获取当前类的方法列表,包括从父类中继承的方法Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);for (Method method : methods) {// 使用 methodMatcher 匹配方法,匹配成功即可立即返回if ((introductionAwareMethodMatcher != null &&introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) ||methodMatcher.matches(method, targetClass)) {return true;}}}return false;
}

以上是通知器筛选的过程,筛选的工作主要由 ClassFilter 和 MethodMatcher 完成。关于 ClassFilter 和 MethodMatcher 我在导读一文中已经说过了,这里再说一遍吧。在 AOP 中,切点 Pointcut 是用来匹配连接点的,以 AspectJExpressionPointcut 类型的切点为例。该类型切点实现了ClassFilter 和 MethodMatcher 接口,匹配的工作则是由 AspectJ 表达式解析器复杂。除了使用 AspectJ 表达式进行匹配,Spring 还提供了基于正则表达式的切点类,以及更简单的根据方法名进行匹配的切点类。大家有兴趣的话,可以自己去了解一下,这里就不多说了。

在完成通知器的查找和筛选过程后,还需要进行最后一步处理 – 对通知器列表进行拓展。怎么拓展呢?我们一起到下一节中一探究竟吧。

2.2.3 拓展筛选出通知器列表

拓展方法 extendAdvisors 做的事情并不多,逻辑也比较简单。我们一起来看一下,如下:

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
27
28
29
30
31
32
33
34
35
36
protected void extendAdvisors(List<Advisor> candidateAdvisors) {AspectJProxyUtils.makeAdvisorChainAspectJCapableIfNecessary(candidateAdvisors);
}public static boolean makeAdvisorChainAspectJCapableIfNecessary(List<Advisor> advisors) {// 如果通知器列表是一个空列表,则啥都不做if (!advisors.isEmpty()) {boolean foundAspectJAdvice = false;/** 下面的 for 循环用于检测 advisors 列表中是否存在 * AspectJ 类型的 Advisor 或 Advice*/for (Advisor advisor : advisors) {if (isAspectJAdvice(advisor)) {foundAspectJAdvice = true;}}/** 向 advisors 列表的首部添加 DefaultPointcutAdvisor,* 至于为什么这样做,我会在后续的文章中进行说明*/if (foundAspectJAdvice && !advisors.contains(ExposeInvocationInterceptor.ADVISOR)) {advisors.add(0, ExposeInvocationInterceptor.ADVISOR);return true;}}return false;
}private static boolean isAspectJAdvice(Advisor advisor) {return (advisor instanceof InstantiationModelAwarePointcutAdvisor ||advisor.getAdvice() instanceof AbstractAspectJAdvice ||(advisor instanceof PointcutAdvisor &&((PointcutAdvisor) advisor).getPointcut() instanceof AspectJExpressionPointcut));
}

如上,上面的代码比较少,也不复杂。由源码可以看出 extendAdvisors 是一个空壳方法,除了调用makeAdvisorChainAspectJCapableIfNecessary,该方法没有其他更多的逻辑了。至于 makeAdvisorChainAspectJCapableIfNecessary 这个方法,该方法主要的目的是向通知器列表首部添加 DefaultPointcutAdvisor 类型的通知器,也就是 ExposeInvocationInterceptor.ADVISOR。至于添加此种类型通知器的意图,我会在后面文章里说明,这里不便展开。关于 extendAdvisors 这个方法,这里就先说到这了。

3.总结

到这里,本篇文章就接近尾声了。这篇文章有点长,大家看下来应该蛮累的吧。由于个人能力问题,暂时未能做到对本篇文章中所贴的源码进行更为细致的分析,有点遗憾。不过好在目前把主逻辑分析弄清楚了,总的来说还算合格吧,给个及格分。大家在阅读的过程中,如果发现文章中出现错误或不妥之处,这里还请指明,也请多多指教。大家共同学习,一起进步。

好了,本篇文章就到这里了。谢谢大家的阅读。

参考

  • 《Spring 源码深度解析》- 郝佳

附录:Spring 源码分析文章列表

Ⅰ. IOC

更新时间 标题
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 容器源码分析 - 余下的初始化工作

Ⅱ. AOP

更新时间 标题
2018-06-17 Spring AOP 源码分析系列文章导读
2018-06-20 Spring AOP 源码分析 - 筛选合适的通知器
2018-06-20 Spring AOP 源码分析 - 创建代理对象
2018-06-22 Spring AOP 源码分析 - 拦截器链的执行过程

Ⅲ. MVC

更新时间 标题
2018-06-29 Spring MVC 原理探秘 - 一个请求的旅行过程
2018-06-30 Spring MVC 原理探秘 - 容器的创建过程
  • 本文链接: https://www.tianxiaobo.com/2018/06/20/Spring-AOP-源码分析-筛选合适的通知器/

from: http://www.tianxiaobo.com/2018/06/20/Spring-AOP-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90-%E7%AD%9B%E9%80%89%E5%90%88%E9%80%82%E7%9A%84%E9%80%9A%E7%9F%A5%E5%99%A8/

Spring AOP 源码分析 - 筛选合适的通知器相关推荐

  1. Spring AOP 源码分析 - 拦截器链的执行过程

    1.简介 本篇文章是 AOP 源码分析系列文章的最后一篇文章,在前面的两篇文章中,我分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器,以及如何创建代理对象的过程.现在我们的得 ...

  2. Spring AOP 源码分析 - 创建代理对象

    1.简介 在上一篇文章中,我分析了 Spring 是如何为目标 bean 筛选合适的通知器的.现在通知器选好了,接下来就要通过代理的方式将通知器(Advisor)所持有的通知(Advice)织入到 b ...

  3. 【Spring】Spring AOP源码分析-导读(一)

    文章目录 1.简介 2.AOP 原理 3.AOP 术语及相应的实现 3.1 连接点 - Joinpoint 3.2 切点 - Pointcut 3.3 通知 - Advice 3.4 切面 - Asp ...

  4. spring AOP源码分析(一)

    spring AOP源码分析(一) 对于springAOP的源码分析,我打算分三部分来讲解:1.配置文件的解析,解析为BeanDefination和其他信息然后注册到BeanFactory中:2.为目 ...

  5. 一步一步手绘Spring AOP运行时序图(Spring AOP 源码分析)

    相关内容: 架构师系列内容:架构师学习笔记(持续更新) 一步一步手绘Spring IOC运行时序图一(Spring 核心容器 IOC初始化过程) 一步一步手绘Spring IOC运行时序图二(基于XM ...

  6. Spring AOP源码分析(八)SpringAOP要注意的地方

    2019独角兽企业重金招聘Python工程师标准>>> SpringAOP要注意的地方有很多,下面就举一个,之后想到了再列出来: (1)SpringAOP对于最外层的函数只拦截pub ...

  7. Spring AOP源码分析(七)ProxyFactoryBean介绍

    2019独角兽企业重金招聘Python工程师标准>>> 这篇文章里面就要说说Spring自己的AOP,搞清楚哪种方式是Spring自己实现的AOP,哪种方式是Spring引入aspe ...

  8. Spring AOP源码分析二

    上一篇中,我们已经找到了AOP的源码入口,我们今天继续分析下面的代码,不过在此之前我们需要看下Spring中如何使用切面的,以便于我们理解我们的源码.代码如下: package com.younger ...

  9. Spring AOP源码分析(四)Spring AOP的JDK动态代理

    2019独角兽企业重金招聘Python工程师标准>>> 本篇文章将会介绍上一个例子中的源码执行情况,从中熟悉整个SpringAOP的一些概念和接口设计. 首先整个SpringAOP的 ...

最新文章

  1. docfetcher,filelocator使用
  2. 冯怡:我想给大家展示一种可能性(图灵访谈)
  3. django中聚合aggregate和annotate GROUP BY的使用方法
  4. 这个开源项目...看了就停不下来啊!
  5. Linux内存映射mmap原理分析
  6. 怎么用计算机计算出选手最后得分,WPS技巧:TRIMMEAN函数计算选手得分
  7. MacBook/MacOS如何写入MacBook特有的符号(例如:⌘/⌥/⎋/⇪)
  8. ARM 汇编学习——编写简单的ARM汇编程序
  9. ●BZOJ 1855 [Scoi2010]股票交易
  10. Web前端开发规范 之html命名规范
  11. bert获取词向量做textcnn文本分类
  12. 财帮子,一个非常不错的投资理财社区
  13. html div 作用,div是什么意思,都有什么作用?
  14. html自动生成段落,HTML中的段落文本怎么换行
  15. ssm 基于微信小程序美容理发店预约系统app
  16. 互动媒体技术——Processing创意绘画系统
  17. GIS开源库GEOS库学习教程(二):geos中的几何图形(Geometry)
  18. 移动光猫调整桥接模式
  19. 【2019年04月03日】A股最便宜的股票
  20. 2021年世界地图shp数据arcgis中英文字段美化mxd(含效果图)

热门文章

  1. 从netty-example分析Netty组件续
  2. 编码与乱码(05)---GBK与UTF-8之间的转换--转载
  3. 【数据分析】Python :知乎数据清洗整理和结论研究
  4. CDH6.3.0 HUE 整合 Oozie调度
  5. 基于若依框架的二次开发_浅谈若依框架
  6. 重庆市推进组建区块链数字资产交易所
  7. 杰夫 · 贝佐斯:事情瞬息万变,需要马上行动
  8. App市场的“繁荣”背后 隐藏令人唏嘘的真相
  9. 小工匠聊架构-分布式场景下的并发幂等性常见的解决方案
  10. MyBatis-11MyBatis动态SQL之【if】