天天看点

2.3 spring5源码系列---内置的后置处理器PostProcess加载源码

1. BeanFactoryPostProcessor调用过程源码剖析

2. 配置类的解析过程源码

3. 配置类@Configuration加与不加的区别

4. 重复beanName的覆盖规则

5. @ComponentScan的解析原理

我们经常会在一个类上打上@Configuration, @Component, @Bean等. 带有这些注解的类, 就是我们所说的配置类. 那么, spring启动的时候,是如何加载这些配置类的呢?

下面就以此为目的, 分析spring源码. 本节的内容是对上一节内容的实战分析, 同时更加详细的解读spring源码

2.3 spring5源码系列---内置的后置处理器PostProcess加载源码

我们知道, spring启动的时候做了3件事, 就是上面的三件事. 

我们先定义好要分析加载的配置类

这个配置类很简单, 使用@ComponentScan注解指定了扫描的包. @Configuration指定当前是一个配置类

在main里, 通过AnnotationConfigurationApplicationContext读取配置类MainConfig.class.

配置类被传进来以后, 到底是怎么被解析的呢? 这就是我们分析的线索

始终不要忘记我们的整体架构图. 对照这个图来分析. 思路更清晰. 整体内容讲解在这里: https://www.cnblogs.com/ITPower/p/13677635.html

2.3 spring5源码系列---内置的后置处理器PostProcess加载源码

下面, 从入口进入. 我们的入口就是这里

下面进入AnnotationConfigApplicationContext的构造方法

在初始化AnnotatedBeanDefinitionReader(this);的时候, 注册了很多后置处理器

我们看到, 注册了6个原始RootBeanDefinition, 这些bean是spring自己提前定义好的, 他们的加载是整个spring的基础. 用于解析spring中其他的类

而这一次我们要研究配置类是如何被读取的, 所以重点关注的是下面这个后置处理器

这里还有很多其他的原始类被注册了, 但我们的目标是分析配置类是如何被读取的, 所以, 其他的先忽略, 只看ConfigurationClassPostProcessor.

2.3 spring5源码系列---内置的后置处理器PostProcess加载源码

 可以看到ConfigurationClassPostProcessor是同时实现了BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor. 这一点我们需要记住, 后面会使用到

首先,构建了一个RootBeanDefinition. 然后调用了registerPostProcessor方法, 三个入参分别是

这里面的关键代码是标红的部分, 将ConfigurationClassPostProcessor放入到了beanDefinitionMap里面

下面的else是处理循环引用的问题, 暂时先不要看. 

2.3 spring5源码系列---内置的后置处理器PostProcess加载源码

在this()构造方法里, 还初始化了ClassPathBeanDefinitionScanner, 这里只说一句. 

我们在扫描配置类的时候, 确实使用的是ClassPathBeanDefinitionScanner, 但是, 不是this.scanner对象. 而是自己new的一个ClassPathBeanDefinitionScanner.

这里的scanner仅仅是为了程序员可以手动调用AnnotationConfigApplicationContext对象的scan方法

通过调用context.scan("package name");扫描处理配置类

使用方式如下:

2.3 spring5源码系列---内置的后置处理器PostProcess加载源码

到目前为止完成了后置处理器注册为BeanDefinition

备注: 

ConfigurationClassPostProcessor是一个工具类, 这个类的作用是解析配置类.

工具类有了, 那么还得有主角呀, 那就是我们上面的配置类. 下面看看配置类的加载

 注册配置类,入口自然是这里了

跟踪进去找到doRegisterBean(...)方法

重点就是红色这句话, 其他可以略过, 因为我们的配置类很简单, 直接看BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);

我们找到 registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());方法, 进入到DefaultListableBeanFactory查看方法, 这个方法之前我们已经调用过一次

就是在注册ConfigurationClassPostProcessor的时候, 我们需要将其解析为BeanDefinition然后放到BeanDefinitionMap中, 这里也是一样的, 将我们的配置类MainConfig解析成BeanDefinition放入到BeanDefinitionMap中.

这里的代码在整个框架中处于什么位置呢? 将MainConfig解析为BeanDefinition放入到BeanDefinitionMap中

