目录
前言
如今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 beanFactory
IoC容器内的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 beanFactory
IoC容器完成,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容器,分了两步:
- 如果当前上下文已经存在IoC容器了,那么刷新该容器
- 获取容器,容器为空时创建一个新的并返回
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机制。