溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Spring中Bean掃描原理是什么

發(fā)布時(shí)間:2022-09-20 15:46:33 來源:億速云 閱讀:126 作者:iii 欄目:開發(fā)技術(shù)

本篇內(nèi)容主要講解“Spring中Bean掃描原理是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“Spring中Bean掃描原理是什么”吧!

    環(huán)境建設(shè)

    由于創(chuàng)建包掃描的條件很簡單,只要在Xml中配置一個(gè)屬性即可。

    Spring中Bean掃描原理是什么

    正式開始

    在我前面的文章的閱讀基礎(chǔ),我們直接這里節(jié)省時(shí)間,直接定位到ComponentScanBaeanDefinitionParser類中的parse方法。

    @Override
    @Nullable
    public BeanDefinition parse(Element element, ParserContext parserContext) {
       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.
       // 實(shí)際上,掃描bean定義并注冊它們。
       ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
       Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
       registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
    
       return null;
    }

    這個(gè)代碼的前半部分比較簡單,就是可能當(dāng)前傳進(jìn)來的basePackage可能是多個(gè),所以這里使用方法去處理這個(gè)字符串。比較重要的代碼在下半部分。

    也就是三個(gè)方法:

    • configureScanner:配置一個(gè)掃描器

    • doScan:使用掃描器去掃描

    • registerComponents:注冊掃描到的BeanDefintion

    configureScanner

    第一段代碼

    boolean useDefaultFilters = true;
    if (element.hasAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE)) {
       useDefaultFilters = Boolean.parseBoolean(element.getAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE));
    }

    這一段聲明了個(gè)變量,默認(rèn)為True,在下方的If中去會去修改這個(gè)值。由于我們在applicatio.xml中沒有設(shè)置這個(gè)屬性,這里還是默認(rèn)值。

    第二段代碼

    // Delegate bean definition registration to scanner class.
    // 將 bean 定義注冊委托給掃描程序類。
    ClassPathBeanDefinitionScanner scanner = createScanner(parserContext.getReaderContext(), useDefaultFilters);
    scanner.setBeanDefinitionDefaults(parserContext.getDelegate().getBeanDefinitionDefaults());
    scanner.setAutowireCandidatePatterns(parserContext.getDelegate().getAutowireCandidatePatterns());

    createScanner方法中,就是new了一個(gè)ClassPathBeanDefinitionScanner對象給返回回來了。 隨后又為該掃描器加入了兩個(gè)屬性。

    第三段代碼

    if (element.hasAttribute(RESOURCE_PATTERN_ATTRIBUTE)) {
       scanner.setResourcePattern(element.getAttribute(RESOURCE_PATTERN_ATTRIBUTE));
    }

    這里判斷有無配置ResourcePattern屬性,有的話設(shè)置。

    第四段代碼

    try {
       parseBeanNameGenerator(element, scanner);
    }
    catch (Exception ex) {
        // ...
    }
    
    try {
       parseScope(element, scanner);
    }
    catch (Exception ex) {
        // ...
    }

    這兩個(gè)方法代碼跟進(jìn)去有個(gè)共性。都是判斷有沒有配置一個(gè)屬性,然后給sanner設(shè)置屬性,具體看下方代碼截圖。

    Spring中Bean掃描原理是什么

    Spring中Bean掃描原理是什么

    這里這兩個(gè)方法是干嘛的,我心里想了想,不知道,也不知道在什么地方會用到,所以這里接著往下看。

    parseTypeFilters

    protected void parseTypeFilters(Element element, ClassPathBeanDefinitionScanner scanner, ParserContext parserContext) {
       // Parse exclude and include filter elements.
       ClassLoader classLoader = scanner.getResourceLoader().getClassLoader();
       NodeList nodeList = element.getChildNodes();
       for (int i = 0; i &lt; nodeList.getLength(); i++) {
          Node node = nodeList.item(i);
          if (node.getNodeType() == Node.ELEMENT_NODE) {
             String localName = parserContext.getDelegate().getLocalName(node);
             try {
                if (INCLUDE_FILTER_ELEMENT.equals(localName)) {
                   TypeFilter typeFilter = createTypeFilter((Element) node, classLoader, parserContext);
                   scanner.addIncludeFilter(typeFilter);
                }
                else if (EXCLUDE_FILTER_ELEMENT.equals(localName)) {
                   TypeFilter typeFilter = createTypeFilter((Element) node, classLoader, parserContext);
                   scanner.addExcludeFilter(typeFilter);
                }
             }
             catch (ClassNotFoundException ex) {
                // ...
             }
             catch (Exception ex) {
                // ...
             }
          }
       }
    }

    首先看這個(gè)方法名parseTypeFilters,轉(zhuǎn)換類型類型過濾器。

    Spring中Bean掃描原理是什么

    通過查看Spring的DTD文件,看到component-scan標(biāo)簽下還有兩個(gè)子標(biāo)簽,想必就是對應(yīng)上方的代碼中解釋了。

    隨后該方法運(yùn)行完后,就把創(chuàng)建好的scanner對象,給返回回去了。

    doScan

    protected Set&lt;BeanDefinitionHolder&gt; doScan(String... basePackages) {
       Assert.notEmpty(basePackages, "At least one base package must be specified");
       Set&lt;BeanDefinitionHolder&gt; beanDefinitions = new LinkedHashSet&lt;&gt;();
       for (String basePackage : basePackages) {
          Set&lt;BeanDefinition&gt; 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);
             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);
                registerBeanDefinition(definitionHolder, this.registry);
             }
          }
       }
       return beanDefinitions;
    }

    在第一行代碼中,創(chuàng)建了個(gè)BeanDefinitions的Set,大概是用來存放結(jié)果的。

    隨后根據(jù)basePacage,查找到了所有的候選BeanDefinition,至于獲取的方法我在下方有講到。

    隨后遍歷了剛剛獲取到的BeanDefinition。

    findCandidateComponents

    private Set&lt;BeanDefinition&gt; scanCandidateComponents(String basePackage) {
       Set&lt;BeanDefinition&gt; candidates = new LinkedHashSet&lt;&gt;();
       try {
          String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                resolveBasePackage(basePackage) + '/' + this.resourcePattern;
          Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
          // ... 
          for (Resource resource : resources) {
             // ... 暫時(shí)不看
          }
       }
       catch (IOException ex) {
          // ... throw
       }
       return candidates;
    }

    上面一段代碼,方法一進(jìn)入,創(chuàng)建了一個(gè)set集合,這個(gè)set機(jī)會也就是方法最后的返回值,后續(xù)的代碼中會向這個(gè)set去追加屬性。

    隨后到了packageSearchPath,這里是通過拼接字符串的方式最終得到這個(gè)變量,拼接規(guī)則如下:

    classpath*: + 轉(zhuǎn)換后的xml路徑 + **/*.class classpath*:org/springframework/study/**/*.class

    隨后根據(jù)resourceLoader,可以加載上方路徑下的所有class文件。

    Spring中Bean掃描原理是什么

    隨后進(jìn)入For遍歷環(huán)節(jié)。

    For遍歷每一個(gè)資源

    當(dāng)前的resource資源也就是讀取到的class文件。

    for (Resource resource: resources) {
        try {
            MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
            if (isCandidateComponent(metadataReader)) {
               ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
               sbd.setSource(resource);
               if (isCandidateComponent(sbd)) {
                  // ... 打印日志
                  candidates.add(sbd);
               }
               else {
                  // ... 打印日志
               }
            }
            else {
               // ... 打印日志
            }
        }
        catch (FileNotFoundException ex) {
        // ... 打印日志
        }
        catch (Throwable ex) {
        // ... throw
        }
    }

    進(jìn)入For后,首先獲取metadataReader。這里代碼簡單追一下。

    Spring中Bean掃描原理是什么

    Spring中Bean掃描原理是什么

    主要做的是兩件事,一個(gè)new了一個(gè)SimpleMetaDataReader。然后把這個(gè)MetaDataReader放入了緩存中。隨后返回了這個(gè)Reader對象。

    然后就進(jìn)入了第一個(gè)比較關(guān)鍵的方法代碼,isCandidateComponent方法,仔細(xì)一看,這個(gè)方法怎么被調(diào)用了兩次,因?yàn)檫@個(gè)if進(jìn)入后還會調(diào)用isCandidateComponent方法,然后我看了看入?yún)ⅲ灰恢?,一個(gè)入?yún)⑹翿eader,一個(gè)入?yún)⑹翨eanDefinition。我們第一個(gè)if中點(diǎn)用的Reader的isCandidateComponent方法。

    isCandidateComponent(MetadataReader metadataReader)

    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;
    }

    上方的excludeFilters排除的我們不用看,主要是看下方的include

    Spring中Bean掃描原理是什么

    后續(xù)的代碼我就不讀了,大概實(shí)現(xiàn)我猜測是通過Reader去讀到類上的注解,看看有沒有當(dāng)前filter中設(shè)置的注解。有的話返回true。

    繼續(xù)后面的邏輯

    ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
    sbd.setSource(resource);
    if (isCandidateComponent(sbd)) {
       if (debugEnabled) {
          logger.debug("Identified candidate component class: " + resource);
       }
       candidates.add(sbd);
    }

    剛剛外層的If為True后,這里會創(chuàng)建一個(gè)ScannedGenericBeanDefinition,既然是BeanDefinition,那就可以被Spring加載。

    后面把創(chuàng)建的BeanDefinition放入了isCandidateComponent方法。

    isCandidateComponent(AnnotatedBeanDefintion)

    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
       AnnotationMetadata metadata = beanDefinition.getMetadata();
       return (metadata.isIndependent() &amp;&amp; (metadata.isConcrete() ||
             (metadata.isAbstract() &amp;&amp; metadata.hasAnnotatedMethods(Lookup.class.getName()))));
    }
    
    @Override
    public boolean isIndependent() {
       // enclosingClassName 為 null
       return (this.enclosingClassName == null || this.independentInnerClass);
    }
    
    default boolean isConcrete() {
       return !(isInterface() || isAbstract());
    }

    到這個(gè)方法基本第一個(gè)判斷就返回了。isIndependent方法中一開始看到其中的兩個(gè)單詞我有點(diǎn)懵,enclosingClass和innerClass,可能是我英文不好的緣故或者基礎(chǔ)差吧,百度搜了才知道的。我這里就不講了,有興趣你們可以自己搜索一下。自己搜索的記憶更深刻。只要是普通的Component的時(shí)候,這里為True。

    至于下民的isConcrete方法,就是判斷一下當(dāng)前類是不是接口,或者抽象類。很明顯如果是正常的Component,這里是false,隨后取反為True。

    繼續(xù)后面的邏輯

    if (isCandidateComponent(sbd)) {
       if (debugEnabled) {
          logger.debug("Identified candidate component class: " + resource);
       }
       candidates.add(sbd);
    }

    當(dāng)把BeanDefinition傳入后返回為True,進(jìn)入If,也就是添加當(dāng)前的BeanDefinition進(jìn)入結(jié)果集,返回結(jié)果集。

    doScan 繼續(xù)后面的邏輯

    Set&lt;BeanDefinition&gt; 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);
       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);
          registerBeanDefinition(definitionHolder, this.registry);
       }
    }

    把剛剛獲取到BeanDefinition拿出來遍歷.

    第一步獲取MetaData,這個(gè)在剛剛的代碼中有寫到。隨后把他的ScopeName賦值給了MetaData。

    接下來有兩個(gè)if是對這個(gè)BeanDefinition設(shè)置一些參數(shù)的??梢院唵螔咭谎?。捕捉一些關(guān)鍵信息即可。

    Spring中Bean掃描原理是什么

    這個(gè)里面設(shè)置一個(gè)屬性,這里記錄一下,后面有用到再看。

    Spring中Bean掃描原理是什么

    這個(gè)里面是針對類里添加的一些別的注解,來給BeanDefinition添加一些配置??吹綆讉€(gè)比較眼熟的,Lazy,Primary,Description這些注解比較眼熟。

    doScan 最后一個(gè)IF

    if (checkCandidate(beanName, candidate)) {
       BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
       definitionHolder =
             AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
       beanDefinitions.add(definitionHolder);
       registerBeanDefinition(definitionHolder, this.registry);
    }

    粗略的掃一眼,這里可以看幾個(gè)重要的地方,一個(gè)是進(jìn)入If的條件,注冊BeanDefinition。
    至于applyScopedProxyMode方法,因?yàn)槲覜]的類上沒有加Scope注解,所以這里都是不會配置代理。也就是直接返回當(dāng)前傳入的BeanDefinition。

    Spring中Bean掃描原理是什么

    checkCandidate方法

    protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {
       if (!this.registry.containsBeanDefinition(beanName)) {
          return true;
       }
       BeanDefinition existingDef = this.registry.getBeanDefinition(beanName);
       BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition();
       if (originatingDef != null) {
          existingDef = originatingDef;
       }
       if (isCompatible(beanDefinition, existingDef)) {
          return false;
       }
       // ... throw Exception.
    }

    因?yàn)槭峭ㄟ^Bean掃描進(jìn)入的,也就是BeanDefinitionRegister當(dāng)中是沒有這個(gè)BeanDefinition的。所以這里直接就返回True,不會有走到下面的機(jī)會。
    這個(gè)時(shí)候大家可以思考一下,如果走到下面了會怎么樣。歡迎評論區(qū)討論。

    繼續(xù)代碼邏輯

    beanDefinitions.add(definitionHolder);
    registerBeanDefinition(definitionHolder, this.registry);

    接下來就去registerBeanDefinition了,然后還把registry傳進(jìn)入了方法,那很明顯了。這里是去注冊BeanDefinition了。

    Spring中Bean掃描原理是什么

    到此,相信大家對“Spring中Bean掃描原理是什么”有了更深的了解,不妨來實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI