【小白的Spring源码手册】 Bean的扫描、装配和注册,面试学习可用


前言

如今Spring框架功能众多,每次打开Spring源码,要么就是自顶向下从整个框架来了解Spring整体流程,然后一不小心就陷入了细节无法自拔,要么就是从某个核心功能点看起,然后过了几分钟就陷入茫然。总之过程是十分的痛苦,结果却不尽人意。

这一次,我打算从最熟悉的功能模块IoC开始学习。IoC容器在Spring中扮演着重要作用,它控制了所有bean对象的整个生命周期。看源码时,接触到的第一个接口BeanFactory就是IoC容器的顶层接口,能控制对象生命周期;而ApplicationContext对前者进行了扩展,拥有事件发布、国际化信息支持等新特性。在这里,我打算先忽略IoC的顶层流程,根据Bean生成的过程,学习Bean在Spring中是如何被扫描、加载、生成、最后实例化。最后再将Bean的整个声明周期与IoC容器串起来。

通过扫描注解类的Bean实例化流程参考下图:
在这里插入图片描述

下面的源码解析中,省略了很多不必要的类、方法和代码块,将核心代码提了出来,以方便大家忽略非主流程的细节更专注于我们的核心流程。整个流程我已经提取出了可执行的核心代码,大家可以debug熟悉一下定义Bean的流程,地址见文末连接。





源码学习

Bean配置

spring要对bean进行管理,需要扫描相应配置,完成后进行统一管理,spring(5.0+)目前比较流程扫描方式为注解xml,相关配置如下:

1. 注解

配置包:

// spring
@ComponentScan(value = {"com.example.springreading",})
// spring boot,默认对启动类同包下的所有包进行扫描
@SpringBootApplication(scanBasePackages = {"com.example.springreading",})

在spring中,应用上下文会扫描被@Component @Repository @Controller @Service等被Component注解的类。只有被这些注解标注的类,才会被IoC容器管理。

AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext("com.example.springreading");

AnnotationConfigApplicationContext类中有两个关键字段:

  • AnnotatedBeanDefinitionReader reader:用于扫描指定class类(Class<?>… componentClasses)文件。
  • ClassPathBeanDefinitionScanner scanner:用于扫描指定包(String… basePackages)中文件。

它们两者的功能都是一样的,就是扫描类文件,并将Bean定义注册到内部的IoC容器。

// AnnotationConfigApplicationContext字段
private final AnnotatedBeanDefinitionReader reader;
private final ClassPathBeanDefinitionScanner scanner;
// AnnotationConfigApplicationContext父类GenericApplicationContext字段
private final DefaultListableBeanFactory beanFactory; // new DefaultListableBeanFactory()

注意:AnnotationConfigApplicationContext父类中是有维护一个IoC容器的,应用上下文的Bean操作都是通过该容器实现的。

通过类名初始化,AnnotationConfigApplicationContext类会通过reader来进行类扫描和注册,通过包名初始化,则会通过scanner

// reader
public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
	this();
	register(componentClasses);
	refresh();
}
// scanner
public AnnotationConfigApplicationContext(String... basePackages) {
	this();
	scan(basePackages);
	refresh();
}
// 无参构造
public AnnotationConfigApplicationContext() {
	StartupStep createAnnotatedBeanDefReader = this.getApplicationStartup().start("spring.context.annotated-bean-reader.create");
	this.reader = new AnnotatedBeanDefinitionReader(this);
	createAnnotatedBeanDefReader.end();
	this.scanner = new ClassPathBeanDefinitionScanner(this);
}



2. xml配置

配置包:

<!-- spring.xml配置文件 -->
<!-- 注解依赖扫描 加载@Component等注解标注的bean 这样不用在下面添加bean标签来手动生成bean实例-->
<context:component-scan base-package="com.example.springreading"/>

xml配置文件扫描

ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("/xml/bean.xml");
// XmlBeanDefinitionReader是其内部的bean定义扫描类
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(registry);

精力有限,本文仅学习注解有关类,xml原理是类似的,大家可以根据兴趣自行学习。





Bean扫描、装配、注册

这里选择通过包名初始化应用上下文,扫描、装配、注册等一系列操作均由ClassPathBeanDefinitionScanner scanner来实现的。

String[] basePackages = new String[]{"com.example.springreading"};
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(basePackages);



1. 扫描

scanner的扫描入口

int scan = scanner.scan(basePackages);
// 由doScan执行具体的扫描操作
doScan(basePackages);

遍历所有包,scanner会遍历所有包,依次加载包下面的所有class类

for (String basePackage : basePackages) {
	Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
	// ……
}	

findCandidateComponents方法中,会将扫描到的class文件读取为Resource资源,然后将Resource资源转为元数据读取器MetadataReader,最后通过包装元数据的方式创建BeanDefinition

源码如下:

A. Resource类载入Java class文件:

String packageSearchPath = "classpath*:" + "com/example/springreading" + '/' + "**/*.class";
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);

B. 生成元数据读取器(MetadataReader)

MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory();
// 遍历resources
MetadataReader metadataReader = metadataReaderFactory .getMetadataReader(resource);

在这里插入图片描述

C. 生成BeanDefinition:

ScannedGenericBeanDefinition beanDefinition= new ScannedGenericBeanDefinition(metadataReader);

到目前为止一个bean定义就被创建了,但是Bean的初始化还没完,我们还有很多bean相关的内容需要完善。



2. 装配BeanDefinition

BeanDefinition接口或实现类说明:

BeanDefinition:是Bean定义的顶层接口类。
AnnotatedBeanDefinition:该接口是BeanDefinition子类接口,用于承载注解内容。
ScannedGenericBeanDefinition:继承GenericBeanDefinition类,是AnnotatedBeanDefinition的实现之一。

在装配BeanDefinition这块,涉及到很多业务经常使用的注解,@Component、@Scope、@Lazy、@Primary、@DependsOn等注解都会在这个阶段被解析并装配进BeanDefinition。这些注解的解析和装配的过程类似。

源码参考:

A. @Scope(value = "singleton", proxyMode = ScopedProxyMode.DEFAULT)
设置作用域,这个bean作用范围是singleton还是prototype

ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver();
// 作用域元数据
ScopeMetadata scopeMetadata = scopeMetadataResolver.resolveScopeMetadata(beanDefinition);
// 设置作用域
beanDefinition.setScope(scopeMetadata.getScopeName());

作用域这个注解,是通过Spring的一套公用的注解提取工具提取的,原理是通过当前AnnotatedBeanDefinition元数据读取器 读取元数据,以得到注解类,最后根据注解名字得到指定的注解以及注解的字段内容。

在这里插入图片描述

ScopeMetadataResolver#resolveScopeMetadata内部实现如下:

ScopeMetadata metadata = new ScopeMetadata();
AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) beanDefinition;
// 获取元数据中的注解对象
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(annDef.getMetadata(), Scope.class);
// 获取注解属性值并设置到作用域类中
metadata.setScopeName(attributes.getString("value"));
metadata.setScopedProxyMode(attributes.getEnum("proxyMode"));

B. @Component(value = "businessService")

设置bean的实例名称:beanName,通过读取注解的value来获取bean名,大小写敏感。

除了Component,还有很多注解可以实现beanName设置。譬如大家常见的@Controller、@Service、@Repository等等。

// 单例模式的类名生成器
BeanNameGenerator beanNameGenerator = AnnotationBeanNameGenerator.INSTANCE;
String beanName = beanNameGenerator.generateBeanName(beanDefinition, context);

要实现通过注解设置BeanName,只需要实现如下几个条件之一:

  • 使用@Component注解,设置value值
  • 使用@Component注解注解的注解,类似@Service,拥有value属性并设值
  • 使用@ManagedBean或@Named(年代有些久远就不提了),有value属性并设值
boolean isStereotype = annotationType.equals("org.springframework.stereotype.Component") ||
				metaAnnotationTypes.contains("org.springframework.stereotype.Component") ||
				annotationType.equals("javax.annotation.ManagedBean") ||
				annotationType.equals("javax.inject.Named");
// 注解的属性值,要有string类型的value属性,且属性值不能为空
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(amd, type);

当上面的条件不满足时,beanName默认使用bean定义的类名,首字母小写

// 为空时的处理
String decapitalize = Introspector.decapitalize(ClassUtils.getShortName(Objects.requireNonNull(beanDefinition.getBeanClassName())));

C. 其它注解
@Lazy(value = false)
@DependsOn
@Primary
@Description(value = "Description of bean definition")
通过AnnotationConfigUtils工具来加载bean的一些通用bean注解,流程跟Scope注解是相同的。

AnnotationConfigUtils.processCommonDefinitionAnnotations(beanDefinition);
// 解析
AnnotationAttributes lazy = attributesFor(metadata, Lazy.class);
// ……



3. 校验BeanDefinition

在BeanDefinition装配完成后,需要通过AnnotationConfigApplicationContext context来校验是否存在beanName

// DefaultListableBeanFactory#containsBeanDefinition
boolean containsBeanDefinition = context.containsBeanDefinition(beanName);

在上下文校验代码中,由DefaultListableBeanFactory beanFactoryIoC容器内的Map<String, BeanDefinition> beanDefinitionMap完成最终校验(BeanDefinition最终都会被注册到beanDefinitionMap中)。

context.getBeanFactory().containsBeanDefinition(beanName);
// IoC容器中保存初始Bean定义的缓存
// Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
// this.beanDefinitionMap.containsKey(beanName)



4. 注册BeanDefinition

现在Bean的扫描、加载、定义、装配乃至校验都做完了,可以完成注册了。

// 使用BeanDefinitionHolder来包装,如果有别名的话,这里会添加Bean的别名名称,但是通过注解类扫描的Bean,没有别名
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(beanDefinition, beanName);

注册和校验相同,均由DefaultListableBeanFactory beanFactoryIoC容器完成,beanFactory会将该BeanDefinition保存至beanDefinitionMap中。

