1. 程式人生 > 實用技巧 >BeanFactory後置處理器 - ConfigurationClassPostProcessor - Component

BeanFactory後置處理器 - ConfigurationClassPostProcessor - Component

在接上一篇之前, 我想先寫幾個測試demo, 應該能幫助更好的理解.

demo:

com.study.elvinle.ioc.xml.IndexService:

public class IndexService {
    private String name = "index";public String getName() {
        System.out.println("IndexService.getName() --> " + name);
        return name;
    }

    public void setName(String name) {
        
this.name = name; } }

com.study.elvinle.ioc.fac.IndexFactoryBean:

@Component("index")
public class IndexFactoryBean  implements FactoryBean<Object> {

    public void printf(){
        System.out.println("IndexFactoryBean printf");
    }

    @Override
    public Object getObject() throws
Exception { return new IndexService("小明"); } @Override public Class<?> getObjectType() { return null; } @Override public boolean isSingleton() { return true; } }

com.study.elvinle.ioc.scan.TempDao:

@Component
public class TempDao {
    public
TempDao() { System.out.println("tempDao constructor"); } private String name = "tempDao"; public String getName() { return name; } public void setName(String name) { this.name = name; } }

com.study.elvinle.ioc.scan.TempScan:

// 這裡使用 Configuration 也是能被掃描到的, 因為 Configuration中, 也有Component註解
//@Configuration
@Component
@ComponentScan("com.study.elvinle.ioc.fac")
public class TempScan {

}

com.study.elvinle.ioc.StartConfig:

@Configuration
@ComponentScan("com.study.elvinle.ioc.scan")
public class StartConfig {

    public StartConfig() {
        System.out.println("startConfig Constructor ... ");
    }

}

從這裡的掃描範圍來看, IndexFactoryBean 並不在這個配置的掃描範圍內.

測試程式碼:

public static void main(String[] args) {
    AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext(StartConfig.class);
    System.out.println("---------------------");

    IndexService index = (IndexService) acac.getBean("index");
    index.getName();
    IndexFactoryBean bean = (IndexFactoryBean) acac.getBean("&index");
    bean.printf();

    System.out.println("=====================");

    TempDao tempDao = acac.getBean(TempDao.class);
    System.out.println(tempDao.getName());
}

結果:

從這個結果看, 掃描發生了接力, 也就是說,

1. StartConfig 掃描的時候, 掃到了 TempScan

2. 解析 TempScan 的時候, 發現 TempScan 上面, 有@Component 和 ComponentScan 註解

3. 解析 TempScan的@ComponentScan註解, 再次進行掃描操作

4. TempScan 掃描到了IndexFactoryBean, 然後對 IndexFactoryBean 進行解析

原始碼:

org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass

//region 處理 @ComponnentScan 註解
// Process any @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
        //掃描普通類
        //這裡掃描出來所有 @Component
        //並且把掃描的出來的普通bean放到 map 當中
        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
        //檢查掃描出來的類當中是否還有configuration
        for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
            BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
            if (bdCand == null) {
                bdCand = holder.getBeanDefinition();
            }
            //檢查掃描出來的類中, 是否還有載入了 @Configuration 的類, 如果有, 則接著遞迴處理
            if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
                parse(bdCand.getBeanClassName(), holder.getBeanName());
            }
        }
    }
}
//endregion

在spring原始碼裡面, 會經常看到 遞迴呼叫. 因為不確定性. 此方法裡面, 對掃描出來的類, 還需要進行遞迴處理.

因為被掃描出來的類, 可能並不是一個普通的類, 他也可能會加一些需要解析的註解, 如 @ComponentScan, @Import

進入 parse 方法看:

org.springframework.context.annotation.ComponentScanAnnotationParser#parse

public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
    //掃描的時候, spring 自己new了一個ClassPathBeanDefinitionScanner來使用, 此處證實, 前面那個new出來的 reader , 確實不是給自己用的
    //預設情況下,  useDefaultFilters 是true, 
   //建立ClassPathBeanDefinitionScanner的時候, 會預設使用一個過濾器: AnnotationTypeFilter(Component.class)
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry, componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader); //BeanNameGenerator // 獲取 bean 的名字生成器, ComponentScan 預設的是 BeanNameGenerator, 也可以自定義覆蓋 Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator"); boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass); scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator : BeanUtils.instantiateClass(generatorClass)); //web當中在來講 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); } } //預設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); } for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) { basePackages.add(ClassUtils.getPackageName(clazz)); } 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)); }

這個方法, 不用細看, 主要是設定一些屬性的預設值, 只是有幾個地方需要注意一下:

1. 這裡new 了一個ClassPathBeanDefinitionScanner 出來, 進行掃描的工作, 也印證了前面篇幅說的,

在建立AnnotationConfigApplicationContext 的時候, 其建構函式中, 建立的 scanner , 並不是給spring自己用的, 而是給開發人員使用的.

