Spring源碼分析之——從component-scan看註解的運用

前言

先做個基礎知識普及,如何理解註解?

我的理解,註解就是擴展版的接口,接口的使用場景很有限,只能由類去實現接口,而註解則豐富的多,它可以用在類,實例屬性,方法,參數等上面;

我對接口的理解是:接口的核心是用來表示一類事物的,比如:Person接口,凡是繼承Person接口的都是人,Driver接口,凡是繼承這個接口的都是司機。這種設計的作用就是業務邏輯(框架代碼)可用通過類的這種繼承關係來找到某一類對象。

註解大致延續和豐富了接口的這種先天的作用,一般情況下,註解被用作:標註一類事物(同接口一樣);擴展一種功能;

1,標註一類事務

很好理解,比如Spring Bean,當一個類被@Component,@Service,@Repository等修飾的時候,它就表示是一個Spring Bean,在初始化的時候就會被Spring容器管理;

2,擴展一種功能

通常框架代碼通過動態代理的形式增強被標註的類或者方法,比如:@Transactional,@Aspect,@Pointcut等;

 

原理

上面說的是註解的作用,但是註解是如何起作用的?這就好比定一個標準,得有人去執行標準,而執行標準的就是框架的源碼,也就是本篇要研究的,標準定義被Service修飾就是一個Bean,框架代碼在處理的時候當遇到類被Service註解修飾,就會把類封裝成BeanDefinition進行註冊;其他人隨便寫一個註解@MyService,加到類頭上,Spring就不會把它當成Bean來管理。

 

配置

spirng配置文件,配置掃描路徑:

<!-- 開啓註解掃描 -->
<context:component-scan base-package="org.liuwei"></context:component-scan>

 這麼一行配置,背後當然是有對應處理代碼,代碼就在命名空間句柄類中初始化

META-INF/spring.handler ——句柄配置文件,會把對應關係維護起來,在解析配置文件的時候方便找到對應的解析類

http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
public class ContextNamespaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
        registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
        registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
        registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
        registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
        registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
        registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
        registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
        registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
    }
}

 

解析與掃描

Spring處理文件,通常是由解析類和掃描類組成,或者叫解析器和掃描器,解析器中獲取掃描器,由掃描器負責掃描配置文件,得到數據後,解析器再做後續處理。

從上面句柄類可以看到 component-scan 對應的處理類:ComponentScanBeanDefinitionParser,處理註解Bean就是這樣的套路,下面解析代碼中先得到一個掃描器,調用掃描器doScan方法獲取BeanDefinitionHolder集合,掃描器能夠掃描出哪些是Bean,其中必定有掃描的過濾規則,可用猜想到規則就是那些註解(接口)。跟代碼發現在構造掃描器對象的時候,就會把過濾規則生成。

org.springframework.context.annotation.ComponentScanBeanDefinitionParser

@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
    // 這個方法雖然返回類型是BeanDefinition,但最終返回的是null,
    // 因爲掃描路徑下肯定不止一個bean,即便要返回也應該是返回集合纔對。
    String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
    basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
    // 拆分配置的多個路徑
    String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
            ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

    // Actually scan for bean definitions and register them.
    ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);

    // 這裏獲取beanDefinitions集合,那麼判斷邏輯肯定也在這個裏面,我們重點看這裏
    Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
    // 註冊BeanDefinition到BeanFactory
    registerComponents(parserContext.getReaderContext(), beanDefinitions, element);

    return null;
}

 

掃描器,在器構造方法中調用父類的registerDefaultFilters方法初始化過濾規則。

org.springframework.context.annotation.ClassPathBeanDefinitionScanner

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Assert.notEmpty(basePackages, "At least one base package must be specified");
    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
    // 循環配置的多個路徑
    for (String basePackage : basePackages) {
        // 找一個目錄下的所有BeanDefinition
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);

        // 循環這些BeanDefinition
        for (BeanDefinition candidate : candidates) {
            ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
            candidate.setScope(scopeMetadata.getScopeName());
            String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
            if (candidate instanceof AbstractBeanDefinition) {
                postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
            }
            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);

                // 把BeanDefinition註冊進registry(這個registry就是BeanFactory)
                registerBeanDefinition(definitionHolder, this.registry);
            }
        }
    }
    return beanDefinitions;
}

下面來看看匹配邏輯,就是:爲什麼只有常用的那幾個註解纔會被解析成Bean?自定義的不行

掃描器父類:org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider

父類的  註冊默認過濾規則  方法

