SpringBoot源码解读与原理分析(二十二)IOC容器的刷新(三)ConfigurationClassPostProcessor

文章目录

前面两节,详细梳理了IOC容器刷新的前面五步(7.1-7.5),详见:

SpringBoot源码解读与原理分析(二十)IOC容器的刷新(一)
SpringBoot源码解读与原理分析(二十一)IOC容器的刷新(二)

这一节的内容是:在7.5节后置处理器执行阶段的一个重要的BeanDefinitionRegistryPostProcessor:ConfigurationClassPostProcessor

java 复制代码
代码清单1:AbstractApplicationContext.java

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // Prepare this context for refreshing.
        // 7.1 初始化前的预处理
        prepareRefresh();

        // Tell the subclass to refresh the internal bean factory.
        // 7.2 获取BeanFactory,加载所有bean的定义信息(未实例化)
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // Prepare the bean factory for use in this context.
        // 7.3 BeanFactory的预处理配置
        prepareBeanFactory(beanFactory);

        try {
            // Allows post-processing of the bean factory in context subclasses.
            // 7.4 BeanFactory准备工作完成后的后置处理
            postProcessBeanFactory(beanFactory);

            // Invoke factory processors registered as beans in the context.
            // 7.5 BeanFactory创建后的后置处理器的执行
            invokeBeanFactoryPostProcessors(beanFactory);

            // ...
    }
}

7.5.7 ConfigurationClassPostProcessor

BeanFactoryPostProcessorused for bootstrapping processing of @Configurationclasses.

ConfigurationClassPostProcessor用于引导处理标注了@Configuration注解的类。

由javadoc和官方文档可知,ConfigurationClassPostProcessor的核心作用是解析、处理所有被@Configuration标注的配置类,并向BeanDefinitionRegistry中注册新的BeanDefinition

这个后置处理器在注解驱动的IOC容器中默认生效;在基于XML配置文件的IOC容器中,只要配置文件声明<context:annotation-config/><context:component-scan/>标签,该后置处理器就生效。

java 复制代码
代码清单2:ConfigurationClassPostProcessor.java

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    int registryId = System.identityHashCode(registry);
    if (this.registriesPostProcessed.contains(registryId)) {
        // throw ...
    }
    if (this.factoriesPostProcessed.contains(registryId)) {
        // throw ...
    }
    this.registriesPostProcessed.add(registryId);
    // 解析配置类
    processConfigBeanDefinitions(registry);
}

由 代码清单2 可知,ConfigurationClassPostProcessor类的postProcessBeanDefinitionRegistry方法的核心逻辑在最后一行:解析注解配置类中的Bean定义信息,并封装为BeanDefinition。

7.5.7.1 processConfigBeanDefinitions的主体逻辑
java 复制代码
代码清单3:ConfigurationClassPostProcessor.java

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
    // 获取所有的BeanDefinition名称
    String[] candidateNames = registry.getBeanDefinitionNames();
    // 筛选出所有的注解配置类
    for (String beanName : candidateNames) {
        BeanDefinition beanDef = registry.getBeanDefinition(beanName);
        if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
            // BeanDefinition中已有CONFIGURATION_CLASS_ATTRIBUTE标记,跳过
            if (logger.isDebugEnabled()) {
                logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
            }
        } else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
            // 通过检查BeanDefinition发现是一个注解配置类(被@Configuration标注)
            // 则加入到候选配置类集合中,并添加标记CONFIGURATION_CLASS_ATTRIBUTE
            configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
        }
    }

    // Return immediately if no @Configuration classes were found
    // 没有筛选出任何注解配置类
    if (configCandidates.isEmpty()) {
        return;
    }

    // Sort by previously determined @Order value, if applicable
    // 对筛选出的注解配置类进行排序
    configCandidates.sort((bd1, bd2) -> {
        int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
        int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
        return Integer.compare(i1, i2);
    });

    // Detect any custom bean name generation strategy supplied through the enclosing application context
    // 应用自定义的Bean名称生成器,默认情况下没有自定义的
    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();
    }

    // Parse each @Configuration class
    // 解析注解配置类,使用的组件是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.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
        // 读取配置类并封装为BeanDefinition,使用的组件是ConfigurationClassBeanDefinitionReader
        if (this.reader == null) {
            this.reader = new ConfigurationClassBeanDefinitionReader(
                    registry, this.sourceExtractor, this.resourceLoader, this.environment,
                    this.importBeanNameGenerator, parser.getImportRegistry());
        }
        // 加载配置类
        this.reader.loadBeanDefinitions(configClasses);
        alreadyParsed.addAll(configClasses);
        // 一些额外的处理
        candidates.clear();
        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) {
                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());
    // 一些额外的处理
    // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
    if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
        sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
    }

    if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
        // Clear cache in externally provided MetadataReaderFactory; this is a no-op
        // for a shared cache since it'll be cleared by the ApplicationContext.
        ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
    }
}

