spring容器加载分析 三Configuration类解析

Spring中对Configuration类的解析是通过ConfigurationClassPostProcessor进行的,这个类是BeanFactoryPostProcessor的实现,在容器刷新方法中invokeBeanFactoryPostProcessors(beanFactory)这个方法调用所有的BeanFactoryPostProcessor,同时也就启动了Configuration类解析的。
解析的总体过程:

1、从Bean工厂找出所有Configuratio类加入configCandidates列表中,所谓Configuratio类就是被@Configuration注解的类或者包含@Bean、@Component、@ComponentScan、@Import、@ImportResource注解的类。
2、根据@Order对configCandidates列表进行排序
3、遍历configCandidates,使用委托类ConfigurationClassParser解析配置项,包含@PropertySources注解解析、@ComponentScan注解解析、@Import注解解析、@Bean注解解析。
4、遍历configCandidates,使用委托类ConfigurationClassBeanDefinitionReader注册解析好的BeanDefinition

具体配置项解析过程在ConfigurationClassParser类中实现:

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
        throws IOException {
    // Recursively process any member (nested) classes first
    processMemberClasses(configClass, sourceClass);
    // 1、处理@PropertySources注解,解析属性文件
    // 将解析出来的属性资源添加到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.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
                    "]. Reason: Environment must implement ConfigurableEnvironment");
        }
    }
    // 2、处理@ComponentScan注解,通过ComponentScanAnnotationParser解析@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
            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) {
                // 检查是否是ConfigurationClass,如果是走递归
                if (ConfigurationClassUtils.checkConfigurationClassCandidate(holder.getBeanDefinition(), this.metadataReaderFactory)) {
                    parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
                }
            }
        }
    }
    //3、处理@Import注解
    processImports(configClass, sourceClass, getImports(sourceClass), true);
    // Process any @ImportResource annotations
    // 处理@ImportResource注解:获取@ImportResource注解的locations属性,得到资源文件的地址信息。
    // 然后遍历这些资源文件并把它们添加到配置类的importedResources属性中
    if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) {
        AnnotationAttributes importResource =
                AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
        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);
        }
    }
    // 4、处理@Bean注解:获取被@Bean注解修饰的方法,然后添加到配置类的beanMethods属性中
    Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
    for (MethodMetadata methodMetadata : beanMethods) {
        configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
    }
    // 处理接口和父类
    // Process default methods on interfaces
    processInterfaces(configClass, sourceClass);
    // Process superclass, if any
    if (sourceClass.getMetadata().hasSuperClass()) {
        String superclass = sourceClass.getMetadata().getSuperClassName();
        if (!superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) {
            this.knownSuperclasses.put(superclass, configClass);
            // Superclass found, return its annotation metadata and recurse
            return sourceClass.getSuperClass();
        }
    }
    // No superclass -> processing is complete
    return null;
}

step 1 处理@PropertySources注解
解析属性文件,将解析出来的属性资源添加到Environment中
step 2 处理@ComponentScan注解
通过ComponentScanAnnotationParser解析@ComponentScan注解,解析方法为parse:
2.1)、实例化元数据(注解)扫描器ClassPathBeanDefinitionScanner
2.2)、分析出Bean名称生成器BeanNameGenerator
2.3)、分析出代理模型ScopedProxyMode
2.4)、分析出resourcePattern,默认值"*/.class"
2.5)、分析出扫描包含的目录includeFilters、排除的目录excludeFilters,生成过滤规则
2.6)、分析出加载类型,延迟或非延迟
2.7)、将上述属性设置到ClassPathBeanDefinitionScanner
2.8)、分析出扫描的包路径数组basePackages,
2.9)、使用ClassPathBeanDefinitionScanner扫描basePackages包中符合条件的Bean注册到容器,然后检查Bean是否为ConfigurationClass,如果是则递归解析。扫描过程:

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Assert.notEmpty(basePackages, "At least one base package must be specified");
    //创建一个集合,存放扫描到BeanDefinition
    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
     //遍历扫描所有给定的包  
    for (String basePackage : basePackages) {
        //调用父类ClassPathScanningCandidateComponentProvider的方法扫描给定类路径,获取符合条件的BeanDefinition
        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);
            // 普通的BeanDefinition
            if (candidate instanceof AbstractBeanDefinition) {
                postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
            }
            // 注解的BeanDefinition
            // 处理注解@Primary、@DependsOn等Bean注解
            if (candidate instanceof AnnotatedBeanDefinition) {
                AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
            }
            // 检查候选的,主要是检查BeanFactory中是否包含此BeanDefinition
            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;
}

