Spring Boot扫描bean之ClassPathBeanDefinitionScanner

本文源码基于Spring Boot 2.2.8
本文不赘述上下文信息,需要大家自己确定这个过程在Spring Boot启动流程中地位,否则本文的意义将大打折扣。

从Spring 2.5开始,用户就可以通过编程的方式注册bean,而不用在xml中通过复杂的方式配置bean,这对当时Spring使用者来说是一个天大的惊喜,终于可以告别动辄几千行甚至几万行的xml配置文件了。而这一功能的核心是在指定路径扫描带指定注解的bean,并根据自动装配注解完成属性注入。这样用户就只用提供一个basePackage路径,然后在代码上写上适当的注解就完成所有bean的注入了。

ClassPathBeanDefinitionScanner就是一个具备扫描指定路径,并注入对应BeanDefinition能力的工具类。官方的解释为:
org.springframework.context.annotation.ClassPathBeanDefinitionScanner
始自Spring 2.5
这个类提供的时候,Spring的xml中已经可以不用配置业务bean了,完全可以通过配置来达到自动扫描,自动DI的效果。
官方对这个类的说明是:
ClassPathBeanDefinitionScanner是一个BefanDefinition扫描器,它检测classpath上的bean候选者,使用注册器(BeanFactory或ApplicationContext)注册相应的BeanDefiniton。通过可配置的类型过滤器检测候选类。 默认的类型过滤器含有使用Spring的@Component,@Repository,@Service或@Controller注解的类。还支持Java EE 6的javax.annotation.ManagedBean和JSR-330的javax.inject.Named注解(如果可用)。

一般而言,我们应用的环境是Servlet 的WebServer,我们还需要使用注解配置,最后ApplicationContext的具体类型会是:
org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
他的无参构造方法实例化了2个类型作为自己的成员变量,还是需要知道这2个类型在实例化时有那些逻辑。
AnnotationConfigServletWebServerApplicationContext是ApplicationContext继承体系中最底层的,也就是说这个ClassPathBeanDefinitionScanner只有他自己会使用,他的父类是没有办法使用到的。
另外这里的postProcessBeanFactory()方法refresh容器中的一个流程,即refresh()之postProcessBeanFactory()流程。

public AnnotationConfigServletWebServerApplicationContext() {
	this.reader = new AnnotatedBeanDefinitionReader(this);
	this.scanner = new ClassPathBeanDefinitionScanner(this);

	@Override
	protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
		super.postProcessBeanFactory(beanFactory);
		if (this.basePackages != null && this.basePackages.length > 0) {
			this.scanner.scan(this.basePackages);
		}
		if (!this.annotatedClasses.isEmpty()) {
			this.reader.register(ClassUtils.toClassArray(this.annotatedClasses));
		}
	}
}

 org.springframework.context.annotation.ClassPathBeanDefinitionScanner (since 2.5),早期通过扫描路径+@Component(since 2.5)实现bean的注册
org.springframework.context.annotation.AnnotatedBeanDefinitionReader (since 3.0),通过手动方式实现bean的注册
本文只讲解ClassPathBeanDefinitionScanner这个类型,有关AnnotatedBeanDefinitionReader的讲解请点击这篇文章《Spring Boot扫描bean之AnnotatedBeanDefinitionReader》
ClassPathBeanDefinitionScanner是一个工具类,只要传递相应的参数实例化了即可使用其扫描bean的能力。
可以看到,ClassPathBeanDefinitionScanner构造函数中最多为4个参,其中重要的有3个,registry是BeanDefinition存储的地方,environment是环境,resourceLoader是类路径相关信息。
其实AnnotationConfigServletWebServerApplicationContext中的这个scanner没有多大意义,因为后续真正扫描bean的是重新new出来的一个scanner在工作。我们通过断点可以知道,这里的org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext#postProcessBeanFactory()方法中基本是没有条件调用到scan()的,所以真正扫描bean的是流程是在:
refresh()之invokeBeanFactoryPostProcessors()。
org.springframework.context.annotation.ClassPathBeanDefinitionScanner