通读 代码清单3 ,可以大致了解到processConfigBeanDefinitions方法的主体逻辑,包括筛选出注解配置类、排序、解析、封装为BeanDefinition等

其中的核心是两个API:用于解析配置类的ConfigurationClassParser;用于读取配置类内容的ConfigurationClassBeanDefinitionReader

7.5.7.2 ConfigurationClassParser

ConfigurationClassParser的作用是解析配置类,针对不同的配置源,它可以提供不同的解析逻辑(即重载的parse方法)。

java 复制代码
代码清单4:ConfigurationClassParser.java

public void parse(Set<BeanDefinitionHolder> configCandidates) {
    for (BeanDefinitionHolder holder : configCandidates) {
        BeanDefinition bd = holder.getBeanDefinition();
        try {
            // 注解配置类
            if (bd instanceof AnnotatedBeanDefinition) {
                parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
            } 
            // 编程式注入配置类
            else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
                parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
            } 
            // 其他配置类
            else {
                parse(bd.getBeanClassName(), holder.getBeanName());
            }
        } catch (BeanDefinitionStoreException ex) {
            throw ex;
        } catch (Throwable ex) {
            throw new BeanDefinitionStoreException(
                    "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
        }
    }
    // 回调ImportSelector
    this.deferredImportSelectorHandler.process();
}

由 代码清单4 可知,parse方法的核心逻辑是,for循环会提取出配置类的全限定名,并根据配置类的BeanDefinition类型转调不同的重载parse方法。for循环调用完成后,执行DeferredImportSelectorHandler类的process方法。

DeferredImportSelectorHandler是DeferredImportSelector的处理类。

SpringBoot源码解读与原理分析(二)组件装配 2.2.6 扩展:DeferredImportSelector 中提到,ImportSelector的子接口DeferredImportSelector,类似于ImportSelector,但执行时机比ImportSelector晚。ImportSelector在注解配置类的解析期间执行,DeferredImportSelector在注解配置类的解析完成之后执行(代码清单4 可以体现这一点,for循环就是在解析注解配置类,解析完毕后再执行DeferredImportSelector)。

java 复制代码
代码清单5:ConfigurationClassParser.java

private class DeferredImportSelectorHandler {
    public void process() {
        // 提取出所有解析阶段保存的DeferredImportSelector
        List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
        this.deferredImportSelectors = null;
        try {
            if (deferredImports != null) {
                DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
                deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
                // 依次执行register方法
                deferredImports.forEach(handler::register);
                handler.processGroupImports();
            }
        } finally {
            this.deferredImportSelectors = new ArrayList<>();
        }
    }
}

由 代码清单5 可知,process方法会提取出所有解析阶段保存的DeferredImportSelector并依次执行。

7.5.7.3 ConfigurationClassParser的parse方法

由 代码清单4 可知,解析配置类时会根据配置类的BeanDefinition类型转调不同的重载parse方法。

java 复制代码
代码清单6:ConfigurationClassParser.java

protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
    processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}

protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
    // 前置校验 ...
    SourceClass sourceClass = asSourceClass(configClass, filter);
    do {
        // 真正的解析动作
        sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
    }
    while (sourceClass != null);

    this.configurationClasses.put(configClass, configClass);
}

由 代码清单6 可知,重载的parse方法会调用processConfigurationClass方法,传入一个ConfigurationClass包装对象。真正的解析动作是doProcessConfigurationClass方法,该方法的内容很多,但逻辑很清晰,下面拆解来看。

