转载

Spring源码解析(三) - 纯注解启动spring?怎么可能!

上一篇我们有讲到使用 context:component-scan 标签,注册扫描器之后,扫描器可以把目标包下面有符合过滤器条件(默认情况下会注册一个 @Component 注解的 AnnotationTypeFilter )的类封装成 beanDefinition 并且注册到 IOC 容器中来。

而对于 @Configuration@Bean 注解的支持,上一篇只是简单的讲了一下他注册了一个 ConfigurationClassPostProcessorbeanDefinitionIOC 容器中来,这一篇我们就主要讲一下 ConfigurationClassPostProcessor 的工作原理。

二、关于 BeanPostProcessorBeanFactoryPostProcessor

Spring 设计时,留下了很多拓展点,这些预留的拓展点可以再之后 Spring /用户需要添加新的功能时,可以不需要改动到主流程。而这些特定的拓展点可以大致分为两类:

BeanFactoryPostProcessorBeanFactoryPostProcessor 和其子接口,这一类拓展点主要是在 Spring 容器相关 的时机被调用的。

BeanPostProcessorBeanPostProcessor 和其子接口,这一类拓展点主要是在 bean 的整个生命周期中被调用。

而我们的 ConfigurationClassPostProcessor

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, xxx {}

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
	void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;

}
复制代码

可以看到我们的 ConfigurationClassPostProcessor 也是 BeanFactoryPostProcessor 的派生类。

三、 BeanFactoryPostProcessor 调用时机

我们回到容器启动时的 refresh() 方法(具体是 AbstractApplicationContext#refresh ):

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        prepareRefresh();
        // 上两篇已经讲过了,这里是xml解析的入口
        // Tell the subclass to refresh the internal bean factory
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        // 这里对beanFactory做了一些预处理,感兴趣的同学可以去看下,逻辑不复杂
        // Prepare the bean factory for use in this context.
        prepareBeanFactory(beanFactory);
        // 为子类预留的一个钩子方法
        // Allows post-processing of the bean factory in context subclasses.
        postProcessBeanFactory(beanFactory);
        // !!! 调用BeanFactoryPostProcessor,这一篇我们主要讲这里!!!
        // Invoke factory processors registered as beans in the context.
        invokeBeanFactoryPostProcessors(beanFactory);
        // skip ...        
    }
}
复制代码

可以看到,在 beanFactory 准备好后,我们有一个很明显的方法用来调用 BeanFactoryPostProcessor ,我们点进去看一下逻辑:

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
    PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
    // skip ... 
}
复制代码

可以看到,我们把具体逻辑又代理给了 PostProcessorRegistrationDelegate 来处理,这里其实就是把注册 PostProcessor 的调用逻辑聚合到一个类里面了而已,我们继续跟下去看看:

public static void invokeBeanFactoryPostProcessors(
    ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessorPostProcessors) {
	// 保存所有调用过的PostProcessor的beanName
    Set<String> processedBeans = new HashSet<>();
	// 如果这个beanFactory也是一个BeanDefinitionRegistry的话,我们也需要调用
    // BeanFactoryPostProcessor的子接口BeanDefinitionRegistryPostProcessor的方法
    // 正常都是会走这个分支的,因为我们默认的DefaultListableBeanFactory实现了BeanDefinitionRegistry接口
    if (beanFactory instanceof BeanDefinitionRegistry) {
        BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
        // 这两个list主要用来分别收集BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor
        List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
        List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();
		// 首先我们先把传入的BeanFactoryPostProcessor实例分类-beanFactory初始化的时候
        // 会注册一下实例到AbstractApplicationContext#beanFactoryPostProcessors这个列表,
        // 这个列表的值也是这个方法的第二个入参
        for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
            if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
                BeanDefinitionRegistryPostProcessor registryProcessor =
                    (BeanDefinitionRegistryPostProcessor) postProcessor;
                // 对于已经注册到spring的beanFactoryPostProcessors的registryProcessor,直接调用
                // 优先级最高
                registryProcessor.postProcessBeanDefinitionRegistry(registry);
                registryProcessors.add(registryProcessor);
            }
            else {
                regularPostProcessors.add(postProcessor);
            }
        }
        // 一个中间容器,用来保存当前需要调用的registryProcessor
        List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();
        // 接下来我们需要先把我们注册的beanDefinition中实现了BeanDefinitionRegistryPostProcessor接口的类的名称找出来,
        // 注意,这里不会直接实例化这个bean(主要是这些PostProcessor需要按顺序初始化和调用,先调用的PostProcessor是可能对后初始化的bean造成影响的)
        String[] postProcessorNames =
            beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
        // 我们找出所有实现了PriorityOrdered接口的PostProcessor
        for (String ppName : postProcessorNames) {
            if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                // 这里直接创建了这个registryProcessor的实例,并且加入当前需要处理的容器
                // beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class)
                // 逻辑之后讲bean生命周期的时候会系讲,这里先略过
                currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                // 标记为已处理
                processedBeans.add(ppName);
            }
        }
        // 对所有实现了PriorityOrdered接口的PostProcessor排序
        sortPostProcessors(currentRegistryProcessors, beanFactory);
        // 加入registryProcessors列表
        registryProcessors.addAll(currentRegistryProcessors);
        // 调用BeanDefinitionRegistryPostProcessor的方法,我们的ConfigurationClassPostProcessor的逻辑就是在这里被调用的
        invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
        // 清除currentRegistryProcessors
        currentRegistryProcessors.clear();

        // 接下来处理实现了Ordered接口的BeanDefinitionRegistryPostProcessor
        // 逻辑就不讲了,跟上面是一样的
        postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
        for (String ppName : postProcessorNames) {
            if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {
                currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                processedBeans.add(ppName);
            }
        }
        sortPostProcessors(currentRegistryProcessors, beanFactory);
        registryProcessors.addAll(currentRegistryProcessors);
        invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
        currentRegistryProcessors.clear();

        // 接下来处理普通的,没实现排序接口的BeanDefinitionRegistryPostProcessor
        // 需要主要的是,这里有一个循环,主要原因是BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)方法是传入了一个BeanDefinitionRegistry的
        // 这意味着我们可以在(实际上正常实现这个接口就是为了注册beanDefinition的)这个PostProcessor注册新的beanDefinition
        // 而新注册的beanDefinition对应的类也是可能实现BeanDefinitionRegistryPostProcessor接口的,所以这里需要循环处理,知道不会注册新的BeanDefinitionRegistryPostProcessor为止
        boolean reiterate = true;
        while (reiterate) {
            reiterate = false;
            postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
            for (String ppName : postProcessorNames) {
                if (!processedBeans.contains(ppName)) {
                    // 如果还有未处理的BeanDefinitionRegistryPostProcessor,则实例化它们
                    // 并且把标记需要在循环一次,因为之后新的BeanDefinitionRegistryPostProcessor的调用可能注册新的BeanDefinitionRegistryPostProcessor
                    currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                    processedBeans.add(ppName);
                    reiterate = true;
                }
            }
            // 排序、收集、调用一条龙
            sortPostProcessors(currentRegistryProcessors, beanFactory);
            registryProcessors.addAll(currentRegistryProcessors);
            invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
            currentRegistryProcessors.clear();
        }

        // 所有的BeanDefinitionRegistryPostProcessor都调用完了,接下来要调用BeanFactoryPostProcessor的逻辑了
        // 由于BeanDefinitionRegistryPostProcessor是继承BeanFactoryPostProcessor的,所以这里registryProcessors也需要调用
        invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
        invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);
    }
    else {
        // 如果当前beanFactory没有实现BeanDefinitionRegistry接口,则这里只需要调用BeanFactoryPostProcessor的逻辑就行了
        invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);
    }

    // 接下来是处理注册了beanDifinition而没有实例化的BeanFactoryPostProcessor的逻辑,这里逻辑和处理BeanDefinitionRegistryPostProcessor基本是一样的,都是按顺序初始化(PriorityOrdered->Ordered->None),然后排序,调用的流程
    // 只是没有了循环的逻辑,因为从设计上来讲,BeanFactoryPostProcessor#postProcessBeanFactory里不应当负责beanDefinition的注册的逻辑的,所以也不会产生新的beanDifinition,所以这里就不需要循环处理了。
    // 你当然可以在postProcessBeanFactory逻辑里把beanFactory强转成BeanDefinitionRegistry并且注册一些BeanFactoryPostProcessor的beanDefinition,导致这里解析不全,可是,我们为什么要跟自己过不去呢?
}
复制代码

