聊透Spring类扫描机制
我正在参加「掘金·启航计划」
大家好,我是贰师兄,好久不见,甚是想念。今天我们来聊聊Spring的类扫描机制
,也就是Spring判断哪些类归自己管辖,哪些类只能远观不可亵玩的类识别机制
。关于Spring的类扫描机制的设计,贰师兄梳理下来的感受是:设计的精巧,实现的优雅
,是值得拿出来和小伙伴们解读一波的。吹捧了这么久,我们赶紧进入正题吧,先来看看我们准备从哪几个方面,和大家一起解读Spring的类扫描机制吧。
- 配置类扫描的流程分析。
- 扫描过滤器的工作流程分析。
- 首个配置类解析,触发扫描流程跟踪。
- Mybatis拓展类扫描机制,实现Mapper接口类注册分析(本章节限于篇幅,暂不分析)。
当然,我们还是遵循聊透
的底线,让大家做到:知其然,且知其所以然
。每个方面我们都会结合源码详细展开,有理有据,一步一图,让大家都能的搞得懂。
贰师兄始终坚信,学不会不是你的错,是教你知识的老师的问题。不知道要得罪多少老师了😅
1.扫描配置类的流程分析
从整个Spring容器的生命周期来看,类扫描发生容器启动的时候,在聊透Spring bean的生命周期中我们也反复提及,Spring的流程是:先扫描出所有的需要处理类,然后存放到beanDefinitionMap中,最后统一执行实例化操作。
所以,必然的,类扫描的时机在Spring容器的生命周期中是比较靠前的,具体由ConfigurationClassPostProcessor
这个bean工厂的后置处理器触发,后面第三章有详细介绍,这里小伙伴有个印象即可。
关于扫描机制的触发,具体的源码位置在:refresh() -> invokeBeanFactoryPostProcessors() -> ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry(),感兴趣的小伙伴可以自行查看。
对类的扫描时机有了解后,下面我们一起看一下类扫描的整体流程,大致可以分为下面四个流程:
- 通过
@ComponentScan
获取需要扫描的包,并获取路径下所有的类资源文件。 - 通过ASM读取类资源文件的字节码信息。
- 判断是否符合Spring处理的条件(使用TypeFilter判断)。
- 存放到beanDefinitionMap中,供后续使用。
流程比较简单,1、4流程很好理解,对于2和3可能大家有些疑问,主要是关于ASM和Spring符合条件判断,这里给大家解答下。
- Spring为什么采用ASM读取字节码的方式解析类,而没有采用Class字节码解析?
这里可能有两个原因,一是可能ASM读取的方式效率更高,故Spring采用这种方式。二是Class字节码解析,需要使用类似于Class.forName
的方式,先获取Class对象,但是这个操作必然导致类的加载,如果类有静态代码块,还会触发静态代码块的执行,这会影响用户的行为。因此,Spring采用ASM的方式。
贰师兄认为应该是第二种原因导致Spring采用SAM的方式的,毕竟启动阶段那一点点效率真的无足轻重,但是破坏类加载原则却是很严重的。我们知道
JVM加载类的原则是懒加载
,也就是用到谁加载谁,Spring没必要去破坏这个原则。不管用不用,因为我要判断是扫描,都加载进来,这显然是不合理的,也不是Spring的格调。
- 什么样的类符合Spring的处理条件?
正常情况下,加了@Component
族群的全配置类或者半配置类,都可以被Spring处理。同时为了兼容其他框架,加了javax.annotation.@ManagedBean
、javax.inject.@Named
的类也能被Spring处理。不过这只是第一道槛,想被Spring管理,还需要类是非抽象类、非接口类(或者加了@Lookup
的抽象类)。单单加了@Component
还不行呢,看来委身于人,还不太容易呢。
对于
@Component
族群、全配置和半配置类不太了解的小伙伴,可以查看贰师兄的聊透spring @Configuration配置类一文,里面说的很清楚了,这里不再叨叨。
当然,如果@ComponentScan
配置了includeFilters
和excludeFilters
属性也会影响能否被处理的判断,具体逻辑和原理,我们放到下一章节讲解。这里我们先知道:加了@Component
注解的非抽象类,都能被Spring扫描并处理就行了。
OK,现在大家对Spring类扫描流程应该已经很清晰了,那下面又到了我们的总结时刻,我们画一张图加深一下印象,同时给大家附上源码,感兴趣的小伙伴可以自行跟踪调试源码。
# ConfigurationClassParser.java
// ①. 解析配置类上的@ComponentScan,触发扫描
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {
// 省略部分非相关代码
// 3: 获取@ComponentScan,包括@ComponentScans
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
for (AnnotationAttributes componentScan : componentScans) {
// 3.1: 解析ComponentScan配置信息,完成扫描(符合IncludeFilter的添加,符合excludeFilter的排除) (查看②)
// 扫描出来的已经到放入beanDefinitionMap中了
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
}
}
# ComponentScanAnnotationParser.java
// ②. 解析@ComponentScan注解,进行扫描
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, String declaringClass) {
// 1: 初始化ClassPathBeanDefinitionScanner,
// 初始化时会注册默认的IncludeFilter,其中包含处理@Component的AnnotationTypeFilter
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
// 2: 处理在@ComponentScan中配置的信息
// 2.1 解析配置的includeFilters和excludeFilters
// 添加我们自定义的includeFilters,在ComponentScan中配置
for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addIncludeFilter(typeFilter);
}
}
// 添加我们自定义的excludeFilters,在ComponentScan中配置
for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addExcludeFilter(typeFilter);
}
}
// 3: 解析配置的扫描路径(扫描路径可以配置多个)
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));
}
// 5: 进行扫描 (查看③)
// a: 把所有的类文件获取到(ASM)
// b: 获取到类能不能变成beanDefinition需要看是否合格()
// c: 放入beanDefinitionMap中
return scanner.doScan(StringUtils.toStringArray(basePackages));
}
# ClassPathBeanDefinitionScanner.java
// ③ 根据包路径扫描,验证是否可以处理,存储到beanDefinitionMap中
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
// 1: 遍历扫描的包名
for (String basePackage : basePackages) {
//2: 在包路径下,加载所有符合条件的类定义, 该方法内部完成了扫描,(查看④)
// a: 验证包下class文件的合法性验证
// b: 转换是否为非抽象类
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
// 3: 放入beanDefinitionMap中,即注册到spring容器中
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
#ClassPathScanningCandidateComponentProvider.java
// ④ 判断类是否能被Spring处理
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
// 1: 包名转换为路径
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
// 根据路径转换为Resource,本质是一个输入流
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
// 2: 遍历包下所有的文件
for (Resource resource : resources) {
// 3: 读取到的类文件的信息(ASM扫描出来的)
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
// 4: 合规性验证(核心)
// a: 符合excludeFilters规则的,直接排除
// b: 符合includeFilters规则的,进行处理
// > 是否需要spring处理(加了@Compont等必要注解)
if (isCandidateComponent(metadataReader)) {
// 5:判断当前类,是否非抽象的,非接口的
if (isCandidateComponent(sbd)) {
// 添加到集合返回
candidates.add(sbd);
}
}
}
}
2. 扫描过滤器的工作流程
上一章节我们已经清楚了类扫描的整体流程,不过也埋了一个到底什么条件的类Spring才会处理
没有讲透,本章节我们的目标就是:把这个坑填上。
关于类是否符合Spring处理的判断,是由TypeFilter
过滤器来决定的,类扫描器有两个TypeFilter过滤器集合,分别是includeFilters
包含过滤器和excludeFilters
排除过滤器,存放着进行符合判断的过滤器。这两个过滤器的关系是:只有满足包含过滤器的,才能被Spring处理,只要满足排除过滤器,则一定不能被Spring处理
。
我们借助源码,加深一下这两个过滤器的功能:
// 判断类是否满足Spring扫描条件
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
// 满足任一排除过滤器条件,直接不进行处理
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return false;
}
}
// 满足任一包含过滤器条件,才进行处理(比如加了@Component)
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return isConditionMatch(metadataReader);
}
}
// 其他情况,不满足包含过滤器的,也不进行处理
return false;
}
OK,现在皮球滚到了tf.match()
逻辑里面了,生杀大权都在他手里了,我们只能继续一探究竟。这种只能依靠源码才能清晰的底层逻辑,还是直接对核心源码进行解读。
#AbstractTypeHierarchyTraversingFilter.java
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
// 1: 匹配自身信息
if (matchSelf(metadataReader)) {
return true;
}
ClassMetadata metadata = metadataReader.getClassMetadata();
// 2: 匹配类名
if (matchClassName(metadata.getClassName())) {
return true;
}
// 3: 是否考虑Inherited,在构造方法中传入
if (this.considerInherited) {
String superClassName = metadata.getSuperClassName();
if (superClassName != null) {
//3.1 对继承的父类进行匹配
Boolean superClassMatch = matchSuperClass(superClassName);
if (superClassMatch != null) {
if (superClassMatch.booleanValue()) {
return true;
}
}
else {
// 3.2 使用父类重走匹配逻辑
if (match(metadata.getSuperClassName(), metadataReaderFactory)) {
return true;
}
}
}
}
// 4: 是否考虑接口,在构造方法中传入
if (this.considerInterfaces) {
for (String ifc : metadata.getInterfaceNames()) {
//4.1 对实现的接口进行匹配
Boolean interfaceMatch = matchInterface(ifc);
if (interfaceMatch != null) {
if (interfaceMatch.booleanValue()) {
return true;
}
}
else {
// 4.2 使用实现的接口重走匹配逻辑
if (match(ifc, metadataReaderFactory)) {
return true;
}
}
}
}
return false;
}
好家伙,不看不知道,一看吓一跳,咋又引伸出来这么多判断逻辑。不要慌张,不要害怕,贰师兄和你一起解读一下这些方法,保证包教包会。