2.3 spring5源码系列---内置的后置处理器PostProcess加载源码

以上两步, 一个是将ConfigurationClassPostProcessor配置类后置处理器, 也就是解析配置的工具类, 解析成BeanDefinition放入到BeanDefinitionMap中

另一个是将我们的目标配置类MainConfig加载到内存, 组装成BeanDefinition放入到BeanDefinitionMap中. 

到这里,我们完成了两步.

2.3 spring5源码系列---内置的后置处理器PostProcess加载源码

第一步: 准备工具类ConfigurationClassPostProcessor

第二步: 准备配置类MainConfig. 

接下俩, 就是要使用工具类来解析配置类MainConfig了

在refresh()中有很多步骤, 我们重点来看invokeBeanFactoryPostProcessors(beanFactory);

invokeBeanFactoryPostProcessors(beanFactory);看名字, 调用的是Bean工厂的后置处理器, 上面分析了, 初始化的时候初始化了很多spring原生的后置处理器, 这么多后置处理器, 其实, 只有一个后置处理器实现了BeanFactoryPostProcessor, 它就是ConfigurationClassPostProcessor, 还记得上面的结构图么, 拿下来, 再看一遍. 

2.3 spring5源码系列---内置的后置处理器PostProcess加载源码

 这里调用的时候, 原生处理器只会调用ConfigurationClassPostProcessor

这里要调用bean工厂的后置处理器了. 看上面的注释, 注释写的很清晰.

在调用PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());的时候调用了getBeanFactoryPostProcessors()方法. 

接下来重点来了. PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors()); 方法实现一共分为两大步:

来看看源码是如何定义的. 重点看代码的注释, 每一部分的功能都有明确标出, 注释写的很详细

2.3 spring5源码系列---内置的后置处理器PostProcess加载源码

 下面我们就来分析上图所示的内容. 

首先, 拿到了所有实现了BeanDefinitionRegistryPostProcessor的后置处理器, 上面我们做过铺垫,只有ConfigurationClassPostProcessor实现了BeanDefinitionRegistryPostProcessor后置处理器

所以,这里过滤出来的postProcessorNames只有一个,就是ConfigurationClassPostProcessor, 接下来, 判断这个类是否实现了PriorityOrdered 优先排序的接口, 如果实现了, 那么放入到currentRegistryProcessors中, 后面会进行调用.

接下来, 执行invokeBeanDefinitionRegistryPostProcessors

这是第一次调用BeanDefinitionRegistryPostProcessors

第二次调用的时候 ,依然是获取所有的实现了BeanDefinitionRegistryPostProcessor接口的后置处理器, 且这个处理器没有实现过PriorityOrdered也就是没有被上面调用过. 且实现了Ordered接口

这一类添加到currentRegistryProcessors集合中, 然后调用invokeBeanDefinitionRegistryPostProcessors处理

这是第二次调用BeanDefinitionRegistryPostProcessor

第三次调用的是没有实现过任何排序接口的后置处理器. 并将其放入到currentRegistryProcessors, 然后执行invokeBeanDefinitionRegistryPostProcessors

ConfigurationClassPostProcessor同时实现了BeanDefinitionRegistryPostProcessor 和 BeanFactoryPostProcessors, 调用的是invokeBeanFactoryPostProcessors

一共进行了4次调用

总结: 优先处理的是实现了PriorityOrdered的后置处理器, 然后调用实现了Order接口的后置处理器, 最后调用了没有实现任何排序方法的后置处理器. 最后调用工厂类方法.

下面我们来具体分析invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);

我们在这一步打个断点, 然后跟着断点一步一步点击进去

2.3 spring5源码系列---内置的后置处理器PostProcess加载源码
2.3 spring5源码系列---内置的后置处理器PostProcess加载源码

 这是registryProcessors里面只有一个后置处理器, 就是ConfigurationClassPostProcessor. 

然后进入到ConfigurationClassPostProcessor.postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)方法

这里先看enhanceConfigurationClasses(beanFactory);个方法 

粗体部分就是判断是否需要进行cglib代理. 进行cglib代理的条件是, beanDefinition中属性configurationClass的值是full. 只有full版配置才会创建cglib代理

那么有下面几个问题: 

