更新時(shí)間:2021-09-10 10:54:20 來(lái)源:動(dòng)力節(jié)點(diǎn) 瀏覽2152次
此篇文章會(huì)主要介紹Spring中兩個(gè)非常重要的關(guān)于包掃描的基礎(chǔ)類(lèi),由于Spring代碼太龐大,因此本文不會(huì)細(xì)致地說(shuō)明每一行代碼地作用,只會(huì)講清楚關(guān)鍵的地方有什么作用,以及一些子類(lèi)可以重寫(xiě)的方法,用來(lái)覆蓋默認(rèn)掃描行為。最后會(huì)基于Spring提供的包掃描設(shè)施來(lái)寫(xiě)一個(gè)簡(jiǎn)單的例子來(lái)模仿MyBatis-Spring掃描Mapper接口,生成代理注冊(cè)到容器中。我們主要關(guān)注ClassPathScanningCandidateComponentProvider以及ClassPathBeanDefinitionScanner這兩個(gè)類(lèi),講清楚這兩個(gè)類(lèi)的作用以及開(kāi)發(fā)者需要關(guān)注的方法。
此類(lèi)是Spring中包掃描機(jī)制最底層的類(lèi),用于掃描指定包下面的類(lèi)文件,并且會(huì)根據(jù)用戶(hù)提供的includeFilters以及excludeFilters來(lái)過(guò)濾掉不想注冊(cè)的類(lèi),最后生成一個(gè)基本的BeanDefinition。
先看下兩個(gè)比較重要的屬性吧
/**
* 包含集合,如果類(lèi)文件匹配includeFilters集合中任意一個(gè)TypeFilter條件,那么就通過(guò)篩選。
* 其中最常見(jiàn)的TypeFilter有AnnotationTypeFilter、AssignableTypeFilter
* AnnotationTypeFilter: 代表類(lèi)是否被指定注解標(biāo)注
* AssignableTypeFilter: 代表類(lèi)是否繼承(實(shí)現(xiàn))自指定的超類(lèi)
*/
private final List<TypeFilter> includeFilters = new LinkedList<TypeFilter>();
/**
* 排除集合, 如果類(lèi)文件匹配excludeFilters集合中任何一個(gè)TypeFilter條件,那么就不會(huì)通過(guò)篩選
* 并且excludeFilters優(yōu)先級(jí)高于includeFilters
*/
private final List<TypeFilter> excludeFilters = new LinkedList<TypeFilter>();
初始化,只看參數(shù)最長(zhǎng)的那個(gè)構(gòu)造方法
public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters,
Environment environment) {
// 主要關(guān)注useDefaultFilters這個(gè)參數(shù), 如果為true, 會(huì)注冊(cè)一個(gè)默認(rèn)的
// includeFilter
if (useDefaultFilters) {
registerDefaultFilters();
}
setEnvironment(environment);
setResourceLoader(null);
}
protected void registerDefaultFilters() {
// 注冊(cè)一個(gè)注解的TypeFilter,意思是如果類(lèi)定義時(shí)有被@Component注解標(biāo)注
// 那么就會(huì)通過(guò)篩選,需要注意的是衍生注解也是會(huì)通過(guò)篩選的
// 比如@Service、@Controller、@Repository,它們有一個(gè)共同點(diǎn),那就是這三個(gè)
// 注解本身就是被@Component注解標(biāo)注的
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
// 下面是注冊(cè)JAVA EE里的一些注解,一般開(kāi)發(fā)也不用, 就省略了
}
接下來(lái)就是最重要的方法了,也就是掃描包文件,并且將符合篩選條件的類(lèi)生成BeanDefinition。下面代碼將日志打印剔除了。
/**
* 此方法會(huì)掃描指定包以及子包
* @param basePackage 需要掃描的包,形如com.wangtao.dao
* @return 返回一個(gè)符合篩選條件后的BeanDefinition集合
*/
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
try {
// 將包名解析成路徑
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
// 獲取此包以及子包下的所有.class文件資源
Resource[] resources = this.resourcePatternResolver.
getResources(packageSearchPath);
for (Resource resource : resources) {
if (resource.isReadable()) {
try {
// 得到類(lèi)文件的元數(shù)據(jù),是基于ASM字節(jié)碼技術(shù)實(shí)現(xiàn)的,此時(shí)還沒(méi)有加載類(lèi)文件
// 包括類(lèi)名、注解信息、父類(lèi)、接口等等一系列信息
MetadataReader metadataReader = this.metadataReaderFactory.
getMetadataReader(resource);
// 匹配篩選條件,也就是上述includeFilters、excludeFilters這兩個(gè)集合
if (isCandidateComponent(metadataReader)) {
// 創(chuàng)建一個(gè)BeanDefinition
// 只是簡(jiǎn)單的設(shè)置了beanClassName屬性為類(lèi)的完全限定名
ScannedGenericBeanDefinition sbd = new ScannedGenericBean
Definition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
// 這里會(huì)再有一個(gè)篩選條件,一般是根據(jù)類(lèi)文件的元數(shù)據(jù)篩選
// 比如是不是具體類(lèi),是不是頂層類(lèi),是不是抽象類(lèi)等
// 默認(rèn)情況下只添加頂層的具體類(lèi),頂層的意思是可以獨(dú)立實(shí)例化而不會(huì)依賴(lài)外部類(lèi)
// 成員內(nèi)部類(lèi)需要外部類(lèi)對(duì)象才能實(shí)例化,就不會(huì)通過(guò)。
if (isCandidateComponent(sbd)) {
candidates.add(sbd);
}
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath
scanning", ex);
}
return candidates;
}
再看看兩個(gè)isCandidateComponent方法的默認(rèn)實(shí)現(xiàn),一般來(lái)說(shuō)我們可能需要重寫(xiě)這兩個(gè)方法來(lái)改變默認(rèn)的篩選條件。
// 根據(jù)excludeFilters、excludeFilters初步篩選
// 一目了然,基本不用再需要解釋
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return false;
}
}
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return isConditionMatch(metadataReader);
}
}
return false;
}
// 根據(jù)類(lèi)文件元數(shù)據(jù)篩選
// 只掃描頂層具體類(lèi)或者雖然是抽象類(lèi)但是存在@Lookup標(biāo)記的方法
// 后面那個(gè)是用于方法注入,我從來(lái)沒(méi)用過(guò)。
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
return (metadata.isIndependent() && (metadata.isConcrete() ||
(metadata.isAbstract() && metadata.hasAnnotatedMethods(
Lookup.class.getName()))));
總結(jié):此類(lèi)在默認(rèn)情況下會(huì)將指定包以及子包下中被@Component(以及衍生注解)標(biāo)記的頂層類(lèi)創(chuàng)建一個(gè)BeanDefinition。
說(shuō)到這插下Spring開(kāi)啟注解掃描的配置,有時(shí)我們可能只想在SpringMVC的配置文件中掃描@Controller標(biāo)記的類(lèi),其它層掃描@Service、@Component、@Repository標(biāo)記的類(lèi),就可以像下面這樣分層配置。
springmvc.xml
<context:component-scan base-package="com.wangtao.controller"
use-default-filters="false">
<context:include-filter
type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
applicationContext.xml
<context:component-scan base-package="com.wangtao.service,com.wangtao.dao">
<context:exclude-filter
type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
也就是說(shuō)SpringMVC中禁掉默認(rèn)的includeFilters,添加了一個(gè)使用@Controller標(biāo)記的條件,而applicationContext.xml使用默認(rèn)的includeFilters,但是排除對(duì)@Controller標(biāo)記的類(lèi)。
此類(lèi)繼承自ClassPathScanningCandidateComponentProvider,除了擁有父類(lèi)掃描包的功能外,還會(huì)對(duì)掃描后的BeanDefinition加工并注冊(cè)到Spring容器中,所謂的加工就是指會(huì)設(shè)置一些類(lèi)文件中用注解標(biāo)記的一些屬性值,如@Lazy、@Scope、@Primary等。
先看一些重要屬性
/** 用于注冊(cè)bean **/
private final BeanDefinitionRegistry registry;
/**
* 此類(lèi)存儲(chǔ)了BeanDefinition一些默認(rèn)屬性值
* lazyInit: false
* autowireMode: AbstractBeanDefinition.AUTOWIRE_NO
* initMethodName: null
* destroyMethodName: null
**/
private BeanDefinitionDefaults beanDefinitionDefaults = new BeanDefinitionDefaults();
/** bean name 生成器 **/
private BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
/**
* 默認(rèn)值:true
* 相當(dāng)于開(kāi)啟<context:annotation-config>
* 意味著我們可以使用@Autowired、@Resource、@PostConstruct、@PreDestroy注解
* 會(huì)自動(dòng)幫我們注冊(cè)解析這幾個(gè)注解的BeanPostProcessor
*/
private boolean includeAnnotationConfig = true;
構(gòu)造方法
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry,
boolean useDefaultFilters,
Environment environment,
ResourceLoader resourceLoader) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
this.registry = registry;
// 同ClassPathScanningCandidateComponentProvider
if (useDefaultFilters) {
registerDefaultFilters();
}
setEnvironment(environment);
setResourceLoader(resourceLoader);
}
接下來(lái)看最重要的掃描方法
/**
* 返回掃描真正注冊(cè)bean的數(shù)量
*/
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
doScan(basePackages);
// 注冊(cè)幾個(gè)BeanPostProcessor用來(lái)解析@Autowired、@Reource等幾個(gè)注解
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
/**
* 將BeanDefinition集合返回,子類(lèi)若有必要可以繼續(xù)對(duì)BeanDefinition做修改
*/
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new
LinkedHashSet<BeanDefinitionHolder>();
for (String basePackage : basePackages) {
// 得到所有符合掃描條件的BeanDefinition集合,接下來(lái)會(huì)對(duì)這些BeanDefinition加工
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
// 主要包括bean的scope屬性(默認(rèn)單例)以及代理策略(不需要代理,JDK動(dòng)態(tài)代理、CGLIB)
// 來(lái)適配AOP
ScopeMetadata scopeMetadata = this.scopeMetadataResolver
.resolveScopeMetadata(candidate);
// 設(shè)置scope屬性
candidate.setScope(scopeMetadata.getScopeName());
// 生成bean name
String beanName = this.beanNameGenerator
.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
// 根據(jù)beanDefinitionDefaults設(shè)置一些默認(rèn)值
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
// 如果是注解定義的Bean, findCandidateComponents默認(rèn)實(shí)現(xiàn)返回的BeanDefinition
// 是一個(gè)ScannedGenericBeanDefinition,其實(shí)現(xiàn)了AnnotatedBeanDefinition接口
if (candidate instanceof AnnotatedBeanDefinition) {
// 解析@Scope、@Primary、@Lazy等屬性并設(shè)置到BeanDefinition中
AnnotationConfigUtils.processCommonDefinitionAnnotations(
(AnnotatedBeanDefinition) candidate);
}
// 檢查BeanDefinition
// 主要檢查這個(gè)容器中是否已經(jīng)存在此BeanDefinition
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder =
new BeanDefinitionHolder(candidate, beanName);
// 設(shè)置代理策略
definitionHolder =AnnotationConfigUtils.applyScopedProxyMode(
scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
// 注冊(cè)到Spring容器中
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
因此如果我們需要自定義掃描實(shí)現(xiàn)bean的注冊(cè),基本上就是要繼承ClassPathBeanDefinitionScanner并且重寫(xiě)doScan方法了。大致框架就是
public class MyScanner extends ClassPathBeanDefinitionScanner {
public MyScanner(BeanDefinitionRegistry registry) {
super(registry)
}
@Overide
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 父類(lèi)已經(jīng)將這些bean注冊(cè)了
Set<BeanDefinitionHolder> holders = super.doScan(basePackages);
for(BeanDefinitionHolder holder : holders) {
// 在這里修改BeanDefinition引用的對(duì)象即可
}
return holders;
}
}
以上就是動(dòng)力節(jié)點(diǎn)小編介紹的"Spring包掃描機(jī)制詳解",希望對(duì)大家有幫助,想了解更多可查看Spring框架教程。動(dòng)力節(jié)點(diǎn)在線(xiàn)學(xué)習(xí)教程,針對(duì)沒(méi)有任何Java基礎(chǔ)的讀者學(xué)習(xí),讓你從入門(mén)到精通,主要介紹了一些Java基礎(chǔ)的核心知識(shí),讓同學(xué)們更好更方便的學(xué)習(xí)和了解Java編程,感興趣的同學(xué)可以關(guān)注一下。
0基礎(chǔ) 0學(xué)費(fèi) 15天面授
有基礎(chǔ) 直達(dá)就業(yè)
業(yè)余時(shí)間 高薪轉(zhuǎn)行
工作1~3年,加薪神器
工作3~5年,晉升架構(gòu)
提交申請(qǐng)后,顧問(wèn)老師會(huì)電話(huà)與您溝通安排學(xué)習(xí)