源码揭秘!Spring中AOP编程实现原理!(四) 完结

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第10天,点击查看活动详情

源码揭秘系列|目录

源码揭秘! Spring IOC之Autowired注解!
源码揭秘! Spring中Bean扫描的原理!
源码揭秘! Spring和Mybatis整合的原理!
源码揭秘! Spring中AOP编程实现原理! (一) 准备动作
源码揭秘! Spring中AOP编程实现原理! (二) Advice类的注册流程
源码揭秘! Spring中AOP编程实现原理! (三) 被切面类创建流程(1)

前言

在上一章节中,postProcessBeforeInstantiation方法,这个方法运行完成后,会返回一个null。既然是PostProcessor,有before方法,也有after方法

正式开始

此处进入postProcessAfterInitialization方法看一下。

@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
   if (bean != null) {
      Object cacheKey = getCacheKey(bean.getClass(), beanName);
      if (this.earlyProxyReferences.remove(cacheKey) != bean) {
         return wrapIfNecessary(bean, beanName, cacheKey);
      }
   }
   return bean;
}
复制代码

执行到这里,Bean的init方法已经执行完成了,所以此时,If会进入。getCachekey最后返回BeanName。

在earlyProxyReferences属性中remove掉cacheKey会得到一个null,所以此时也会进入内部的If中,调用warpIfNecessary。方法是做什么的,这里不知道,接着往下看。

warpIfNecessary 方法解读

先来看一下方法的入参。

Object wrapIfNecessary(Object bean, String beanName, Object cacheKey)
复制代码

这里三个形参,见名知意,就不多做解释了。直接看第一段代码。 image.png 进入后可以看到几个If,一个一个来分析:
第一个IF

if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
   return bean;
}
复制代码

先是判断了Beanname,然后去targetsourcedBeans中判断是否有该值,按我说,我没讲过这个属性,应该是empty的。 image.png 断点查看一下,果然如此,所以继续下一个If

第二个IF

if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
   return bean;
}
复制代码

在这个If中,可以看到拿着cacheKey去advisedBeans中获取,然后和False比较。这里犹豫我们当前的Bean是被切入类,所以此时不在此容器中存在,所以不为空。

第三个IF

if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
   this.advisedBeans.put(cacheKey, Boolean.FALSE);
   return bean;
}
复制代码

这个If和after中的代码一样,可以直接跳过,因为不会进入,此处返回false。

后续的代码逻辑

image.png 这里先简单阅读一下当前的代码,通过bean,拿到了切入点的一个集合。
随后判断切入点集合是否为空,不为Null进入代理逻辑。
随后像advisedBeans中打入一个标记,最后返回Bean。

此处阅读的重点有两个,一个是getAdvicesAndAdvisorsForBean方法,另外一个是createProxy方法。这里一个一个看

getAdvicesAndAdvisorsForBean 方法

@Override
@Nullable
protected Object[] getAdvicesAndAdvisorsForBean(
      Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {

   List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
   if (advisors.isEmpty()) {
      return DO_NOT_PROXY;
   }
   return advisors.toArray();
}
复制代码

这里的方法很简单,主要实现逻辑在findEligibleAdvisors方法中,接着往下追。 image.png 看到上方的代码块,有两个List,大致可以猜出是做什么用的。第一个List是获取当前Spring中的所有的切入点。第二个集合是经过过滤的,只有当前类的切入点的一个集合。

findCandidateAdvisors方法我们在before方法中,有涉足到,这里就不讲了,原理其实就是把Spring中的所有Bean遍历一遍,找出所有切入类,然后根据类再找出所有切入点。我们暂时先看一下第二个方法。

findAdvisorsThatCanApply

image.png 这里方法,明眼人都可以看出来,关键在于中间那行代码,其他的看方法就知道是往上下文中塞入一些东西,我们不管他,接着往下追。

image.png 这里先看第一段代码,在这个里面,遍历了candiadateAdvisors,在其中去判断是否instanceof于IntroductionAdvisor,我们这里的Advisor(切入点)都是InstantiationModelAwarePointcutAdvisorImpl,所以不继承自这个接口,此处不会走。

image.png 在这一段代码中,有一个成员变量,hasIntroductions,由于刚刚的eligibleAdvisors集合中并没有添加值,所以此时他是Empty的,取反即为false。
随后判断candidate是否继承自introductionAdvisor。此时不继承,接着判断,调用canApply方法,由此可见关键方法在于此方法中,我们接着往下追。

image.png 查看这里的代码比较简单,两个判断调用不同的方法,看到这里很明显,会进入第二个If中去,想一下我们刚刚提到的,我们Advisor的类型。InstantiationModelAwarePointcutAdvisorImpl,毋庸置疑会进入第二个。我们追进去查看。

image.png 进入方法后,我们引来第一个判断,通过pc获取到classFilter,然后matches当前要注册的Class。还记得Pc是什么吗,切入点。 image.png 这里通过Debug帮诸位回顾一下。这个If很明显就是根据表达式来判断当前类是否为切入类。不是的话直接放回false。我们这里接着往下看。

image.png 这个If,讲真的我没太看明白他是做什么的,不过可以大致看出来,是用来做方法匹配的。有一个行注释,这里要翻译一下

No need to iterate the methods if we're matching any method anyway...
如果我们仍然匹配任何方法,则无需迭代方法......

看这个翻译就明白是什么意思了。那我们接着往下看代码。

image.png 随后创建了个一个方法匹配器,想来是用来做迭代方法用的了。

image.png 到这里就知道了,最后是通过反射,获取到类中的所有方法,遍历Method对象,用方法匹配器去判断是否为有效的。最终一路返回true,回到FindAdvisorsThatCanApply方法。 image.png 最终加入到了eligibleAdvisors中,返回了回去。最终回到了warpIfNecessary方法中,我们接着往下看。

warpIfNecessary方法后续

image.png 接下来也就是正式的,创建代理的环节了。

由于我们这里刚刚拿到切入点集合,所以这里不为null,会进入If。想advisedBeans中打入了一个标记,这里的value有一点变化,是为True。
后续进入了CreateProxy方法。

createProxy方法解读

看方法前,先看方法的入参。此时方法进入,给定了四个参数。

  1. beanClass
  2. beanName
  3. specificInterceptors 切入点集合
  4. tagetSouce new了一个targetsource,把执行完init方法的Bean作为入参传了进去

image.png 方法一进入,迎面而来一个If,这个If是会进入的,因为我们一开始创建的工厂是DefaultListableBeanFactory看名字就是同类。但是里面做了什么讲真的,我不是没搞懂,在里面设置了一个attr > originalTargetClass,可能后面有用到吧,这里先跳过。

image.png 再看第二段代码,这里开头创建了一个proxyFacotry,也就是说,后面可能会使用这个代理工厂来创建代理对象。随后进入了一个If判断,我点进去查看后直接返回了false。所以进入else代码,此处调用了shouldProxyTargetClass方法。 image.png 这里代码追进去后,发现是与xml中的属性进行equals判断,很明显我没有设置该属性,返回false,这里也进入Else环节调用了evaluateProxyInterfaces方法。 image.png 最近去查看,发现就是普通的根据Class获取继承的接口,随后像ProxyFactory中添加了接口,这里猜测,这个接口后面要应用到创建Proxy对象中的。

image.png 在这一段代码中,根据传入进来切入点集合,又再次了build了切入点,随后给ProxyFactory放了一些配置。至于customerProxyFactory,点击进去看了,是个模板方法。

image.png 在这最后一段代码中,getProxy了,所以此时我们继续往里追。 image.png 看到这里,先去创建一个aopProxy。所以此时需要去看一下是如何创建的。往里追代码。 image.png 看到这里就明了了,为什么人们常说,AOP的实现方式有哪些,一种是CGLIB,一种是JDK了。这里有个点值得注意,看当前的方法写,是这样的。如果原来的class是接口,或者原来的class是代理class,再或者是lambda的话,就算配置了cglib他还是会用jdk的动态代理。

image.png 好的,我们回到这里,AopProxy已经创建完毕,接下来就getProxy了。

image.png 我们这里默认是JDK模式,看这个方法是直接调用JDK的实例化方法了。

这里可能有人好奇,他是怎么做到在Bean执行前后干涉的呢。这个原理其实很简单。如果通过反射去调用一个类的方法都是通过invoke方法,这里注意,调用newProxyInstance方法时,第三个方法传入了一个this,点进去看后,发现这个方法是需要一个接口,那么很明显,我们当前的JdkDynamicAopProxy实现了这个接口,通过查看这个接口中有一个invoke方法。那么此时就明了了,我们直接定位到invoke方法。

image.png 通过最终的查看,把代码定位到这里,通过targetClass,也就是代理本身的Class,获取到了一堆chain。也就是责任链。然后把一堆责任链封装进了MethodInvocation类中。随后调用了他的proceed方法,我们接着往下追。 image.png 追到这里,大概率就是这里了,在这里执行切入点的代码。

告一段落

这篇文章到这里就结束了,这一篇最为AOP的终章,看完不知道对你有没有收获呢。 关于CGLib那边的注册流程感兴趣的话,可以试着自己阅读一下看看。

都看到这了,点个赞再走呗,宝~

结束语

写文章的目的是为了帮助自己巩固知识,写的不好或者错误的地方可以在评论区指出。如果您看了文章觉得对您有所帮助可以点个赞,如果发现有些问题产生了疑惑,或者不明白的可以评论、加我微信,一定知无不言。当然也希望和大家交个朋友,相互学习。

猜你喜欢

转载自juejin.im/post/7085437712420831245