springboot筆記-2-.核心的上下文以及配置掃描解析(下)


前言

   上文說了springboot是如何發現並保存我們需要注冊的bean,其最重要的就是依靠一個特殊的BeanFactoryProcessor-》ConfigurationClassPostProcessor,本文則主要來講一下其詳細的加載過程。先放一張大致的加載圖

正文

  這兒先回顧下上文ConfigurationClassPostProcessor解析時最主要的一段代碼

//構建解析器
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 {
        //解析出主啟動類所掃描出的所有需要注冊到spring容器的類(包括我們使用@Import 或者@ImportResource 等引入的配置類)
        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());
        }
        //對這些解析到的類再進行處理  例如@Configuration類中可能有各種@Bean注解的方法需要處理
        this.reader.loadBeanDefinitions(configClasses);
        ../省略
    }
    while (!candidates.isEmpty());

 

1.從parse啟動類開始尋找

   

public void parse(Set<BeanDefinitionHolder> configCandidates) {
    //延遲引入的配置集合    
    this.deferredImportSelectors = new LinkedList<>();

    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());
            }
        }
        ../省略
    }
    //這個時候開始加載上述延遲引入的配置集合
    processDeferredImportSelectors();
}

  這兒主要分兩步,第一步就是拿着我們的啟動類就開始解析,第二步就是對那些延緩加載的配置進行加載(例如我們@Import引入的配置)

  那我們還是先看下如何解析的啟動類,因為需要延緩加載的配置也是在這個方法里找到的。

 

2.重載的parse方法

  最終這個parse會走到如下的方法

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
    //判斷是否應該跳過,如果為true  則跳過加載
    //這兒主要是根據@Conditional類的注解來判定的
    if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
        return;
    }
    //判斷是否已經存在被加載的配置類中
    ConfigurationClass existingClass = this.configurationClasses.get(configClass);
    //說明已經被加載過
    if (existingClass != null) {
        if (configClass.isImported()) {
            if (existingClass.isImported()) {
                existingClass.mergeImportedBy(configClass);
            }
            // Otherwise ignore new imported config class; existing non-imported class overrides it.
            return;
        }
        else {
            // Explicit bean definition found, probably replacing an import.
            // Let's remove the old one and go with the new one.
            this.configurationClasses.remove(configClass);
            this.knownSuperclasses.values().removeIf(configClass::equals);
        }
    }

    // 解析,configurationClass  如果其有父類  那么繼續循環其父類
    SourceClass sourceClass = asSourceClass(configClass);
    do {
        sourceClass = doProcessConfigurationClass(configClass, sourceClass);
    }
    while (sourceClass != null);
    //每個解析的類最終都會放入這個configurationClasses
    this.configurationClasses.put(configClass, configClass);
}

  第一節中有講到我們在制作可拔插組件的時候如何讓系統判斷只有依賴都被加載的時候插件才生效呢,其實上述方法中的第一行代碼就已經給出了答案,我們在需要加載的配置類上使用@Conditional類的注解,具體的注解有如下,一般可以顧名思義,可根據需求使用

 

 

 

 2.1 核心的doProcessConfigurationClass

  

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
        throws IOException {

    // 1.處理內部類  然后會遞歸到processConfigurationClass方法重新執行
    processMemberClasses(configClass, sourceClass);

    // 2.處理 @PropertySource 注解
    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");
        }
    }

    // 3.處理 @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) {
            //解析出每個componentscan所指明的解析位置下的所有需要解析的配置類
            Set<BeanDefinitionHolder> scannedBeanDefinitions =
                    this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
            // 如果componentscan下找到了configuration類,注意這兒的Configuration類不止是帶有@Configuration注解的類  (會打上full標簽)
            //還有@Import  @Component  @ImportResources @ComponentScan
            //或者其有@Bean注解的方法   (剩下的打上little標簽)  
            for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
                BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
                if (bdCand == null) {
                    bdCand = holder.getBeanDefinition();
                }
                //檢查是否為ConfigurationClass  並為其打上標簽 
                if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
                    parse(bdCand.getBeanClassName(), holder.getBeanName());
                }
            }
        }
    }

    // 4.處理 @Import 
    processImports(configClass, sourceClass, getImports(sourceClass), true);

    // 5.處理 @ImportResource 
    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);
        }
    }

    // 6.處理該加載類的所有@Bean注解的方法
    Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
    for (MethodMetadata methodMetadata : beanMethods) {
        configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
    }

    // 7.如果其實現了接口 ,那么執行接口中的default方法
    processInterfaces(configClass, sourceClass);

    // 8.如果有父類則遞歸讀取父類
    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;
}

  這個方法看着雖然多,但是步驟清晰

  1.查看該配置類是否有內部類,如果有為configurationClass的子類則返回本文第二節步驟 重新執行

  2.處理propertySource注解

  3.然后處理我們最為常見的@ComponentScan注解,將解析出來的所有需要配置的類依舊返回第二節步驟重新執行

  4.然后處理Import注解,將所有解析出來的需要配置的類放入deferredImportSelectors這個集合中,后面再進行處理(springboot可拔插配置的關鍵)

  5..處理@ImportResource注解,即我們常用的引入xml配置文件的注解,並將其解析出來的文件地址放入該配置類的importedResources

  6.處理配置類中,有@Bean注解的方法,並將解析出來的方法信息放入該配置類的beanMethods

  7.執行配置類接口的default方法

  8.如果其父類為配置類,返回第二節步驟重新執行

  

  可以看出,springboot核心的解析步驟就是這樣,如果有內部類或者通過配置解析出來的配置類或者父類,那么就遞歸第二節中的返回進行解析,確保所有類都要被加載到。然后對於@Bean和@ImportResource注解則將解析出來的東西放入該配置類的容器中。對於@Import注解解析出來的結果則是放入了特定的deferredImportSelectors中,而這個也就是我們第一節中最后的那個方法主要處理的東西,當然這個放后面說。

  這兒流程較為簡單,所以我主要分析下有兩個地方,第一個是如何解析的@ComponentScan,第二個是如何解析的@Import

 