可以看到, invokeBeanFactoryPostProcessors 的代码虽然比较多,但是逻辑并不复杂。

我们先是处理了 BeanDefinitionRegistryPostProcessor 的逻辑,而处理的顺序则是:

  1. 已创建实例的 registryPostProcessor -->只注册了 beanDefinition ,尚未创建实例的 registryPostProcessor
  2. 对于已创建实例的 registryPostProcessor ,按照他们加入到 AbstractApplicationContext#beanFactoryPostProcessors 列表的顺序执行
  3. 对于未实例化的 registryPostProcessor ,按照 实现了 PriorityOrdered 接口的-->实现了 Ordered 接口的-->未实现排序接口 的顺序 分批创建实例、排序、执行。执行完一批再创建实例、排序、执行下一批。
  4. 最后处理未实现排序接口的 registryPostProcessor ,需要有一个循环处理,保证 registryPostProcessor 逻辑中新注册的 registryPostProcessorbeanDefinition 也执行到。

接下来我们处理了 BeanFactoryPostProcessor 的逻辑,这个逻辑和 BeanDefinitionRegistryPostProcessor 的处理逻辑基本上是一致的,只是不需要做第四点循环的逻辑了。

而调用 invokeBeanDefinitionRegistryPostProcessorsinvokeBeanFactoryPostProcessors 具体又是怎么实现的呢?

private static void invokeBeanDefinitionRegistryPostProcessors(
    Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) {

    for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
        postProcessor.postProcessBeanDefinitionRegistry(registry);
    }
}

private static void invokeBeanFactoryPostProcessors(
    Collection<? extends BeanFactoryPostProcessor> postProcessors, ConfigurableListableBeanFactory beanFactory) {

    for (BeanFactoryPostProcessor postProcessor : postProcessors) {
        postProcessor.postProcessBeanFactory(beanFactory);
    }
}
复制代码

可以看到,其实就是一个简单的循环调用而已,那么 AbstractApplicationContext#refreshinvokeBeanFactoryPostProcessors 的逻辑我们就讲到这里。

四、 ConfigurationClassPostProcessor 工作原理

之前我们有说过, @Configuration@Bean 注解的功能是通过 ConfigurationClassPostProcessor 进行支撑的,那么我们接下来看一下 ConfigurationClassPostProcessor 的逻辑。

首先带大家回忆一下 ConfigurationClassPostProcessor 是何时注册到 IOC 容器的,我们再解析 context:component-scan 标签时,创建扫描器并扫描类之后,会注册一些公共组件:

// ComponentScanBeanDefinitionParser#parse
public BeanDefinition parse(Element element, ParserContext parserContext) {
    String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
    basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
    String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
                                                              ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
	// 创建扫描器并扫描
    ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
    Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
    // 注册组件
    registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
    return null;
}

protected void registerComponents(
			XmlReaderContext readerContext, Set<BeanDefinitionHolder> beanDefinitions, Element element) {
    // ...
	// 是否开启注解,默认就是开启的
    boolean annotationConfig = true;
    if (element.hasAttribute(ANNOTATION_CONFIG_ATTRIBUTE)) {
        annotationConfig = Boolean.parseBoolean(element.getAttribute(ANNOTATION_CONFIG_ATTRIBUTE));
    }
    if (annotationConfig) {
        // 这里注册了一写支持注解的属性
        Set<BeanDefinitionHolder> processorDefinitions =
            AnnotationConfigUtils.registerAnnotationConfigProcessors(readerContext.getRegistry(), source);
		// ...
    }
    // ...
}
复制代码

AnnotationConfigUtils#registerAnnotationConfigProcessors 中,我们把 ConfigurationClassPostProcessor 包装成 beanDefinition 并注册进来了:

// 如果不存在beanName为AnnotationConfigUtils#CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME
// 的beanDefinition,则注册一个新的
if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
    RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
    def.setSource(source);
    beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
}
复制代码

由于 ConfigurationClassPostProcessor 是实现 BeanDefinitionRegistryPostProcessor 接口的,那我们结合 AbstractApplicationContext#invokeBeanFactoryPostProcessors 中的逻辑可以知道, beanFactory 初始化之后,会先调用到 ConfigurationClassPostProcessorpostProcessBeanDefinitionRegistry 方法,我们看一下这个方法的逻辑:

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    // skip ...
    processConfigBeanDefinitions(registry);
}
复制代码

