目 录CONTENT

文章目录

spring-扫包

FatFish1
2025-01-24 / 0 评论 / 0 点赞 / 89 阅读 / 0 字 / 正在检测是否收录...

ClassPathBeanDefinitionScanner是spring提供的扫包能力,基于这个类或者实现这个类都可以完成一些特殊bean的加载

因为spring正常注册bean要么就是配置类,要么就是走正常类加注解,要么就是xml,灵活性还不够,有时可能还想把一些特殊的东西加载成bean

比如mybatis把DAO加载成bean,sql写在xml里面比较方便,又想注册成bean,走的就是这个逻辑

参考mybatis部分:

http://www.chymfatfish.cn/archives/configformybatis#mapperscannerconfigurer---dao%E6%96%87%E4%BB%B6%E6%89%AB%E6%8F%8F%E5%8F%8A%E6%8E%A5%E5%8F%A3-sql%E6%98%A0%E5%B0%84%E5%85%B3%E7%B3%BB%E5%BB%BA%E7%AB%8B

这里直接从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);
				}
			}
		}

主要是两个大步骤:

这里的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的注册部分

http://www.chymfatfish.cn/archives/springxml#registerbeandefinition---%E6%B3%A8%E5%86%8Cbean%E6%A0%B8%E5%BF%83%E6%96%B9%E6%B3%95

0

评论区