2.2 @ComponentScan掃描

  熟悉springboot的同學應該知道這個注解的值關乎着我們哪些路徑的bean能夠注冊到spring容器中,我們現在就來看下系統是如何對其進行解析的呢,我們從如下的代碼開始入手

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

  這兒需要注意@ComponentScan注解是組合在@SpringBootApplication中的

 

 

 

 

 2.1中的方法通過這兒實現了對@ComponentScan的解析,所以我們來看這個方法。

//注意 第一次進來 declaringClass為我們主啟動類
//
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
    //根據@ComponentScan的useDefaultFilters值來構建一個ClassPathBeanDefinitionScanner
    //默認為true  如果為true 會注冊一些默認的注解過濾器,例如@Component @ManagedBean 和 @Named
    ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
            componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
    //類名生成器,默認為BeanNameGenerator接口
    Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
    boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
    //為scanner設置類名生成器,默認為AnnotationBeanNameGenerator  如果不填則為類名且首字母小寫
    scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
            BeanUtils.instantiateClass(generatorClass));
    //scopedProxy默認為DEFAULT
    ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
    if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
        scanner.setScopedProxyMode(scopedProxyMode);
    }
    else {
        //scopeResolver默認為AnnotationScopeMetadataResolver
        Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
        scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
    }
    //設置資源匹配器  默認為**/*.class   即所有class
    scanner.setResourcePattern(componentScan.getString("resourcePattern"));
    //找到所有的設置的filter 默認為空
    for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
        for (TypeFilter typeFilter : typeFiltersFor(filter)) {
            scanner.addIncludeFilter(typeFilter);
        }
    }
    //找到所有排除的filter ,springboot默認有TypeExcludeFilter和AutoConfigurationExcludeFilter
    for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
        for (TypeFilter typeFilter : typeFiltersFor(filter)) {
            scanner.addExcludeFilter(typeFilter);
        }
    }
    //是否懶加載 默認為false
    boolean lazyInit = componentScan.getBoolean("lazyInit");
    if (lazyInit) {
        scanner.getBeanDefinitionDefaults().setLazyInit(true);
    }
    
    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);
    }
    //如果有basePackageClasses則添加  默認為null
    for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
        basePackages.add(ClassUtils.getPackageName(clazz));
    }
    //如果上述的全部為空  則添加聲明類的所在包  
    //這也就是為何@SpringbootApplication默認只解析其所在的包下面的所有的類,其上級包沒法解決
    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));
}

  這個方法很長,但內容很簡單,無非就是挨個解析@ComponentScan的值並將其設置到ClassPathBeanDefinitionScanner中,然后調用scanner的解析方法,這兒有兩點需要注意

   1.默認的includeFilter中加入了@Component注解,這個非常重要

   2.如果注解中沒有找到任何可用的basePackage,那么會默認使用傳入類的package作為basePackage,而這兒第一次進來的一般為主啟動類,所以@springbootApplication之所以默認是解析其類所在的包下所有的類的緣由就在於此。

  然后我們接着看doScan方法

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    //做下判空
    Assert.notEmpty(basePackages, "At least one base package must be specified");
    //創建返回set
    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
    //迭代每個basePackage
    for (String basePackage : basePackages) {
        //找到basePackage下所有的配置類
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
        //處理這次找到的所有配置類
        for (BeanDefinition candidate : candidates) {
        //獲取其Scope信息,也就是分析其@Scope注解 ScopeMetadata scopeMetadata
= this.scopeMetadataResolver.resolveScopeMetadata(candidate); //設置其scope candidate.setScope(scopeMetadata.getScopeName()); //這兒會獲取名字,如果沒有默認為其類名且首字母小寫 String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); //如果配置類的BeanDefinition繼承了AbstractBeanDefinition if (candidate instanceof AbstractBeanDefinition) { //則為這個candidate根據剛才初始化時構建的beanDefinitionDefaults來設置BeanDefinition一些屬性, //例如是否懶加載,自動裝配模型等 postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } //如果配置類 的BeanDefinition實現了AnnotatedBeanDefinition接口 //例如有@Lazy @Primary @DependsOn @Role @Description等注解 if (candidate instanceof AnnotatedBeanDefinition) { //那么就對其每個注解進行特定的處理 例如Lazy設置懶加載 例如Primary設置該類為主要類(例如一個接口有多個實現時,某個實現類加上這個就可以直接使用@Autowire注解不會報錯) AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } //檢查一下該配置類是否在registray中已存在。為true則代表無沖突,否則會報異常 //例如兩個類名一樣且都使用@Component注解沒有指定姓名就會報錯這兒 if (checkCandidate(beanName, candidate)) { //如果沒問題則構造一個BeanDefinitionHolder BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); //這兒是根據scope信息來判定是否需要需要生成代理 默認不生成 //如果要生成可以為配置類加上@Scope(proxyMode = ScopedProxyMode.DEFAULT) 即可//只要不為ScopedProxyMode.NO(默認屬性)即可 // definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); //將該配置類注入到spring的registry中 registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; }

  這個方法主要就是根據basePackage找到所有的配置類並創建BeanDefinition返回,然后為BeanDefinition設置一些必要的屬性,最后根據特定的注解做一些特殊的判斷即可。最后這些配置類代表的BeanDefinitionHolder 會被添加到spring的regitry中,在后面實例化的時候將會有很大的作用。

那我們還是接着看下basePackage下的Bean是如何被找到的,我們接着看findCandidateComponents方法。

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
    Set<BeanDefinition> candidates = new LinkedHashSet<>();
    try {
        //找到解析的路徑匹配規則  resourcePattern默認為**/*.class   即所有的class
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                resolveBasePackage(basePackage) + '/' + this.resourcePattern;
        //根據路徑去循環遞歸查找路徑所包含的每一個class文件        
        Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
        for (Resource resource : resources) {
            //循環處理每個可讀的class文件
            if (resource.isReadable()) {
                try {
                    //讀取文件的元數據
                    MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
                    //如果文件在includeFilter中  且沒有包含在excludeFilters中 則說明是我們需要加入到容器中的配置類
                    if (isCandidateComponent(metadataReader)) {
                        //新建ScannedGenericBeanDefinition  (注意其實現了AnnotatedBeanDefinition接口,且集成了AbstractBeanDefinition類)
                        ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                        sbd.setResource(resource);
                        sbd.setSource(resource);
                    ..//代碼略
    //返回
    return candidates;
}

  這個方法就很簡單了,系統解析了路徑,並找到路徑下所有的class文件,那么spring怎么知道哪些是需要加載配置的呢,這時在上面着重讓大家記住的includeFilter就起作用了,由於其加入了@Component注解的支持,我們看這個isCandidateComponent方法,

    protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
        for (TypeFilter tf : this.excludeFilters) {
            if (tf.match(metadataReader, getMetadataReaderFactory())) {
                return false;
            }
        }
        for (TypeFilter tf : this.includeFilters) {
            if (tf.match(metadataReader, getMetadataReaderFactory())) {
                return isConditionMatch(metadataReader);
            }
        }
        return false;
    }

  看了這個方法相信大家就了然了,只有includeFilter為true才會返回true,這兒還有點需要注意的是isConditionMatch,這個處理的是@Conditional系列的注解,用來判定是否滿足加載配置的條件。如果不滿足則不必加載,會返回false。

  小結

  到此,是不是相信大家對於springboot是如何加載我們項目中所有需要依賴的bean以及配置已經有了一個大概理解了呢,我們可以概括一下。

  spring創建應用上下文后,其重要的BeanPostProcessor  即ConfigurationClassPostProcessor會根據我們的sourceClass 也就是主啟動類來開始加載,然后會解析各種用到的注解以及內部類配置,內部@Bean方法等,而當其遇到@ComponentScan時則會根據我們的@ComponnetScan,也就是組合在@SpringbootApplication中的注解各種信息來加載出其匹配路徑下的所有文件,然后對其進行逐個加載。

  

  好了,到這兒@ComponentScan的解析就完成了,那么為何我們要再講解一下@Import解析呢。因為其實我們的@ComponentScan只能掃描到我們項目下面的路徑,那對於我們引入的各種依賴包呢?例如引入mybatisPlus的springboot啟動包后,我們並沒有對其配置進行專門引入,而@ComponentScan也不可能掃描到他, springboot那么多自動化配置我們不可能說自己去挨個引入。這個任務其實就是交給了@Import注解分析來處理

 

2.3@Import注解分析

  讓我們繼續回到2.1節最后那8個步驟的第四步,代碼位置及ConfigurationClassParser.doProcessConfigurationClass方法中處理@Import注解的那一步

// Process any @Import annotations
        processImports(configClass, sourceClass, getImports(sourceClass), true);

  注意這個getImports即是找到我們當前解析的類,也就是sourceClass的所有Import類的類,例如解析我們的主配置類,也就是使用了@SpringbootApplication注解時,就會引入的兩個類(本系列第一篇開頭有講到)

 

 

 

  也就是AutoConfigurationImportSelector(注意實現了DeferredImportSelector接口)  和AutoConfigurationPackages.Registrar.class(注意實現了ImportBeanDefinitionRegistrar接口),

 然后我們正式開始看processImports方法

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
            Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
        //為空自然返回
        if (importCandidates.isEmpty()) {
            return;
        }
        //檢查下循環引用的問題
        if (checkForCircularImports && isChainedImportOnStack(configClass)) {
            this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
        }
        else {
            //將當前的configClass推入棧importStack
            this.importStack.push(configClass);
            try {
                //迭代每個selector
                for (SourceClass candidate : importCandidates) {
                    //如果為實現了ImportSelector接口   之前所說的AutoConfigurationImportSelector正好滿足
                    //這兒會執行該selector的selectImports方法
                    if (candidate.isAssignable(ImportSelector.class)) {
                        //實例化
                        Class<?> candidateClass = candidate.loadClass();
                        ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
                        //如果實現了前置處理 (即aware)則執行下其前置處理方法
                        ParserStrategyUtils.invokeAwareMethods(
                                selector, this.environment, this.resourceLoader, this.registry);
                        if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
                            //如果該selector實現了DeferredImportSelector接口且deferredImportSelectors容器不為null
                            //則將該selector包裝好放入deferredImportSelectors(延緩加載容器)中   延緩加載
                            this.deferredImportSelectors.add(
                                    new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
                        }
                        else {
                            //如果延緩容器為null或者不為DeferredImportSelector類型  也就是非延緩加載
                            String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                            Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
                            //那么就直接遞歸重新加載
                            processImports(configClass, currentSourceClass, importSourceClasses, false);
                        }
                    }
                    //如過實現了ImportBeanDefinitionRegistrar  之前所說的AutoConfigurationPackages.Registrar正好滿足
                    else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                        //實例化
                        Class<?> candidateClass = candidate.loadClass();
                        ImportBeanDefinitionRegistrar registrar =
                                BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
                        //如果實現了前置處理 (即aware)則執行下其前置處理方法
                        ParserStrategyUtils.invokeAwareMethods(
                                registrar, this.environment, this.resourceLoader, this.registry);
                        //將其加入到該sourceClass的importBeanDefinitionRegistrars容器中        
                        configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                    }
                    else {
                        //否則遞歸到最開始處理配置的方法進行解析
                        this.importStack.registerImport(
                                currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                        processConfigurationClass(candidate.asConfigClass(configClass));
                    }
                }
            }
            ../代碼略
            finally {
                //出棧
                this.importStack.pop();
            }
        }
    }

  該類可以看出,對傳入的importSouces分為了三類處理,

  1.第一類為實現了ImportSelector接口的,執行完其前置方法(如果實現了前置接口)后,該類又分為兩種情況處理,第一種則是如果為延緩加載selector且延緩加載容器不為空,則將其加入延緩加載隊列。否則就執行執行其selectImport方法,將配置類查出並遞歸執行。

  2.第二類為實現了ImportBeanDefinitionRegistrar接口的,執行完其前置方法(如果實現了前置接口)后,將其加入sourceClass的importBeanDefinitionRegistrars容器中

  3.第三類為上面兩種以外的情況,直接遞歸到本文第2節所講的步驟。

  上面第一類中第二種情況的selectImport方法是查找配置的關鍵,但由於第一種加載到延緩容器的情況在下一節就會使用到這個方法就會講到,所以我們下一節一起講。

 

3. 處理延緩加載

  我們把目光回到本文第一節中所講的方法processConfigurationClass中

public void parse(Set<BeanDefinitionHolder> configCandidates) {
        this.deferredImportSelectors = new LinkedList<>();
//1.根據configClass解析其配置
        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);
            }
        }
//2.處理延緩容器
        processDeferredImportSelectors();
    }

  第一步我們也就處理完成了,到目前已經成功的把我們主啟動類所發現的所有需要加載的配置類已經成功加載。第二步就是處理延緩加載容器

我們看延緩加載容器的處理代碼

private void processDeferredImportSelectors() {
    //為空直接返回
    List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
    this.deferredImportSelectors = null;
    if (deferredImports == null) {
        return;
    }
    //排個序
    deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
    Map<Object, DeferredImportSelectorGrouping> groupings = new LinkedHashMap<>();
    Map<AnnotationMetadata, ConfigurationClass> configurationClasses = new HashMap<>();
    //迭代每個import
    for (DeferredImportSelectorHolder deferredImport : deferredImports) {
        Class<? extends Group> group = deferredImport.getImportSelector().getImportGroup();、
        //根據import的group分組
        DeferredImportSelectorGrouping grouping = groupings.computeIfAbsent(
                (group == null ? deferredImport : group),
                (key) -> new DeferredImportSelectorGrouping(createGroup(group)));
        grouping.add(deferredImport);
        //設置引入該import的configurationClass  例如AutoConfigurationImportSelector則為主啟動類
        configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),
                deferredImport.getConfigurationClass());
    }
    //分組進行處理
    for (DeferredImportSelectorGrouping grouping : groupings.values()) {
        grouping.getImports().forEach((entry) -> {
            //獲取到引入import的configurationClass
            ConfigurationClass configurationClass = configurationClasses.get(
                    entry.getMetadata());
            try {
                processImports(configurationClass, asSourceClass(configurationClass),
                        asSourceClasses(entry.getImportClassName()), false);
            }
            ../略
        });
    }
}

  這個方法就是一個對所有的import進行排序分組等。最后循環這些selector 將值重新調入processImports方法(防止我們加載的配置類也是selector)。這兒的代碼可能很多人容易不理解,尤其是最后一循環,其實這兒我們注意這個groupings 所對應的map,這個map的values是DeferredImportSelectorGrouping,而這個DeferredImportSelectorGrouping里面則用了deferredImports集合裝入了我們屬於這個分組的所有的selector,所以我們在最后的分組循環體重進行拆解。

  首先迭代時的值為DeferredImportSelectorGrouping,也就是上述的裝入了屬於該組的selector的容器。然后程序執行了其getImport方法,那我們則看下這個方法。