(1)处理@Component注解
java 复制代码
代码清单7:ConfigurationClassParser.java

protected final SourceClass doProcessConfigurationClass(
    ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
    throws IOException {
    // 一定为true
    if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
        // Recursively process any member (nested) classes first
        processMemberClasses(configClass, sourceClass, filter);
    }
    
    // ...
}

由 代码清单7 可知,doProcessConfigurationClass方法第一个要处理的注解是@Component,通过if判断当前被解析的类是否标注了@Component注解。

由于标注了@Configuration注解的类必定标注了@Component注解,因此该if判断一定为true,processMemberClasses方法必定会触发。processMemberClasses方法的核心动作是解析当前配置类以及当前配置类的内部类。

(2)处理@PropertySource注解
java 复制代码
代码清单8:ConfigurationClassParser.java

protected final SourceClass doProcessConfigurationClass(
    ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
    throws IOException {
    
    // ...
    
    // Process any @PropertySource annotations
    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");
        }
    }
    
    // ...
}

由 代码清单8 可知,第二个要处理的注解是@PropertySource。借助AnnotationConfigUtils工具类,可以轻松提取出配置类上标注的所有注解信息,然后筛选出指定的注解属性。for循环内部的processPropertySource方法会真正地封装PropertySource导入的资源文件。

java 复制代码
代码清单9:ConfigurationClassParser.java

private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
    // 前置处理 ...
    String[] locations = propertySource.getStringArray("value");
    // ...
    for (String location : locations) {
        try {
            // 处理路径
            String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
            // 加载资源
            Resource resource = this.resourceLoader.getResource(resolvedLocation);
            // 添加到Environment中
            addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
        } //catch ...
    }
}

由 代码清单9 可知,processPropertySource方法会在一些前置处理完毕后,将资源文件的内容解析出来,并存入Environment中。

(3)处理@ComponentScan注解
java 复制代码
代码清单10:ConfigurationClassParser.java

protected final SourceClass doProcessConfigurationClass(
    ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
    throws IOException {
    
    // ...
    
    // Process any @ComponentScan annotations
    // 获取标注了@ComponentScans或@ComponentScan的注解属性
    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
            // 配置类标注了@ComponentScan注解,立即执行扫描
            // 使用的组件是ComponentScanAnnotationParser
            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());
                }
            }
        }
    }
    
    // ...
}

由 代码清单10 可知,第三个要处理的注解是@ComponentScan。首先会获取标注了@ComponentScans或@ComponentScan的注解配置类(@ComponentScans注解可以组合多个@ComponentScan注解),然后依次进行扫描,封装成BeanDefinition保存起来,以便在后续逻辑中注册到BeanDefinitionRegistry中。

扫描的核心组件是ComponentScanAnnotationParser,其parse方法执行扫描动作,它内部还集成了一个ClassPathBeanDefinitionScanner。

java 复制代码
代码清单11:ComponentScanAnnotationParser.java

public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
    // 构造ClassPathBeanDefinitionScanner
    ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
            componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
    // 解析@ComponentScan注解中的属性
    // 包括nameGenerator、scopedProxy、scopeResolver、resourcePattern、includeFilters、excludeFilters、lazyInit
    Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
    boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
    scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
            BeanUtils.instantiateClass(generatorClass));

    ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
    if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
        scanner.setScopedProxyMode(scopedProxyMode);
    } else {
        Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
        scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
    }

    scanner.setResourcePattern(componentScan.getString("resourcePattern"));

    for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
        for (TypeFilter typeFilter : typeFiltersFor(filter)) {
            scanner.addIncludeFilter(typeFilter);
        }
    }
    for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
        for (TypeFilter typeFilter : typeFiltersFor(filter)) {
            scanner.addExcludeFilter(typeFilter);
        }
    }

    boolean lazyInit = componentScan.getBoolean("lazyInit");
    if (lazyInit) {
        scanner.getBeanDefinitionDefaults().setLazyInit(true);
    }

    // 整理要扫描的basePackages
    Set<String> basePackages = new LinkedHashSet<>();
    String[] basePackagesArray = componentScan.getStringArray("basePackages");
    for (String pkg : basePackagesArray) {
        String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
                ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
        Collections.addAll(basePackages, tokenized);
    }
    for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
        basePackages.add(ClassUtils.getPackageName(clazz));
    }
    // 如果没有声明basePackages,则当前配置类所在的包即为根包
    if (basePackages.isEmpty()) {
        basePackages.add(ClassUtils.getPackageName(declaringClass));
    }
    
    scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
        @Override
        protected boolean matchClassName(String className) {
            return declaringClass.equals(className);
        }
    });
    // 执行组件扫描动作
    return scanner.doScan(StringUtils.toStringArray(basePackages));
}

