Spring怎么知道我们配置了哪些类?

1,656 阅读5分钟

我们都知道Spring有一个IOC容器,可以帮助我们在写代码的时候,不用手动实例化出一系列的底层对象,只需要简单地使用@Autowired注解,或者从构造函数注入即可获得对象的实例。可Spring是怎么知道这些要注入的对象呢?

如果抛开Spring的代码,让我们自己来实现的话,会怎么做呢?我们可能会先去搜集这些类的信息,然后生成这些类的对象,接着将这些对象设置到被@Autowired注解的变量上,这样就可以实现依赖注入的功能了。

大体实现思路有了,那么我们去看下Spring是如何搜集这些类信息的。

我们先造一个bean:

@Component
public class DemoB {

    private String b = "str from B";

    public DemoB() {
        System.out.println("B construct: ");
    }
}

我们知道,在Spring中,BeanFactory里存在一个Map,这个Map正是存放着所有被扫描到的bean。其定义如下:

// org.springframework.beans.factory.support.DefaultListableBeanFactory
/** Map of bean definition objects, keyed by bean name. */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);	

所以接下来我们debug时,只需要关心这个Map里是否存在上述的DemoB类即可。

在AbstractApplicationContext的refresh方法中,在debug到invokeBeanFactoryPostProcessor(beanFactory)方法之前,map的大小是8个,且里面不存在DemoB类。在执行了invokeBeanFactoryPostProcessor(beanFactory) 之后,却发现map里已经包含了DemoB的BeanDefinition了。这竟然是通过BeanFactoryPostProcessor来实现的?

// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);

StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);

// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);

继续往下追溯,invokeBeanFactoryPostProcessor(beanFactory) 方法中,是通过一个委托去调用BeanFactoryPostProcessors的,

PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

而在这个委托的方法里,会遍历BeanDefinitionRegistryPostProcessor并调用其中的postProcessBeanDefinitionRegistry方法。那这到底是哪个BeanFactoryPostProcessor实现的呢?

/**
 * Invoke the given BeanDefinitionRegistryPostProcessor beans.
 */
private static void invokeBeanDefinitionRegistryPostProcessors(
      Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry, ApplicationStartup applicationStartup) {

   for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
      StartupStep postProcessBeanDefRegistry = applicationStartup.start("spring.context.beandef-registry.post-process")
            .tag("postProcessor", postProcessor::toString);
      postProcessor.postProcessBeanDefinitionRegistry(registry);
      postProcessBeanDefRegistry.end();
   }
}

从代码中可以看到,BeanFactoryPostProcessor是通过beanFactory获取的,上面提到,获取bean信息的地方是beanDefinitionMap,所以在这个Map中,我们可以猜测,将DemoB类注入到beanDefinitionMap里的很有可能是ConfigurationClassPostProcessor。

进入ConfigurationProcessor类中,一直执行到parser.parse()方法后,beanDefinitionMap的大小才有变化。可见,DemoB类是在这个方法被注册到beanDefinitioinMap中的。

// Parse each @Configuration class
ConfigurationClassParser parser = new ConfigurationClassParser(
        this.metadataReaderFactory, this.problemReporter, this.environment,
        this.resourceLoader, this.componentScanBeanNameGenerator, registry);

// ...省略不必要的代码
do {
    // ...
    parser.parse(candidates);
    parser.validate();

继续往下跟踪,在ConfigurationClassParser中有一个doProcessConfigurationClass 方法,在Spring中,方法名为doXXX的,通常都是实际执行逻辑的地方。在方法内部看到,这里会对被@Component、@PropertySources、@ComponentScans等注解的类做解析。看到@Component的出现,我以为找到了答案,但现实再一次打脸,对@Component的逻辑处理之后,beanDefinitionMap的内容并没有改变!

无奈,只能继续往下走,在执行完对@ComponentScan注解的处理逻辑后,beanDefinitionMap竟然注册了DemoB这个bean!

秘密就藏在这个方法中componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName())

// Process any @ComponentScan annotations
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
        sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
        !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
    for (AnnotationAttributes componentScan : componentScans) {
        // The config class is annotated with @ComponentScan -> perform the scan immediately
        Set<BeanDefinitionHolder> scannedBeanDefinitions =
                this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
        // Check the set of scanned definitions for any further config classes and parse recursively if needed
        for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
            BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
            if (bdCand == null) {
                bdCand = holder.getBeanDefinition();
            }
            if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
                parse(bdCand.getBeanClassName(), holder.getBeanName());
            }
        }
    }
}

