老王读Spring IoC-2Spring IoC之BeanDefinition扫描注册的原理

世事洞明皆学问,人情练达即文章。这篇文章主要讲述老王读Spring IoC-2Spring IoC之BeanDefinition扫描注册的原理相关的知识,希望能为你提供帮助。
@TOC
前言Spring 要实现" 控制反转" 的功能,就需要解决 BeanDefinition 的扫描和注册问题。只有将 BeanDefinition 扫描出来了,才知道要创建哪些 bean 的实例。
所以,我们这次要研究的重点是:

  1. BeanDefinition 是怎么样被扫描出来的?
  2. BeanDefinition 是如何注册的?
版本约定Spring 5.3.9 (通过 SpringBoot 2.5.3 间接引入的依赖)
正文我们可以看 BeanDefinition 的源码注释:
BeanDefinition 是用来描述一个 bean 实例的元数据信息,描述信息中包含了该实例具有哪些属性、构造函数等信息。
BeanDefinition 的主要目的是供 BeanFactoryPostProcessor 来处理和修改属性值的。
再看 BeanDefinition 源码中包含的接口(这里我简化了一些不重要的接口):
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {/** * 指定 bean 对应的类名。这个类名可以在 BeanFactory post-processing 阶段被修改。 * Specify the bean class name of this bean definition. * The class name can be modified during bean factory post-processing, * typically replacing the original class name with a parsed variant of it. */ void setBeanClassName(@Nullable String beanClassName); /** * Return the current bean class name of this bean definition. * < p> Note that this does not have to be the actual class name used at runtime, in * case of a child definition overriding/inheriting the class name from its parent. * Also, this may just be the class that a factory method is called on, or it may * even be empty in case of a factory bean reference that a method is called on. * Hence, do < i> not< /i> consider this to be the definitive bean type at runtime but * rather only use it for parsing purposes at the individual bean definition level. */ String getBeanClassName(); /** * Override the target scope of this bean, specifying a new scope name. * @see #SCOPE_SINGLETON #SCOPE_PROTOTYPE */ void setScope(@Nullable String scope); /** * Return the name of the current target scope for this bean, * or {@code null} if not known yet. */ String getScope(); /** * Set whether this bean should be lazily initialized. * < p> If {@code false}, the bean will get instantiated on startup by bean * factories that perform eager initialization of singletons. */ void setLazyInit(boolean lazyInit); /** * Return whether this bean should be lazily initialized, i.e. not * eagerly instantiated on startup. Only applicable to a singleton bean. */ boolean isLazyInit(); /** * 设置当前 Bean 所依赖的 bean。BeanFactory 将会确保它所依赖的 Bean 被先初始化。 * Set the names of the beans that this bean depends on being initialized. * The bean factory will guarantee that these beans get initialized first. */ void setDependsOn(@Nullable String... dependsOn); /** * Return the bean names that this bean depends on. */ String[] getDependsOn(); /** * 设置这个 Bean 是不是一个候选 Bean。注意:它只对按 type 注入的方式起作用。 * Set whether this bean is a candidate for getting autowired into some other bean. * < p> Note that this flag is designed to only affect type-based autowiring. * It does not affect explicit references by name, which will get resolved even * if the specified bean is not marked as an autowire candidate. As a consequence, * autowiring by name will nevertheless inject a bean if the name matches. */ void setAutowireCandidate(boolean autowireCandidate); /** * Return whether this bean is a candidate for getting autowired into some other bean. */ boolean isAutowireCandidate(); /** * 设置此 bean 是否为 autowire 的主要候选 bean。当有多个候选 bean 时起作用。 * Set whether this bean is a primary autowire candidate. * < p> If this value is {@code true} for exactly one bean among multiple * matching candidates, it will serve as a tie-breaker. */ void setPrimary(boolean primary); /** * Return whether this bean is a primary autowire candidate. */ boolean isPrimary(); /** * Specify the factory bean to use, if any. * This the name of the bean to call the specified factory method on. */ void setFactoryBeanName(@Nullable String factoryBeanName); /** * Return the factory bean name, if any. */ String getFactoryBeanName(); /** * Specify a factory method, if any. This method will be invoked with * constructor arguments, or with no arguments if none are specified. * The method will be invoked on the specified factory bean, if any, * or otherwise as a static method on the local bean class. */ void setFactoryMethodName(@Nullable String factoryMethodName); /** * Return a factory method, if any. */ String getFactoryMethodName(); /** * Set the name of the initializer method. * @since 5.1 */ void setInitMethodName(@Nullable String initMethodName); /** * Return the name of the initializer method. * @since 5.1 */ @Nullable String getInitMethodName(); /** * Set the name of the destroy method. * @since 5.1 */ void setDestroyMethodName(@Nullable String destroyMethodName); /** * Return the name of the destroy method. * @since 5.1 */ String getDestroyMethodName(); // Read-only attributes /** * Return whether this a < b> Singleton< /b> , with a single, shared instance * returned on all calls. * @see #SCOPE_SINGLETON */ boolean isSingleton(); /** * Return whether this a < b> Prototype< /b> , with an independent instance * returned for each call. * @since 3.0 * @see #SCOPE_PROTOTYPE */ boolean isPrototype(); /** * Return whether this bean is "abstract", that is, not meant to be instantiated. */ boolean isAbstract(); }

可以看出,我们以前在 xml 中定义 Bean 时配置的一些属性,在这个接口里面都有。
我们再看一下 BeanDefinition 的继承关系图:
老王读Spring IoC-2Spring IoC之BeanDefinition扫描注册的原理

文章图片

查看 RootBeanDefinition 的注释,我们可以看到从 Spring 2.5 以后,以编程方式注册 BeanDefinition 的首选类是 GenericBeanDefinition。
BeanDefinition 是怎么样被扫描出来的?了解了这么多 BeanDefinition 的知识后,接下来我们就来探究 BeanDefinition 是如何被扫描出来的。
由于 Spring 封装的层次太深,我们没办法马上通过跟读源码找到 BeanDefinition 是何时扫描出来的。而且,使用到 BeanDefinition 的地方非常之多,想要通过找到所有引用 BeanDefinition 的地方来猜的话,也不太可能;
况且,BeanDefinition 还有那么多子类,这条路基本行不通。
那怎么办呢?
前面我们知道了 GenericBeanDefinition 是创建 BeanDefinition 的首选类,我们不防在 GenericBeanDefinition 的构造函数上打上断点,再运行 Demo 程序:
老王读Spring IoC-2Spring IoC之BeanDefinition扫描注册的原理

文章图片

从调用堆栈中,我们马上发现了 ConfigurationClassPostProcessor 在处理 BeanDefinition 的扫描。
调用路径是:
AbstractApplicationContext#refresh() --> AbstractApplicationContext#invokeBeanFactoryPostProcessors()--> ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry()
看到这里,我们就搞清楚了我们的第 1 个问题: BeanDefinition 是如何被扫描出来的:
BeanDefinition 扫描的入口是在调用 BeanFactoryPostProcessor 的时候,通过 ConfigurationClassPostProcessor 来处理的。
(这是 SpringBoot 环境下的组件扫描调用路径,其他环境下,基本也大同小异,掌握了方法,换了环境也不怕/^_^)
查看调用栈,我们会发现,创建 BeanDefinition 是由 ConfigurationClassParser#doProcessConfigurationClass() 来处理的。
AbstractApplicationContext#refresh() --> AbstractApplicationContext#invokeBeanFactoryPostProcessors() --> ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry() --> ConfigurationClassParser#doProcessConfigurationClass()// 处理 @Configuration 类相关的注解: @PropertySource、@ComponentScan、@Import、@ImportResource、@Bean

再看 ConfigurationClassParser#doProcessConfigurationClass() 方法的源码,我们可以清楚的看到,它里面依次处理了 @PropertySource、@ComponentScan、@Import、@ImportResource、@Bean 等注解
其中,在处理 @ComponentScan 时,会去扫描所有定义了 Bean 的 class(@Component 标记的类),从而组装成 BeanDefinition。
老王读Spring IoC-2Spring IoC之BeanDefinition扫描注册的原理

文章图片

我们找到了组件扫描的入口是在 ConfigurationClassParser#doProcessConfigurationClass(),那具体扫描动作是怎么执行的呢?
在调用堆栈中,我们可以看到有调用 ClassPathBeanDefinitionScanner#doScan() ,ClassPathBeanDefinitionScanner 类的命名就很清晰——从 classpath 下扫描 BeanDefinition。
再查看方法的注释:
在指定的 basepackage 内执行扫描,返回已注册的 bean 定义。
所以,ClassPathBeanDefinitionScanner#doScan() 方法除了将 BeanDefinition 扫描出来,还会将 BeanDefinition 注册到容器中。
查看源码也确实如此:(可以看出,读源码的注释还是很有必要的^_^)
// ClassPathBeanDefinitionScanner#doScan() protected Set< BeanDefinitionHolder> doScan(String... basePackages) { Set< BeanDefinitionHolder> beanDefinitions = new LinkedHashSet< > (); for (String basePackage : basePackages) { // 扫描 basePackages 下面所有的 BeanDefinition Set< BeanDefinition> candidates = findCandidateComponents(basePackage); for (BeanDefinition candidate : candidates) { ScopeMetadata scopeMetadata = https://www.songbingjia.com/android/this.scopeMetadataResolver.resolveScopeMetadata(candidate); candidate.setScope(scopeMetadata.getScopeName()); String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } if (candidate instanceof AnnotatedBeanDefinition) { // 对 BeanDefinition 的基本属性做填充,比如:@Lazy、@Primary、@DependsOn、@Role、@Description 等 AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); // 注册 BeanDefinition 到 BeanDefinitionRegistry 中 registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; }

BeanDefinition 是如何注册的?根据上面的分析,我们知道 BeanDefinition 是通过 ClassPathBeanDefinitionScanner#doScan() 扫描出来的,同时,这个方法也会将扫描出来的 BeanDefinition 进行注册。
// ClassPathBeanDefinitionScanner#registerBeanDefinition() protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) { BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry); }// BeanDefinitionReaderUtils#registerBeanDefinition() public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry){ // Register bean definition under primary name. String beanName = definitionHolder.getBeanName(); // 通过 beanName 来注册 BeanDefinition registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); // Register aliases for bean name, if any. String[] aliases = definitionHolder.getAliases(); if (aliases != null) { for (String alias : aliases) { // 将 bean 的 alias 注册到 BeanDefinitionRegistry 中 registry.registerAlias(beanName, alias); } } }

所以,BeanDefinition 是被注册到了 BeanDefinitionRegistry 中。注册的时候会注册 beanName 和 bean alias。
对 BeanDefinition 的基本属性的填充(比如:@Lazy、@Primary、@DependsOn、@Role、@Description 等)则是在 AnnotationConfigUtils#processCommonDefinitionAnnotations() 中做的。
思考:Bean 的依赖关系是不是在 BeanDefinition 中存储的?既然 Spring 会扫描 BeanDefinition,那么 bean 的依赖关系是否也在这个时候被解析出来呢?
假设是这样的,那么,在做依赖关系注入时,就可以在创建 bean 的实例之后,通过 BeanDefinition 中的依赖关系,直接获取到相应的 bean 进行注入。
如果在扫描 BeanDefinition 时不存储依赖关系,那么,在创建 bean 的实例之后,可以立即解析 bean 里面的依赖,然后做注入,这种方式不需要存储依赖关系。
那么 Spring 究竟是如何处理依赖关系的呢?
在反复的调试和猜测之后,我发现,BeanDefinition 是不存储依赖关系的。所以,我们猜想 Spring 应该是在给 bean 实例填充依赖的时候,直接解析 bean 的依赖,然后去做注入的。
依赖注入应该是我们后续文章分析的重点,这里提前剧透一下:
Spring 不在 BeanDefinition 中存储依赖关系,而是在 bean 初始化(doCreateBean),进行属性注入 populateBean 的时候解析依赖关系的。
BeanPostProcessor 会扫描 bean 里面的依赖关系(即:扫描需要注入的 field、method),组装成 InjectionMetadata,最终由 InjectionMetadata.InjectedElement 来完成依赖属性的注入。
// 寻找 @Resource 标记的属性 CommonAnnotationBeanPostProcessor#findResourceMetadata()// 寻找 @Autowired、@Value 标记的属性 AutowiredAnnotationBeanPostProcessor#findAutowiringMetadata()

所以,Spring 的 BeanDefinition 中是不存储依赖关系的。
总结BeanDefinition 的扫描注册过程分了三个阶段:
  1. BeanDefinition 的扫描
    扫描的动作是在 AbstractApplicationContext#invokeBeanFactoryPostProcessors() 时触发的。
    具体执行是由 ClassPathBeanDefinitionScanner#doScan() 来完成的。
  2. BeanDefinition 基本属性的填充
    基本属性包括:@Lazy、@Primary、@DependsOn、@Role、@Description 等。
    扫描出 BeanDefinition 之后,通过 AnnotationConfigUtils#processCommonDefinitionAnnotations() 来填充
  3. BeanDefinition 的注册
    BeanDefinition 最终会注册到 BeanDefinitionRegistry 中
如果本文对你有所帮助,欢迎点赞收藏!
【老王读Spring IoC-2Spring IoC之BeanDefinition扫描注册的原理】有关 Spring 源码方面的问题欢迎一起交流,备注:51cto (vx: Kevin-Wang001)

    推荐阅读