public Iterable<Group.Entry> getImports() {
            //迭代該容器中的所有selector
            for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
                //處理每個selector
                this.group.process(deferredImport.getConfigurationClass().getMetadata(),
                        deferredImport.getImportSelector());
            }
            //返回group中的selectImports的返回值
            return this.group.selectImports();
        }

  該類其實看着意圖很明顯,迭代處理了每個selector,然后返回了group(注意這兒的group為默認為DefaultDeferredImportSelectorGroup)中的selectImports方法的返回值,可以預見,肯定是迭代中處理中往這個group的容器中加入了數據,然后返回。我們看下具體的處理是否和料想的一樣。

我們接着看

private static class DefaultDeferredImportSelectorGroup implements Group {
        //selector查找到的返回值的容器
        private final List<Entry> imports = new ArrayList<>();
        //迭代執行selector的查找方法
        @Override
        public void process(AnnotationMetadata metadata, DeferredImportSelector selector) {
            //查找
            for (String importClassName : selector.selectImports(metadata)) {
                this.imports.add(new Entry(metadata, importClassName));
            }
        }
        //返回查找值
        @Override
        public Iterable<Entry> selectImports() {
            return this.imports;
        }
    }

  看來果真和我們料想一致啊。。這兒會執行每個selector的selecImports方法,我們就以2.3節中說道的由@SpringbootConfiguration的組合注解@AutoConfigurationImportSelector為例來說明這個方法吧,我們直接進入到這個方法。

 

