spring高質量系列-IOC (二)

weixin_34208283發表於2018-09-09

bean放入到ioc容器 需要先解析bean的xml或者相關注解獲取到beanDefinition然後根據beanDefinition生產對應的bean例項並放入容器中。

容器中都是存放的都是單例bean

10378860-d5233c44b8dffca2.png
image.png

BeanDefinition相關介面和類的介紹

    1. BeanDefinition繼承了AttributeAccessor(存放和取一些key-value)和BeanMetadataElement(可以配置bean的後設資料資源,即獲取beanDefinition的資料來源,比如xml的標籤和註解)
  • 2.一級實現類或者子介面:AbstractBeanDefinition(完成了大部分的方法)和AnnotatedBeanDefinition(子介面,增加了獲取註解後設資料和方法的後設資料)
  • 3.載入beanDefinition:BeanDefinitionReader介面讀取資源loadBeanDefinition,AbstractBeanDefinitionReader(實現了其大部分方法),XmlBeanDefinitionReader(對xml進行解析獲取bean)。AnnotatedBeanDefinitionReader是一個單獨的類和BeanDefinitionReader沒有關係,但是其提供的方法和其相似
  • 註冊:AliasRegistry(將beanName和別名進行對映),BeanDefinitionRegistry(提供了對beanName和BeanDefinition的贈刪改查的操作)

因為IOC容器想要最終獲取beanName和BeanDefinition需要三步驟

  • 1.BeanDefinition的Resource定位
  • 2.Resource的載入
  • 3.註冊進入IOC容器

筆者對註解和xml 兩個解析細節統一進行詳細闡述.

此處我們講的beanDefinition是我們開發人員通過xml配置 或者@Component等註解注入的beanDefinition,這裡不包含spring在啟動時自動注入的一系列beanDefinition

筆者終於找到了spring.xml和我們自定義註解bean是什麼時候處理

BeanDefinition的Resource定位

    1. AbstractApplicationContext--->refresh()---> invokeBeanFactoryPostProcessors(beanFactory)-->PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors-->
      invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry)-->ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry-->processConfigBeanDefinitions-->ConfigurationClassParser parser () --->ConfigurationClassParser processConfigurationClass--->ConfigurationClassParser doProcessConfigurationClass
    1. 在ConfigurationClassParser類的 方法 processConfigurationClass,將我們的@Configuration類包裝成source
        SourceClass sourceClass = asSourceClass(configClass);
  do {
            sourceClass = doProcessConfigurationClass(configClass, sourceClass);
        }
        while (sourceClass != null);

SourceClass asSourceClass(@Nullable Class<?> classType) throws IOException {
        if (classType == null) {
            return new SourceClass(Object.class);
        }
        try {
            // Sanity test that we can reflectively read annotations,
            // including Class attributes; if not -> fall back to ASM
            for (Annotation ann : classType.getAnnotations()) {
                AnnotationUtils.validateAnnotation(ann);
            }
            return new SourceClass(classType);
        }
        catch (Throwable ex) {
            // Enforce ASM via class name resolution
            return asSourceClass(classType.getName());
        }
    }

上述程式碼就是獲取@Configuration註解類的class同時校驗該class上面的註解是否配置正確即註解的屬性是否合理。並把他包裝成source然後交給doProcessConfigurationClass進行處理

Resource和beanDefinition的載入

概述
  • 1 ConfigurationClassParser doProcessConfigurationClass(),即對獲取到的@Configuration 註解進行處理,而我們的@ImportResource註解則是放在啟動類上,我們匯入spring.xml是依靠@ImportResource,即此時在把相關soucre載入
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
            throws IOException {

        首先遞迴處理任何成員(巢狀)類,即從sourceClass獲取內部類看是否屬於@configuration,屬於就遞迴呼叫doProcessConfigurationClass處理子類
        processMemberClasses(configClass, sourceClass);

        處理@PropertySource 註解,將properties檔案屬性注入到具體bean中,但是前提是environment必須是ConfigurableEnvironment 否則忽略
        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");
            }
        }

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

         處理 any @Import annotations
        processImports(configClass, sourceClass, getImports(sourceClass), true);

        處理 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);
            }
        }

          處理 individual @Bean methods
        Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
        for (MethodMetadata methodMetadata : beanMethods) {
            configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
        }

        // 處理實現介面的方法
        processInterfaces(configClass, sourceClass);

        // 處理 父類, if any
        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();
            }
        }

        // No superclass -> processing is complete
        return null;
    }

其中@PropertySources和@PropertySource 以及@ComponentScans和@ComponentScan 分別是集合和單個元素的關係

處理類中類
    1. 首先遞迴處理任何成員(巢狀)類,即從sourceClass獲取內部類看是否屬於@configuration,屬於就遞迴呼叫doProcessConfigurationClass處理子類
處理@PropertySource 註解
  • 1.將properties檔案屬性注入到具體bean中,但是前提是environment必須是ConfigurableEnvironment 否則忽略
  • 2.最終把PropertySource放入propertySourceList
  • 3.更多的細節請看第三節