在探究新逻辑之前,我们先来看一下TypeFilter的类图关系,有个大致的印象,在分析时,我们也会解析具体子类的实现,便于让小伙伴理解这些判断逻辑的作用和使用方式。
- matchSelf(): 根据自身特征判断是否匹配
该方法根据类元信息判断是否匹配,参数类型为
MetadataReader
,通过该参数可以获取到类信息、注解信息等,进而可以便捷的进行匹配判断。
public interface MetadataReader {
/**
* 获取类资源信息
*/
Resource getResource();
/**
* 获取类元数据信息(是否接口、是否注解、是否抽象等)
*/
ClassMetadata getClassMetadata();
/**
* 获取类上包含的注解信息
*/
AnnotationMetadata getAnnotationMetadata();
}
有了这些信息,我们就可以自定义匹配逻辑了。这我们不自己实现,一起看一下Spring最常用的AnnotationTypeFilter
过滤器对该方法的实现,供小伙伴们学习参考。
# AnnotationTypeFilter.java
@Override
protected boolean matchSelf(MetadataReader metadataReader) {
// 通过metadataReader获取注解信息
AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
// 判断是否存在指定注解
return metadata.hasAnnotation(this.annotationType.getName()) ||
(this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));
}
其实注解过滤器AnnotationTypeFilter
对于matchSelf()
实现很清晰,就是查找是否存在指定注解,如果存在就符合,不存在则符合。
- matchClassName():根据类名判断是否匹配
该方法通过类名判断是否匹配。逻辑比较清晰,同样的,我们看一下Spring另外一个常用的
AssignableTypeFilter
过滤器对该方法的实现,供小伙伴们学习参考。
#AssignableTypeFilter.java
@Override
protected boolean matchClassName(String className) {
//直接判断类名是否相等
return this.targetType.getName().equals(className);
}
- matchSuperClass():根据继承的父类信息判断是否匹配
该方法的执行,需要设置了
considerInherited
为true,翻译过来的意思是:是否考虑继承的父类信息用于匹配
,比如父类注解等。也就是说如果当前类不符合,但继承的父类符合,最终也是可以判断为符合的。
那根据父类的的什么信息判断呢,当然也是看具体子类过滤器的实现了,我们还是拿AnnotationTypeFilter
注解过滤器对该方法的实现来说。
# AnnotationTypeFilter.java
@Override
@Nullable
protected Boolean matchSuperClass(String superClassName) {
return hasAnnotation(superClassName);
}
@Nullable
protected Boolean hasAnnotation(String typeName) {
// 父类是Object,直接返回不符合
if (Object.class.getName().equals(typeName)) {
return false;
}
// 要求类必须是java开头的,也就是JDK的类
else if (typeName.startsWith("java")) {
if (!this.annotationType.getName().startsWith("java")) {
return false;
}
try {
// 根据类型获取Class字节码信息,此时是父类的
Class<?> clazz = ClassUtils.forName(typeName, getClass().getClassLoader());
// 判断父类的注解
return ((this.considerMetaAnnotations ? AnnotationUtils.getAnnotation(clazz, this.annotationType) :
clazz.getAnnotation(this.annotationType)) != null);
}
catch (Throwable ex) {
// Class not regularly loadable - can't determine a match that way.
}
}
return null;
}
关于匹配逻辑,小伙伴们自行查看注释哦。有一点需要注意,如果matchSuperClass()匹配结果为空,会重新执行match()
,也就是执行matchSelf() -> matchSuperClass() -> ...的逻辑(子类没有重写match()的情况下),不过传递的参数已经是父类,不再是子类了哦
。
下面给小伙伴们补充一下关于@Inherited
的知识,大家知道通常情况下,父类的注解,子类是无法继承的,那如果想要继承怎么办呢?
JDK提供了@Inherited注解,该注解作用于一个注解上,也就是说,加了@Inherited注解的注解,如果标注的父类上,子类也是可以继承到该注解的
。不过限定于类继承哦,如果类实现的接口上加了@Inherited标注的注解,是不会生效的哦。给小伙伴们总结一下@Inherited继承生效的情况:
类关系 | @Inherited修饰的注解能够被继承 | 说明 |
---|---|---|
类继承 | ✅ | 子类会继承父类使用的注解中被@Inherited修饰的注解 |
类实现 | ❎ | 子接口不会继承父接口中的任何注解,不管父接口中使用的注解有没有被@Inherited修饰 |
接口继承 | ❎ | 类实现接口时不会继承任何接口中定义的注解 |
- matchInterface():根据实现的接口信息判断是否匹配
该方法的执行,需要设置了considerInterfaces
为true,翻译过来就是:是否考虑实现的接口信息
,比如接口注解等。逻辑和matchSuperClass()
类似,这里我们就不啰嗦了。
过滤器的匹配我们就说完了,是不是很精巧且优雅,在AbstractTypeHierarchyTraversingFilter
抽象类中定义了统一的匹配流程,并将具体实现交由子类,保证统一逻辑且易于拓展。
老规矩,我们继续一起总结一下,加深印象,直接上图:
扫描器的注册
好了,开胃菜上完了,下面我们上主食😂。小伙伴是不是大吃一惊,看了这么多字,痛苦了这么久,你以为学完了,其实才刚刚开始😭。能怎么办,作为一个硬核的博主,我得给你讲透啊,不然写这么多字,画这么多图,为了什么啊。
好了,不废话了,下面我们一起看一下,执行类扫描的这些内置过滤器,是怎么注册和并驱动工作的,帮小伙伴们连贯起来。
这里多说一句,看Spring的功能代码,小伙伴们静下心来都可以读的明白,贰师兄认为Spring源码最难的是串联起来,因为很分散且伏笔很多,需要小伙伴日积月累,且对Spring容器生命周期很熟悉才行。因此贰师兄才会不厌其烦的,在每篇文章都用大量的篇幅帮小伙伴梳理串联流程。
我们再来回顾一下之前分析过的,Spring判断是否扫描接管的核心逻辑。
# ClassPathScanningCandidateComponentProvider.java
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
// 满足任一排除过滤器条件,直接不进行处理
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return false;
}
}
// 满足任一包含过滤器条件,才进行处理(比如加了@Component)
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return isConditionMatch(metadataReader);
}
}
// 其他情况,不满足包含过滤器的,也不进行处理
return false;
}
代码的执行逻辑,上面我们分析过了,这里不再赘述,本章节我们要说的,是进行循环判断的this.excludeFilters
和this.includeFilter
的过滤器都有哪些?分别是什么时候赋值的?有什么作用?
- 类扫描器初始化时,默认注册匹配@Component注解的过滤器
有过Spring使用经验的小伙伴都知道,即使在@ComponentScan
不指定includeFilters
属性的情况下,Spring也会将@Component
标识的类纳入管理范围。结合上面我们的分析,可以笃定:一定有一个匹配@Component
的TypeFilter
在this.includeFilters
中。那到底是不是这样呢,我们找一下。
我们知道,负责类扫描的是ClassPathBeanDefinitionScanner
,通常对于属性的初始化都在构造方法中,我们顺着这个思路找一下:
# ClassPathBeanDefinitionScanner
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);
}
// 注册处理@Component、@ManagedBean、@Named注解的过滤器
protected void registerDefaultFilters() {
// 1:注册处理@Component类型的过滤器
// 如果类上加了 @Component 注解,就在include列表中
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
try {
// 2: 判断是否加了 @ManagedBean注解,如果项目不想使用spring,但是类需要交给spring管理,可以使用ManagedBean
// 这里采用类反射的方式判断类是否存在,类存在的话,才会加进去
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
}
try {
// 3:判断是否加了@Named注解
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-330 API not available - simply skip.
}
}
果然,在类扫描器初始化的时候,注册了能够处理@Component
的过滤器,所以默认加了@Component
的类才能被Spring处理,原来一切都有迹可循啊。
细心的小伙伴应该也发现了,注册默认过滤器的前提是useDefaultFilters
为true。默认情况下,我们不在构造方面中指定,默认都为true。这里Spring也没有设置该参数,所以是成立的。
mybatis实现的类扫描器,就设置的为false哦,其实也好理解,mybatis扫描出需要自己处理的Mapper类即可,这里的默认注册器是处理Spring的
@Component
的,mybatis没必要再处理一次。
- 解析@ComponentScan注解时,注册注性指定的过滤器
我们知道,@ComponentScan
是可以指定包含过滤器(includeFilters
)和排除过滤器(excludeFilters
)的。必然,这两者也是需要加入到this.includeFilters
和中this.excludeFilters
中,我们看一下逻辑。
#ComponentScanAnnotationParser.java
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, String declaringClass) {
//省略非关联代码
// 2: 处理在@ComponentScan中配置的信息
// 2.1 解析配置的includeFilters和excludeFilters
// 添加我们自定义的includeFilters,在ComponentScan中配置
for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addIncludeFilter(typeFilter);
}
}
// 添加我们自定义的excludeFilters,在ComponentScan中配置
for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addExcludeFilter(typeFilter);
}
}
//省略非关联代码
}
通过源码,清晰的知道,在解析@ComponentScan时,也加入了进来,so easy。
对@ComponentScan的
includeFilters
和excludeFilters
如何指定的小伙伴可以自行百度哦,整体来说比较简单,小伙伴们掌握即可,平时工作中也不经常使用。其实,Spring还添加了一个匿名的排除过滤器,用来排除首个配置类的,不过不影响整体流程,细枝末节而已。
老规矩,我们一起总结一下,看图说话。
3. 启动类触发扫描流程概览
在开始本章节之前,我们先回忆一下,Spring是如何启动起来的(怕小伙伴SpringBoot使用太多,忘记了),我们直接上测试代码。
// 测试类
public class CircularDependencyTest {
@Test
public void test(){
// 指定首个配置类为:CircularDependencyConfig
AnnotationConfigApplicationContext
context = new AnnotationConfigApplicationContext(CircularDependencyConfig.class);
A a = context.getBean(A.class);
}
}
// 使用@ComponentScan指定扫描包路径
@ComponentScan("com.ivy.circular.dependency")
public class CircularDependencyConfig {
}
这个就是原始的Spring的写法,在创建ApplicationContext
时,指定首个配置类,在首个配置类上使用@ComponentScan
指定扫描包路径,Spring在解析到@ComponentScan时,就触发了类扫描流程,进而继续上面我们吧啦吧啦的一大堆流程。本章节给小伙伴们连贯一下这个流程,对小伙伴们自主阅读源码,是有帮助的。
SpringBoot的流程和这里的原生Spring写法,区别不大,只是稍微做了一点自动化的操作,本质和上述流程一致的,后面有机会给大家分享SpringBoot时,再跟大家细讲。
首先,我们需要清楚,在创建ApplicationContext
时指定的首个配置类,是直接注册到beanDefinitionMap
中的。后续我们使用时,也是直接从beanDefinitionMap中获取即可。
那后续的流程小伙伴可能有都清楚了,我们上面简单提过,这里再给大家梳理一下:容器进行启动(refresh()
) -> 调用bean工厂的后置处理器(invokeBeanFactoryPostProcessors
) -> 执行ConfigurationClassPostProcessor
后置处理器的postProcessBeanDefinitionRegistry()
方法 ->取出首个配置类,进行配置类解析
(解析出@ComponentScan,触发扫描) -> 扫描出的类存放到beanDefinitionMap中 -> 进行bean的实例化。
关于类的扫描流程我们上述都分析过,这里最最关键的是:根据首个配置类解析,解析到@ComponentScan时触发扫描,对扫描出的类继续递归解析,直到所有配置类都解析完成
。
关于配置类的解析内容,是很大很大的一块内容,涉及到的知识点也很多,限于篇幅和小伙伴的阅读体验,我们这里不赘述了,给大家整理了一张流程图,很清晰明了,大家肯定能看明白,大家认真观看即可,后续大家有需求,我们也可以单独分析。
大家一定要认真看配置类的解析流程,大部分Spring核心功能都在这里触发启动的,比如
@PropertySources
、@ComponentScan
、@Import
、@Bean
等等,可以这么说,搞懂了ConfigurationClassPostProcessor
这个后置处理器,Spring你就会70%了,什么AOP、事务啥的,轻轻松松拿捏。小伙伴们一定要静下心来啃一啃,相信我,会有收获的。
好了,本来还想给小伙伴们分析一下Mybatis是怎么拓展Spring这一套扫描流程,实现Mapper文件的扫描、解析、注册的。篇幅确实有点太长了,大家反馈读一篇贰师兄的文章,比上一天班还累😂。找机会我们单独写吧,就不掺杂在这里面了,感兴趣的小伙伴也可自行研究一下,我们一起交流。
转载自:https://juejin.cn/post/7202135679228231740