Spring核心源码深度解析(四) invokeBeanFactoryPostProcessors(上)

invokeBeanFactoryPostProcessors(上)


protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
    //这里的参数getBeanFactoryPostProcessors()讲道理很容易误导大家,我在调试的时候发现它什么都没返回,注意他们的区别,list和map
    //回到之前我所以提到的实现了BeanFactoryPostProcessor的Config类,按道理应该是有的,但是其实它拿到的是一个list,
    //而我们是放在map里面的,所以这里它只是拿到程序员自己手动add的,注意注解方式也是拿不到的
    PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

    // Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
    // (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
    if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
      beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
      beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
    }
  }

看这里的参数getBeanFactoryPostProcessors()讲道理很容易误导人,我在调试的时候发现它什么都没返回,注意他们的区别,
image
image

回到之前我所以提到的4个后置处理器类使用的map去存起,所以不是同一个类,而我们的getBeanFactoryPostProcessors()只是拿到程序员自己手动add的,如:
image

,这里会有个坑,如果读者尝试在我们的Main函数加上这段代码:

   public static void main(String[] args) {
    AnnotationConfigApplicationContext annotationConfigApplicationContext=
        new AnnotationConfigApplicationContext(AppConfig.class);
    annotationConfigApplicationContext.addBeanFactoryPostProcessor(new BeanFactoryPostProcessor() {
      @Override
      public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        
      }
    });

  }

然后继续调试getBeanFactoryPostProcessors(),得出的结果会是多少呢,其实还是0,至于为什么,我们换一种写法


 AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
annotationConfigApplicationContext.regisrer(Appconfig.class);
annotationConfigApplicationContext.addBeanFactoryPostProcessor();
annotationConfigApplicationContext.refresh();

再次调试getBeanFactoryPostProcessors(),得出结果为1,相信读者应该有多感悟。搞明白这个之后我们继续进入invokeBeanFactoryPostProcessors

这个方法,

public static void invokeBeanFactoryPostProcessors(
      ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {

    // Invoke BeanDefinitionRegistryPostProcessors first, if any.
    Set<String> processedBeans = new HashSet<>();
        //注意这里的beanFactory是最基本的子类,这个类包含了如何对bean注册,创建,维护等功能,所以一定会进去
    if (beanFactory instanceof BeanDefinitionRegistry) {
      BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;

      //这里定义两个list是为了区分子类是实现了BeanFactoryPostProcessor,而且这里是装的程序员自己添加的
      // 还是BeanDefinitionRegistryPostProcessor,因为这两个接口是父子关系,所以功能有所区别
      List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
      List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();
            //这里肯定为空,前面已经说了,我们这里是程序员自己add的,而我们并没有
      for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
        if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
          BeanDefinitionRegistryPostProcessor registryProcessor =
              (BeanDefinitionRegistryPostProcessor) postProcessor;
          registryProcessor.postProcessBeanDefinitionRegistry(registry);
          registryProcessors.add(registryProcessor);
        }
        else {
          regularPostProcessors.add(postProcessor);
        }
      }

首先遍历beanFactoryPostProcessors,注意这个参数是我们传进来,是从getBeanFactoryPostProcessors()获取的,因此一般情况我们不会手动设置,所以这里都会为空,我们往下
image

在这段代码同样是获取后置处理器,只不过获取的来源不同,还记得我们之前说过的那4个后置处理器吗?其中有一个Spring的关键类ConfigurationClassPostProcessor,不知道读者还是否记得这个类,这里Spring就会把它取出来,我们Dubug调试一下
image

获取这个ConfigurationClassPostProcessor类后,Spring会合并之前两个循环的后置处理器,然后到主干方法invokeBeanDefinitionRegistryPostProcessors


image

我们在跟踪进去:


image

看第二句代码String[] candidateNames = registry.getBeanDefinitionNames();这里会拿出6个Name,添加到configCandidates里,看调试结果回想我在前文所提出的注册的后置处理器就明白为什么是6个,其目的就是为了找出我们的配置类,如果找到就标记成full,有些读者可能了解Spring的@configuration注解是配置类的意思,但不知道读者是否了解过加和不加这个注解的作用是什么,这里我们把@configuration注解注释掉,尝试运行会发现结果并不会报错,


image

至于为什么Spring搞这么个注解,笔者会在后文对@configuration的作用以及源码实现进行详细介绍,我们继续回到代码:


image

这里我们就拿到了我们的配置类,既然拿到了配置类,那么下一步就对配置类进行解析,看代码:


image

看到这句代码 parser.parse();这句代码是Spring解析的核心代码,我们点进去


image

这里判断需要解析的BeanDefinition是那种类型,这里回到我们的入口


image

,我们注册的是AppConfig配置类,但其实我们还可以配置普通类,比如我们直接将我们的User类放进去


image

随后我们在调试parse->processConfigurationClass->


image

-> doProcessConfigurationClass(configClass, sourceClass);


image

看第一段代码,如果我们的配置类有标识了@Component 这样的类

如果有就会返回ture,但可以看到我们并没有标志@Component,缺能够进入这个判断,看下面代码:
image

,看到这里想到大家应该明白了吧。好了我们回到代码进入->processMemberClasses 进行分析,我们先在配置类添加一段代码
image
image

,可以看到已经找到这么一个类,这段代码我们点到为止,因为下面的配置类信息解析包含了这里的设计思想,所以这里不重点分析。


image

首先取出我们的扫描注解


image

然后执行 Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan,
sourceClass.getMetadata().getClassName());这段代码,这里就是解析的核心代码,我们跟进去,