由 代码清单11 可知,ComponentScanAnnotationParser的parse方法的逻辑主要有三步:

  1. 构造ClassPathBeanDefinitionScanner,并处理@ComponentScan注解中的nameGenerator、scopedProxy、scopeResolver、resourcePattern、includeFilters、excludeFilters、lazyInit属性;
  2. 整理要进行包扫描的basePackages,以及include和exclude的过滤器;
  3. 调用ClassPathBeanDefinitionScanner执行实际的组件扫描动作doScan方法。
java 复制代码
代码清单12:ClassPathBeanDefinitionScanner.java

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) {
        // 执行组件扫描,获取BeanDefinition集合
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
        for (BeanDefinition candidate : candidates) {
            ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
            candidate.setScope(scopeMetadata.getScopeName());
            // 生成bean的名称
            String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
            if (candidate instanceof AbstractBeanDefinition) {
                postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
            }
            // 处理Bean中的@Lazy、@Primary、@DependsOn等注解
            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);
                // 注册到BeanDefinitionRegistry中
                registerBeanDefinition(definitionHolder, this.registry);
            }
        }
    }
    return beanDefinitions;
}

由 代码清单12 可知,执行组件扫描的是findCandidateComponents方法,并将扫描到的结果封装为BeanDefinition集合,随后设置Bean的名称、@Lazy、@Primary、@DependsOn等注解,最后注册到BeanDefinitionRegistry中。

BeanDefinition注册到BeanDefinitionRegistry中,代表着组件扫描完毕,@ComponentScan注解的处理完成。注意,这一步是在解析阶段就直接把BeanDefinition注册到BeanDefinitionRegistry中了,下文还会提及。

(4)处理@Import注解
java 复制代码
代码清单13:ConfigurationClassParser.java

protected final SourceClass doProcessConfigurationClass(
    ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
    throws IOException {
    
    // ...
    
    // Process any @Import annotations
    processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
    
    // ...
}

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
                            Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
                            boolean checkForCircularImports) {

    if (importCandidates.isEmpty()) {
        return;
    }

    if (checkForCircularImports && isChainedImportOnStack(configClass)) {
        this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
    } else {
        this.importStack.push(configClass);
        try {
            for (SourceClass candidate : importCandidates) {
                // 处理ImportSelector
                if (candidate.isAssignable(ImportSelector.class)) {
                    // Candidate class is an ImportSelector -> delegate to it to determine imports
                    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);
                    }
                    if (selector instanceof DeferredImportSelector) {
                        // 如果该ImportSelector是DeferredImportSelector
                        // 执行时机要后延
                        this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
                    } else {
                        // 执行ImportSelector的selectImports方法
                        String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                        Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
                        // 注册要导入的类
                        processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
                    }
                }
                // 处理ImportBeanDefinitionRegistrar
                else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                    // Candidate class is an ImportBeanDefinitionRegistrar ->
                    // delegate to it to register additional bean definitions
                    Class<?> candidateClass = candidate.loadClass();
                    ImportBeanDefinitionRegistrar registrar =
                            ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                                    this.environment, this.resourceLoader, this.registry);
                    configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                } 
                // 处理普通类/配置类
                else {
                    // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
                    // process it as an @Configuration class
                    this.importStack.registerImport(
                            currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                    processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
                }
            }
        } catch (BeanDefinitionStoreException ex) {
            throw ex;
        } catch (Throwable ex) {
            throw new BeanDefinitionStoreException(
                    "Failed to process import candidates for configuration class [" +
                            configClass.getMetadata().getClassName() + "]", ex);
        } finally {
            this.importStack.pop();
        }
    }
}