继续往下跟:

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
    // 拿到当前registry中所有已经注册的beanDefinition的名称
    String[] candidateNames = registry.getBeanDefinitionNames();
	// 过滤所有的beanDefinition,把未被处理过的配置类收集起来
    for (String beanName : candidateNames) {
        BeanDefinition beanDef = registry.getBeanDefinition(beanName);
        // 如果beanDefinition中有这个ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE属性,说明这个beanDefinitiony是一个配置类,且已经被处理过了,不需要再处理
        if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
            // only log
        }
        // !!!判断当前beanDefinition是不是一个配置类,如果是,收集起来!!!
        else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
            // 如果是一个需要处理的配置类,加入列表
            configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
        }
    }
	// 完全没找到待处理的ConfigurationClass,就直接返回了
    if (configCandidates.isEmpty()) {
        return;
    }

    // 排序
    configCandidates.sort((bd1, bd2) -> {
        int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
        int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
        return Integer.compare(i1, i2);
    });

    // 这里主要是初始化一些工具类,环境变量之类的
    SingletonBeanRegistry sbr = null;
    if (registry instanceof SingletonBeanRegistry) {
        sbr = (SingletonBeanRegistry) registry;
        if (!this.localBeanNameGeneratorSet) {
            BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
                AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
            if (generator != null) {
                this.componentScanBeanNameGenerator = generator;
                this.importBeanNameGenerator = generator;
            }
        }
    }
    if (this.environment == null) {
        this.environment = new StandardEnvironment();
    }

    // 创建一个ConfigurationClassParser
    ConfigurationClassParser parser = new ConfigurationClassParser(
        this.metadataReaderFactory, this.problemReporter, this.environment,
        this.resourceLoader, this.componentScanBeanNameGenerator, registry);

    Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
    Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
    do {
        // !!!委托给Parser来解析这些配置类!!!
        // 这里主要是把配置类上的注解信息解析封装成ConfigurationClass对象
        // 如果是配置类导入(入import/componentScan)的普通类(非配置类),将会在这里生成beanDefinition并注册
        parser.parse(candidates);
        // 校验扫描出来的beanDefinitionu是否合法,这里其实主要是校验
        // 1.proxyBeanMethods=true的情况下配置类是否可以重新(非final,需要生成cglib代理类)
        // 2.@Bean修饰的方法是否可以重写(非final,需要生成cglib代理类)
        parser.validate();
		
        Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
        configClasses.removeAll(alreadyParsed);
		// 初始化一个ConfigurationClassBeanDefinitionReader
        if (this.reader == null) {
            this.reader = new ConfigurationClassBeanDefinitionReader(
                registry, this.sourceExtractor, this.resourceLoader, this.environment,
                this.importBeanNameGenerator, parser.getImportRegistry());
        }
        // !!!把封装好的ConfigurationClass对象委托给BeanDefinitionReader处理!!!
        // 通过配置类加载注册beanDefinition
        this.reader.loadBeanDefinitions(configClasses);
        alreadyParsed.addAll(configClasses);

        candidates.clear();
        // 处理完ConfigurationClass后,可能会注册新的配置类,这里就是收集这些新注册的配置类的
        if (registry.getBeanDefinitionCount() > candidateNames.length) {
            String[] newCandidateNames = registry.getBeanDefinitionNames();
            Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
            Set<String> alreadyParsedClasses = new HashSet<>();
            for (ConfigurationClass configurationClass : alreadyParsed) {
                alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
            }
            for (String candidateName : newCandidateNames) {
                // 只有新注册的beanDefinition才需要处理
                if (!oldCandidateNames.contains(candidateName)) {
                    BeanDefinition bd = registry.getBeanDefinition(candidateName);
                    // 是否是配置类且未处理过
                    if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
                        !alreadyParsedClasses.contains(bd.getBeanClassName())) {
                        candidates.add(new BeanDefinitionHolder(bd, candidateName));
                    }
                }
            }
            candidateNames = newCandidateNames;
        }
    }
    // 循环,直到没有新增的配置类为止
    while (!candidates.isEmpty());

    // skip ...
}
复制代码

通过以上源码,我们大体知道了处理配置类的流程,接下来我们重点看一下几个重要的细节

1. 判断某个类是否是一个配置类

processConfigBeanDefinitions 方法中我们主要是对配置类进行处理的逻辑,那么什么是配置类呢?

有的同学可能要说了,这还不简单么,就是 @Configuration 注解修饰的类呀!

这种说法没错,但是不全,那么我们来看下 ConfigurationClassUtils#checkConfigurationClassCandidate 方法中是怎么判断一个类是配置类的:

public static boolean checkConfigurationClassCandidate(
    BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
    String className = beanDef.getBeanClassName();
    // 首先如果这个bean是通过factoryMethod来注册的,那它就不是一个配置类
    if (className == null || beanDef.getFactoryMethodName() != null) {
        return false;
    }

    AnnotationMetadata metadata;
    // skip ... 这里跳过了一些获取AnnotationMetadata的逻辑,我们只需要知道
    // 这个AnnotationMetadata能拿到类上的所有注解的信息就可以了
	// 获取类上@Configuration注解的属性
    Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
    // 有@Configuration注解且注解的proxyBeanMethods=true(这个是默认值)
    // 这里解释一下这个proxyBeanMethods属性,这个属性我之前也没有注意,看注解是说如果这个属性为true
    // 则这个配置类里被@Bean注解修饰的方法会被spring代理,使我们通过方法调用的时候获取到的实例也是属于spring管理的。
    if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
        // 注意这里在beanDefinition中塞入了CONFIGURATION_CLASS_ATTRIBUTE这个属性
        // 外面是通过判断这个是否有属性来确定某个beanDefinition是否是配置类
        beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
    }
    // 有@Configuration 或者 isConfigurationCandidate(metadata)
    else if (config != null || isConfigurationCandidate(metadata)) {
        beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
    }
    else {
        return false;
    }

    // 获取排序值
    Integer order = getOrder(metadata);
    if (order != null) {
        beanDef.setAttribute(ORDER_ATTRIBUTE, order);
    }

    return true;
}
复制代码

我们可以看到,类上有 @Configuration 的类确实是属于配置类的,但是,没有 @Configuration 的时候,只要

isConfigurationCandidate 方法返回 true ,也是认为这个类是配置类的,我们看一下这个方法:

public static boolean isConfigurationCandidate(AnnotationMetadata metadata) {
    // Do not consider an interface or an annotation...
    if (metadata.isInterface()) {
        return false;
    }
    // 类上是否有被candidateIndicators列表中任一注解修饰?
    for (String indicator : candidateIndicators) {
        if (metadata.isAnnotated(indicator)) {
            return true;
        }
    }
    try {
        // 类中是否包含@Bean注解修饰的方法
        return metadata.hasAnnotatedMethods(Bean.class.getName());
    }
    catch (Throwable ex) {
        return false;
    }
}
复制代码

那么这个 ConfigurationClassUtils#candidateIndicators 中包含哪些注解呢?

