Spring中Bean掃描原理詳情
前言
在上一章節(jié)Spring和Mybatis整合的原理詳解中有寫到Spring和MyBatis整合時用到的Bean掃描是Spring本身提供的。這一篇文章就寫到Spring是如何實現(xiàn)Bean掃描的。
不得不說Bean掃描是一個很重要的技術,在SpringMVC中的Controller掃描,和SpringBoot中的Bean掃描,Component掃描,Configuration掃描,原理我這里猜測都是由這個實現(xiàn)的。
環(huán)境建設
由于創(chuàng)建包掃描的條件很簡單,只要在Xml中配置一個屬性即可。

正式開始
在我前面的文章的閱讀基礎,我們直接這里節(jié)省時間,直接定位到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可能是多個,所以這里使用方法去處理這個字符串。比較重要的代碼在下半部分。
也就是三個方法:
- configureScanner:配置一個掃描器
- doScan:使用掃描器去掃描
- registerComponents:注冊掃描到的BeanDefintion
configureScanner
第一段代碼
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設置屬性,具體看下方代碼截圖。


這里這兩個方法是干嘛的,我心里想了想,不知道,也不知道在什么地方會用到,所以這里接著往下看。
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 < 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,轉(zhuǎn)換類型類型過濾器。

通過查看Spring的DTD文件,看到component-scan標簽下還有兩個子標簽,想必就是對應上方的代碼中解釋了。
隨后該方法運行完后,就把創(chuàng)建好的scanner對象,給返回回去了。
doScan
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;
}
在第一行代碼中,創(chuàng)建了個BeanDefinitions的Set,大概是用來存放結(jié)果的。
隨后根據(jù)basePacage,查找到了所有的候選BeanDefinition,至于獲取的方法我在下方有講到。
隨后遍歷了剛剛獲取到的BeanDefinition。
findCandidateComponents
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;
}
上面一段代碼,方法一進入,創(chuàng)建了一個set集合,這個set機會也就是方法最后的返回值,后續(xù)的代碼中會向這個set去追加屬性。
隨后到了packageSearchPath,這里是通過拼接字符串的方式最終得到這個變量,拼接規(guī)則如下:
classpath*: + 轉(zhuǎn)換后的xml路徑 + **/*.classclasspath*:org/springframework/study/**/*.class
隨后根據(jù)resourceLoader,可以加載上方路徑下的所有class文件。

隨后進入For遍歷環(huán)節(jié)。
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方法,仔細一看,這個方法怎么被調(diào)用了兩次,因為這個if進入后還會調(diào)用isCandidateComponent方法,然后我看了看入?yún)?,不一致,一個入?yún)⑹翿eader,一個入?yún)⑹翨eanDefinition。我們第一個if中點用的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

后續(xù)的代碼我就不讀了,大概實現(xiàn)我猜測是通過Reader去讀到類上的注解,看看有沒有當前filter中設置的注解。有的話返回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)建一個ScannedGenericBeanDefinition,既然是BeanDefinition,那就可以被Spring加載。
后面把創(chuàng)建的BeanDefinition放入了isCandidateComponent方法。
isCandidateComponent(AnnotatedBeanDefintion)
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。
繼續(xù)后面的邏輯
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}
當把BeanDefinition傳入后返回為True,進入If,也就是添加當前的BeanDefinition進入結(jié)果集,返回結(jié)果集。
doScan 繼續(xù)后面的邏輯
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設置一些參數(shù)的。可以簡單掃一眼。捕捉一些關鍵信息即可。

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

這個里面是針對類里添加的一些別的注解,來給BeanDefinition添加一些配置??吹綆讉€比較眼熟的,Lazy,Primary,Description這些注解比較眼熟。
doScan 最后一個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);
}
粗略的掃一眼,這里可以看幾個重要的地方,一個是進入If的條件,注冊BeanDefinition。
至于applyScopedProxyMode方法,因為我沒的類上沒有加Scope注解,所以這里都是不會配置代理。也就是直接返回當前傳入的BeanDefinition。

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.
}
因為是通過Bean掃描進入的,也就是BeanDefinitionRegister當中是沒有這個BeanDefinition的。所以這里直接就返回True,不會有走到下面的機會。
這個時候大家可以思考一下,如果走到下面了會怎么樣。歡迎評論區(qū)討論。
繼續(xù)代碼邏輯
beanDefinitions.add(definitionHolder); registerBeanDefinition(definitionHolder, this.registry);
接下來就去registerBeanDefinition了,然后還把registry傳進入了方法,那很明顯了。這里是去注冊BeanDefinition了。

總結(jié)
由于在這個環(huán)節(jié),掃描器把BeanDefinition放進Registry,那么在之后的Refresh方法中的finishBeanFactoryInitialization方法就會把BeanDefinition都實例化完畢。
到此這篇關于Spring中Bean掃描原理詳情的文章就介紹到這了,更多相關Spring Bean掃描原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java多線程編程之使用Exchanger數(shù)據(jù)交換實例
這篇文章主要介紹了Java多線程編程之使用Exchanger數(shù)據(jù)交換實例,本文直接給出實例代碼,需要的朋友可以參考下2015-05-05
springboot學習之Thymeleaf模板引擎及原理介紹
本文主要介紹一下SpringBoot給我們推薦的Thymeleaf模板引擎,這模板引擎呢,是一個高級語言的模板引擎,他的這個語法更簡單而且功能更強大,對springboot?Thymeleaf模板引擎相關知識感興趣的朋友一起看看吧2022-02-02
Vue中computed計算屬性和data數(shù)據(jù)獲取方式
這篇文章主要介紹了Vue中computed計算屬性和data數(shù)據(jù)獲取方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03
一文告訴你為什么要重寫hashCode()方法和equals()方法
本篇文章帶大家了解一下為什么重寫hashCode()方法和equals()方法,文中有非常詳細的說明以及代碼示例,對正在學習java的小伙伴們很有幫助,需要的朋友可以參考下2021-05-05
Maven一鍵部署Springboot到Docker倉庫為自動化做準備(推薦)
這篇文章主要介紹了Maven一鍵部署Springboot到Docker倉庫,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07