由 代码清单13 可知,第四个要处理的注解是@Import。processImports方法会针对@Import注解支持的4种类型(普通类、配置类、ImportSelector、ImportBeanDefinitionRegistrar)分别应用不同的处理逻辑,将解析结果保存到ConfigurationClass对象中。

(5)处理@ImportResource注解
java 复制代码
代码清单14:ConfigurationClassParser.java

protected final SourceClass doProcessConfigurationClass(
    ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
    throws IOException {
    
    // ...
    
    // Process any @ImportResource annotations
    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.addImportedResource(resolvedResource, readerClass);
        }
    }
    
    // ...
}

由 代码清单14 可知,第五个要处理的注解是@ImportResource。SpringFramework在使用注解驱动IOC容器时,当需要导入XML配置文件时,可以使用@ImportResource注解进行导入。这段逻辑会将这些配置文件收集好,并在配置类解析完成后统一加载。

由于SpringBoot已不推荐使用XML配置文件作为配置源,因此不再深入探究。

(6)处理@Bean注解
java 复制代码
代码清单15:ConfigurationClassParser.java

protected final SourceClass doProcessConfigurationClass(
    ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
    throws IOException {
    
    // ...
    
    // Process individual @Bean methods
    Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
    for (MethodMetadata methodMetadata : beanMethods) {
        configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
    }
    
    // ...
}

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...
        // 尝试通过ASM读取类文件以确定声明顺序...
        // Unfortunately, the JVM's standard reflection returns methods in arbitrary
        // order, even between different runs of the same application on the same JVM.
        // 不幸的是,JVM的标准反射返回方法的顺序是任意的,即使在同一个JVM上运行同一应用程序的不同版本之间也是如此。
        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;
}

由 代码清单15 可知,第五个要处理的注解是@Bean。retrieveBeanMethodMetadata方法会扫描所有标注了@Bean注解的方法,然后保存到ConfigurationClass中。

retrieveBeanMethodMetadata方法中,使用的是**ASM技术(一种读取字节码的技术)**得到被@Bean注解标注的方法。在源码中,一段注释解释了这样做的原因:

Try reading the class file via ASM for deterministic declaration order...

尝试通过ASM读取类文件以确定声明顺序...

Unfortunately, the JVM's standard reflection returns methods in arbitrary order, even between different runs of the same application on the same JVM.

不幸的是,JVM的标准反射返回方法的顺序是任意的,即使在同一个JVM上运行同一应用程序的不同版本之间也是如此。

使用JVM的标准反射,在不同的JVM或者同一JVM的不同应用中返回的方法列表顺序可能是不同的,JVM的标准反射不保证方法列表返回的顺序一致。

若想保证程序在任何JVM上、任何应用中加载同一字节码文件的方法列表时都返回相同的顺序,就只能通过读取字节码来解决,SpringFramework选择了ASM这种技术。

简言之,SpringFramework使用ASM读取字节码的目的是,保证加载的配置类中标注了@Bean注解的方法列表的顺序与源文件.java中的一致。

至此,注解配置类的解析全部完成,分别解析了@Component注解、@PropertySource注解、@ComponentScan注解、@Import注解、@ImportResource注解、@Bean注解。

7.5.7.4 ConfigurationClassBeanDefinitionReader

注解配置类的解析过程中,可以看到很多需要注册的Bean信息被记录在ConfigurationClass对象中。接下来的步骤会将这些记录的内容依次转换为BeanDefinition并注册到BeanDefinitionRegistry中。

由 代码清单3 可知,读取Bean信息并封装为BeanDefinition,使用的组件是ConfigurationClassBeanDefinitionReader。由类名可知这是一个配置类的BeanDefinition读取器 ,对应的关键方法是loadBeanDefinitions方法。

java 复制代码
代码清单16:ConfigurationClassBeanDefinitionReader.java

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