private static final Set<String> candidateIndicators = new HashSet<>(8);
static {
    candidateIndicators.add(Component.class.getName());
    candidateIndicators.add(ComponentScan.class.getName());
    candidateIndicators.add(Import.class.getName());
    candidateIndicators.add(ImportResource.class.getName());
}
复制代码

那么我们知道了当一个类被 @Configuration@Component@ComponentScan@Import@ImportResource 修饰,或者类中有 @Bean 注解修饰的方法时, spring 就认为这个类是一个配置类(有 @Component 注解的话我们基本上可以认为大部分 beanDefinition 都会被这个 PostProcessor 处理)。

2. 解析配置类上的配置信息

a. ConfigurationClass 结构

刚刚我们有看到, spring 把配置类的信息解析并且封装到了 ConfigurationClass 对象里面,那么我们先看一下这个类的结构是怎样的:

final class ConfigurationClass {
	// 配置类的注解信息
    private final AnnotationMetadata metadata;
	
    private final Resource resource;

    @Nullable
    private String beanName;
	// 当前类是哪个配置类导入的
    private final Set<ConfigurationClass> importedBy = new LinkedHashSet<>(1);
	// 这个配置类中被@Bean注解标记的方法
    private final Set<BeanMethod> beanMethods = new LinkedHashSet<>();
	// 配置类上的@ImportResource 注解中的消息,配置文件地址-对应处理器Class
    private final Map<String, Class<? extends BeanDefinitionReader>> importedResources =
        new LinkedHashMap<>();
	// 配置类上的@Import 注解导入的类,如果是实现了ImportBeanDefinitionRegistrar接口,将会封装到这里
    private final Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars =
        new LinkedHashMap<>();
    
    // 这里重新了equals和hashCode方法,只要配置类的全类名相等这边就认为两个对象一致了。
    @Override
    public boolean equals(@Nullable Object other) {
        return (this == other || (other instanceof ConfigurationClass &&
                                  getMetadata().getClassName().equals(((ConfigurationClass) other).getMetadata().getClassName())));
    }
    @Override
    public int hashCode() {
        return getMetadata().getClassName().hashCode();
    }
}
复制代码

b. ConfigurationClassParser#processConfigurationClass 处理配置类的入口

ConfigurationClassParser#parse 方法中经过简单的封装之后会跳转到 ConfigurationClassParser#processConfigurationClass ,我们直接看一下这个方法的逻辑:

private final Map<ConfigurationClass, ConfigurationClass> configurationClasses = new LinkedHashMap<>();
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
    // 这里是判断@Condition那些,看是否需要跳过
    if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
        return;
    }
	// 看一下这个配置类是否已经解析过了,configurationClasses是一个Map
    // 这里相当于是通过配置类的类名去获取配置类的封装信息的
    ConfigurationClass existingClass = this.configurationClasses.get(configClass);
    if (existingClass != null) {
        // 如果已经解析过,会做一些处理,这里可能会直接返回,即本次就不再解析了
        // 这里的处理逻辑不太重要,我们不看了
    }

    // 这里把configClass又包装成了一个SourceClass, 这个filter的值默认是 DEFAULT_EXCLUSION_FILTER,意思就是这两个包内的类在解析的时候会被排除
    // private static final Predicate<String> DEFAULT_EXCLUSION_FILTER = className ->(className.startsWith("java.lang.annotation.") || className.startsWith("org.springframework.stereotype."));
    SourceClass sourceClass = asSourceClass(configClass, filter);
    // 处理配置类时,处理完当前类之后,还会往上处理它的父类,直到父类是Object就不再处理了
    // 这个循环就是做这个作用的
    do {
        // 处理配置类的逻辑,配置类中的信息将会被封装到configClass
        sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
    }
    while (sourceClass != null);
    // 把解析后的配置类信息储存到configurationClasses,这里这个key可以认为就是一个类名
    this.configurationClasses.put(configClass, configClass);
}
复制代码

真正的处理逻辑还在 doProcessConfigurationClass 中,我们继续跟:

protected final SourceClass doProcessConfigurationClass(
    ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
    throws IOException {

    if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
        // 如果配置类被@Component修饰,先处理内部类
        processMemberClasses(configClass, sourceClass, filter);
    }

    // 这里是处理配置类上的@PropertySources注解的
    // 简单来说就是把properties文件中的内容加载到内存中的Environment中了
	// 我们不细看
    for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
        sourceClass.getMetadata(), PropertySources.class,
        org.springframework.context.annotation.PropertySource.class)) {
        if (this.environment instanceof ConfigurableEnvironment) {
            processPropertySource(propertySource);
        }
        else {
            logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
                        "]. Reason: Environment must implement ConfigurableEnvironment");
        }
    }

    // 这里开始是处理类上的@ComponentScan注解的逻辑
    Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
        sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
    if (!componentScans.isEmpty() &&
        !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
        // 循环处理每一个注解(可以用@ComponentScans包装多个注解-jdk<java8,或者直接打上多个@ComponentScan注解-jdk>=java8)
        for (AnnotationAttributes componentScan : componentScans) {
            // 委托给componentScanParser处理,这里处理完之后返回了一批已注册的BeanDefinition
            // 这里的parse逻辑其实就是创建了一个扫描器并且进行扫描,毕竟这个注解就是做这个事的。
            // 我们之后会看一下
            Set<BeanDefinitionHolder> scannedBeanDefinitions =
                this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
            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());
                }
            }
        }
    }

    // 处理@Import注解
    processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

    // 处理@ImportResource注解
    AnnotationAttributes importResource =
        AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
    if (importResource != null) {
        String[] resources = importResource.getStringArray("locations");
        // 这个值默认是 BeanDefinitionReader.class
        Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
        for (String resource : resources) {
            String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
            // 把@ImportResource注解上的信息封装到configClass
            configClass.addImportedResource(resolvedResource, readerClass);
        }
    }

    // 处理有@Bean注解的方法
    // 这里找到类里所有有@Bean注解修饰的方法
    Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
    for (MethodMetadata methodMetadata : beanMethods) {
        // 封装成BeanMethod并且也放入configClass
        configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
    }
    // 由于java8之后接口也可以有默认方法(default修饰的方法)
    // 这里会找到所有接口中被@Bean修饰的非抽象的方法,也封装成BeanMethod放入configClass
    processInterfaces(configClass, sourceClass);

    // 如果有父类的话,会返回父类的sourceClass,继续在外层循环中解析
    // 并且把解析的信息封装到当前这个configClass
    if (sourceClass.getMetadata().hasSuperClass()) {
        String superclass = sourceClass.getMetadata().getSuperClassName();
        if (superclass != null && !superclass.startsWith("java") &&
            !this.knownSuperclasses.containsKey(superclass)) {
            this.knownSuperclasses.put(superclass, configClass);
            // Superclass found, return its annotation metadata and recurse
            return sourceClass.getSuperClass();
        }
    }

    // 没有父类了就处理完了
    return null;
}
复制代码

