Spring创建BeanDefinition之路径扫描

134 阅读4分钟

一 从示例开始

当我们创建AnnotationConfigApplicationContext对象时,Spring底层到底做了些什么?

来看下面示例。

package com.xiakexing;

import com.xiakexing.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Test {

	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
		UserService userService = context.getBean("userService", UserService.class);
		userService.test();
    }
}
package com.xiakexing;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(value = "com.xiakexing")
public class AppConfig {

}
package com.xiakexing.service;

import org.springframework.stereotype.Component;

@Component
public class UserService {

    public void test() {
        System.out.println("hello spring");
    }
}

我们猜测这几行代码执行逻辑:

  1. new AnnotationConfigApplicationContext(AppConfig.class)时,从AppConfig类解析扫描路径即@ComponentScan;
  2. 遍历扫描路径下的所有Java类,如果某个类上有@Component、@Service等注解,Spring就为这个类创建BeanDefinition,保存到Map中,比如Map<String, Class>,key是根据规则生成的beanName,value就是当前类的class对象。
  3. context.getBean("userService")时,Spring根据beanName找到类的class对象,反射调用构造器创建对象。

带着上面的猜想,我们来看看源码。本文暂且关注路径扫描的实现,随后的文章将讲解BeanDefinition的创建过程。

二 创建AnnotationConfigApplicationContext

构造方法的入参是componentClasses,即可以传入多个配置类。

this()中创建了AnnotatedBeanDefinitionReader、ClassPathBeanDefinitionScanner,将用于扫描指定路径下的类,创建BeanDefinition。

JFR 是 Java Flight Record (Java飞行记录),是JVM 内置的基于事件的JDK监控记录框架。

StartupStep是Spring基于JFR对运行过程的监控,阅读源码时可忽略它。

注意,AnnotationConfigApplicationContext间接实现了BeanDefinitionRegistry接口,具备向容器中注册BeanDefinition的能力。

在创建ClassPathBeanDefinitionScanner对象时,指定了使用DefaultFilters:将扫描所有带有@Component注解的类。

总结:this()仅仅实例化了容器对象,创建了Reader、Scanner,用于解析类信息。

三 注册Configuration类

3.1 创建BeanDefinition

第二行代码register(componentClasses),显然是将配置类注册到容器中。

来看AnnotatedBeanDefinitionReader#doRegisterBean的核心逻辑:

  1. 为配置类创建AnnotatedGenericBeanDefinition对象;
  2. 处理@Conditional,如果条件不满足,将舍弃这个类;
  3. 给BeanDefinition对象属性赋值;
  4. 生成beanName,解析@Lazy、@Primary、@DependsOn等注解;
  5. 创建BeanDefinitionHolder对象,发起注册。

3.2 注册BeanDefinition

BeanDefinitionHolder类只是对BeanDefinition的包装,仅有三个属性:beanDefinition、beanName和aliases。

在BeanDefinitionReaderUtils#registerBeanDefinition中,

最终会调用DefaultListableBeanFactory#registerBeanDefinition方法,执行这几行代码:

看到了BeanFactory的两个核心数据结构:

  • beanDefinitionMap保存了beanName与beanDefinition的映射;
  • beanDefinitionNames保存了所有的beanName;

果然与我们当初的猜想一致。

至此,配置类已被添加到beanDefinitionMap中,可是@ComponentScan指定的包路径,在哪儿被处理了呢?

四 扫描包路径

先说结论: @ComponentScan包路径下的类,是在ClassPathBeanDefinitionScanner#scan中被处理的。

先来看AnnotationConfigApplicationContext的另一个构造方法:入参就是指定包路径。

转调到ClassPathBeanDefinitionScanner#scan。 基于配置类创建AnnotationConfigApplicationContext时,是在哪儿调了scan()或doScan()呢?答案就在refresh()中。

4.1 BeanFactoryPostProcessor接口

先看类注释:

Factory hook that allows for custom modification of an application context's bean definitions, adapting the bean property values of the context's underlying bean factory.

工厂钩子,允许自定义修改应用程序上下文的bean定义,调整上下文的底层bean工厂的bean属性值。

A BeanFactoryPostProcessor may interact with and modify bean definitions, but never bean instances.

BeanFactoryPostProcessor可以与bean定义交互和修改,但不能与bean实例交互。

可见,该接口是BeanFactory的后置处理器,在创建Bean实例前,干涉 BeanDefinition 创建和更新。 仅有一个方法:

void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

该接口有个重要的子接口BeanDefinitionRegistryPostProcessor,能够向容器注册更多的BeanDefinition。

Extension to the standard BeanFactoryPostProcessor SPI, allowing for the registration of further bean definitions before regular BeanFactoryPostProcessor detection kicks in.

对标准BeanFactoryPostProcessor SPI的扩展,允许在常规BeanFactoryPostProcessor检测开始之前注册进一步的bean定义。

4.2 ConfigurationClassPostProcessor类

源码中,BeanDefinitionRegistryPostProcessor接口仅有唯一实现ConfigurationClassPostProcessor。

在ConfigurationClassPostProcessor#processConfigBeanDefinitions中, 检查已注册的每一个BeanDefinition,是否是候选配置类(或组件),满足以下任意条件即可:

  • 类上有@Configuration注解;
  • 类上有以下任意一个注解;
  • 类中有@Bean注解的方法;

得到Set candidates后,会调用ConfigurationClassParser.parse()

接下来会遍历处理每一个候选类

  • 在ConfigurationClassParser#doProcessConfigurationClass中,解析@ComponentScan、@ComponentScans注解;
  • 执行Filter逻辑后,得到basePackages路径集;
  • 调用ClassPathBeanDefinitionScanner#doScan,为路径下的Bean创建BeanDefinition,并注册到容器中。

关于doScan方法的详细逻辑,我们下一篇再看。

五 逻辑闭环

要用ConfigurationClassPostProcessor来处理配置类,Spring容器中就得先有该类的实例。那么,这个类是何时注册到容器中的?

答案就在new AnnotatedBeanDefinitionReader(this)中:

为ConfigurationClassPostProcessor创建BeanDefinition并注册。

当执行AbstractApplicationContext#refresh时,其中有一步是调用容器中BeanFactoryPostProcessor接口所有实现。

此时,ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry将被执行。

流程图