您好,登錄后才能下訂單哦!
Spring IoC 源碼如何解析包掃描,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
我們通過AnnotationConfigApplicationContext類傳入一個包路徑啟動Spring之后,會首先初始化包掃描的過濾規則。那我們今天就來看下包掃描的具體過程。
還是先看下面的代碼:
AnnotationConfigApplicationContext類
//該構造函數會自動掃描以給定的包及其子包下的所有類,并自動識別所有的Spring Bean,將其注冊到容器中 public AnnotationConfigApplicationContext(String... basePackages) { //初始化 this(); //掃描包、注冊bean scan(basePackages); refresh(); }
上文我們分析了this()方法,會去初始化AnnotatedBeanDefinitionReader讀取器和ClassPathBeanDefinitionScanner掃描器,并初始化掃描過濾規則。
接下來我們看一下scan(basePackages)方法:
一直跟蹤下去,發現調用了ClassPathBeanDefinitionScanner類中的scan()方法
//調用類路徑Bean定義掃描器入口方法 public int scan(String... basePackages) { //獲取容器中已經注冊的Bean個數 int beanCountAtScanStart = this.registry.getBeanDefinitionCount(); //啟動掃描器掃描給定包 doScan(basePackages); // Register annotation config processors, if necessary. //注冊注解配置(Annotation config)處理器 if (this.includeAnnotationConfig) { AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); } //返回注冊的Bean個數 return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart); }
可以看到主要是doScan(basePackages)方法實現了掃描的邏輯,我們繼續跟蹤進去看下
//類路徑Bean定義掃描器掃描給定包及其子包 protected Set<BeanDefinitionHolder> doScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); //創建一個集合,存放掃描到Bean定義的封裝類 Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>(); //遍歷掃描所有給定的包 for (String basePackage : basePackages) { //調用父類ClassPathScanningCandidateComponentProvider的方法 //掃描給定類路徑,獲取符合條件的Bean定義 Set<BeanDefinition> candidates = findCandidateComponents(basePackage); //遍歷掃描到的Bean for (BeanDefinition candidate : candidates) { //獲取@Scope注解的值,即獲取Bean的作用域 ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); //為Bean設置作用域 candidate.setScope(scopeMetadata.getScopeName()); //為Bean生成名稱 String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); //如果掃描到的Bean不是Spring的注解Bean,則為Bean設置默認值, //設置Bean的自動依賴注入裝配屬性等 if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } //如果掃描到的Bean是Spring的注解Bean,則處理其通用的Spring注解 if (candidate instanceof AnnotatedBeanDefinition) { //處理注解Bean中通用的注解,在分析注解Bean定義類讀取器時已經分析過 AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } //根據Bean名稱檢查指定的Bean是否需要在容器中注冊,或者在容器中沖突 if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); //根據注解中配置的作用域,為Bean應用相應的代理模式 definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); //向容器注冊掃描到的Bean registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; }
這一大段代碼基本上就是spring掃描識別注解,并注冊Bean到IOC容器中的代碼。
在第10行有一個findCandidateComponents(basePackage)方法,這個方法里就是具體的掃描邏輯。
繼續跟蹤:
ClassPathScanningCandidateComponentProvider類
//掃描給定類路徑的包 public Set<BeanDefinition> findCandidateComponents(String basePackage) { //spring5.0開始 索引 開啟的話生成文件META-INF/spring.components 后面加載直接從本地文件讀取(一般不建議開啟 spring.index.ignore=true) if (this.componentsIndex != null && indexSupportsIncludeFilters()) { return addCandidateComponentsFromIndex(this.componentsIndex, basePackage); } else { return scanCandidateComponents(basePackage); } }
這里有一個if判斷,我們默認走的是else里的分支,即scanCandidateComponents(basePackage)方法。
private Set<BeanDefinition> scanCandidateComponents(String basePackage) { Set<BeanDefinition> candidates = new LinkedHashSet<>(); try { //補全掃描路徑,掃描所有.class文件 classpath*:com/mydemo/**/*.class 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 { //通過ASM獲取class元數據,并封裝在MetadataReader元數據讀取器中 MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource); //判斷該類是否符合@CompoentScan的過濾規則 //過濾匹配排除excludeFilters排除過濾器(可以沒有),包含includeFilter中的包含過濾器(至少包含一個)。 if (isCandidateComponent(metadataReader)) { //把元數據轉化為 BeanDefinition ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); sbd.setResource(resource); sbd.setSource(resource); //判斷是否是合格的bean定義 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 { //不符@CompoentScan過濾規則 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; }
這里就是主要的掃描邏輯,代碼中的注釋已經說的很清楚了。
主要過程:
根據包路徑,掃描所有.class文件
根據包路徑,生成.class對應的Resource對象
通過ASM獲取class元數據,并封裝在MetadataReader元數據讀取器中
判斷該類是否符合過濾規則
判斷該類是否為獨立的類、具體的類
加入到集合中
我們來詳細看下過濾的方法 isCandidateComponent(metadataReader)
//判斷元信息讀取器讀取的類是否符合容器定義的注解過濾規則 //@CompoentScan的過濾規則支持5種 (注解、類、正則、aop、自定義) protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException { //如果讀取的類的注解在排除注解過濾規則中,返回false for (TypeFilter tf : this.excludeFilters) { if (tf.match(metadataReader, getMetadataReaderFactory())) { return false; } } //如果讀取的類的注解在包含的注解的過濾規則中,則返回ture for (TypeFilter tf : this.includeFilters) { //判斷當前類的注解是否match規則 if (tf.match(metadataReader, getMetadataReaderFactory())) { //是否有@Conditional注解,進行相關處理 return isConditionMatch(metadataReader); } } //如果讀取的類的注解既不在排除規則,也不在包含規則中,則返回false return false; }
接著跟蹤 tf.match()方法
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; } //check 類名是否符合規則 ClassMetadata metadata = metadataReader.getClassMetadata(); if (matchClassName(metadata.getClassName())) { return true; } //如果有繼承父類 if (this.considerInherited) { String superClassName = metadata.getSuperClassName(); if (superClassName != null) { // Optimization to avoid creating ClassReader for super class. Boolean superClassMatch = matchSuperClass(superClassName); if (superClassMatch != null) { if (superClassMatch.booleanValue()) { return true; } } else { // Need to read super class to determine a match... try { if (match(metadata.getSuperClassName(), metadataReaderFactory)) { return true; } } catch (IOException ex) { logger.debug("Could not read super class [" + metadata.getSuperClassName() + "] of type-filtered class [" + metadata.getClassName() + "]"); } } } } //如果有實現接口 if (this.considerInterfaces) { for (String ifc : metadata.getInterfaceNames()) { // Optimization to avoid creating ClassReader for super class Boolean interfaceMatch = matchInterface(ifc); if (interfaceMatch != null) { if (interfaceMatch.booleanValue()) { return true; } } else { // Need to read interface to determine a match... try { if (match(ifc, metadataReaderFactory)) { return true; } } catch (IOException ex) { logger.debug("Could not read interface [" + ifc + "] for type-filtered class [" + metadata.getClassName() + "]"); } } } } return false; }
這里面最主要的是 matchSelf(metadataReader) 方法
AnnotationTypeFilter類
protected boolean matchSelf(MetadataReader metadataReader) { //獲取注解元數據 AnnotationMetadata metadata = metadataReader.getAnnotationMetadata(); //check 注解及其派生注解中是否包含@Component //獲取當前類的注解 metadata.hasAnnotation @Controller //獲取當前類的注解及其派生注解 metadata.hasAnnotation @Controller包含的@Component\@Documented等等 return metadata.hasAnnotation(this.annotationType.getName()) || (this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName())); }
在這段代碼代碼中,可以解決我們之前的疑惑“Spring是怎么發現@Configuration、@Controller、@Service這些注解修飾的類的?”
原來@Configuration、@Controller、@Service這些注解其實都是@Component的派生注解,我們看這些注解的代碼會發現,都有@Component注解修飾。而spring通過metadata.hasMetaAnnotation()方法獲取到這些注解包含@Component,所以都可以掃描到。如下:
然后我們再看回 scanCandidateComponents(basePackage)方法,接下來有一個 isCandidateComponent(sbd)方法,如下:
//是否是獨立的類、具體的類 protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { AnnotationMetadata metadata = beanDefinition.getMetadata(); return (metadata.isIndependent() && (metadata.isConcrete() || (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName())))); }
這個方法的作用是,判斷該類是否為
頂層的類(沒有父類或靜態內部類)
具體的類(不是抽象類或接口)
至此,ClassPathBeanDefinitionScanner類中的doScan(basePackages)方法中的findCandidateComponents(basePackage)方法已經結束了,即我們的包掃描也結束了,已經把掃描到的類存入到了集合中,結下來就是解析注冊Bean的過程了。
總結
通過這篇文章,我們可以回答之前的一些問題了:
Spring是怎么發現@Bean、@Controller、@Service這些注解修飾的類的?
通過 matchSelf(metadataReader)方法,判斷這些注解中是否包含@Component
@CompoentScan注解是怎么起作用的?
通過 isCandidateComponent(metadataReader)方法過濾
關于Spring IoC 源碼如何解析包掃描問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。