由于需要处理的注解比较多,所以这里逻辑还是比较复杂的,我们一个一个讲。

c.处理 @Component 注解

进入 doProcessConfigurationClass 方法时,我们最开始就会处理 @Component 注解:

if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
    // 如果配置类被@Component修饰,先处理内部类
    processMemberClasses(configClass, sourceClass, filter);
}
复制代码

我们来看一下 processMemberClasses 的逻辑:

private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass,
                                  Predicate<String> filter) throws IOException {
	// 获取所有的内部类
    Collection<SourceClass> memberClasses = sourceClass.getMemberClasses();
    if (!memberClasses.isEmpty()) {
        List<SourceClass> candidates = new ArrayList<>(memberClasses.size());
        // 循环处理每个内部类
        for (SourceClass memberClass : memberClasses) {
			// 如果内部类也是一个配置类,且内部类与当前类不一致(其实我也不知道为什么会有这种情况?)
    	    // 则加入待处理的配置类列表
            if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) &&
                !memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) {
                candidates.add(memberClass);
            }
        }
        // 对待处理的配置类排序
        OrderComparator.sort(candidates);
        // 循环按顺序处理每个配置类
        for (SourceClass candidate : candidates) {
            // 这里是判断是否有循环import的配置类的,如果有循环导入会直接报错
            if (this.importStack.contains(configClass)) {
                this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
            }
            else {
                this.importStack.push(configClass);
                try {
                    // 把每一个内部类也需要处理一下,这里其实又回到上层了,是一个递归
                    processConfigurationClass(candidate.asConfigClass(configClass), filter);
                }
                finally {
                    this.importStack.pop();
                }
            }
        }
    }
}
复制代码

可以看到,处理配置类上的 @Component 注解,实际上就是获取到配置类中的所有内部类,并且解析其中的配置类,即调用 processConfigurationClass 方法 。

d.处理 @ComponentScan 注解

@ComponentScan 的作用是扫描类上的 @Component 注解,这个功能是不是跟我们 context:component-scan 标签的功能有点像呢?其实,他们在扫描阶段的功能也确实是一致的。可以看到,我们在处理 @ComponentScan 注解时,是委托给 componentScanParser 处理的:

Set<BeanDefinitionHolder> scannedBeanDefinitions =
    this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
复制代码

我们来看一下这个 ComponentScanAnnotationParser#parse 的逻辑:

public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
    // 第一行就创建了一个扫描器Scanner
    ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
                                                                                componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);

    // skip ... 中间就是解析了@ComponentScan注解中的信息,并通过这些配置信息配置scanner的属性
    // 这些信息其实跟自定义标签context:component-scan中的属性/子标签是对应的,有兴趣的同学可以看一下我的上一篇博客
	// 新增了一个ExcludeFilter,意思就是扫描的时候就不需要处理当前类了-毕竟当前类已经在处理了。
    scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
        @Override
        protected boolean matchClassName(String className) {
            return declaringClass.equals(className);
        }
    });
    // 扫描器扫描~
    return scanner.doScan(StringUtils.toStringArray(basePackages));
复制代码

哈哈,可以看到,这里就是创建了一个扫描器,然后进行扫描了,逻辑跟我们 context:component-scan 的扫描过程是一致的,这里就不再跟了。

需要注意的是,扫描器扫描出来的 beanDifinition ,如果也是配置类的话,会调用 parse 方法解析这个配置类,最后又会走到 processConfigurationClass 方法 进行处理:

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());
    }
}
复制代码

e.处理 @Import 注解

@Import 注解一般用来引入第三方 jar 包的类到 spring 容器,当然 @bean 也有这个功能,但是 @Import@Bean 不一样的地方是, @Import 是用来修饰类的, spring 中很多 @EnableXxx 的注解就是通过 @Import 的功能实现的,我们现在就来看下它的处理过程:

processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
复制代码

getImports() 是用来收集所有 @import 导入的类的,我们看一下这个收集逻辑:

private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
    //  用来保存import导入的类
    Set<SourceClass> imports = new LinkedHashSet<>();
    // 用来标记哪些类是已经处理过的
    Set<SourceClass> visited = new LinkedHashSet<>();
    // 递归收集
    collectImports(sourceClass, imports, visited);
    return imports;
}

private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
    throws IOException {
	// 把当前类标记为已处理过
    if (visited.add(sourceClass)) {
        // 拿到并循环处理类上的所有注解
        for (SourceClass annotation : sourceClass.getAnnotations()) {
            String annName = annotation.getMetadata().getClassName();
            // 如果当前类上的这个注解不是@Import
            if (!annName.equals(Import.class.getName())) {
                // 则继续递归收集这个注解上的注解(有点绕...)
                collectImports(annotation, imports, visited);
            }
        }
        // 把当前类上的所有@Import注解的value属性(即import的Class<?>)封装成sourceClass
        // 并且加入收集到的容器
        imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
    }
}
复制代码

这里逻辑可能有点绕,不过它的功能就是把我们类上的 @Import 注解的信息收集起来,并且递归收集注解上的注解,例如我们的 @EnableAsync 注解:

@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {}

@Configuration
@EnableAsync
public class Test {}
复制代码

我们在收集 Test 上的 @Import 注解信息的时候,第一次进入 collectImports 方法时, sourceClass=Test ,这个时候,除了会收集 Test 类上的 @Import 注解信息外,还会获取 Test 类上的其他注解,例如这里有 @EnableAsync 注解,然后把 @EnableAsync 注解类的信息作为 sourceClass (即 sourceClass=EnableAsync )继续调用 collectImports 方法递归收集 @Import 注解信息,这时候 EnableAsync 上的 @Import(AsyncConfigurationSelector.class) 注解信息就被收集到了。

