Spring 中的扫描
前言
spring 是如何去扫描的
以 springboot + mybatis 为例,看一下是如何扫描的
代码结构:
不使用 @MapperScanner 注解,使用 @Mapper 注解
我们的关注点在扫描的过程,至于扫描的前因后果,我们在其它文章中进行探究。
一、Spring 中的包扫描
类结构:
ClassPathScanningCandidateComponentProvider
ClassPathBeanDefinitionScanner
1.1 ComponentScanAnnotationParser
Spring 在扫描组件是在 ComponentScanAnnotationParser.parse() 方法中。它的前因过程,大致为:
- ConfigurationClassBeanDefinitionReader 在解析 ConfigurationClass 时,需要解析 @ComponentScan 注解
- 创建一个 ComponentScanAnnotationParser 对象,并将 @ComponentScan 注解作为参数,进行解析
- 处理扫描到的 beanDefinition 集合,对每一个 beanDefinition 在进行处理
扫描的起点就在 ComponentScanAnnotationParser.parse() 中,它的大体流程为:
- 创建 Scnanner
- 给 scanner 设置参数
- 执行扫描
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
/**
* step 1: 创建 ClassPathBeanDefinitionScanner
*/
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
/**
* step 2:从 @ComponentScan 注解中获取需要扫描的信息,并设置到 scanner 中,以备最后执行扫描
*/
Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
BeanUtils.instantiateClass(generatorClass));
ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
scanner.setScopedProxyMode(scopedProxyMode);
}
else {
Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
}
// 设置需要扫描的资源通配符,默认 resourcePattern -> **/*.class
scanner.setResourcePattern(componentScan.getString("resourcePattern"));
// 设置【包含过滤】和【排除过滤】,他们作为扫描后是否进行
for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addIncludeFilter(typeFilter);
}
}
for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addExcludeFilter(typeFilter);
}
}
// 设置是否需要延迟加载
boolean lazyInit = componentScan.getBoolean("lazyInit");
if (lazyInit) {
scanner.getBeanDefinitionDefaults().setLazyInit(true);
}
Set<String> basePackages = new LinkedHashSet<>();
// 需要扫描的包路径
String[] basePackagesArray = componentScan.getStringArray("basePackages");
for (String pkg : basePackagesArray) {
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
Collections.addAll(basePackages, tokenized);
}
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
//如果设置的包路径为空,则默认使用当前配置类的包名
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(declaringClass));
}
scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
@Override
protected boolean matchClassName(String className) {
return declaringClass.equals(className);
}
});
/**
* step 3:执行扫描
*/
return scanner.doScan(StringUtils.toStringArray(basePackages));
}
1.2、ClassPathBeanDefinitionScanner
ClassPathBeanDefinitionScanner 是 ClassPathScanningCandidateComponentProvider 的子类,
执行扫描的过程大致为:
- 获取到扫描到的 beanDefinition 集合
- 对每个 beanDefinition 设置参数
- 将 beanDefinition 都注册到 spring 中(最终都维护在 beanDefinitionMap 中)
/**
* Perform a scan within the specified base packages,
* returning the registered bean definitions.
* <p>This method does <i>not</i> register an annotation config processor
* but rather leaves this up to the caller.
* @param basePackages the packages to check for annotated classes
* @return set of beans registered if any for tooling registration purposes (never {@code null})
*/
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) {
/**
* step 1: 扫描包下所有的 beanDefinition 资源
*/
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
/**
* step 2: 处理 BeanDefinition 对象
*/
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
//给 BeanDefinition 对象设置一些默认值
//如:lazyInit、dependencyCheck 等
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
//如果是 AnnotatedBeanDefinition,则获取该类上面的 @Lazy、@Primary、@DependsOn 等注解,给 BeanDefinition 设置值
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
//判断 spring 中是否已经包含了当前 BeanDefinition,如果未包含,则进入
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
/**
* step 3:将 BeanDefinition 注册到 spring 中
* 此处 registry 是spring 的 beanFactory
*/
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
通过代码可以看到,实际完成扫描的并不是 ClassPathBeanDefinitionScanner,它仅仅是通过父类去获取 beanDefinition 的集合,它的核心在于对 beanDefinition 集合进行完善和注册。实际执行扫描工作的是它的父类 ClassPathScanningCandidateComponentProvider。
1.3、ClassPathScanningCandidateComponentProvider
ClassPathScanningCandidateComponentProvider 是 spring 中真正进行资源扫描的处理类,它的大致流程为:
- 通过包名和通配符,扫描所有目标资源
- 对每一个资源进行判断,符合条件的资源的添加到结果集合中
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
/**
* step 1: 根据包路径扫描所有资源
*/
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
/**
* step 2: 对每一个资源进行处理
*/
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
// 对当前资源进行条件过滤
if (isCandidateComponent(metadataReader)) {
// 创建为 ScannedGenericBeanDefinition
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
// ... 省略部分代码
candidates.add(sbd);
}
// ... 省略部分代码
}
// ... 省略部分代码
}
// ... 省略部分代码
}
else {
if (traceEnabled) {
logger.trace("Ignored because not readable: " + resource);
}
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
对资源进行的判断是非常重要的一个点。
它要求参数中的类:只有 “不满足所有排除条件过滤器”,并且满足至少一个 “包含条件过滤器”。
/**
* Determine whether the given class does not match any exclude filter
* and does match at least one include filter.
* @param metadataReader the ASM ClassReader for the class
* @return whether the class qualifies as a candidate component
*/
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;
}
二、Mybatis 中的 mapper 扫描
再来看看 mybatis 中对 mapper 的扫描处理。
类结构:
ClassPathScanningCandidateComponentProvider
ClassPathBeanDefinitionScanner
ClassPathMapperScanner
2.1 MapperScannerConfigurer
MapperScannerConfigurer 的 postProcessBeanDefinitionRegistry 方法是 mapper 扫描和处理的起点。
MapperScannerConfigurer 的前因流程,大致为:
- MapperScannerConfigurer 被 mybatis 中其他类加载入 spring 中
- 由于 MapperScannerConfigurer 是 BeanDefinitionRegistryPostProcessor,所以会在 spring 启动时,调用 postProcessBeanDefinitionRegistry 方法
与上方扫描所有组件类相同,它的大致流程也分为:
- 创建 Scanner
- 设置 Scanner 参数
- 执行扫描过程
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
if (StringUtils.hasText(lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
}
if (StringUtils.hasText(defaultScope)) {
scanner.setDefaultScope(defaultScope);
}
scanner.registerFilters();
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
值得注意的是:
在第一部分,spring 用来扫描组件的 scanner 类为 ClassPathBeanDefinitionScanner;而此处 mybatis 用来扫描 mapper 的类为 ClassPathMapperScanner。ClassPathMapperScanner 为 ClassPathBeanDefinitionScanner 的子类。
2.2、ClassPathMapperScanner
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
/**
* Calls the parent search that will search and register all the candidates. Then the registered objects are post
* processed to set them as MapperFactoryBeans
*/
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
/**
* step 1: 执行父类的扫描过程,获取包下所有的 beanDefinition
* 此时获取到的 beanDefinition 都是 mapper 对象的 beanDefinition
*/
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 {
/**
* step 2: 处理扫描的结果
*/
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
}
作为 ClassPathBeanDefinitionScanner 的子类,ClassPathMapperScanner 直接在父类将扫描到的 beanDefinition 设置默认参数、注册到 beanFactory 的基础上,再给 beanDefinition 进行属于 mybatis 自己特殊的处理。
处理过程为 processBeanDefinitions 方法。
2.2.1、Mybatis 中对于 mapper beanDefinition 的特殊处理
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
AbstractBeanDefinition definition;
BeanDefinitionRegistry registry = getRegistry();
//遍历处理每一个 mapper beanDefinition
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (AbstractBeanDefinition) holder.getBeanDefinition();
boolean scopedProxy = false;
if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) {
definition = (AbstractBeanDefinition) Optional
.ofNullable(((RootBeanDefinition) definition).getDecoratedDefinition())
.map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> new IllegalStateException(
"The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]"));
scopedProxy = true;
}
String beanClassName = definition.getBeanClassName();
LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
+ "' mapperInterface");
// mapper 的接口是mapper类本身的类型,但是在 beanDefinition 中,它的实际 beanClass 是一个 FactoryBean。之后再获取 mapper 实例的时候,都是通过 FactoryBean 的 getObject 方法来获取的
// 如:beanClassName:com.baowen.springdetail.mapper.UserMapper,mapperFactoryBeanClass:org.mybatis.spring.mapper.MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
definition.setBeanClass(this.mapperFactoryBeanClass);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
// Attribute for MockitoPostProcessor
// https://github.com/mybatis/spring-boot-starter/issues/475
definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClassName);
boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory",
new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
LOGGER.warn(
() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate",
new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
LOGGER.warn(
() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
definition.setLazyInit(lazyInitialization);
if (scopedProxy) {
continue;
}
if (ConfigurableBeanFactory.SCOPE_SINGLETON.equals(definition.getScope()) && defaultScope != null) {
definition.setScope(defaultScope);
}
if (!definition.isSingleton()) {
BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true);
if (registry.containsBeanDefinition(proxyHolder.getBeanName())) {
registry.removeBeanDefinition(proxyHolder.getBeanName());
}
registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition());
}
}
}
}
代码比较冗长,其中的内容都是给 beanDefinition 设置属性。比较关键的属性是:
// mapper 的接口是mapper类本身的类型,但是在 beanDefinition 中,它的实际 beanClass 是一个 FactoryBean。之后再获取 mapper 实例的时候,都是通过 FactoryBean 的 getObject 方法来获取的
// 如:beanClassName:com.baowen.springdetail.mapper.UserMapper,mapperFactoryBeanClass:org.mybatis.spring.mapper.MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
definition.setBeanClass(this.mapperFactoryBeanClass);
它意味着 mapper 对象在获取实例的时候,是通过 FactoryBean 来间接进行创建对象的,这与存在于 spring 中普通的 bean 有所不同。
由于 mybatis 在代码定义的方式为仅仅定义 mapper 接口,没有实现类:
- 实际获取的 mapper 对象是动态代理类
- mapper 的动态代理生成对象过程,就在 mybatis 中的mapper 对应的 FactoryBean 的 getObject 方法中定义的
2.2.2、对于 @Mapper 注解的判断
由于本次整合 mybatis 的方式为用 @Mapper 注解标注在 mapper 接口之上,所以在扫描期间,判断资源是否有 @Mapper 注解,如果有,则确定就是 mybatis 所需要的 mapper,才会将其封装为 BeanDefinition,并保存在结果中进行返回。
此过程的关键点在于:在哪里对 @Mapper 注解进行的判断。
在第一部分 spring 扫描组件过程中,在 ClassPathScanningCandidateComponentProvider 中,维护了一个两个条件过滤器:一个是排除条件过滤器,一个是包含条件过滤器。由于 ClassPathMapperScanner 是 ClassPathBeanDefinitionScanner 的子类,所以也就是 ClassPathScanningCandidateComponentProvider 的子类。ClassPathMapperScanner 中重写了父类的 registerFilters 方法,在创建 ClassPathMapperScanner 对象去扫描 mapper 对象时,如果调用 registerFilters
方法,就会设置 mybatis 的扫描条件过滤器:
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
/**
* Configures parent scanner to search for the right interfaces. It can search for all interfaces or just for those
* that extends a markerInterface or/and those annotated with the annotationClass
*/
public void registerFilters() {
boolean acceptAllInterfaces = true;
// if specified, use the given annotation and / or marker interface
if (this.annotationClass != null) {
addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
acceptAllInterfaces = false;
}
// override AssignableTypeFilter to ignore matches on the actual marker interface
if (this.markerInterface != null) {
addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
@Override
protected boolean matchClassName(String className) {
return false;
}
});
acceptAllInterfaces = false;
}
if (acceptAllInterfaces) {
// default include filter that accepts all classes
addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
}
// exclude package-info.java
addExcludeFilter((metadataReader, metadataReaderFactory) -> {
String className = metadataReader.getClassMetadata().getClassName();
return className.endsWith("package-info");
});
}
}
ClassPathMapperScanner 有 this.annotationClass 属性,在创建 ClassPathMapperScanner 对象时,它的 annotationClass 属性会被设置为 @Mapper 注解类型。在设置条件过滤器时,添加了一个注解类型判断过滤器。
if (this.annotationClass != null) {
addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
acceptAllInterfaces = false;
}
在 ClassPathScanningCandidateComponentProvider 扫描到资源,并执行 isCandidateComponent 方法进行判断时,isCandidateComponent 方法中就会拿到这个包含条件过滤器进行判断。所以整个过程就是判断当前这个资源(类)上是否存在 @Mapper 注解。
三、总结
通过前两个部分源码的解析,可以了解到:
要想获取到目标的 beanDefinition 范围,进行的扫描过程有好几层处理:
- ClassPathScanningCandidateComponentProvider 中对所有资源的扫描、过滤和整合
- ClassPathBeanDefinitionScanner 对 beanDefinition 的完善
- ClassPathMapperScanner 对 beanDefinition 的特殊化处理
如果我们自己想要定义一个整合在 spring 中的轮子、定义我们自己的功能类时,完全可以像 mybatis 一样:
- 自定义注解
- 自定义一个 scanner 类,并继承于ClassPathBeanDefinitionScanner
- 创建一个 scanner 对象,并设置所需的条件过滤器和 annotationClass
- 进行扫描
spring 中 ClassPathScanningCandidateComponentProvider 的两个条件过滤器具有相当强的拓展性,它将扫描的包路径、判断条件以及其他参数全部可配,只要继承它,重写它的一些方法,就可以获取自己想要扫描到的类。