public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {

	public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
		this(registry, true);
	}
	
	public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
		this(registry, useDefaultFilters, getOrCreateEnvironment(registry));
	}
	
	public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
			Environment environment) {

		this(registry, useDefaultFilters, environment,
				(registry instanceof ResourceLoader ? (ResourceLoader) registry : null));
	}
	
	public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
			Environment environment, @Nullable ResourceLoader resourceLoader) {

		Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
		this.registry = registry;

		if (useDefaultFilters) {
			registerDefaultFilters();
		}
		setEnvironment(environment);
		setResourceLoader(resourceLoader);
	}
	
	

}

下面我们看看几个注解的时间
@Autowire(since 2.0)
@Repository(since 2.0)
@Component(since 2.5)
@Service(since 2.5)

@Controller(since 2.5)
@Scope(since 2.5)
@ComponentScan(since 3.1) 意味全注解开发的时代来临

可以看到ClassPathBeanDefinitionScanner早于AnnotatedBeanDefinitionReader诞生,也就是说,Spring 2.5 时期,没有AnnotatedBeanDefinitionReader也是完全可以提供给用户使用的。
那时的Spring 可以使用依赖注入功能,但是bean还是要在xml中配置,只不过在xml中配置的bean不用配置依赖了,这个已经减少了一大半以上的xml内容了,大大提高了编程效率。

三、ClassPathBeanDefinitionScanner源码解析

这个类是个非常重要的类,其提供的方法是可以被第三方组件调用的,所以需要大家注意
其中scan()方法可以通过对象调用,doScan()方法需要继承后调用,mybatis-spring组件就调用了这2个方法,用来扫描Mapper接口,并生成代理对象。
真正工作的方法是:
org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan()

public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {

	/**
	 * Perform a scan within the specified base packages.
	 * @param basePackages the packages to check for annotated classes
	 * @return number of beans registered
	 */
	public int scan(String... basePackages) {
		int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

		doScan(basePackages);

		// Register annotation config processors, if necessary.
		if (this.includeAnnotationConfig) {
			AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
		}

		return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
	}

	/**
	 * Perform a scan within the specified base packages,
	 * returning the registered bean definitions.
	 * 

This method does not 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 doScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); Set beanDefinitions = new LinkedHashSet<>(); for (String basePackage : basePackages) { Set candidates = findCandidateComponents(basePackage); for (BeanDefinition candidate : candidates) { ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); candidate.setScope(scopeMetadata.getScopeName()); String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } if (candidate instanceof AnnotatedBeanDefinition) { AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; } }

Spring内部调用doScan()的地方有2处:分别是
refresh()之postProcessBeanFactory()
refresh()之invokeBeanFactoryPostProcessors()
前者因为条件不满足,无法调用到doScan()内部去,后者能够调用到,并扫描出了所有bean。
第一处使用的地方比较简单,所以不做过多说明
第一处:
Spring Boot扫描bean之ClassPathBeanDefinitionScanner_第1张图片
org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext#postProcessBeanFactory()

@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
	super.postProcessBeanFactory(beanFactory);
	if (this.basePackages != null && this.basePackages.length > 0) {
		this.scanner.scan(this.basePackages);
	}
	if (!this.annotatedClasses.isEmpty()) {
		this.reader.register(ClassUtils.toClassArray(this.annotatedClasses));
	}
}

第二处:是在解析@ComponentScan注解时调用
也就是说doScan()被调用多少次取决于@ComponentScan注解使用了多少次。
对于这个调用,我们需要学到的只是点时Spring实在refresh()方法的
Spring Boot扫描bean之ClassPathBeanDefinitionScanner_第2张图片

上面调用链路不太适合阅读,整个调用链路先后涉及的类是:
org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors()
org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors()
org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors()
org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry()
org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions()
org.springframework.context.annotation.ConfigurationClassParser#parse()
org.springframework.context.annotation.ConfigurationClassParser#parse()
org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass()
org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass()
org.springframework.context.annotation.ComponentScanAnnotationParser#parse()
org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan()

你可能感兴趣的:(#,Spring原理篇,spring,boot,spring,java)