继续往下,我们看一下收集到 @Import 导入的类之后,~又是怎么处理的:

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
                            Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
                            boolean checkForCircularImports) {
	// 这里把循环导入的处理逻辑和异常处理逻辑去掉了    
	// 循环每一个@Import导入的类
    for (SourceClass candidate : importCandidates) {
        if (candidate.isAssignable(ImportSelector.class)) {
            // 如果实现了ImportSelector接口
			// 这里把这个ImportSelector实例化了
            Class<?> candidateClass = candidate.loadClass();
            ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
                                                                           this.environment, this.resourceLoader, this.registry);
            Predicate<String> selectorFilter = selector.getExclusionFilter();
            if (selectorFilter != null) {
                exclusionFilter = exclusionFilter.or(selectorFilter);
            }
            // 这里判断是否是延迟导入,如果是延迟导入的话,会在parse方法中,所有配置类都处理完之后再处理,有兴趣的同学可以自己看一下
            // 具体代码在ConfigurationClassParser#parse(java.util.Set<org.springframework.beans.factory.config.BeanDefinitionHolder>)
            if (selector instanceof DeferredImportSelector) {
                this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
            }
            else {
                // 如果是一个普通的ImportSelector
                String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
                // 调用ImportSelector.selectImports方法,将获取到的类名作为参数递归调用当前方法
                // 也就是说这个ImportSelector接口和@Import注解实现的功能是一样的
                // 估计@Import是spring支持注解之后对ImportSelector接口做的注解版吧
                processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
            }
        }
        else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
            // 如果导入的类实现了ImportBeanDefinitionRegistrar接口
            // 这里也会把这个类先实例化
            Class<?> candidateClass = candidate.loadClass();
            ImportBeanDefinitionRegistrar registrar =
                ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                                                     this.environment, this.resourceLoader, this.registry);
            // 然后加入到当前配置类的属性中了
            configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
        }
        else {
            // 如果导入的类既没实现ImportSelector接口,又没实现ImportBeanDefinitionRegistrar接口
            // 则认为是一个普通的配置类,进行配置类的处理逻辑
            processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
        }
    }
}
复制代码

可以看到,对于导入的类,这里有三种处理逻辑:

  1. 实现了 ImportSelector 接口的导入类
    ImportSelector.selectImports
    @Import
    
  2. 实现 ImportBeanDefinitionRegistrar 接口
    • 实例化为 ImportBeanDefinitionRegistrar 对象后,放入当前 configClass
    • ImportBeanDefinitionRegistrar 接口有一个传入 BeanDefinitionRegistryregisterBeanDefinitions 方法,不难猜测这个接口是可以用来注册 beanDefinition 的,这个方法应该只会会调用到。
  3. 普通的类
    • 作为普通的配置类,递归调用 processConfigurationClass 方法 进行处理

f.处理 @ImportResource 注解

@ImportResource 功能是导入 springxml 配置文件,一般是用来接入一些比较老的,使用 xml 定义 bean 的二方库。

AnnotationAttributes importResource =
    AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
if (importResource != null) {
    String[] resources = importResource.getStringArray("locations");
    Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
    for (String resource : resources) {
        String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
        // 这里是直接把配置文件路径和处理类类型放入了configClass对象
        configClass.addImportedResource(resolvedResource, readerClass);
    }
}
复制代码

g.处理 @Bean 注解

@Bean 注解可能是这一批注解中,我们日常用的最多的注解了,我们经常会用他来引入一些三方库的类来让 spring 管理。我们来看一下 parse 阶段它的处理逻辑:

// 收集当前类里所有有@bean注解的方法
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
    // 封装成BeanMethod对象,加入configClass
    configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}

// 处理当前类的接口,接口是可能有有默认实现(jdk>=1.8)的@Bean修饰方法
processInterfaces(configClass, sourceClass);
复制代码

收集类上 @Bean 注解修饰的方法其实很简单,我们正常通过反射就能拿到了,不过 spring 为了确定这些方法处理的顺序,使用了 asm 字节码技术来获取方法在类中的声明顺序:

private Set<MethodMetadata> retrieveBeanMethodMetadata(SourceClass sourceClass) {
    AnnotationMetadata original = sourceClass.getMetadata();
    // 获取所有被@Bean修饰的方法
    Set<MethodMetadata> beanMethods = original.getAnnotatedMethods(Bean.class.getName());
    if (beanMethods.size() > 1 && original instanceof StandardAnnotationMetadata) {
        // Try reading the class file via ASM for deterministic declaration order...
        // Unfortunately, the JVM's standard reflection returns methods in arbitrary
        // order, even between different runs of the same application on the same JVM.
        // 这里注释是说由于jvm返回的方法列表顺序不能保证,这里尝试使用asm字节码技术拿到方法在类中的声明顺序,以此来为这些被@Bean修饰的方法排序
        try {
            AnnotationMetadata asm =
                this.metadataReaderFactory.getMetadataReader(original.getClassName()).getAnnotationMetadata();
            Set<MethodMetadata> asmMethods = asm.getAnnotatedMethods(Bean.class.getName());
            // 排序
            if (asmMethods.size() >= beanMethods.size()) {
                Set<MethodMetadata> selectedMethods = new LinkedHashSet<>(asmMethods.size());
                for (MethodMetadata asmMethod : asmMethods) {
                    for (MethodMetadata beanMethod : beanMethods) {
                        if (beanMethod.getMethodName().equals(asmMethod.getMethodName())) {
                            selectedMethods.add(beanMethod);
                            break;
                        }
                    }
                }
                if (selectedMethods.size() == beanMethods.size()) {
                    // All reflection-detected methods found in ASM method set -> proceed
                    beanMethods = selectedMethods;
                }
            }
        }
        catch (IOException ex) {
            logger.debug("Failed to read class file via ASM for determining @Bean method order", ex);
            // No worries, let's continue with the reflection metadata we started with...
        }
    }
    return beanMethods;
}
复制代码

我们接下来看一下接口的处理逻辑:

private void processInterfaces(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
    for (SourceClass ifc : sourceClass.getInterfaces()) {
        // 获取接口上所有被@Bean修饰的方法
        Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(ifc);
        for (MethodMetadata methodMetadata : beanMethods) {
            if (!methodMetadata.isAbstract()) {
                // A default method or other concrete method on a Java 8+ interface...
                // 只有不是抽象的方法才封装成BeanMethod加入configClass
                configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
            }
        }
        // 递归处理
        processInterfaces(configClass, ifc);
    }
}
复制代码

h. parse 方法小结

可以看到, parser.parse(candidates) 的处理逻辑还是蛮复杂的,这个方法基本上对我们平常使用的注解都做了处理,而且其中包含大量递归调用的逻辑,我也是看了好多遍才看明白的,感兴趣的同学不妨多看几遍。

3. 通过配置类加载注册 beanDefinition

配置类解析完之后,我们需要通过配置类的信息来加载注册 beanDefinition

parser.parse(candidates);
parser.validate();
// 获取所有解析出来的配置类
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);

// Read the model and create bean definitions based on its content
if (this.reader == null) {
    this.reader = new ConfigurationClassBeanDefinitionReader(
        registry, this.sourceExtractor, this.resourceLoader, this.environment,
        this.importBeanNameGenerator, parser.getImportRegistry());
}
// 加载注册beanDefinition
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
复制代码