private void loadBeanDefinitionsForConfigurationClass(
        ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
    // ...
    
    // 如果当前配置类是被@Imported标注的,则把配置类自身注册到BeanDefinitionRegistry
    if (configClass.isImported()) {
        registerBeanDefinitionForImportedConfigurationClass(configClass);
    }
    // 注册被@Bean注解标注的Bean
    for (BeanMethod beanMethod : configClass.getBeanMethods()) {
        loadBeanDefinitionsForBeanMethod(beanMethod);
    }
    // 注册来自XML配置文件的Bean
    loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
    // 注册来自ImportBeanDefinitionRegistrar的Bean
    loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

由 代码清单16 可知,注册BeanDefinition包含多个来源(@Imported、@Bean、XML配置文件、ImportBeanDefinitionRegistrar)。

(1)注册@Imported注解标注的配置类

由本文的【7.5.7.3(3)处理@ComponentScan注解】可知,如果一个类标注了@Configuration注解,就相当于标注了@Component注解,当该类被成功扫描时,就会直接自动地注册到BeanDefinitionRegistry中。

然而,由本文的【7.5.7.3(4)处理@Import注解】可知,标注了@Import的配置类只是被记录在ConfigurationClass对象中,并没有直接注册到BeanDefinitionRegistry中。

因此,loadBeanDefinitionsForConfigurationClass方法中的registerBeanDefinitionForImportedConfigurationClass方法就会把这些被@Import标注的配置类也全部注册到BeanDefinitionRegistry中。

java 复制代码
代码清单17:ConfigurationClassBeanDefinitionReader.java

private void registerBeanDefinitionForImportedConfigurationClass(ConfigurationClass configClass) {
    AnnotationMetadata metadata = configClass.getMetadata();
    // 构造BeanDefinition
    AnnotatedGenericBeanDefinition configBeanDef = new AnnotatedGenericBeanDefinition(metadata);
    // 处理作用域、Bean名称
    ScopeMetadata scopeMetadata = scopeMetadataResolver.resolveScopeMetadata(configBeanDef);
    configBeanDef.setScope(scopeMetadata.getScopeName());
    String configBeanName = this.importBeanNameGenerator.generateBeanName(configBeanDef, this.registry);
    AnnotationConfigUtils.processCommonDefinitionAnnotations(configBeanDef, metadata);
    // 封装BeanDefinitionHolder
    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(configBeanDef, configBeanName);
    definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
    // 注册到BeanDefinitionRegistry
    this.registry.registerBeanDefinition(definitionHolder.getBeanName(), definitionHolder.getBeanDefinition());
    configClass.setBeanName(configBeanName);

    // logger...
}

由 代码清单17 可知,registerBeanDefinitionForImportedConfigurationClass方法就是一个普通的BeanDefinition注册:先创建BeanDefinition在注册到BeanDefinitionRegistry。

(2)注册@Bean注解标注的方法
java 复制代码
代码清单18:ConfigurationClassBeanDefinitionReader.java

private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
    ConfigurationClass configClass = beanMethod.getConfigurationClass();
    MethodMetadata metadata = beanMethod.getMetadata();
    String methodName = metadata.getMethodName();

    // 如果是条件装配的,则跳过,其BeanDefinition不会注册
    if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {
        configClass.skippedBeanMethods.add(methodName);
        return;
    }
    if (configClass.skippedBeanMethods.contains(methodName)) {
        return;
    }

    // 一些校验,处理Bean的名称、别名
    
    // 如果@Bean标注的方法和XML配置文件配置的Bean一致,则抛出异常
    if (isOverriddenByExistingDefinition(beanMethod, beanName)) {
        if (beanName.equals(beanMethod.getConfigurationClass().getBeanName())) {
            // throw ...
        }
        return;
    }

    // 构造BeanDefinition
    ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata, beanName);
    beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));

    if (metadata.isStatic()) {
        // static @Bean method
        // 如果是静态@Bean方法
        if (configClass.getMetadata() instanceof StandardAnnotationMetadata) {
            beanDef.setBeanClass(((StandardAnnotationMetadata) configClass.getMetadata()).getIntrospectedClass());
        } else {
            beanDef.setBeanClassName(configClass.getMetadata().getClassName());
        }
        beanDef.setUniqueFactoryMethodName(methodName);
    } else {
        // instance @Bean method
        // 如果是实例@Bean方法
        beanDef.setFactoryBeanName(configClass.getBeanName());
        beanDef.setUniqueFactoryMethodName(methodName);
    }
    
    // 处理@Bean的属性(name、initMethod等)、额外的注解(@Lazy、@DependsOn等)

    // 注册到BeanDefinitionRegistry
    this.registry.registerBeanDefinition(beanName, beanDefToRegister);
}