其中findCandidateComponents(basePackage)是调用父类ClassPathScanningCandidateComponentProvider中的方法来获取符合条件的BeanDefinition。这个类在初始化的时候,会注册一些默认的过滤规则,与includeFilters和excludeFilters协调工作来过滤候选BeanDefinition,注册Spring默认规则:

protected void registerDefaultFilters() {
    //  向include过滤规则中添加@Component注解
    this.includeFilters.add(new AnnotationTypeFilter(Component.class));
    ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
    try {
        // 向include过滤规则添加JSR-250:@ManagedBean注解 
        this.includeFilters.add(new AnnotationTypeFilter(
                ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
        logger.debug("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
    }
    catch (ClassNotFoundException ex) {
        // JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
    }
    try {
        // 向include过滤规则添加JSR-330:@Named注解  
        this.includeFilters.add(new AnnotationTypeFilter(
                ((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
        logger.debug("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
    }
    catch (ClassNotFoundException ex) {
        // JSR-330 API not available - simply skip.
    }
}

可以看到使用使用Spring @Component注解类、使用JSR-250:@ManagedBean、JSR-330:@Named注解的类都在include规则中,另外Spring中@Repository 、@Service、@Controller、@Configuration都是被@Component注解过的组合注解,所以添加了这些注解的类都会作为候选的Bean。获取候选Bean:

public Set<BeanDefinition> findCandidateComponents(String basePackage) {
    // 创建存储扫描到的类的集合  
    Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
    try {
        // 默认的包路径:this.resourcePattern=” **/*.class”,  
        // resolveBasePackage方法将包名中的”.”转换为文件系统的”/”  
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                resolveBasePackage(basePackage) + '/' + this.resourcePattern;
        Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
        boolean traceEnabled = logger.isTraceEnabled();
        boolean debugEnabled = logger.isDebugEnabled();
        // 循环获取到的资源文件
        for (Resource resource : resources) {
            if (traceEnabled) {
                logger.trace("Scanning " + resource);
            }
            if (resource.isReadable()) {
                try {
                    //为指定资源获取元数据读取器,元信息读取器通过汇编(ASM)读//取资源元信息
                    MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
                    //如果扫描到的类符合容器配置的过滤规则  
                    if (isCandidateComponent(metadataReader)) {
                        //通过汇编(ASM)读取资源字节码中的Bean定义元信息
                        ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                        //设置Bean定义来源于resource 
                        sbd.setResource(resource);
                         //为元数据元素设置配置资源对象  
                        sbd.setSource(resource);
                        //检查Bean是否是一个可实例化的对象
                        if (isCandidateComponent(sbd)) {
                            if (debugEnabled) {
                                logger.debug("Identified candidate component class: " + resource);
                            }
                            candidates.add(sbd);
                        }
        // 省略 debug和异常处理的代码 ... ...
    return candidates;
}

2.10)、扫描完成返回beanDefinitions,如果发现beanDefinitions中有Configuration类,进行递归。

step 3 处理@Import注解
通过getImports(sourceClass)获取Configuration类中使用Import注解的类

private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
    Set<SourceClass> imports = new LinkedHashSet<SourceClass>();
    Set<SourceClass> visited = new LinkedHashSet<SourceClass>();
    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();
            if (!annName.startsWith("java") && !annName.equals(Import.class.getName())) {
                collectImports(annotation, imports, visited);
            }
        }
        imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
    }
}

collectImports方法中包含一个递归,如果你使用过Springboot可以会对里面的组合注解的解析有过疑问,其实springboot中组合注解的解析过程就是这个递归的过程:先递归找出所有的注解,然后再过滤出只有@Import注解的类,得到@Import注解的值。比如查找@SpringBootApplication注解的@Import注解数据的话,首先发现@SpringBootApplication不是一个@Import注解,然后递归调用修饰了@SpringBootApplication的注解,发现有个@EnableAutoConfiguration注解,再次递归发现被@Import(EnableAutoConfigurationImportSelector.class)修饰,还有@AutoConfigurationPackage注解修饰,再次递归@AutoConfigurationPackage注解,发现被@Import(AutoConfigurationPackages.Registrar.class)注解修饰,所以@SpringBootApplication注解对应的@Import注解有2个,分别是@Import(AutoConfigurationPackages.Registrar.class)和@Import(EnableAutoConfigurationImportSelector.class)。所以递归的目的就是找出所有的Import类,拿到这个Import列表进行解析过程:

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
        Collection<SourceClass> importCandidates, boolean checkForCircularImports) throws IOException {
    if (importCandidates.isEmpty()) {
        return;
    }
    if (checkForCircularImports && isChainedImportOnStack(configClass)) {
        this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
    }
    else {
        this.importStack.push(configClass);
        try {
            // 遍历这些@Import注解内部的属性类集合ComponentScanAnnotationParser
            for (SourceClass candidate : importCandidates) {
                if (candidate.isAssignable(ImportSelector.class)) {//如果这个类是个ImportSelector接口的实现类
                    // Candidate class is an ImportSelector -> delegate to it to determine imports
                    Class<?> candidateClass = candidate.loadClass();
                    // 实例化这个ImportSelector
                    ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
                    ParserStrategyUtils.invokeAwareMethods(
                            selector, this.environment, this.resourceLoader, this.registry);
                    // 如果这个类也是DeferredImportSelector接口的实现类,
                    // 那么加入ConfigurationClassParser的deferredImportSelectors
                    if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
                        this.deferredImportSelectors.add(
                                new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
                    }else {
                        // 否则调用ImportSelector的selectImports方法得到需要Import的类
                        // 然后对这些类递归做@Import注解的处理
                        String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                        Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
                        processImports(configClass, currentSourceClass, importSourceClasses, false);
                    }
                }
                // 如果这个类是ImportBeanDefinitionRegistrar接口的实现类
                // 设置到配置类的importBeanDefinitionRegistrars属性中
                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 =
                            BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
                    ParserStrategyUtils.invokeAwareMethods(
                            registrar, this.environment, this.resourceLoader, this.registry);
                    configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                } else {
                    // 其它情况下把这个类入队到ConfigurationClassParser的importStack(队列)属性中
                    // 然后把这个类当成是@Configuration注解修饰的类递归重头开始解析这个类
                    this.importStack.registerImport(
                            currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                    processConfigurationClass(candidate.asConfigClass(configClass));
                }
            }
        }
        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();
        }
    }
}

@Import可以引入普通类、Configuration类、ImportBeanDefinitionRegistrar和ImportSelector的实例,不排斥这些类中也包含Import,所以方法中也包含一个processConfigurationClass递归。
ImportSelector和ImportBeanDefinitionRegistrar是Spring中两个扩展接口,分别通过selectImports方法和registerBeanDefinitions方法向容器注入Bean。
另外ImportSelector还有一个子接口DeferredImportSelectors,这
个接口的实现类会等到Configuration类解析完之后在进行再进行processImports处理。
它们也是springboot加载自动配置的使用的注入方式。

step 4 处理@Bean注解
这个处理过程很容易理解:

// 获取被@Bean注解修饰的方法,然后添加到配置类的beanMethods属性中
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
    configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}

至此一个基于注解的Configuration类就解析完成了,各个Bean类的BeanDefinition也注册进了容器,等待实例化。

码字不易,转载请保留原文连接https://www.jianshu.com/p/b61809506d0b

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342