目录
- 前言
- 源码学习
- Bean配置
- 1. 注解
- 2. xml配置
- Bean扫描、装配、注册
- 1. 扫描
- 2. 装配BeanDefinition
- 3. 校验BeanDefinition
- 4. 注册BeanDefinition
- 总结
前言
如今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);
总结
有关Bean的扫描、装配、校验到包装到此就结束了,本文的重点虽然不在IoC容器,但是我们在进行Bean定义的过程中,我们仍然会接触到它,以小见大,从一些基础的功能开始慢慢了解整个Spring框架,我觉得这是一个很好的立足点。
接下来,我会从IoC容器实例化Bean的流程来进一步了解整个Spring IoC机制。
DEMO代码:https://github.com/VsLegend/spring-reading.git