// 注册方法
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, context);
// AnnotationConfigApplicationContext#registerBeanDefinition
context.registerBeanDefinition(beanName, beanDefinition);
// DefaultListableBeanFactory#registerBeanDefinition
beanFactory.registerBeanDefinition(beanName, beanDefinition);
// 最后添加至缓存中
this.beanDefinitionMap.put(beanName, beanDefinition);

refresh流程

1. 总体流程

refresh的一些关键流程如下:

public void refresh() {
	// 准备应用上下文,以保证刷新进行,这里将会设置启动日期、活动标志(容器是否处理某个处理事件中)等等
	prepareRefresh();
	// 初始化一个IoC容器(或清理已经存在的IoC容器),就是应用上下文中的beanFactory
	ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
	// 为IoC容器配置标准特征,比如ClassLoader和默认后置处理器
	prepareBeanFactory(beanFactory);
	// 这里实际上是应用上下文AnnotationConfigApplicationContext,
	// 在初始化IoC容器之后,应用上下文直接对IoC容器做的后置处理(这里执行内容为空,主要用于子类扩展)
	postProcessBeanFactory(beanFactory);
	// 应用上下文通过BFPP对IoC容器做的后置处理
	invokeBeanFactoryPostProcessors(beanFactory);
	// 注册BPP,实例化过程中才会执行BPP
	registerBeanPostProcessors(beanFactory);
	// 初始化消息,MessageSource用于消息的参数化和国际化
	initMessageSource();
	// 初始化事件组播器,ApplicationEventMulticaster,用于管理一系列ApplicationListener对象
	initApplicationEventMulticaster();
	onRefresh();
	// 注册监听器并交由事件组播器管理,ApplicationListener,监听器
	registerListeners();
	// IoC容器实例化所有非懒加载的单例Bean
	finishBeanFactoryInitialization(beanFactory);
	finishRefresh();
}	

我们不必每个方法都要追根究底的去了解清楚,就简单提几个关键流程就行。



2. obtainFreshBeanFactory

这一步用于初始化IoC容器,分了两步:

  1. 如果当前上下文已经存在IoC容器了,那么刷新该容器
  2. 获取容器,容器为空时创建一个新的并返回
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
	refreshBeanFactory();
	return getBeanFactory();
}

当然AnnotationConfigApplicationContext内部的刷新代码没有任何清理操作,因为该应用上下文仅允许执行一次刷新和实例化操作,并且其内部仅维护了单例beanFactory,所以在获取该容器前不需要对该容器进行清理。

protected final void refreshBeanFactory() throws IllegalStateException {
	if (!this.refreshed.compareAndSet(false, true)) {
		throw new IllegalStateException(
				"GenericApplicationContext does not support multiple refresh attempts: just call 'refresh' once");
	}
	this.beanFactory.setSerializationId(getId());
}

有关清理的代码实现可以参考ClassPathXmlApplicationContext容器(XML应用上下文),它的清理逻辑在其父类AbstractRefreshableApplicationContext#refreshBeanFactory中实现。



3. invokeBeanFactoryPostProcessors

PostProcessorRegistrationDelegate这个委托类,是AbstractApplicationContext专门用来注册、处理、执行后处理器类的。invokeBeanFactoryPostProcessors方法中,主要任务有:

  • 从IoC容器(beanFactory)中实例化特殊的BFPP类(BeanDefinitionRegistryPostProcessor),并与应用上下文中(context)注册的BFPP类进行合并
  • 按特定顺序执行所有BFPP类
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
	// 执行应用上下文中的BFPP,同时注册、执行IoC容器中的BFPP
	PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
	// ……
}



4. registerBeanPostProcessors

同样使用PostProcessorRegistrationDelegate类,registerBeanPostProcessors内容比较简单,主要内容有:

  • 提取IoC容器中的BPP类定义,并将其实例化
  • 按指定顺序将BPP 注册进IoC容器的后置处理器容器List<BeanPostProcessor> beanPostProcessors
protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {
	PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);
}



5. finishBeanFactoryInitialization

这个方法最终完成IoC容器的剩余内容:初始化、实例化IoC容器中所有剩余的单例bean。我们注册的BPP也将在次方法中完成对Bean生命周期的管理。

注意这里是剩余的单例Bean,因为在此之前,BFPP、BPP类型的Bean将会被提前实例化。

finishBeanFactoryInitialization是上下文实例化对象的核心方法。不过暂时不在本文的讨论范围内,后续Bean实例化内容会进行详细解读。








总结

有关Bean的扫描、装配、校验到包装到此就结束了,本文的重点虽然不在IoC容器,但是我们在进行Bean定义的过程中,我们仍然会接触到它,以小见大,从一些基础的功能开始慢慢了解整个Spring框架,我觉得这是一个很好的立足点。

接下来,我会从IoC容器实例化Bean的流程来进一步了解整个Spring IoC机制。

DEMO代码:https://github.com/VsLegend/spring-reading.git

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值