• 曹工说Spring Boot源码(18)-- Spring AOP源码分析三部曲,终于快讲完了 (aop:config完整解析【下】)


    写在前面的话

    相关背景及资源:

    曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享

    曹工说Spring Boot源码(2)-- Bean Definition到底是什么,咱们对着接口,逐个方法讲解

    曹工说Spring Boot源码(3)-- 手动注册Bean Definition不比游戏好玩吗,我们来试一下

    曹工说Spring Boot源码(4)-- 我是怎么自定义ApplicationContext,从json文件读取bean definition的?

    曹工说Spring Boot源码(5)-- 怎么从properties文件读取bean

    曹工说Spring Boot源码(6)-- Spring怎么从xml文件里解析bean的

    曹工说Spring Boot源码(7)-- Spring解析xml文件,到底从中得到了什么(上)

    曹工说Spring Boot源码(8)-- Spring解析xml文件,到底从中得到了什么(util命名空间)

    曹工说Spring Boot源码(9)-- Spring解析xml文件,到底从中得到了什么(context命名空间上)

    曹工说Spring Boot源码(10)-- Spring解析xml文件,到底从中得到了什么(context:annotation-config 解析)

    曹工说Spring Boot源码(11)-- context:component-scan,你真的会用吗(这次来说说它的奇技淫巧)

    曹工说Spring Boot源码(12)-- Spring解析xml文件,到底从中得到了什么(context:component-scan完整解析)

    曹工说Spring Boot源码(13)-- AspectJ的运行时织入(Load-Time-Weaving),基本内容是讲清楚了(附源码)

    曹工说Spring Boot源码(14)-- AspectJ的Load-Time-Weaving的两种实现方式细细讲解,以及怎么和Spring Instrumentation集成

    曹工说Spring Boot源码(15)-- Spring从xml文件里到底得到了什么(context:load-time-weaver 完整解析)

    曹工说Spring Boot源码(16)-- Spring从xml文件里到底得到了什么(aop:config完整解析【上】)

    曹工说Spring Boot源码(17)-- Spring从xml文件里到底得到了什么(aop:config完整解析【中】)

    工程代码地址 思维导图地址

    工程结构图:

    概要

    本篇是接着前两篇讲的,为了避免不必要的重复,请大家先看下。

    曹工说Spring Boot源码(16)-- Spring从xml文件里到底得到了什么(aop:config完整解析【上】)

    曹工说Spring Boot源码(17)-- Spring从xml文件里到底得到了什么(aop:config完整解析【中】)

    本篇主要讲解spring如何在bean生成过程中,完成“狸猫换太子”操作的。(利用代理对象,替换ioc容器中的原有bean)。

    回顾前文

    spring解析的xml如下:

    <!--目标对象-->
    <bean id="performer" class="foo.Performer"/>
    
    <!--切面-->
    <bean id="performAspect" class="foo.PerformAspect"/>
    
    <!--配置切入点-->
    <aop:config>
        <aop:pointcut id="mypointcut" expression="execution(public * foo.Perform.sing(..))"/>
        <aop:aspect id="myAspect" ref="performAspect">
            <aop:after method="afterPerform" pointcut-ref="mypointcut"/>
        </aop:aspect>
    </aop:config>
    

    经前文的理解,总共生成了如下几个bean definition。

    bean definition 中bean class 备注
    PerformAspect 通知
    Performer 要切的目标
    AspectJExpressionPointcut 切点,即<aop:pointcut />那一行
    org.springframework.aop.aspectj.AspectJPointcutAdvisor advisor,解析<aop:after method="afterPerform" pointcut-ref="mypointcut"/> 这一行得到
    AspectJAwareAdvisorAutoProxyCreator 实现了BeanPostProcessor接口,解析<aop:config>得到

    spring大致启动流程

    1. 解析xml、注解,通过各种渠道,得到bean definition;

    2. 从bean definition集合中,找出bean class实现了BeanFactoryPostProcessor接口的子集,然后通过getBean来获取这部分特殊的bean,然后依次调用其postProcessBeanFactory方法

      来对其余的bean definition进行处理;

      public interface BeanFactoryPostProcessor {
      
      	/**
      	 * Modify the application context's internal bean factory after its standard
      	 * initialization. All bean definitions will have been loaded, but no beans
      	 * will have been instantiated yet. This allows for overriding or adding
      	 * properties even to eager-initializing beans.
      	 * @param beanFactory the bean factory used by the application context
      	 * @throws org.springframework.beans.BeansException in case of errors
      	 */
      	void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory);
      }
      
    3. 从bean definition集合中,找出bean class实现了BeanPostProcessor接口的子集,然后通过getBean来获取这部分特殊的bean,然后保存起来。

    4. 找出不具有特殊功能的(没实现BeanFactoryPostProcessor,也没实现BeanPostProcessor)bean definition 集合,再过滤出单例bean,且lazy-init属性为false(说明要在spring容器初始化过程中,实例化的bean)的这部分,作为子集;然后依次调用其getBean方法,在这个过程中,会使用第三步获取到的beanPostProcessor来处理这些bean,时机大致包括:实例化前后,初始化前后。

    以上就是spring比较粗的流程,当然,很多细节没说,不过核心流程是这样的。

    spring aop如何生效

    1. 实例化AspectJAwareAdvisorAutoProxyCreator

    在前面的spring流程中,第三步,就会去查找实现了BeanPostProcessor的bean definition集合,其中,就会包含AspectJAwareAdvisorAutoProxyCreator 这个bean definition;然后通过getBean方法,将这个bean definition实例化为bean。(这个过程,类似于通过class来创建对象)

    这个步骤的具体实现入口在:

    public void refresh() throws BeansException, IllegalStateException {
       synchronized (this.startupShutdownMonitor) {
          // Prepare this context for refreshing.
          prepareRefresh();
    
          // Tell the subclass to refresh the internal bean factory.
          ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    
          /**
           * 配置beanFactory
           */
          prepareBeanFactory(beanFactory);
    
          try {
             postProcessBeanFactory(beanFactory);
    
             // Invoke factory processors registered as beans in the context.
             invokeBeanFactoryPostProcessors(beanFactory);
    
             // 入口在这里!这里面会去 getBean
             registerBeanPostProcessors(beanFactory);
    
             // Initialize message source for this context.
             initMessageSource();
    
             // Initialize other special beans in specific context subclasses.
             onRefresh();
    
             // Check for listener beans and register them.
             registerListeners();
    
             /**
              * 这里对单例bean、且lazy-init=false进行实例化
              */
             finishBeanFactoryInitialization(beanFactory);
    
             // Last step: publish corresponding event.
             finishRefresh();
          }
       }
    }
    

    具体跟进去后,在如下位置,809行,就通过getBean,获取到了AspectJAwareAdvisorAutoProxyCreator这个bean了:

    2.AspectJAwareAdvisorAutoProxyCreator对target bean进行后置处理,生成代理

    在“spring大致启动流程”中的第四步,去预先实例化bean时,会进入以下逻辑:

    public void refresh() throws BeansException, IllegalStateException {
       synchronized (this.startupShutdownMonitor) {
    	  ...
    
          try {
    		...
    
             // Check for listener beans and register them.
             registerListeners();
    
             /**
              * 入口在这里。这里对单例bean、且lazy-init=false进行实例化
              */
             finishBeanFactoryInitialization(beanFactory);
    
             // Last step: publish corresponding event.
             finishRefresh();
          }
       }
    }
    

    然后会进入以下逻辑:

    org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons
    public void preInstantiateSingletons() throws BeansException {
       if (this.logger.isInfoEnabled()) {
          this.logger.info("Pre-instantiating singletons in " + this);
       }
       // 其中 this.beanDefinitionNames 保存了所有的bean definition名称
       for (String beanName : this.beanDefinitionNames) {
          RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
          // 实例化那些:非抽象、单例、非lazy-init的bean definition
          if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
             if (isFactoryBean(beanName)) {
    		   ...
                if (isEagerInit) {
                   getBean(beanName);
                }
             }
             else {
                getBean(beanName);
             }
          }
       }
    }
    

    所以,这里就是对那些:非抽象、单例、非lazy-init的bean definition 进行实例化。

    我们这里,大家看下当前容器中包含的全部的bean definition:

    我这边的demo,第一个要实例化的bean,就是performer。在getBean()处理performer这个bean的过程中,会经历以下的流程:

    其中,AspectJAwareAdvisorAutoProxyCreator 作为beanPostProcessor,在实例化performer这个bean时,有两个时间点参与进来。

    哪两个时间点呢,大家再看看,AspectJAwareAdvisorAutoProxyCreator 的类图:

    其实它不止实现了BeanPostProcessor接口,它还实现了InstantiationAwareBeanPostProcessor接口。这个多出来的接口,干嘛的?大家看看它的方法就知道了:

    public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
    
    	// 注意,before Instantiation的意思是,实例化之前调用
    	Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException;
    
    	// 这个,实例化之后调用
    	boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException;
    
    	...无关方法
    }
    

    所以,InstantiationAwareBeanPostProcessor扩展了两个时间点出来,一个是实例化前,一个是实例化后。

    什么叫实例化?把一个对象new出来,通俗来讲,就叫做实例化。

    在spring的getBean流程里,实例化肯定是早于初始化的。所以,一个bean,会经历如下顺序(大家直接看前面点的图,更清晰):

    1. InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation;

    2. 根据bean definition来实例化,假设bean class为A,bean definition中的constructorArgumentValues参数,如果为空,则使用A的默认构造函数;如果constructorArgumentValues有值,表示:在此之前,需要先获取到相应的构造函数值,才能去反射通过构造器来创建对象

      这里面,会涉及到bean的递归调用。

      比如,以前一篇和开头提到的,AspectJPointcutAdvisor 这个bean definition来说,其中它的构造函数,就要求一个AbstractAspectJAdvice 对象:

      public AspectJPointcutAdvisor(AbstractAspectJAdvice advice) {
         Assert.notNull(advice, "Advice must not be null");
         this.advice = advice;
         this.pointcut = advice.buildSafePointcut();
      }
      

      那AbstractAspectJAdvice 这个对象要怎么生成,这个在我们的场景下,是被抽象成一个内部bean的,bean class为AspectJAfterAdvice。AspectJAfterAdvice这个类呢,构造函数又是下面这样的:

      public AspectJAfterAdvice(
            Method aspectJBeforeAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) {
      
         super(aspectJBeforeAdviceMethod, pointcut, aif);
      }
      

      又依赖了一堆其他的参数,这三个参数,其中2个也是被定义为了内部bean definition,一个为bean 引用。(具体请参照前一篇)。

      所以,这个bean的实例化过程就相对繁琐,涉及到bean的递归生成。

    3. InstantiationAwareBeanPostProcessor的postProcessAfterInstantiation;

    4. 属性注入,此时是autowired等发挥作用的地方;

    5. BeanPostProcessor的postProcessBeforeInitialization

    6. BeanPostProcessor#postProcessAfterInitialization

    这里面6个步骤,AspectJAwareAdvisorAutoProxyCreator 在其中两个地方,实现了自己的业务逻辑。

    2.1 InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation

    具体实现在:

    org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
       Object cacheKey = getCacheKey(beanClass, beanName);
    
       if (beanName == null || !this.targetSourcedBeans.contains(beanName)) {
          if (this.advisedBeans.containsKey(cacheKey)) {
             return null;
          }
          // 入口在这:shouldSkip 
          if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
             this.advisedBeans.put(cacheKey, Boolean.FALSE);
             return null;
          }
       }
    
       // Create proxy here if we have a custom TargetSource.
       // Suppresses unnecessary default instantiation of the target bean:
       // The TargetSource will handle target instances in a custom fashion.
       if (beanName != null) {
          TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
          if (targetSource != null) {
             this.targetSourcedBeans.add(beanName);
             Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
             Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
             this.proxyTypes.put(cacheKey, proxy.getClass());
             return proxy;
          }
       }
    
       return null;
    }
    

    以上逻辑中,我们需要进入到shouldSkip方法,重点是下面的findCandidateAdvisors方法:

    @Override
    protected boolean shouldSkip(Class beanClass, String beanName) {
       // TODO: Consider optimization by caching the list of the aspect names
       /**
        * 这里调用了父类中实现的findCandidateAdvisors,获取候选的advisor bean;这里也是真正根据bean
        * definition去生成advisor bean的地方
        */
       List<Advisor> candidateAdvisors = **findCandidateAdvisors()**;
       for (Advisor advisor : candidateAdvisors) {
          /**
           * 如果当前要检查的bean,就是advisor里的通知类,则跳过
           */
          if (advisor instanceof AspectJPointcutAdvisor) {
             if (((AbstractAspectJAdvice) advisor.getAdvice()).getAspectName().equals(beanName)) {
                return true;
             }
          }
       }
       return super.shouldSkip(beanClass, beanName);
    }
    

    下面会找出ioc容器中,实现了Advisor接口的bean definition,并全部实例化。Advisor是什么?

    我们前面提到的AspectJPointcutAdvisor,就实现了这个接口。

    public List<Advisor> findAdvisorBeans() {
       // Determine list of advisor bean names, if not cached already.
       String[] advisorNames = null;
       synchronized (this) {
          advisorNames = this.cachedAdvisorBeanNames;
          if (advisorNames == null) {
             // 1.从spring容器查找Advisor类型的bean definition
             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>();
       for (String name : advisorNames) {
          if (isEligibleBean(name) && !this.beanFactory.isCurrentlyInCreation(name)) {
                // 遍历那些bean definition,通过getBean,来获取bean
                advisors.add(this.beanFactory.getBean(name, Advisor.class));
          }
       }
       return advisors;
    }
    

    再啰嗦一句,advisor差不多是aop的核心数据结构,你通过Aspect注解方式,最终也是解析为一个个的Advisor。

    一个切点+一个切面方法 基本就等于一个Advisor对象。比如,一个before方法,算一个;在一个after,又算一个。

    2.2 BeanPostProcessor的postProcessAfterInitialization

    这是第二个时间点,在这里,检查bean要不要被拦截,生成代理。大家可以简单理解,因为每个bean的创建过程,都要被它处理,它呢,就会检查,这个bean,是不是匹配切点,如果匹配,就生成代理。

    举个例子,以前看过一个小说,是京官去很偏远的地方上任,路上被人杀了,并被另外一个人拿了印章,到了地方,靠着印章,招摇撞骗,当了地方官。假设切点是:每个过路人;切面:偷天换日。那这个PostProcessor,就是那伙人,拦截每个过路人,并判断是不是那个倒霉的京官,如果是,就杀了并且换个人拿了印章去当地方官。

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
       if (bean != null) {
          Object cacheKey = getCacheKey(bean.getClass(), beanName);
          if (!this.earlyProxyReferences.contains(cacheKey)) {
             // 返回生成的代理对象
             return wrapIfNecessary(bean, beanName, cacheKey);
          }
       }
       return bean;
    }
    

    这里的下面这句,bean是原始bean,wrapIfNecessary返回的,就作为最终的bean。如果你不需要切这个bean,那你就返回它本身;如果要代理,你就返回另一个代理对象即可。

    return wrapIfNecessary(bean, beanName, cacheKey);
    

    我们仔细看这个方法内部:

    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;
       }
       if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
          this.advisedBeans.put(cacheKey, Boolean.FALSE);
          return bean;
       }
    
       // 获取前面2.1章节里的Advisor对象,并保存到Object[] specificInterceptors 里
       Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
       if (specificInterceptors != null) {
          this.advisedBeans.put(cacheKey, Boolean.TRUE);
          // 创建代理,注意,Advisor数组,已经被传进去了。
          Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
          this.proxyTypes.put(cacheKey, proxy.getClass());
          return proxy;
       }
    
       this.advisedBeans.put(cacheKey, Boolean.FALSE);
       return bean;
    }
    

    大家看上面的注释吧,这里获取了ioc容器里的Advisor对象,如果advisor数组不为null,则创建代理,并返回代理。大家看下面的debug图就理解了。

    创建代理的过程就简单了,基本就是:有接口就搞个jdk 动态代理,否则就cglib代理。

    我做了个小测试,

    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
       if (bean.getClass().getName().equals("foo.Performer")) {
          return "hahha";
       }
       ...其他代码省略,主要加了上面那个if
       return bean;
    }
    
    public final class Main {
    
        public static void main(String[] args) {
            ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
                    "context-namespace-test-aop.xml");
    
            Perform performer = (Perform) ctx.getBean(Perform.class);
            performer.sing();
        }
    }
    

    然后在倒数第二行,获取Perform类型的bean时,报错了。为啥呢?因为本来foo.Performer实现了Perform接口,但现在,我用一个string作为代理返回去了,所以就没有实现Perform接口的bean存在了

    Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [foo.Perform] is defined
    	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:296)
    	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1196)
    	at foo.Main.main(Main.java:23)
    

    总结

    spring aop这个东西,还是不简单,我本来打算这一讲把全部内容说清楚;但现在发现,创建代理这部分,还是得放到下一篇。

    至于源码那些,在第16篇里给了地址的,大家可以看看。有啥问题,及时联系我。

  • 相关阅读:
    ArcGIS Engine开发-三维视图(SceneControl)的刷新
    c#开发基础知识及有用技巧(一)
    ArcGIS Engine开发-取得ArcMap文档缩略图
    RichTextBox中表格不能折行的问题
    Windows WorkFlow Foundation入门(六) 编译工作流
    你还在用 VMware?快试试这款更轻量级的虚拟机!
    RedisJson 横空出世,性能碾压 ES 和 MongoDB!NoSQL 要变天了吗?
    基于 ElasticSearch 实现站内全文搜索,写得太好了!
    团灭!Log4j 1.x 也爆雷了。。。速速弃用!!
    Spring Boot + Redis:抗住 10w 人,秒杀抢单!
  • 原文地址:https://www.cnblogs.com/grey-wolf/p/12322587.html
Copyright © 2020-2023  润新知