2. 在建立這裡的 scanner 的時候, 默認了一個AnnotationTypeFilter , 並且在建立他的時候, 預設給了一個 @Component,

 然後將這個設定到了 IncludeFilter 屬性中.

 Filter顧名思義, 就是過濾器, 這個關係到掃描出來的內容是否符合我們的要求

3. 這裡還設定了一個匿名的AbstractTypeHierarchyTraversingFilter 實現類, 並設定給了 ExcludeFilter 屬性

4. 真正的掃描工作是 doScan 來完成的.

接著來看 doScan 方法:

org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan

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) {
        //掃描basePackage路徑下的java檔案, 並進行過濾, 獲取載入了 @Component 註解的部分
        //符合條件的並把它轉成BeanDefinition型別
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
        for (BeanDefinition candidate : candidates) {
            //解析scope屬性
            ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
            candidate.setScope(scopeMetadata.getScopeName());
            String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
            if (candidate instanceof AbstractBeanDefinition) {
                //如果這個類是AbstractBeanDefinition的子類
                //則為他設定預設值,比如lazy,init destory
                postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
            }
            if (candidate instanceof AnnotatedBeanDefinition) {
                //檢查並且處理常用的註解
                //這裡的處理主要是指把常用註解的值設定到AnnotatedBeanDefinition當中
                //當前前提是這個類必須是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);
                //將這個掃描到的 bd 註冊到 spring 容器中(this.beanDefinitionMap)
                registerBeanDefinition(definitionHolder, this.registry);
            }
        }
    }
    return beanDefinitions;
}

findCandidateComponents這個方法, 內部會呼叫scanCandidateComponents 方法.

scanCandidateComponents

org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
    Set<BeanDefinition> candidates = new LinkedHashSet<>();
    try {
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                resolveBasePackage(basePackage) + '/' + this.resourcePattern;
        Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
        boolean traceEnabled = logger.isTraceEnabled();
        boolean debugEnabled = logger.isDebugEnabled();
        for (Resource resource : resources) {
            if (traceEnabled) {
                logger.trace("Scanning " + resource);
            }
            if (resource.isReadable()) {
                try {
                    MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
                    if (isCandidateComponent(metadataReader)) {
                        ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                        sbd.setResource(resource);
                        sbd.setSource(resource);
                        if (isCandidateComponent(sbd)) {
                            if (debugEnabled) {
                                logger.debug("Identified candidate component class: " + resource);
                            }
                            candidates.add(sbd);
                        }
                        else {
                            if (debugEnabled) {
                                logger.debug("Ignored because not a concrete top-level class: " + resource);
                            }
                        }
                    }
                    else {
                        if (traceEnabled) {
                            logger.trace("Ignored because not matching any filter: " + resource);
                        }
                    }
                }
                catch (Throwable ex) {
                    throw new BeanDefinitionStoreException(
                            "Failed to read candidate component class: " + resource, ex);
                }
            }
            else {
                if (traceEnabled) {
                    logger.trace("Ignored because not readable: " + resource);
                }
            }
        }
    }
    catch (IOException ex) {
        throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
    }
    return candidates;
}

從這段程式碼看, spring是通過掃描 java 檔案的方式, 來載入的, 然後根據規則進行轉換和過濾.

isCandidateComponent 方法, 如果能滿足, 就會為載入的資源來建立 bd .

所以主要看一下

org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#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;
}

這裡的 excluderFilters 裡面裝的, 就是前面的那個AbstractTypeHierarchyTraversingFilter 的匿名實現類

includeFilters 裡面裝的, 就是前面的AnnotationTypeFilter

AbstractTypeHierarchyTraversingFilter

這個匿名實現類, 對 match 進行了重寫覆蓋, 判斷條件為:

declaringClass.equals(className)

主要就是為了將自己排除.

declaringClass 是方法傳入的類, 也就是被解析的類

className 是掃描到的類.

AnnotationTypeFilter

這個類中, 並沒有重寫match 方法, 但是重寫了 matchSlef 方法, 所以會先進父類中的 match 方法.

在其父類方法

org.springframework.core.type.filter.AbstractTypeHierarchyTraversingFilter#match

中, 呼叫了 matchSelf 方法, 這個方法就是 AnnotationTypeFilter 的方法了.

@Override
protected boolean matchSelf(MetadataReader metadataReader) {
    AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
    //這裡判斷的其實就是 自己加了 指定的註解, 如 @Component, 或者 自己加的註解裡面, 有指定的註解, 
   //如 @Configuration 中, 就有 @Component
return metadata.hasAnnotation(this.annotationType.getName()) || (this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName())); }

還記的前面提到過, 在建立這個類的時候, 傳了一個 @Component 進來了, 所以這裡判斷的其實是,

被掃描到的類, 是否直接或者間接的有 @Component 註解.

如果沒有這個註解, scanner 是會將這個類丟棄的.

看原始碼的時候, 有個技巧:

1.很多方法是不需要看的, 先看主要方法, 不能鑽牛角尖

2. 看原始碼之前, 必須對要看的的功能, 起碼有個大致的瞭解, 否則會看的雲裡霧裡, 不知其意.