image

看第一段代码new ClassPathBeanDefinitionScanner,这里读者可以回顾下我们在Spring源码第三章执行构造器提到过一个scanner,笔者曾说那个scanner并不是Spring在解析时内部使用的扫描器,而当前笔者已经给出证明,当然之前定义的this.scanner有什么作用,笔者猜测可能是交给外部工程师自己操作的吧,好了话不过说,我们回到代码,接着Spring又拿到了generatorClass,随后给scanner赋予一些属性,最后来到核心代码
image

我们进入doscan方法


image

这里我们看到在经过了findCandidateComponents(basePackage);这个方法后,Spring就已经拿到我了我们定义的注解类,我们继续进入


image
image

可以看出Spring真正解决包扫描结束是采用的ASM字节码提取技术,笔者对此也是一知半解,所以就不多做介绍了,有兴趣的读者们可以自行深入研究,这里我就不做文章了,我们还是回到代码,下面的代码不太重要,因此Spring拿到扫描出来的类后就返回,好的我们继续回到代码


image

在对普通注解类解析完后,会将普通类拿出来作为当做配置类继续递归解析,当然通常都会跳过,然后开始对@import注解解析,有些读者可能还没见过这个@import类,我们看下@Import注解类


image

,它的作用很清晰,目的就是向Spring容器注册类,看下实例:


image

这样就和在MyImportBeanDefinitionRegistrar加上@Component是一样效果,这里源码的细节我带着大家细看了,其目的就是解析@import三种类:普通类,ImportSelector,ImportBeanDefinitionRegistrar。这里我将笔记注释贴出来,大家自行理解:

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
      Collection<SourceClass> importCandidates, boolean checkForCircularImports) {

    if (importCandidates.isEmpty()) {
      return;
    }

    if (checkForCircularImports && isChainedImportOnStack(configClass)) {
      this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
    }
    else {
      this.importStack.push(configClass);
      try {
        for (SourceClass candidate : importCandidates) {
          //这里是处理ImportSelector,转换成bd,放入一个集合
          if (candidate.isAssignable(ImportSelector.class)) {
            // Candidate class is an ImportSelector -> delegate to it to determine imports
            Class<?> candidateClass = candidate.loadClass();
            ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
            ParserStrategyUtils.invokeAwareMethods(
                selector, this.environment, this.resourceLoader, this.registry);
            //这里是否是延迟执行,如果目标对象实现的是DeferredImportSelector,就表示需要延迟
            if (selector instanceof DeferredImportSelector) {
              this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
            }
            else {
              //这里是递归,因为Import的类可能还有Import,如果没有会递归到下面的普通类放入集合
              String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
              Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
              processImports(configClass, currentSourceClass, importSourceClasses, false);
            }
          }
          //ImportBeanDefinitionRegistrar这个是Import的第二种类,实现可以直接拿到注册器
          else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
            // Candidate class is an ImportBeanDefinitionRegistrar ->
            // delegate to it to register additional bean definitions
            Class<?> candidateClass = candidate.loadClass();
            ImportBeanDefinitionRegistrar registrar =
                BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
            ParserStrategyUtils.invokeAwareMethods(
                registrar, this.environment, this.resourceLoader, this.registry);
            //将实现了ImportBeanDefinitionRegistrar的子类放进importBeanDefinitionRegistrars,后面spring会对其处理
            configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
          }
          else {
            // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
            // process it as an @Configuration class
            //普通import类和ImportSelector都放进这个configurationClasses
            this.importStack.registerImport(
                currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
            processConfigurationClass(candidate.asConfigClass(configClass));
          }
        }
      }
      catch (BeanDefinitionStoreException ex) {
        throw ex;
      }
      catch (Throwable ex) {
        throw new BeanDefinitionStoreException(
            "Failed to process import candidates for configuration class [" +
            configClass.getMetadata().getClassName() + "]", ex);
      }
      finally {
        this.importStack.pop();
      }
    }
  }

读者这里重点对ImportBeanDefinitionRegistrar的作用进行分享,看下面实例:


image

如果我们继承了ImportBeanDefinitionRegistrar,那么我们就可以拿到BeanDefinitionRegistry,并就可以手动的向Spring添加一个 BeanDefinition,我们调试一下


image

当前我们的BeanDefinitionMap是11个,等到我们执行完这段代码后就会变成12个,看下图:


image

,我们改变了BeanDefinitionMap,而BeanDefinitionMap在Spring创建对象起到了绝对作用,所以我们就改变了Bean的创建,而这就是扩展Spring的一个接口之一,后期我们有机会的话我们会重点分享Spring-Mybatis和Spring-AOP是如何利用这些接口拓展的。
在对@Import注解解析完之后,又开始对配置类的@Bean解析


image

直到这里我们才算是对parser.parse(candidates);代码执行完毕了,最后我们将这块整体流程用一张图简介
image

,然后我们继续回到代码,在执行完parse方法后


image

然后做校验,重点从这里开始->this.reader.loadBeanDefinitions(configClasses);,我们一直更进去会知道


image

这里就是对我们之前存储的@Import注解类进行运行,并且会执行自定义的@Import类,到目前为止我们总算跑完了
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);这句代码


image

接来下的内容笔者打算分开描述。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342