问题1: full版本配置是什么呢?

我们使用@Configuration注解了, 在加载的时候, 就会将configurationClass属性设置为full.当设置为full以后, 我们在调用的时候, 就会创建一个cglib动态代理.

问题2: 为什么要创建动态代理呢?

动态代理可以保证, 每次创建的bean对象只有一个

问题3:那么加@Configuration和不加本质上的区别是什么?

当在配置类中一个@Bean使用方法的方式引入另一个Bean的时候, 如果不加@Configuration注解, 就会重复加载Bean.如果加了@Configuration, 则会在这里创建一个cglib代理, 当调用了@Bean方法是会先检测容器中是否存在这个Bean, 如果不存在则创建, 存在则直接使用.

这是在上面调用invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);接口的时候, 标记的是full还是Lite

下面来看一下源码

2.3 spring5源码系列---内置的后置处理器PostProcess加载源码

 在这里一步,执行的时候,进行了这个类是full的还是lite,继续忘下看

2.3 spring5源码系列---内置的后置处理器PostProcess加载源码

 此时满足条件的postProcessor只有一个, 那就是ConfigurationClassPostProcessor. 下面直接看ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry()方法

2.3 spring5源码系列---内置的后置处理器PostProcess加载源码

 前面都是一些条件判断, 重点看processConfigBeanDefinitions(registry);

2.3 spring5源码系列---内置的后置处理器PostProcess加载源码

 在这里,这个方法判断了, 这个类是full的还是lite的. 下面直接上代码

上面主要是获取元数据, 然后判断元数据中是否有Configuration注解. 如果有,返回其属性. 我们判断其属性中proxyBeanMethods是否true, 如果是true, 那么将其设置为full. 

如果配置中带有@Component, @ComponentScan @Import @ImportResource @Bean这几种属性之一, 那么就将其设置为lite.

这也是@Configuration 和其他注解类似@Component和@ComponentScan的本质区别:

当在配置类中一个@Bean使用方法的方式引入另一个Bean的时候, 如果不加@Configuration注解, 就会重复创建Bean

如果加了@Configuration, 则会在这里创建一个cglib代理, 当调用了@Bean方法是会先检测容器中是否存在这个Bean, 如果不存在则创建, 存在则直接使用.

下面来看个例子

这是定义的car和tank的基础类

当配置类使用了@Configuration注解的时候, 运行main方法

2.3 spring5源码系列---内置的后置处理器PostProcess加载源码

当去掉@Configuration注解的时候, 再次运行, 我们看到创建了两次tank

2.3 spring5源码系列---内置的后置处理器PostProcess加载源码

在main方法中调用了两次(Car) context.getBean("car");

在new一个对象的时候, 如果不取ioc容器中取, 那么每一次都会创建一个新的.

在ioc容器中, car对象只有一个, 但是在构建car的时候, 调用了tank, tank在ioc容器中却不一定只有一份. 只有使用了@Configuration, 表示需要使用cglib动态代理查找tank类, 保证ioc容器中只有一份.

通过跟踪@ComponentScan注解是如何解析的, 分来理解BeanDefinitionScan, BeanDefinitionRegistry, BeanDefinitionReader是如何工作的. 

这里也有两大步

第一步: 初始化bean工厂的后置处理器

  通过调用beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class) 初始化了bean工厂的后置处理器,

第二步: 解析配置

   调用invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);调用beanDefinitionRegistry的后置处理器. 筛选出符合条件的配置类. 

2.3 spring5源码系列---内置的后置处理器PostProcess加载源码

如上图所示, 最后筛选出的配置类只有MainConfig配置类. 也就是说configCandidates配置候选集合中只有一个MainConfig

然后, 接下来创建了一个对象ConfigurationClassParser, 这是一个配置类解析器. 下面将使用这个解析器解析配置类.

重点是如何解析的, 代码已重点标注出来了.

我们这里是通过注解解析的, 所以直接看下面的代码

解析主要做了几件事呢?如下图:

2.3 spring5源码系列---内置的后置处理器PostProcess加载源码

 解析配置类, 看看配置类是否含有如上标记的注解, 如果有, 则调用响应的返回对其进行解析,处理.

