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