ClassPathBeanDefinitionScanner是spring提供的扫包能力,基于这个类或者实现这个类都可以完成一些特殊bean的加载
因为spring正常注册bean要么就是配置类,要么就是走正常类加注解,要么就是xml,灵活性还不够,有时可能还想把一些特殊的东西加载成bean
比如mybatis把DAO加载成bean,sql写在xml里面比较方便,又想注册成bean,走的就是这个逻辑
参考mybatis部分:
这里直接从mybatis的实现类ClassPathMapperScanner引入
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
……
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
MapperScannerConfigurer#postProcessBeanDefinitionRegistry
中构造ClassPathMapperScanner并调用scan方法
ClassPathBeanDefinitionScanner
scan
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
// 扫包+注册
doScan(basePackages);
// 注册XxxProcessor
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
这里取了beanFactory里面的原始加载BD数量,加载完了之后再取下当前数量,作为实际加载的量
加载的流程就在doScan里面,即扫包+注册
doScan
ClassPathMapperScanner实现了doScan,先看下实现类里面
// ClassPathMapperScanner
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
+ "' package. Please check your configuration.");
} else {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
首先调用父类的doScan方法,然后自己做了一个额外处理,在processBeanDefinitions补链接
这里先看父类ClassPathBeanDefinitionScanner#doScan
核心部分就在这个循环中,即遍历basePackages找到对应的beanDefinition,然后生成bean
// 步骤一:循环遍历刚刚解的basePackage,生成BeanDefinition
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
…………
// 步骤二:注册BeanDefinition
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
主要是两个大步骤:
调用findCandidateComponents方法扫包,这里开启了循环,即把用逗号分割的多个路径,分成了不同的部分
调用registerBeanDefinition方法注册BD
这里的basePackage就是核心变量,即要扫的bean定义在哪里。在mybatis中就是
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.test.fatfish.udrmetric.**.dao,com.test.fatfish2.**" />
<property name="sqlSessionFactoryBeanName" value="targetSqlSessionFactory" />
</bean>
即mapperScannerConfigurer中定义的basePackage属性解析得到
findCandidateComponents
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
// 开启spring.components配置,并且includeFilters过滤器仅支持@Indexed及其子注解条件时:从配置中扫描
if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
}
else {
// 从指定包中扫描
return scanCandidateComponents(basePackage);
}
}
这里有两个场景,一个是开启spring.components
配置的场景,另一个就是基于指定包的场景,调用scanCandidateComponents,重点看下
scanCandidateComponents
实际解析流程和spring解析xml的逻辑基本一致
首先把对应的classPath下面的包里面的内容解析成resource
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
委托ScannedGenericBeanDefinition基于spring的Metadata解析工具解析成BeanDefinition。
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
这里有一个判断方法isCandidateComponent。
tf.match(metadataReader, getMetadataReaderFactory())
里面就是在调用TypeFilter的match方法,在mybatis的应用案例中可以看到mybatis是如何添加filter的
就是在MapperScannerConfigurer#postProcessBeanDefinitionRegistry
方法中
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.set……
……
scanner.registerFilters();
Mybatis更具体的源码分析可以参考Mybtais部分补链接
registerBeanDefinition
protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) {
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
}
注册解出来的BeanDefinition,这里直接调用到spring去了,是注册到map的那一套流程,见spring的注册部分
评论区