下面来看看源码. 是如何处理这一块的.

下面我们重点看对@ComponentScan和@ComponentScans注解的解析, 为什么看他呢? 因为很多注解都标记了@Component注解. 

比如@Service注解,本身使用@Component

2.3 spring5源码系列---内置的后置处理器PostProcess加载源码

 再来看@Controller注解, 其实质也是一个@Component注解

2.3 spring5源码系列---内置的后置处理器PostProcess加载源码

 我们在自定义配置类的时候, 会使用@ComponentScan注解. 并传递一个包, 作为扫描包. 如MainConfig配置

2.3 spring5源码系列---内置的后置处理器PostProcess加载源码

 这就会扫描包下所有的配置类. 

它主要的逻辑如下:

2.3 spring5源码系列---内置的后置处理器PostProcess加载源码

 在拿到@ComponentScan注解以后, 会对其进行parse. 主要解析里面的注解. 并对每一个注解进行处理. 处理后将其添加到scanner属性中. 最后调用scanner.doScan(....)方法.

源码如下:

调用doScan方法扫描配置类. 我们来看看主要做了哪些事情

2.3 spring5源码系列---内置的后置处理器PostProcess加载源码

 第一步: 找到所有候选的BeanDefinition.

  上面解析出了@ComponentScan注解传递过来的basePackages包. 扫描包中所有的类, 得到候选类. 

  扫描的时候做了几件事呢? 看最上图最右侧部分. 这扫描出来就是我们的目标类.

 第二步: 解析这些准目标类. 

第三步: 设置默认的beanDefinition属性

 第四步: 将解析出来的bean定义注册到ioc容器中

 这里就调用了BeanDefinitionReaderUtils.registerBeanDefinition注册bean定义. 之前注册过配置类, 这里和其是一样的. 所以不再赘述了

这里有两个细节:

1. excludeFilter中排除了自己

在解析配置类的时候, 除了@ComponentScan注解中定义的ExcludeFilter和IncludeFilter以外, 还有默认的排除类. 如上加粗字体的部分. 这里是排除了配置类本身, 我们这里的配置类是MainConfig, 也就说, 会排除掉自己. 

matchClassName是一个钩子方法. 在执行到这里的时候, 不会真的去执行. 什么时候执行呢? 后面调用doScan的时候执行.

在寻找候选配置类的时候, 进行了排除了配置类本身.

进入这个方法

在第四步的时候调用了isCandidateComponent(metadataReader, 这里就判断了是否是包含的类,或者是排除的类

紫色加错的部分是就是判读是否符合排除的类. 红色加错的部分是判断是否是包含的类. 

先来看紫色的部分, 排除的类

看到了么, 这里调用了matchClassName. 这就是上面定义的钩子方法, 

此时declaringClass表示的是当前的配置类, className表示的是目标类, 如果当前目标类 == 配置类, 那么就放回true. 返回true, 则会排除掉

2. includeFilter中包含了默认的配置类

下面来看红色加错的部分

我们看到这里有this.includeFilters.包含的过滤器. 这里面是有值的

2.3 spring5源码系列---内置的后置处理器PostProcess加载源码

 我们没有在配置类MainConfig上设置includeFilter啊, 这里面怎么会有值呢?

这是因为我们有默认包含的过滤器, 下面看看默认包含的过滤器是在哪里设置的.

首先从入口类点击AnnotationConfigApplicationContext

2.3 spring5源码系列---内置的后置处理器PostProcess加载源码

然后在点击this();

2.3 spring5源码系列---内置的后置处理器PostProcess加载源码

再点击ClassPathBeanDefinitionScanner

2.3 spring5源码系列---内置的后置处理器PostProcess加载源码

 然后一路点击三个this(...)

2.3 spring5源码系列---内置的后置处理器PostProcess加载源码

 最后看到上图 registerDefaultFilter();注册默认的过滤器

如上图看到, 注册了3个默认的过滤器. 分别是Component, ManagedBean, Named. 他们都是注解类型的过滤器AnnotationTypeFilter

其中javax.annotation.ManagedBean和javax.inject.Named是jdk提供给我们的.

2.3 spring5源码系列---内置的后置处理器PostProcess加载源码