3.1 自動化配置的關鍵

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    //檢查下環境變量中spring.boot.enableautoconfiguration是否為true,如果不為true則不會執行
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    //加載元數據
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
            .loadMetadata(this.beanClassLoader);
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    //獲取配置信息
    List<String> configurations = getCandidateConfigurations(annotationMetadata,
            attributes);
    //去重        
    configurations = removeDuplicates(configurations);
    //獲取不需要配置的
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    //再次檢查不需要的
    checkExcludedClasses(configurations, exclusions);
    //剔除不需要的配置
    configurations.removeAll(exclusions);
    //過濾
    configurations = filter(configurations, autoConfigurationMetadata);
    //出發自動引入配置時間
    fireAutoConfigurationImportEvents(configurations, exclusions);
    //返回配置
    return StringUtils.toStringArray(configurations);
}

 

  這兒主要兩個步驟比較重要,1.檢查環境是否滿足自動化配置,這兒主要查看的環境變量spring.boot.enableautoconfiguration。如果不專門設置的話默認為true,也就是說我們如果不想讓spring自動化配置生效,可以在jvm啟動參數上添加
-Dspring.boot.enableautoconfiguration=false 即可
  第二個關鍵的步驟就是getCandidateConfigurations,這個方法會獲取到所有配置,我們繼續看。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
            AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
                getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
        Assert.notEmpty(configurations,
                "No auto configuration classes found in META-INF/spring.factories. If you "
                        + "are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

  這段代碼相信如果有看過本系列的第一篇文章相信會很眼熟,在講第一篇的時候談到spring實現自動發現配置的關鍵便是其會解析所有依賴下的META-INF/spring.factories文件,然后獲取到所有的配置。到這兒@Import這個注解的功能算是終於明白了,而Springboot如何實現自動發現和配置也搞清楚了正是因為@SpringbootApplication這個注解中引入了AutoConfigurationImportSelector這個selector,所以才能實現其自動化發現並配置。

spring的spring-boot-autoconfigure-xxx.jar依賴中則提供了很多的自動化配置,當然我們也可以自己編寫,關於這些本系列第一篇就講解,有興趣的可以看看。

 到此我們的依賴就成功返回給了spring解析器。我們再次回到那個循環處理的方法中。

  
for (DeferredImportSelectorGrouping grouping : groupings.values()) {
            grouping.getImports().forEach((entry) -> {
                ConfigurationClass configurationClass = configurationClasses.get(
                        entry.getMetadata());
                try {
                    processImports(configurationClass, asSourceClass(configurationClass),
                            asSourceClasses(entry.getImportClassName()), false);
                }
            
            });
        }

  這個getImports得到的便是該組所有的selector所查找到的所有配置類,然后foreach每個配置類去重復執行我們已經分析過的processImports方法即可。

  到此,對@Import分析就算完成了

   小結

  @Import注解的主要作用便是讓我們有了自定義擴展配置的功能,而其中的AutoConfigurationImportSelector自動查找加載所有依賴下的META-INF/spring.factories文件更是Springboot實現自動發現並配置的關鍵。

 

4.loadBeanDefinitions  掃尾工作

  回到本文開頭,我們算是終於執行完了ConfigurationClassParser.parse方法,但是緊跟其后的還有ConfigurationClassBeanDefinitionReader.loadBeanDefinitions方法需要我們仔細分析

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());
            }
            this.reader.loadBeanDefinitions(configClasses);

  記住這兒我們解析到的所有的需要加載的類都已經放在了這個configClasses中

  我們看下這個load方法

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

  代碼還是少的,主要是new了一個TrackedConditionEvaluator,然后迭代處理。這個TrackedConditionEvaluator 主要用來處理@Condinal系列的注解。即用來再次評估判斷是否應該跳過。例如有個配置類上有@ConditionalOnMissingClass,可能在一開始加載到他的時候沒有這個missingClass,但是后面全部加載后,這個類又被加載進去了。但是我們的配置類已經注冊到系統中了,所以需要再次校驗。

  我們接着看方法