@SuppressWarnings("unchecked")
protected void registerDefaultFilters() {
    // 上來就把Component註解加進去
    this.includeFilters.add(new AnnotationTypeFilter(Component.class));

    // 後面兩個分別是tomcat和標準java的兩個註解
    ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
    try {
        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 {
        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.
    }
}

這裏提一下includeFilters 和 excludeFilters

includeFilters是配合use-default-filters一起使用,只有use-default-filters=false才行,下面配置表示Spring只掃描用Controller註解修飾的類

<context:component-scan base-package="org.liuwei.smart.service" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

excludeFilters在use-default-filters=true或者不配置use-default-filters時可用,下面配置表示Spring不會管理用Controller註解修飾的類

<context:component-scan base-package="org.liuwei.smart.service">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

 說完這個,其實已經解釋了爲什麼自定義的不行,因爲還要配置才行,比如:自定義一個特殊的目錄,目錄下的class只有用自定義的註解纔會被Spring管理

<context:component-scan base-package="org.liuwei.smart.service" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.liuwei.annotation.MyService"/>
</context:component-scan>

 

 繼續看查詢Bean的邏輯,依然是父類提供的方法,方法很簡單:

1,遍歷目錄下所有的資源(class),

2,逐一判斷是否符合Bean條件;

// 查詢候選的組件bean
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
    Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
    try {
        // 要掃描的路徑
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                resolveBasePackage(basePackage) + '/' + this.resourcePattern;
        // 獲取全部的Class
        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 {
                    MetadataReader metadataReader = this.metadataReaderFactory.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;
}

 校驗是否符合Bean的條件,邏輯match方法中

protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
    // excludeFilters剔除哪些註解,通常不會配置
    for (TypeFilter tf : this.excludeFilters) {
        if (tf.match(metadataReader, this.metadataReaderFactory)) {
            return false;
        }
    }

    // includeFilters包含的註解,通常也不配置,但是有默認值
    for (TypeFilter tf : this.includeFilters) {
        if (tf.match(metadataReader, this.metadataReaderFactory)) {
            return isConditionMatch(metadataReader);
        }
    }
    return false;
}

調用TypeFilter的match方法,上面已經分析了includeFilters裏面放的TypeFilter是封裝了Component.class。

    // 上來就把Component註解加進去
    this.includeFilters.add(new AnnotationTypeFilter(Component.class));

 

org.springframework.core.type.filter.AbstractTypeHierarchyTraversingFilter

過濾器父類提供的匹配模板方法 ,具體的匹配規則在子類中實現

public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
        throws IOException {

    // This method optimizes avoiding unnecessary creation of ClassReaders
    // as well as visiting over those readers.
    if (matchSelf(metadataReader)) {
        return true;
    }
    ClassMetadata metadata = metadataReader.getClassMetadata();
    if (matchClassName(metadata.getClassName())) {
        return true;
    }
    
    // 略去部分代碼

    return false;
}

 

過濾器子類:org.springframework.core.type.filter.AnnotationTypeFilter 

@Override
protected boolean matchSelf(MetadataReader metadataReader) {
    AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
    // 這裏面兩個判斷邏輯
    // metadata.hasAnnotation 是否被@Component註釋
    // metadata.hasMetaAnnotation 是否被@Component註釋的子註解註釋
    return metadata.hasAnnotation(this.annotationType.getName()) ||
            (this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));
}

org.springframework.core.type.classreading.AnnotationMetadataReadingVisitor

這是資源的註解元數據(可能的Bean對象的元數據),它有兩個成員變量:

  • Set<String> annotationSet :類的頭上加了哪些註解,你可能給一個類加n個註解,都會記錄在這個集合裏面,在加載類的時候就會初始化;
  • Map<String, Set<String>> metaAnnotationMap :key=註解,value=父註解  (測試結果如此,沒有詳細考究)

具體的判斷邏輯則是先從Set集合中找,沒有的話再到Map中找,找到了則返回true,表示是一個Bean。

例如:用Service標註

Set集合記錄:[org.springframework.stereotype.Service]

Map集合記錄:{org.springframework.stereotype.Service=[org.springframework.stereotype.Component]}

但是includeFilters集合中的數據是Component而不是Service,所以通過集合校驗不成立,再通過Map校驗,邏輯見下面代碼片段,方法:hasMetaAnnotation,拿value中的數據與includeFilters比對,這樣就匹配成功了。

至此,把Bean封裝成BeanDefinitionHolder數組返回給處理器。

protected final Set<String> annotationSet = new LinkedHashSet<String>(4);

protected final Map<String, Set<String>> metaAnnotationMap = new LinkedHashMap<String, Set<String>>(4);

public boolean hasAnnotation(String annotationName) {
    // 集合操作
    return this.annotationSet.contains(annotationName);
}

@Override
public boolean hasMetaAnnotation(String metaAnnotationType) {
    Collection<Set<String>> allMetaTypes = this.metaAnnotationMap.values();
    for (Set<String> metaTypes : allMetaTypes) {
        if (metaTypes.contains(metaAnnotationType)) {
            return true;
        }
    }
    return false;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章