fescar源码解析系列(一)之启动详解

  fescar是gts刚开源的版本,对gts关注已久,比较熟悉其原理,而半年前自己又开发了一个可用版本meepo(详情),所以对fescar的源码也是必看。通过比较,可以看meepo设计上的不足,以及一些编码细节上的困惑。

  项目架构及环境搭建不多叙述,github官网已经够详细了,直接开始撸源码。

    demo的使用上,和meepo大体一致,在需要分布式事务的方法上,加上事务注解即可。

一、@GlobalTransactional注解的使用

很普通的dubbo程序,再看下purchase方法

  和meepo唯一不同的是,fescar使用了自定义注解@GlobalTransactional,meepo则是spring事务注解@Transactional。

  自定义注解代表了fescar弃用了JTA规范,自己承担TM的角色,好处是加入分布式事务后,不会影响到原来的事务实现。meepo默认会修改@Transactional标注的原单机事务实现,虽然不影响使用,但不算合理,可以手动指定transmanager解决,如@Transactional("transactionManager1")。坏处则是放弃了spring 已经实现了的成熟稳定JTA规范。

  现在问题来了,同样实现事务,同样用的注解,两者实现机制一样吗?先说答案,是一样的。本篇文章在讲解@GlobalTransactional注解的同时,也过了一遍spring的@Transactional。

  先跟踪代码,在第一张图的的purchase处,打了一个断点,debug到时,发现service是cglib生成的一个代理。第一个问题来了,为什么是cglib?spring的常识是,aop有接口的bean默认jdk动态代理,没接口的cglib代理,或者xml配置 proxy-target-class="true" 强制使用cglib。但是在本例中,bussinesService有接口,xml未配置,却为何使用cglib代理?

  ok,这个问题一会再找找,我们得先解决另外一个重要的问题,代理类是什么时候生成的?

二、BeanPostProcessor接口

  这个问题好解答,就是BeanPostProcessor接口。fescar、dubbo很多框架包括spring本身,都有使用BeanPostProcessor来为xml中配置的bean生成代理,我写的meepo也有,这是spring很常见的一种扩展使用方式。

  看一下spirng的生命周期。 

 

  在xml里配置的每一个bean,在初始化的前后,会先找到缓存的BeanPostProcessor list,本身作为参数,传入BeanPostProcessor的postProcessBeforeInitialization和postProcessAfterInitialization方法,为bean做一些处理如生成代理类,然后返回。

  fercas的GlobalTransactionScanner承担了这个角色,GlobalTransactionScanner继承了spring aop包下的AbstractAutoProxyCreator类,这个类实现了InstantiationBeanPostProcessor接口,是spring实现aop最关键的一个抽象类,类似JTA事务里的AbstractPlatformTransactionManager。

三、spring aop的核心类AbstractAutoProxyCreator

  默认情况下,代理是通过AbstractAutoProxyCreator中的postProcessAfterInitialization()创建的。

上述方法的执行逻辑为:
a.获取缓存的bean名字,getCacheKey()方法实际上是便于子类扩展。默认返回className_beanName
b.若earlyProxyReferences缓存中不包含当前cacheKey,则调用wrapIfNecessary()创建代理对象。
earlyProxyReferences这个缓存是干嘛的呢?
从上下文中可以找到getEarlyBeanReference()方法中会将相应的cacheKey保存到earlyProxyReferences中,同时也会调用wrapIfNecessary()方法,因此可以看出该缓存用于保存已经创建过代理对象的cachekey,避免重复创建。代码如下:

  那么getEarlyBeanReference()方法又是何时被调用的呢?
  通过分析可以看到,getEarlyBeanReference()方法在SmartInstantiantionAwareBeanPostProcessor中声明,我们知道,spring为了解决单例bean之间的循环依赖问题,提前将代理对象暴露出去。
  接下来是wrapIfNecessary方法,该方法是真正生成代理类的地方。GlobalTransactionScanner重写了该方法,我们来看一下。

  主要逻辑有:

  a. 查看当前bean是否有@GlobalTransactional注解,如果没有,自己返回bean,不再继续生成代理。

  b. new了 一个实现了MethodInterceptor接口的方法拦截器GlobalTransactionalInterceptor,类似jdk动态代理的InvocationHandler,关键逻辑所在

  !AopUtils.isAopProxy(bean) 判断是否已是代理类,避免重复代理,若未被代理,则调用父类的wrapIfNecessary方法。

  AbstractAutoProxyCreator是所有使用spring aop的关键类,和GlobalTransactionScanner一样,spring自身的一些aop机制也使用了AbstractAutoProxyCreator。我们看下@Transactional的使用,配置xml后在需要事务的方法加上注解即可,如下

<!-- 事务管理器配置,单数据源事务 -->
<bean id="pkgouTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="pkGouDataSource" />
</bean>
<!-- 使用annotation定义事务 -->
<tx:annotation-driven transaction-manager="pkgouTransactionManager" />

  tx标签初始化的时候,由对应的解析器AnnotationDrivenBeanDefinitionParser来解析,在解析方法parse中,会注册一个InfrastructureAdvisorAutoProxyCreator类,这个类继承了AbstractAutoProxyCreator。使用普通的aop标签,解析时也会注册一个继承AbstractAutoProxyCreator的AspectJAwareAdvisorAutoProxyCreator类。类结构图如下:

 

  