我们直接去看一下 loadBeanDefinitions 的逻辑:

public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
    TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
    for (ConfigurationClass configClass : configurationModel) {
        // 循环解析
        loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
    }
}

private void loadBeanDefinitionsForConfigurationClass(
    ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {

    if (trackedConditionEvaluator.shouldSkip(configClass)) {
        // skip ...
    }

    if (configClass.isImported()) {
        // 如果是导入的配置类,先把自己的beanDefinition注册到spring
        // 里面就是一个简单的beanDefinition封装注册的流程,我们就不看了
        registerBeanDefinitionForImportedConfigurationClass(configClass);
    }
    for (BeanMethod beanMethod : configClass.getBeanMethods()) {
        // 从beanMethod注册beanDefinition
        loadBeanDefinitionsForBeanMethod(beanMethod);
    }
	// 从导入的配置文件注册beanDefinition
    loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
    // 从导入的ImportBeanDefinitionRegistrar注册beanDefinition
    loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
复制代码

可以看到,加载注册 beanDefinition 的逻辑还是蛮清晰的,基本上就是对我们封装到 ConfigurationClass 对象里的信息逐个加载。接下来我们逐一看一下。

a.通过 BeanMethod 加载注册 beanDefinition

BeanMethod 对象是配置类中被 @Bean 注解修饰的方法封装而成,我们看一下它的处理逻辑:

private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
    // 我这里把代码简化了一下,因为一个beanDefinition的封装过程,无非是把@Bean注解中的信息获取封装一遍
    // 那些信息的封装我们在讲解xml标签的时候已经讲过了,这些属性都是一一对应的
    // 这里我只把我们需要额外关注的地方写出来了 -- 即我们一般意义上说的@Bean的实现原理
    ConfigurationClass configClass = beanMethod.getConfigurationClass();
    MethodMetadata metadata = beanMethod.getMetadata();
    // skip ...
    // 新建一个ConfigurationClassBeanDefinition
    ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata);
    // skip ...
    // !!!关键点!!!
    if (metadata.isStatic()) {
        // 如果是静态的@Bean方法,需要设置beanClass/beanClassName,用于在bean初始化时调用
        if (configClass.getMetadata() instanceof StandardAnnotationMetadata) {
            beanDef.setBeanClass(((StandardAnnotationMetadata) configClass.getMetadata()).getIntrospectedClass());
        }
        else {
            beanDef.setBeanClassName(configClass.getMetadata().getClassName());
        }
        // 设置FactoryMethodName
        beanDef.setUniqueFactoryMethodName(methodName);
    }
    else {
        // 如果是非静态的@Bean方法,还需要设置工厂类的beanName
        beanDef.setFactoryBeanName(configClass.getBeanName());
        // 设置FactoryMethodName
        beanDef.setUniqueFactoryMethodName(methodName);
    }
    // skip ...
    // 注册beanDefinition
    this.registry.registerBeanDefinition(beanName, beanDefToRegister);
}

复制代码

可以看到,我们的 @Bean 创建的 beanDefinition ,与普通的 beanDefinition 不一样的地方在于,它是设置了 factoryMethodName 的,也就是说,他说通过使用 xml 方式中的 factory-beanfactory-method 标签的功能,来实现 bean 的创建的!

b.通过 @ImportedResource 注解信息加载注册 beanDefinition

@ImportedResource 注解中携带了需要导入的文件的路径,以及文件 Reader 的信息,所以我们加载注册 beanDefinition 的时候,也是从这些信息入手的:

private void loadBeanDefinitionsFromImportedResources(
			Map<String, Class<? extends BeanDefinitionReader>> importedResources) {
	// reader实例缓存
    Map<Class<?>, BeanDefinitionReader> readerInstanceCache = new HashMap<>();
	// 循环处理<配置文件路径-reader>
    importedResources.forEach((resource, readerClass) -> {
        // 如果注解配置的Reader是默认的(我们一般其实也不改)
        if (BeanDefinitionReader.class == readerClass) {
            if (StringUtils.endsWithIgnoreCase(resource, ".groovy")) {
                // 如果文件名是.groovy结尾,则使用GroovyBeanDefinitionReader
                // 说实话我也第一次知道还可以用groovy脚本来做spring的配置文件
                // 后面我去看了一下BeanDefinitionReader这个接口的实现类,发现还一个
                // PropertiesBeanDefinitionReader,感兴趣的同学可以去研究一下
                readerClass = GroovyBeanDefinitionReader.class;
            }
            else {
                // 默认情况下我们使用XmlBeanDefinitionReader 
                // 有没有点眼熟这个类?xml配置解析的时候就是用的它呀
                readerClass = XmlBeanDefinitionReader.class;
            }
        }
		// 先从缓存拿
        BeanDefinitionReader reader = readerInstanceCache.get(readerClass);
        if (reader == null) {
            try {
                // 拿不到就新建一个,配置的reader类必须有一个只有BeanDefinitionRegistry参数的构造器
                reader = readerClass.getConstructor(BeanDefinitionRegistry.class).newInstance(this.registry);
                // Delegate the current ResourceLoader to it if possible
                if (reader instanceof AbstractBeanDefinitionReader) {
                    AbstractBeanDefinitionReader abdr = ((AbstractBeanDefinitionReader) reader);
                    abdr.setResourceLoader(this.resourceLoader);
                    abdr.setEnvironment(this.environment);
                }
                readerInstanceCache.put(readerClass, reader);
            }
            catch (Throwable ex) {
                throw new IllegalStateException(
                    "Could not instantiate BeanDefinitionReader class [" + readerClass.getName() + "]");
            }
        }
		// 使用reader从文件加载bean
        reader.loadBeanDefinitions(resource);
    });
}
复制代码

默认情况下(大部分情况我们都不会自行配置 BeanDefinitionReader )是创建一个 XmlBeanDefinitionReader 来解析加载我们的配置文件中定义的 bean 的,这和我们在 xml 解析那一节讲的内容一样,这里就不在讲了。

c.通过 @Import 注解导入的 ImportBeanDefinitionRegistrar 类加载注册 beanDefinition

@Import 导入的类有实现 ImportBeanDefinitionRegistrar 接口时,我们会把这个类收集起来,直到这里才会处理:

private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
    registrars.forEach((registrar, metadata) ->
                       // 直接调用registerBeanDefinitions方法
                       registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator));
}
复制代码

处理的逻辑也很简单,就是直接调用 ImportBeanDefinitionRegistrar.registerBeanDefinitions 方法,进行 beanDefinition 的注册。

