<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在上一章節Spring和Mybatis整合的原理詳解中有寫到Spring和MyBatis整合時用到的Bean掃描是Spring本身提供的。這一篇文章就寫到Spring是如何實現Bean掃描的。
不得不說Bean掃描是一個很重要的技術,在SpringMVC中的Controller掃描,和SpringBoot中的Bean掃描,Component掃描,Configuration掃描,原理我這裡猜測都是由這個實現的。
由於建立包掃描的條件很簡單,只要在Xml中設定一個屬性即可。
在我前面的文章的閱讀基礎,我們直接這裡節省時間,直接定位到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. // 實際上,掃描bean定義並註冊它們。 ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element); Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages); registerComponents(parserContext.getReaderContext(), beanDefinitions, element); return null; }
這個程式碼的前半部分比較簡單,就是可能當前傳進來的basePackage可能是多個,所以這裡使用方法去處理這個字串。比較重要的程式碼在下半部分。
也就是三個方法:
boolean useDefaultFilters = true; if (element.hasAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE)) { useDefaultFilters = Boolean.parseBoolean(element.getAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE)); }
這一段宣告了個變數,預設為True,在下方的If中去會去修改這個值。由於我們在applicatio.xml中沒有設定這個屬性,這裡還是預設值。
// 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了一個ClassPathBeanDefinitionScanner物件給返回回來了。 隨後又為該掃描器加入了兩個屬性。
if (element.hasAttribute(RESOURCE_PATTERN_ATTRIBUTE)) { scanner.setResourcePattern(element.getAttribute(RESOURCE_PATTERN_ATTRIBUTE)); }
這裡判斷有無設定ResourcePattern屬性,有的話設定。
try { parseBeanNameGenerator(element, scanner); } catch (Exception ex) { // ... } try { parseScope(element, scanner); } catch (Exception ex) { // ... }
這兩個方法程式碼跟進去有個共性。都是判斷有沒有設定一個屬性,然後給sanner設定屬性,具體看下方程式碼截圖。
這裡這兩個方法是幹嘛的,我心裡想了想,不知道,也不知道在什麼地方會用到,所以這裡接著往下看。
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 < 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) { // ... } } } }
首先看這個方法名parseTypeFilters,轉換型別型別過濾器。
通過檢視Spring的DTD檔案,看到component-scan標籤下還有兩個子標籤,想必就是對應上方的程式碼中解釋了。
隨後該方法執行完後,就把建立好的scanner物件,給返回回去了。
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) { Set<BeanDefinition> 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; }
在第一行程式碼中,建立了個BeanDefinitions的Set,大概是用來存放結果的。
隨後根據basePacage,查詢到了所有的候選BeanDefinition,至於獲取的方法我在下方有講到。
隨後遍歷了剛剛獲取到的BeanDefinition。
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); // ... for (Resource resource : resources) { // ... 暫時不看 } } catch (IOException ex) { // ... throw } return candidates; }
上面一段程式碼,方法一進入,建立了一個set集合,這個set機會也就是方法最後的返回值,後續的程式碼中會向這個set去追加屬性。
隨後到了packageSearchPath,這裡是通過拼接字串的方式最終得到這個變數,拼接規則如下:
classpath*: + 轉換後的xml路徑 + **/*.class
classpath*:org/springframework/study/**/*.class
隨後根據resourceLoader,可以載入上方路徑下的所有class檔案。
隨後進入For遍歷環節。
當前的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 } }
進入For後,首先獲取metadataReader。這裡程式碼簡單追一下。
主要做的是兩件事,一個new了一個SimpleMetaDataReader。然後把這個MetaDataReader放入了快取中。隨後返回了這個Reader物件。
然後就進入了第一個比較關鍵的方法程式碼,isCandidateComponent方法,仔細一看,這個方法怎麼被呼叫了兩次,因為這個if進入後還會呼叫isCandidateComponent方法,然後我看了看入參,不一致,一個入參事Reader,一個入參事BeanDefinition。我們第一個if中點用的Reader的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; }
上方的excludeFilters排除的我們不用看,主要是看下方的include
後續的程式碼我就不讀了,大概實現我猜測是通過Reader去讀到類上的註解,看看有沒有當前filter中設定的註解。有的話返回true。
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後,這裡會建立一個ScannedGenericBeanDefinition,既然是BeanDefinition,那就可以被Spring載入。
後面把建立的BeanDefinition放入了isCandidateComponent方法。
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { AnnotationMetadata metadata = beanDefinition.getMetadata(); return (metadata.isIndependent() && (metadata.isConcrete() || (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName())))); } @Override public boolean isIndependent() { // enclosingClassName 為 null return (this.enclosingClassName == null || this.independentInnerClass); } default boolean isConcrete() { return !(isInterface() || isAbstract()); }
到這個方法基本第一個判斷就返回了。isIndependent方法中一開始看到其中的兩個單詞我有點懵,enclosingClass和innerClass,可能是我英文不好的緣故或者基礎差吧,百度搜了才知道的。我這裡就不講了,有興趣你們可以自己搜尋一下。自己搜尋的記憶更深刻。只要是普通的Component的時候,這裡為True。
至於下民的isConcrete方法,就是判斷一下當前類是不是介面,或者抽象類。很明顯如果是正常的Component,這裡是false,隨後取反為True。
if (isCandidateComponent(sbd)) { if (debugEnabled) { logger.debug("Identified candidate component class: " + resource); } candidates.add(sbd); }
當把BeanDefinition傳入後返回為True,進入If,也就是新增當前的BeanDefinition進入結果集,返回結果集。
Set<BeanDefinition> 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,這個在剛剛的程式碼中有寫到。隨後把他的ScopeName賦值給了MetaData。
接下來有兩個if是對這個BeanDefinition設定一些引數的。可以簡單掃一眼。捕捉一些關鍵資訊即可。
這個裡面設定一個屬性,這裡記錄一下,後面有用到再看。
這個裡面是針對類裡新增的一些別的註解,來給BeanDefinition新增一些設定。看到幾個比較眼熟的,Lazy,Primary,Description這些註解比較眼熟。
if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); registerBeanDefinition(definitionHolder, this.registry); }
粗略的掃一眼,這裡可以看幾個重要的地方,一個是進入If的條件,註冊BeanDefinition。
至於applyScopedProxyMode方法,因為我沒的類上沒有加Scope註解,所以這裡都是不會設定代理。也就是直接返回當前傳入的BeanDefinition。
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. }
因為是通過Bean掃描進入的,也就是BeanDefinitionRegister當中是沒有這個BeanDefinition的。所以這裡直接就返回True,不會有走到下面的機會。
這個時候大家可以思考一下,如果走到下面了會怎麼樣。歡迎評論區討論。
beanDefinitions.add(definitionHolder); registerBeanDefinition(definitionHolder, this.registry);
接下來就去registerBeanDefinition了,然後還把registry傳進入了方法,那很明顯了。這裡是去註冊BeanDefinition了。
由於在這個環節,掃描器把BeanDefinition放進Registry,那麼在之後的Refresh方法中的finishBeanFactoryInitialization方法就會把BeanDefinition都範例化完畢。
到此這篇關於Spring中Bean掃描原理詳情的文章就介紹到這了,更多相關Spring Bean掃描原理內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45