AbstractAutoProxyCreator这个抽象类,有一个唯一抽象方法getAdvicesAndAdvisorsForBean。该方法作用是为bean获取所有适用的Advice和Advisor。

四、AOP基础概念

Advice和Advisor以及PointCut都是aop都基础概念,简单理解下:

Advice 即通知,具体的处理逻辑,比如方法前后加日志,具体实现可以是MethodInterceptor的invoke方法;

PointCut 是要拦截的范围,比如哪个包下的controller;

Advisor是对Advice和PointCut的一个封装,一个服务可能初始化了几个Advisor,那么bean初始化时,遍历这些Advisor,先判断自己是否在Advisor的PointCut范围内,如果是,则生成aop代理。

  AbstractAutoProxyCreator的类结构图可以看到,它有一个子类AbstractAdvisorAutoProxyCreator,同时是几个标签解析生成的Creator的父类,AbstractAdvisorAutoProxyCreator的作用是自动获取Advisor集合,我们来看下它的getAdvicesAndAdvisorsForBean方法。

  findEligibleAdvisors处理两件事

  • 找到Spring中Advisor的实现类(findCandidateAdvisors)
  • 将所有拥有@Aspect注解的类转换为advisors(aspectJAdvisorsBuilder.buildAspectJAdvisors)
现在问题来了,Advisor的实现类哪里来?我们看前面的标签解析类AnnotationDrivenBeanDefinitionParser的configureAutoProxyCreator方法的一个片段。

标签解析的中间,动态生成一个Advisor

  • 定义一个Advisor
  • 注入interceptor即Advice
  • 注册到spring容器
  以上几步后, findEligibleAdvisors方法就可以找到这个Advisor了。可能有人会有疑问,为什么上面没有PointCut?这个后面会处理,没有PointCut时会生成一个TruePointCut,表示全部范围。毕竟@Transactional这种注解,并没有限制使用的范围。

五、fescar的 GlobalTransactionScanner如何处理

   GlobalTransactionScanner并没有继承AbstractAdvisorAutoProxyCreator,因为fercas的注解并没有在xml里配置,也不需要自动找其他Advisor,它只要处理自己的拦截器就好了,看一下它的getAdvicesAndAdvisorsForBean方法。

  很简单,就是返回了之前在wrap里new的GlobalTransactionalInterceptor,GlobalTransactionalInterceptor实现MethodInterceptor,间接实现了Advice。继续跟踪wrap方法,获取Advice和Advisor后,返回Object[](这里是只有一个Advice)。接着把Object数组和bean,传入到createProxy方法,生成代理类。

这里关键是两步:

  • Advisor[] advisors = buildAdvisors(beanName, specificInterceptors); 
  • proxyFactory.getProxy(getProxyClassLoader());

buildAdvisors把Advice和Advisor的Object[] 混合物,处理后统一成Advisor[],放入new的一个ProxyFactory中,proxyFactory继续生成代理类。

我们看下buildAdvisors关键代码

  如果Object[]里的元素是Advisor,直接返回,如果是Advice而且是MethodInterceptor,包装成一个DefaultPointcutAdvisor,这个Advisor的构造方法,生成一个默认的TruePointcut,也就是前面提到的全部范围。

继续看proxyFactory.getProxy(getProxyClassLoader()) 这句,终于到了选择代理的地方,我们看下。

  这里传入的config的属性拷贝自GlobalTransactionScanner的属性(点进方法可以看到,不贴代码了),当optimize或者ProxyTargetClass任一属性是true的时候,会进入方法体,进一步判断当前bean是否是接口,注意,这里不是判断是否实现接口,显然,业务service是一个class,这时候,就会使用cglib来生成代理类。我们来看下GlobalTransactionScanner构造方法。

  终于可以解答了前面的问题,为什么是cglib的代理类。和xml配置效果一样,只要找到这个属性配置的地方就好了,如果没配,默认是false。

  最后,动态代理的生成过程不再叙述,代理类(jdk动态代理)执行业务接口方法的路径是

$proxy.purchase -> InvocationHandler.invoke ->intercept.invoke 

  cglib代理类的路径没看,应该大体一致。

总结

  本篇主要讲解了@GlobalTransactional 生成代理类的整个过程,其实和fercas框架关系不大,因为其原理和spring的@transactionanl注解基本一样(@transactional详解

  整理下我们的思路,方法上的自定义注解,会为方法所在的类生成aop代理,方法前后加上一些自定义逻辑,比如fescar的全局事务管理。生成代理的路径如下:

  GlobalTransactionScanner.postProcessAfterInitialization->GlobalTransactionScanner.wrapIfNecessary->AbstractAutoProxyCreator.wrapIfNecessary->GlobalTransactionScanner.getAdvicesAndAdvisorsForBean->AbstractAutoProxyCreator.createProxy->AbstractAutoProxyCreator.buildAdvisors->proxyFactory.getProxy

  再总结一下,如果我们想使用自定义注解生成AOP代理,需要哪些步骤:

  • 自定义注解
  • 编写Intercept类
  • 编写代理生成类继承 AbstractAutoProxyCreator,xml中配置该bean
  • 重写getAdvicesAndAdvisorsForBean 定义遍历范围,效果同xml中aop配置的PointCut
  • 重写wrapIfNecessary ,过滤非自己定义注解的bean



 

posted @ 2019-02-21 13:49  妄言12345  阅读(378)  评论(0编辑  收藏  举报