處理@ComponentScan
  • 1.首先從@ComponentScans和@ComponentScan獲取,而我們的啟動類上有註解@SpringBootApplication,其內部定義了預設的scanBasePackages()和scanBasePackageClasses(),我們可以從中獲取到@ComponetScan
  • 2.this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());的主要邏輯是設定ClassPathBeanDefinitionScanner的屬性:registry,resourceLoader,beanNameGenerator,scopedProxyMode,resolverClass,resourcePattern,includeFilters(必須是@Component和@ManagedBean註解才可以),excludeFilters(componentScan註解所在的類不需要掃描),lazyInit,basePackages。
    1. 其中獲取basePackages的邏輯是:從basePackages和basePackageClasses獲取他們的包名字放入到集合中,如果集合還是為空就把我們註解類的包名放入進去。
    1. scanner.doScan(StringUtils.toStringArray(basePackages)); 這是最終開始掃描的我們包的操作
    1. ClassPathScanningCandidateComponentProvider類的Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath)是獲取到了我們注入的bean資源
  • 6.獲取到資源後(也就是basePackages下面的class檔案),通過檔案流載入class然後獲取class和註解的相關屬性。然後獲取註解是@Component和@ManagedBean的類
    1. 將上述獲取的候選類包裝成ScannedGenericBeanDefinition,繼續判斷該類是否不依賴其他bean且該類要麼是具體的實現類 要麼是抽象類的同時其方法上面還有@Lookup的註解
  • 8.通過scopeMetadataResolver獲取ScopeMetadata 即判斷是否是單例 同時是否需要包裝成proxy預設是不需要。
  • 9 .依次判斷候選BeanDefinition是否是AbstractBeanDefinition和AnnotatedBeanDefinition 然後分別呼叫
    postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
    其中前者將beanDefinitionDefaults設定到候選BeanDefinition,後者是將註解上面的一些屬性設定到候選BeanDefinition
  • 10.將beanName和候選BeanDefinition包裝成definitionHolder,同時看是否需要代理然後將該候選bean註冊到beanFactory,但是目前看@bean 和@component 都沒有提供ScopeMetadata 的屬性配置
  • 11.ScopedProxyCreator.createScopedProxy(definition, registry, proxyTargetClass) 就是建立bean的proxy的地方。
  • 12.建立動態代理的方式是把原先的BeanDefinition和targetBeanName(在原來的名字上加個字首)註冊到beanFactory中,且該BeanDefinition的AutowireCandidate和Primary屬性被置為false
  • 13.建立一個proxyDefinition 其beanClass是ScopedProxyFactoryBean,然後將原先的BeanDefinition和proxyDefinition 進行繫結 最終返回一個definitionHolder是proxyDefinition 和originalBeanName組成的
  • 14 若返回的bean是屬於@Component ,@ComponentScan ,@Import,@ImportResource.繼續呼叫ConfigurationClassParser的parse方法進行重新處理
  • 15我們通過上述可知@ComponentScan 有ScopedProxyMode 可以指定類是否啟用代理。
處理 @Import 註解
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 {
            this.importStack.push(configClass);
            try {
                for (SourceClass candidate : importCandidates) {
                    if (candidate.isAssignable(ImportSelector.class)) {
                        // Candidate class is an ImportSelector -> delegate to it to determine imports
                        Class<?> candidateClass = candidate.loadClass();
                        ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
                        ParserStrategyUtils.invokeAwareMethods(
                                selector, this.environment, this.resourceLoader, this.registry);
                        if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
                            this.deferredImportSelectors.add(
                                    new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
                        }
                        else {
                            String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                            Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
                            processImports(configClass, currentSourceClass, importSourceClasses, false);
                        }
                    }
                    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 {
                        // 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));
                    }
                }
            }
            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();
            }
        }
    }
  • 0.也會堅持是否有迴圈匯入,尋找的@Import,ImportSelector介面,ImportBeanDefinitionRegistrar介面 都是作為@Import一樣處理
  • 1.可以匯入@Configuration,ImportSelector介面,ImportBeanDefinitionRegistrar介面
    或者常規的 regular component classes 。
  • 2.@ImportSelector可以返回一些列的需要匯入的類
  • 3.@ImportBeanDefinitionRegistrar 可以直接向beanFactory註冊beanDefinition
  • 4.經過測試@Configuration類都會被Spring用Cglib增強,但是具體在哪增加筆者尚未知曉@Configuration類下面的@Bean 不會被增強,且Component類下面的@Bean註解不會注入SpringIOC容器
    1. 下面是@Import ImportSelector 類 importingClassMetadata是我們@Import註解所在的類,最終可以匯入我們想匯入的類
public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        System.out.println(importingClassMetadata.toString());
        String[] importString = new String[]{"testImport.ImportBean"};
        return importString;
    }
}
    1. 下面是ImportBeanDefinitionRegistrar 可以在bean例項化前bean進行增刪改查
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        BeanDefinition beanDefinition =new RootBeanDefinition();
        ((RootBeanDefinition) beanDefinition).setBeanClass(ImportBean.class);
        registry.registerBeanDefinition("xujieBean",beanDefinition);
    }
}
處理@ImportResource
    1. 主要作用就是把xml的地址和readerClass放入importedResources集合,留作後期解析使用
處理@Bean
  • 1.獲取當前正在處理的class的方法上有bean的,獲取到MethodMetadata,然後將MethodMetadata和當前class 包裝成BeanMethod 加入到當前class的beanMethods
processInterfaces(configClass, sourceClass)
  • 看樣子是處理當前configClass實現的介面中的子類且加@Bean的方法(該註解放在父類或者子類方法都可以),但是這個方法至今筆者沒太看懂他的意義
Process superclass,

-1 .即處理當前configclass 是否還有父類且非介面,如果有繼續按照之前的流程進行處理。

此篇文章還留有疑問,後期筆者會在研讀過程中進行解說
1.缺少spring.xml在哪一步載入
2.@configuration在哪一步開始進行了動態代理

  1. 何時處理beanMethods集合中的@Bean方法
    4 .如何讓propertySource生效

總結:doProcessConfigurationClass處理了類中類,@PropertySource,@ComponentScan,@Import,@ImportResource,@Bean,父類和子類,
其中只有@ComponentScan這一塊遇到@Component註解才會註冊bean,其他都存入其內部集合中留待真正的註冊beanDefinition,詳情請關注下篇文章。

相關文章