到这里位置,其实整个 ConfigurationClassPostProcessor 的逻辑我们就讲完了。 spring 正是通过这个 PostProcessor 提供了对 @Bean@Import 等常见 IOC 注解的支持。

五、纯注解启动 spring

刚刚我们一直都在讲 ConfigurationClassPostProcessor 对注解的支持,但是这个组件确是spring在解析 xml 配置文件中的 context:component-scan 标签时注入的,带同学们回忆一下(通过自定义标签找处理类的逻辑这边就不重复了,感兴趣的同学可以看一下我的上一篇博文):

public BeanDefinition parse(Element element, ParserContext parserContext) {
    // 获取标签上配置并处理的base-package属性
    String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
    // 处理占位符
    basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
    // 最终获取到的是一个数组 - 因为我们配置的时候是可以配置多个的
    String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
                                                              ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

    // 获取一个扫描器 - 这个东西很重要,我们以后还会看到
    ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
    // 嗯,扫描器进行扫描,看来就是这个方法会扫描那些注解了
    Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
    // !!!注册一些组件!!! 就是这里注入的ConfigurationClassPostProcessor
    registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
    return null;
}

protected void registerComponents(
    XmlReaderContext readerContext, Set<BeanDefinitionHolder> beanDefinitions, Element element) {
    // skip ...
    // 默认是true
    if (annotationConfig) {
        // 注意这个AnnotationConfigUtils.registerAnnotationConfigProcessors
        Set<BeanDefinitionHolder> processorDefinitions =
            AnnotationConfigUtils.registerAnnotationConfigProcessors(readerContext.getRegistry(), source);
        // skip ...
    }
    // skip ...
}
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
    BeanDefinitionRegistry registry, @Nullable Object source) {
    // skip ...
    if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
        // 就是里注册了一个ConfigurationClassPostProcessor
        RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
        def.setSource(source);
        beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
    }
	// skip ...
}
复制代码

可以看到,我们在解析 context:component-scan 标签的时候,最终调用 AnnotationConfigUtils.registerAnnotationConfigProcessors 方法向spring注册了一批支撑注解的组件,其中就有 ConfigurationClassPostProcessor

那么问题来了,既然我是想通过注解来定义,声明 bean ,那么我为什么还要有一个 xml 文件,还要去解析 xml 中的

context:component-scan 标签呢?有没有一种纯注解的方式,能让我启动 spring 呢?

答案当然是有的,不过这个时候我们要使用 AnnotationConfigApplicationContext 来启动 spring 了。

1. 使用 AnnotationConfigApplicationContext 启动 spring

首先我们定义一个业务类:

@Data
@Service
public class MyAnnoClass {
    public String username = "xiaoxizi";
}
复制代码

然后使用 AnnotationConfigApplicationContext 启动:

@Test
public void test() {
    applicationContext = new AnnotationConfigApplicationContext("com.xiaoxizi.spring");
    MyAnnoClass myAnnoClass = applicationContext.getBean(MyAnnoClass.class);
    System.out.println(myAnnoClass);
}
复制代码

运行结果:

MyAnnoClass(username=xiaoxizi)
复制代码

spring 启动成功,运行结果也符合我们的预期, MyAnnoClass 确实被 spring 管理了。

2. AnnotationConfigApplicationContext 纯注解启动 spring 原理分析

话不多说,接下来我们之间分析一下 AnnotationConfigApplicationContext 支持纯注解启动 spring 的原理。

我们先看一下 AnnotationConfigApplicationContext 的构造器:

public AnnotationConfigApplicationContext(String... basePackages) {
    // 调用自己的无参构造器
    this();
    // 这个scan,看着就像扫描的意思啊
    scan(basePackages);
    // 这个refresh就是我们常说的spring启动的核心流程了
    refresh();
}
// 看一下无参构造器
public AnnotationConfigApplicationContext() {
    // 创建一个AnnotatedBeanDefinitionReader
    this.reader = new AnnotatedBeanDefinitionReader(this);
    // 创建一个ClassPathBeanDefinitionScanner
    this.scanner = new ClassPathBeanDefinitionScanner(this);
}
复制代码

可以看到,我们的创建 AnnotationConfigApplicationContext 对象的时候,创建了一个

AnnotatedBeanDefinitionReaderClassPathBeanDefinitionScanner 。等等,这个 ClassPathBeanDefinitionScanner 是不是好像有点眼熟?这不就是我们解析 context:component-scan 标签的时候创建的那个扫描器么?那么构造器中调用的 scan 方法的逻辑岂不是...?

@Override
public void scan(String... basePackages) {
    Assert.notEmpty(basePackages, "At least one base package must be specified");
    // 使用扫描器扫描
    this.scanner.scan(basePackages);
}
复制代码

你猜的没错,同学,就是通过扫描器去扫描对应的包( ╹▽╹ )。跟 context:component-scan 标签的的处理方式一模一样呢。

那么,到这里为止,我们已经可以把 basePackage 下的被 @Component 修饰的类,扫描封装注册到 spring 了,但是我们好像还没办法处理 @Bean 等标签,毕竟还没有看到哪里注入 ConfigurationClassPostProcessor

这个时候就需要往回看了, AnnotationConfigApplicationContext 的构造器中,只有一个动作我们是不熟悉的,就是创建 AnnotatedBeanDefinitionReader ,那么我们来看一下这个类的构造逻辑:

public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
    this(registry, getOrCreateEnvironment(registry));
}

public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
    this.registry = registry;
    this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
    // !!! 注册支持注解的组件 !!!
    AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
复制代码

好了,破案了,原来是 AnnotatedBeanDefinitionReader 在创建的时候,会调用 AnnotationConfigUtils.registerAnnotationConfigProcessors 方法向spring注册了支撑注解的组件,其中就有 ConfigurationClassPostProcessor 。所以我们就能愉快的使用纯注解的方式启动 spring 啦。

六、小结

这一篇博文从 xml 解析的方式讲到了纯注解方式来启动 spring ,并且通过对 ConfigurationClassPostProcessor 的源码分析,了解了 spring 是如何对注解进行支持的。

到这里为止, spring 启动流程中的 beanDefinition 的加载、解析、注册就讲完了,之后将会讲 spring 的启动流程,包括单例类的实例化、生命周期等。

博文整体行文好像比较啰嗦,估计也没啥人看,不过尽量自己还是坚持下下来吧,写的过程中也是对知识的一个很好的梳理,很多东西一知半解的时候是很难写下来的,这样就能逼迫自己去更细致的看源码,搞懂逻辑。

原文  https://juejin.im/post/5f1aadcdf265da22c636dda0
正文到此结束
Loading...