进入ComponentScanAnnotationParser#parse 中,发现这里做的事情就是对ComponentScan注解的解析。在方法的最后,调用了ClassPathBeanDefinitionScanner#doScan 方法,而这个方法的实现如下

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Assert.notEmpty(basePackages, "At least one base package must be specified");
    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
    for (String basePackage : basePackages) {
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
        for (BeanDefinition candidate : candidates) {
            ScopeMetadata scopeMetadata = 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) {
                AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
            }
            if (checkCandidate(beanName, candidate)) {
                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                definitionHolder =
                        AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                beanDefinitions.add(definitionHolder);
                registerBeanDefinition(definitionHolder, this.registry);
            }
        }
    }
    return beanDefinitions;
}

在这个方法中,会先找出需要扫描的basePackage中有哪些待选择的component,此处称为候选人(candidates),然后对这些candidate转化成BeanDefinitionHolder,最终注册到beanDefinitionMap中。注意此处正是利用了传进来的registry进行注册,而这个registry,就是从一开始传入的DefaultListableBeanFactory。

我们在整个流程之上,去画出这个相关的类图:

宏观上来看,Spring利用了启动流程中的BeanFactoryPostProcessor的机制,在其子类ConfigurationClassPostProcessor中,实现了对配置类的解析。在这个例子中,Spring并不知道我们自定义的DemoB类。要找到DemoB所在的类信息,就要通过扫描包路径下的类,以找到被@Component修饰的类,才能找到DemoB的类信息。所以,ComponentScanAnnotationParser负责解析注解里的内容,提取出包路径。ClassPathBeanDefinitionScanner则是真正扫描并注册BeanDefinition到BeanDefinitionMap中,最终完成了对DemoB类信息的注册。

还有一个问题,ConfigurationClassPostProcessor仿佛从debug开始就存在了,那么它是在什么时候被注册到beanDefinintionMap中的呢?追寻源码,可以发现SpringBoot启动的ApplicationContext其实是AnnotationConfigServletWebServerApplicationContext,在这个类构造时,会实例化出一个AnnotatedBeanDefinitionReader,而此时,就会将ConfigurationClassPostProcessor类信息注册到beanDefinitionMap中。所以才会在后续的invokeBeanFactoryPostProcessors方法中,调用到了ConfigurationClassPostProcessor中的方法注册DemoB。

一点启示

从上述的流程我们已经知道一个自定义的Component,是如何注册到Spring中。在这个流程中我领会到,整个流程其实十分复杂,但是大神们的做法并不是将整个bean注册的流程都放到了ApplicationContext里,而是将其中bean注册的能力抽象成接口,接着,通过引用这个接口的实现对象,可以构造出不同模块各司其职,以降低复杂度。

总的来说,整个过程可以归为三部分。

context负责整个流程的启动和组织

registry负责执行真正的bean注册的功能,提供registerBeanDefinition的接口

adapter对PostProcessor来说可能并不准确,但在这个例子中,我认为是实现了通过@ComponentScan注解实现bean扫描和注册的适配。从这个角度看,我们完全可以自定义一个注解,然后通过实现BeanFactoryPostProcessor接口去实现自定义注解扫描和注册bean的功能。

另外,在我们平常的开发中,往往需要用到扫描注解然后获取类信息的场景,也可仿照这个模式来实现。

由AnnotationProcessor负责解析注解,如涉及包扫描的话,也可以仿照Spring的做法,抽象出一个xxxAnnotationParser的处理类。xxxHandler(要根据实现的意图,根据业务起一个含义准确的名字)负责处理解析后的信息,实现真正的业务逻辑。而Context则是一个引子,负责流程的引导和组合。

抛开上述Spring的启动流程,我们完全可以自己写出一个简单的包扫描的demo:

public class ComponentScanDemo {

    public static void main(String[] args) {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(beanFactory);
        scanner.scan(ComponentScanDemo.class.getPackage().getName());
        System.out.println("beanNames: " + Arrays.toString(beanFactory.getBeanDefinitionNames()));
    }
}

最终会看到,和ComponentScanDemo在同一个包下面的DemoB将会被注册beanFactory中。而实际上,这几行简单的代码,就可以清晰展现了一个Component注册到BeanFactory的beanDefinitionMap的过程了。