Spring IoC 源碼如何解析包掃描,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
成都創(chuàng)新互聯(lián)于2013年開始,是專業(yè)互聯(lián)網技術服務公司,擁有項目成都網站制作、成都網站設計網站策劃,項目實施與項目整合能力。我們以讓每一個夢想脫穎而出為使命,1280元安平做網站,已為上家服務,為安平各地企業(yè)和個人服務,聯(lián)系電話:18980820575
我們通過AnnotationConfigApplicationContext類傳入一個包路徑啟動Spring之后,會首先初始化包掃描的過濾規(guī)則。那我們今天就來看下包掃描的具體過程。
還是先看下面的代碼:
AnnotationConfigApplicationContext類
//該構造函數(shù)會自動掃描以給定的包及其子包下的所有類,并自動識別所有的Spring Bean,將其注冊到容器中
public AnnotationConfigApplicationContext(String... basePackages) {
//初始化
this();
//掃描包、注冊bean
scan(basePackages);
refresh();
}上文我們分析了this()方法,會去初始化AnnotatedBeanDefinitionReader讀取器和ClassPathBeanDefinitionScanner掃描器,并初始化掃描過濾規(guī)則。
接下來我們看一下scan(basePackages)方法:
一直跟蹤下去,發(fā)現(xiàn)調用了ClassPathBeanDefinitionScanner類中的scan()方法
//調用類路徑Bean定義掃描器入口方法
public int scan(String... basePackages) {
//獲取容器中已經注冊的Bean個數(shù)
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
//啟動掃描器掃描給定包
doScan(basePackages);
// Register annotation config processors, if necessary.
//注冊注解配置(Annotation config)處理器
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
//返回注冊的Bean個數(shù)
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}可以看到主要是doScan(basePackages)方法實現(xiàn)了掃描的邏輯,我們繼續(xù)跟蹤進去看下
//類路徑Bean定義掃描器掃描給定包及其子包
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
//創(chuàng)建一個集合,存放掃描到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)方法,這個方法里就是具體的掃描邏輯。
繼續(xù)跟蹤:
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元數(shù)據,并封裝在MetadataReader元數(shù)據讀取器中
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
//判斷該類是否符合@CompoentScan的過濾規(guī)則
//過濾匹配排除excludeFilters排除過濾器(可以沒有),包含includeFilter中的包含過濾器(至少包含一個)。
if (isCandidateComponent(metadataReader)) {
//把元數(shù)據轉化為 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過濾規(guī)則
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元數(shù)據,并封裝在MetadataReader元數(shù)據讀取器中
判斷該類是否符合過濾規(guī)則
判斷該類是否為獨立的類、具體的類
加入到集合中
我們來詳細看下過濾的方法 isCandidateComponent(metadataReader)
//判斷元信息讀取器讀取的類是否符合容器定義的注解過濾規(guī)則
//@CompoentScan的過濾規(guī)則支持5種 (注解、類、正則、aop、自定義)
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
//如果讀取的類的注解在排除注解過濾規(guī)則中,返回false
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return false;
}
}
//如果讀取的類的注解在包含的注解的過濾規(guī)則中,則返回ture
for (TypeFilter tf : this.includeFilters) {
//判斷當前類的注解是否match規(guī)則
if (tf.match(metadataReader, getMetadataReaderFactory())) {
//是否有@Conditional注解,進行相關處理
return isConditionMatch(metadataReader);
}
}
//如果讀取的類的注解既不在排除規(guī)則,也不在包含規(guī)則中,則返回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.
//檢查當前類的注解是否符合規(guī)律規(guī)則
if (matchSelf(metadataReader)) {
return true;
}
//check 類名是否符合規(guī)則
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() + "]");
}
}
}
}
//如果有實現(xiàn)接口
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) {
//獲取注解元數(shù)據
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是怎么發(fā)現(xiàn)@Configuration、@Controller、@Service這些注解修飾的類的?”
原來@Configuration、@Controller、@Service這些注解其實都是@Component的派生注解,我們看這些注解的代碼會發(fā)現(xiàn),都有@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()))));
}這個方法的作用是,判斷該類是否為
頂層的類(沒有父類或靜態(tài)內部類)
具體的類(不是抽象類或接口)
至此,ClassPathBeanDefinitionScanner類中的doScan(basePackages)方法中的findCandidateComponents(basePackage)方法已經結束了,即我們的包掃描也結束了,已經把掃描到的類存入到了集合中,結下來就是解析注冊Bean的過程了。
總結
通過這篇文章,我們可以回答之前的一些問題了:
Spring是怎么發(fā)現(xiàn)@Bean、@Controller、@Service這些注解修飾的類的?
通過 matchSelf(metadataReader)方法,判斷這些注解中是否包含@Component
@CompoentScan注解是怎么起作用的?
通過 isCandidateComponent(metadataReader)方法過濾
關于Spring IoC 源碼如何解析包掃描問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注創(chuàng)新互聯(lián)行業(yè)資訊頻道了解更多相關知識。
文章名稱:SpringIoC源碼如何解析包掃描
URL網址:http://chinadenli.net/article16/gdesgg.html
成都網站建設公司_創(chuàng)新互聯(lián),為您提供網站營銷、關鍵詞優(yōu)化、企業(yè)建站、ChatGPT、微信小程序、網站制作
聲明:本網站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)