private void loadBeanDefinitionsForConfigurationClass(
            ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
        //再次評估是否應該不加載
        if (trackedConditionEvaluator.shouldSkip(configClass)) {
            String beanName = configClass.getBeanName();
            //如果不應該被加載但是又被加載進去了  那么直接移除
            if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
                this.registry.removeBeanDefinition(beanName);
            }
            this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
            return;
        }
        //如果是被其他配置類以import方式加載的例如@Import注解,或者內部類的方式
        if (configClass.isImported()) {
            //那么要將其的BeanDefinition注冊到spring中
            registerBeanDefinitionForImportedConfigurationClass(configClass);
        }
        //處理配置類中的@Bean的注解  並將其BeanDefinition注冊到spring中
        for (BeanMethod beanMethod : configClass.getBeanMethods()) {
            loadBeanDefinitionsForBeanMethod(beanMethod);
        }
        //處理@ImportResource加載的配置類 常用來加載xml文件,並將其BeanDefinition注冊到spring中
        loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
        //處理@ImportBeanDefinitionRegistrars加載的類,並將其BeanDefinition注冊到spring中  
        loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
    }

    1.首先做了下驗證,如果不需要加載卻已經加載了就移除。

    2.如果本身是被Import進來的  一般返回自己需要的注冊到spring中的容器,優先級靠后。那么需要將其加入到spring的容器中。

    3.處理配置類中的@Bean注解,即我們常用的java代碼配置。記住我們在2.1節中最后的那個8個步驟中第6個步驟中說道配置類中所有@Bean方法都將會加載到這個配置類的BeanMethods容器中

    4.處理@ImportResource加載的類 像xml配置文件等,即2.1節中最后的那8個步驟中的第5個步驟,說道會將@ImportResource的資源加載到這個配置類的importedResources容器中

    5.最后處理ImportBeanDefinitionRegistrars,這個一般引入自定義的beanDefinition  ,例如feign框架和spring-aop都會用到這個,即2.3分析@Import注解中分析中processImports方法里,如果import為ImportBeanDefinitionRegistrar則會加載到這個配置類的importBeanDefinitionRegistrars容器中。

  通過這個也可以發現,xml的解析時要晚於注解的,也就是說我們如果允許覆蓋注入,那么xml會覆蓋掉注解的配置

  這兒邏輯基本相似,主要說下加載@Bean,因為我們一般用的比較多,加載的原理基本都類似。我們看下loadBeanDefinitionsForBeanMethod方法