由 代码清单18 可知,整个处理过程分为四步:前置检查、构造BeanDefinition、设置BeanDefinition的属性、注册到BeanDefinitionRegistry。

值得注意的是,构造BeanDefinition之后进入了一个if判断逻辑,该逻辑会使用setBeanClassName/setFactoryBeanName方法以及setUniqueFactoryMethodName方法给BeanDefinition设置两个属性,分别是当前@Bean方法所在的配置类以及方法名。

这样设计是因为,@Bean方法有实际的代码执行,属于编程式创建bean对象。为了在后面能正常创建出bean对象,就需要记录该Bean的定义源(所在配置类和方法名),以确保在创建bean对象时,能够使用反射机制调用该配置类的方法生成bean对象

(3)注册来自XML配置文件的Bean
java 复制代码
代码清单19:ConfigurationClassBeanDefinitionReader.java

private void loadBeanDefinitionsFromImportedResources(
        Map<String, Class<? extends BeanDefinitionReader>> importedResources) {

    Map<Class<?>, BeanDefinitionReader> readerInstanceCache = new HashMap<>();

    importedResources.forEach((resource, readerClass) -> {
        // ...
        // 创建XmlBeanDefinitionReader
        readerClass = XmlBeanDefinitionReader.class;
        // ...
        BeanDefinitionReader reader = readerInstanceCache.get(readerClass);
        // ...
        // 解析资源文件
        reader.loadBeanDefinitions(resource);
    });
}

由本文的【7.5.7.3(5)处理@ImportResource注解 】和 代码清单19 可知,在处理@ImportResource注解时会将XML配置文件的路径保存起来。loadBeanDefinitionsFromImportedResources方法则逐个加载并解析这些XML配置文件。

由于SpringBoot已不推荐使用XML配置文件作为配置源,因此不再深入探究。

(4)注册来自ImportBeanDefinitionRegistrar的Bean
java 复制代码
代码清单20:ConfigurationClassBeanDefinitionReader.java

private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
    registrars.forEach((registrar, metadata) ->
        registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator));
}

由 代码清单20 可知,loadBeanDefinitionsFromRegistrars提取出所有的ImportBeanDefinitionRegistrar,逐个调用其registerBeanDefinitions方法。

7.5.7.5 小结

至此,ConfigurationClassPostProcessor的工作全部完成,小结如下:

  • ConfigurationClassPostProcessor实现了BeanDefinitionRegistryPostProcessor接口,具备向BeanDefinitionRegistry注册新的BeanDefinition的能力。
  • ConfigurationClassPostProcessor组合了一个ConfigurationClassParser,该类具备解析配置类的能力,会具体负责注解配置类的解析并提取关键注解的信息,封装到ConfigurationClass对象中。
  • ConfigurationClassPostProcessor利用ConfigurationClassBeanDefinitionReader读取ConfigurationClass对象中的配置类信息,封装成BeanDefinition,并注册到BeanDefinitionRegistry中。

本节完,更多内容请查阅分类专栏:SpringBoot源码解读与原理分析

相关推荐
Bubluu4 分钟前
浏览器点击视频裁剪当前帧,然后粘贴到页面
开发语言·javascript·音视频
Cosmoshhhyyy12 分钟前
LeetCode:3083. 字符串及其反转中是否存在同一子字符串(哈希 Java)
java·leetcode·哈希算法
AI人H哥会Java25 分钟前
【Spring】基于XML的Spring容器配置——<bean>标签与属性解析
java·开发语言·spring boot·后端·架构
计算机学长felix29 分钟前
基于SpringBoot的“大学生社团活动平台”的设计与实现(源码+数据库+文档+PPT)
数据库·spring boot·后端
sin220129 分钟前
springboot数据校验报错
spring boot·后端·python
开心工作室_kaic35 分钟前
springboot493基于java的美食信息推荐系统的设计与实现(论文+源码)_kaic
java·开发语言·美食
缺少动力的火车37 分钟前
Java前端基础—HTML
java·前端·html
析木不会编程42 分钟前
【C语言】动态内存管理:详解malloc和free函数
c语言·开发语言
loop lee1 小时前
Redis - Token & JWT 概念解析及双token实现分布式session存储实战
java·redis