本文源码基于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了,完全可以通过配置
官方对这个类的说明是:
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内容了,大大提高了编程效率。
这个类是个非常重要的类,其提供的方法是可以被第三方组件调用的,所以需要大家注意
其中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。
第一处使用的地方比较简单,所以不做过多说明
第一处:
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()方法的
上面调用链路不太适合阅读,整个调用链路先后涉及的类是:
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()