private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
        //@Bean方法所在的配置類
        ConfigurationClass configClass = beanMethod.getConfigurationClass();
        //方法元數據
        MethodMetadata metadata = beanMethod.getMetadata();
        //方法名
        String methodName = metadata.getMethodName();

        //判斷該方法是否應該不加載  即@Condional類的注解
        //如果跳過的會在配置類中記載
        if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {
            configClass.skippedBeanMethods.add(methodName);
            return;
        }
        if (configClass.skippedBeanMethods.contains(methodName)) {
            return;
        }
        //得到這個@Bean注解的元數據
        AnnotationAttributes bean = AnnotationConfigUtils.attributesFor(metadata, Bean.class);
        Assert.state(bean != null, "No @Bean annotation attributes");

        // 得到name屬性值,
        List<String> names = new ArrayList<>(Arrays.asList(bean.getStringArray("name")));
        //有多個的話就用第一個 沒設置就為方法名
        String beanName = (!names.isEmpty() ? names.remove(0) : methodName);

        // Register aliases even when overridden
        for (String alias : names) {
            //為方法設置別名
            this.registry.registerAlias(beanName, alias);
        }

        // 如果已經被加載就報異常 (如果allowBeanDefinitionOverride設置為true則不會報錯,會直接覆蓋) 
        if (isOverriddenByExistingDefinition(beanMethod, beanName)) {
            if (beanName.equals(beanMethod.getConfigurationClass().getBeanName())) {
                throw new BeanDefinitionStoreException(beanMethod.getConfigurationClass().getResource().getDescription(),
                        beanName, "Bean name derived from @Bean method '" + beanMethod.getMetadata().getMethodName() +
                        "' clashes with bean name for containing configuration class; please make those names unique!");
            }
            return;
        }
        //新建一個BeanDefinition 並設置必要屬性
        ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata);
        beanDef.setResource(configClass.getResource());
        beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));
        
        if (metadata.isStatic()) {
            // static @Bean method
            beanDef.setBeanClassName(configClass.getMetadata().getClassName());
            beanDef.setFactoryMethodName(methodName);
        }
        else {
            // instance @Bean method
            beanDef.setFactoryBeanName(configClass.getBeanName());
            beanDef.setUniqueFactoryMethodName(methodName);
        }
        beanDef.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR);
        beanDef.setAttribute(RequiredAnnotationBeanPostProcessor.SKIP_REQUIRED_CHECK_ATTRIBUTE, Boolean.TRUE);

        AnnotationConfigUtils.processCommonDefinitionAnnotations(beanDef, metadata);

        Autowire autowire = bean.getEnum("autowire");
        if (autowire.isAutowire()) {
            beanDef.setAutowireMode(autowire.value());
        }
        //初始化方法
        String initMethodName = bean.getString("initMethod");
        if (StringUtils.hasText(initMethodName)) {
            beanDef.setInitMethodName(initMethodName);
        }
        //設置銷毀時方法
        String destroyMethodName = bean.getString("destroyMethod");
        beanDef.setDestroyMethodName(destroyMethodName);

        // 設置scope屬性
        ScopedProxyMode proxyMode = ScopedProxyMode.NO;
        AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(metadata, Scope.class);
        if (attributes != null) {
            beanDef.setScope(attributes.getString("value"));
            proxyMode = attributes.getEnum("proxyMode");
            if (proxyMode == ScopedProxyMode.DEFAULT) {
                proxyMode = ScopedProxyMode.NO;
            }
        }

        // 如果scope不為ScopedProxyMode.NO 則返回代理
        BeanDefinition beanDefToRegister = beanDef;
        if (proxyMode != ScopedProxyMode.NO) {
            BeanDefinitionHolder proxyDef = ScopedProxyCreator.createScopedProxy(
                    new BeanDefinitionHolder(beanDef, beanName), this.registry,
                    proxyMode == ScopedProxyMode.TARGET_CLASS);
            beanDefToRegister = new ConfigurationClassBeanDefinition(
                    (RootBeanDefinition) proxyDef.getBeanDefinition(), configClass, metadata);
        }

        if (logger.isDebugEnabled()) {
            logger.debug(String.format("Registering bean definition for @Bean method %s.%s()",
                    configClass.getMetadata().getClassName(), beanName));
        }
        //注冊到spring
        this.registry.registerBeanDefinition(beanName, beanDefToRegister);
    }

  其實也就是判斷了下是否應該加載,然后為其構建BeanDefinition注冊到spring中即可。

  好了,到此springboot如何加載系統中的配置類就講完了。

 

總結

  我們主要分析了ConfigurationClassParser.parse方法 以及ConfigurationClassBeanDefinitionReader.loadBeanDefinitions方法。

  ConfigurationClassParser.parse則主要是根據我們傳入的主啟動類進行解析,然后分析其@PropertySource,@ComponentScans,@Import,@ImportResource,@Bean注解。而ComponentScans注解則會默認加載啟動類所在包下所有的需要加載的class文件,然后遞歸進行解析。對於@Import加載的類則分為了兩種,如果為DeferredImportSelector則加載到延時容器,如果為ImportBeanDefinitionRegistrar類型則放置到配置類指定容器中,@ImportResource和@Bean注解解析后的內容也會放到配置類專門的容器中。最后在解析完成后該方法會開始處理延時容器中的selector,而其中的AutoConfigurationImportSelector更是spring自動配置的核心。

  ConfigurationClassBeanDefinitionReader.loadBeanDefinitions則是對配置條件進行再次核實防止出錯,然后開始加載之前放置到配置類容器中的各個容器資源。

  到此spring就已經完成了對所有需要加載的類的BeanDefinition的定義,但需要注意此時類並沒有被實例化,真正的實例化會在下一篇中說明。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM