Spring源码:Bean生命周期(三)
前言
在之前的文章中,我们已经对 bean 的准备工作进行了讲解,包括 bean 定义和 FactoryBean 判断等。在这个基础上,我们可以更加深入地理解 getBean 方法的实现逻辑,并在后续的学习中更好地掌握createBean 方法的实现细节。
getBean用法
讲解getBean方法之前,我们先来看看他有几种常见的用法:
// 创建一个Spring容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
UserService bean1 = applicationContext.getBean(UserService.class);
UserService bean2 = (UserService)applicationContext.getBean("userService");
UserService bean3 = applicationContext.getBean("userService",UserService.class);
UserService bean4 = (UserService) applicationContext.getBean("userService",new OrderService());
bean1.test();
bean2.test();
bean3.test();
bean4.test();
关于获取 bean 的方法,前两种方法应该比较常见,这里就不再赘述。第三种方法实际上是在获取 bean 的时候,会先判断是否符合指定的类型,如果符合,则进行类型转换并返回对应的 bean 实例。第四种方法则是在创建 bean 实例时,通过推断构造方法的方式来选择使用带有参数的构造方法进行实例化。
如果我们想要让第四种方法生效,可以考虑使用多例的形式,即通过设置 scope 属性为 prototype 来实现。这样,每次获取 bean 时,都会创建新的 bean 实例,从而可以触发使用带有参数的构造方法进行实例化,比如这样:
@Component
@Scope("prototype")
public class UserService {
public UserService(){
System.out.println(0);
}
public UserService(OrderService orderService){
System.out.println(1);
}
public void test(){
System.out.println(11);
}
}
getBean大体流程
由于方法代码太多,我就不贴代码了,我这边只贴一些主要的伪代码,方便大家阅读,然后我在对每个流程细讲下:
protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
// name有可能是 &xxx 或者 xxx,如果name是&xxx,那么beanName就是xxx
// name有可能传入进来的是别名,那么beanName就是id
String beanName = transformedBeanName(name);
Object beanInstance;
// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
// 如果sharedInstance是FactoryBean,那么就调用getObject()返回对象
}
else {
//检查是否本beanfactory没有当前bean定义,查看有父容器,如果有,则调用父容器的getbean方法
try {
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
// 检查BeanDefinition是不是Abstract的
checkMergedBeanDefinition(mbd, beanName, args);
// Guarantee initialization of beans that the current bean depends on.
//查看是否有dependsOn注解,如果存在循环依赖则报错
// Create bean instance.
if (mbd.isSingleton()) {
//调用createBean方法
//如果是FactoryBean则调用getObject
}
else if (mbd.isPrototype()) {
//调用createBean方法,与单例只是前后逻辑不一样
//如果是FactoryBean则调用getObject
}
else {
Scope不同类型有不同实现
//调用createBean方法,与单例只是前后逻辑不一样
//如果是FactoryBean则调用getObject
}
}catch{
......
}
}
// 检查通过name所获得到的beanInstance的类型是否是requiredType
return adaptBeanInstance(name, beanInstance, requiredType);
}
单例缓存池
在 Spring 中,不管传入的 beanName 是多例的还是单例的,都会先从单例缓存池中获取。有些人可能会觉得这样做会浪费一些性能,但实际上 Spring 考虑到了大部分托管的 bean 都是单例的情况,因此忽略了这一点性能。实际上,这样的性能消耗并不大。可以将其类比于 Java 的双亲委派机制,都会先查看本加载器是否有缓存,如果没有再向父加载器去加载。
parentBeanFactory
在分析 bean 定义是如何创建的时,我们可以不考虑单例缓存池中获取对象的情况,而是逐步分析 bean 定义是如何创建的。在这个过程中,即使存在 parentBeanFactory,我们也可以跳过它,因为我们的启动容器并没有设置任何父容器。源码也很简单,如果本容器没有 bean 定义,就直接调用父容器的 getBean 相关方法:
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
// Not found -> check parent.
// &&&&xxx---->&xxx
String nameToLookup = originalBeanName(name);
if (parentBeanFactory instanceof AbstractBeanFactory) {
return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
nameToLookup, requiredType, args, typeCheckOnly);
}
else if (args != null) {
// Delegation to parent with explicit args.
return (T) parentBeanFactory.getBean(nameToLookup, args);
}
else if (requiredType != null) {
// No args -> delegate to standard getBean method.
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
else {
return (T) parentBeanFactory.getBean(nameToLookup);
}
}
dependsOn
在调用 getBean 方法之前,已经将合并的 bean 定义存入了容器中。因此,我们可以直接获取已经合并好的 bean 定义,并解析 bean 定义上的 dependsOn 注解。具体的源码逻辑如下:
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
// 检查BeanDefinition是不是Abstract的
checkMergedBeanDefinition(mbd, beanName, args);
// Guarantee initialization of beans that the current bean depends on.
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
// dependsOn表示当前beanName所依赖的,当前Bean创建之前dependsOn所依赖的Bean必须已经创建好了
for (String dep : dependsOn) {
// beanName是不是被dep依赖了,如果是则出现了循环依赖
if (isDependent(beanName, dep)) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
}
// dep被beanName依赖了,存入dependentBeanMap中,dep为key,beanName为value
registerDependentBean(dep, beanName);
// 创建所依赖的bean
try {
getBean(dep);
}
catch (NoSuchBeanDefinitionException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
}
}
}
这里的逻辑还是相对简单的。如果当前 bean 被 dependsOn 注解所依赖,那么会先去创建所依赖的 bean。但是这种方式是解决不了循环依赖的问题的。在实现上,只使用了两个 Map 进行判断:
// 某个Bean被哪些Bean依赖了
private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap<>(64);
// 某个Bean依赖了哪些Bean
private final Map<String, Set<String>> dependenciesForBeanMap = new ConcurrentHashMap<>(64);
isSingleton
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
在这个阶段,我们可以看到代码已经在准备创建单例 bean 实例了,因此我们可以不去深入理解这部分的源码逻辑。反正,在 getSingleton 方法中,会调用 createBean 方法。这里使用了 lambda 表达式,如果有不太了解的读者,可以参考下之前发的文章进行学习:
isPrototype
if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
在这个阶段,我们发现创建 bean 的方法已经改变了,直接调用了 createBean 方法,而不是通过 getSingleton 方法进行调用。至于 beforePrototypeCreation 和 afterPrototypeCreation,我们可以不用管它们,因为它们只是存储一些信息,对我们创建 bean 并没有太大的影响。
其他Scope
讲解这部分源码之前,我们先来看看还有哪些Scope域:
//@RequestScope
@SessionScope
public class User {
}
现在我们来看一下 RequestScope 和 SessionScope,它们与其他作用域类似,只是一个组合注解。它们的元注解信息如下:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {
/**
* Alias for {@link Scope#proxyMode}.
* <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
*/
@AliasFor(annotation = Scope.class)
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}
然后我们再来看下Spring对其他Scope注解的逻辑判断:
String scopeName = mbd.getScope();
if (!StringUtils.hasLength(scopeName)) {
throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'");
}
Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try { // session.getAttriute(beaName) setAttri
Object scopedInstance = scope.get(beanName, () -> {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
});
beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
catch (IllegalStateException ex) {
throw new ScopeNotActiveException(beanName, scopeName, ex);
}
其实他主要用的getAttriute方法,我们看下scope.get主要的逻辑判断:
public Object get(String name, ObjectFactory<?> objectFactory) {
RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
Object scopedObject = attributes.getAttribute(name, getScope());
if (scopedObject == null) {
scopedObject = objectFactory.getObject();
attributes.setAttribute(name, scopedObject, getScope());
// Retrieve object again, registering it for implicit session attribute updates.
// As a bonus, we also allow for potential decoration at the getAttribute level.
Object retrievedObject = attributes.getAttribute(name, getScope());
if (retrievedObject != null) {
// Only proceed with retrieved object if still present (the expected case).
// If it disappeared concurrently, we return our locally created instance.
scopedObject = retrievedObject;
}
}
return scopedObject;
}
在这个阶段,我们可以看到,通过 objectFactory.getObject() 方法,会调用外层定义的 lambda 表达式,也就是 createBean 方法的逻辑。假设这个过程成功地创建了 bean 实例,并返回了它,那么 Spring 会调用 setAttribute 方法,将这个 bean 实例以及其 scope 值放入以 beanName 为 key 的属性中。这样,当需要获取这个 bean 实例时,Spring 就可以直接从作用域中获取了。
结语
getBean 方法主要包含以下几个步骤:
- 首先,从单例缓存池中获取 bean 实例。如果没有,Spring 会创建新的 bean 实例,并将其添加到单例缓存池中。
- 接着,Spring 会检查当前容器是否有指定名称的 bean 定义。如果没有,Spring 会调用父容器的 getBean 方法,直到找到为止。
- 一旦找到了 bean 定义,Spring 会根据不同的作用域类型,创建对应的 bean 实例,并将其存储在作用域中。
- 最后,Spring 会返回创建好的 bean 实例。
非常好,这样我们对 getBean 方法的逻辑判断有了一个大体的了解,有助于我们更好地理解 createBean 方法的实现细节。如果在后续的学习中有任何问题或疑问,可以随时联系我进行咨询。

Spring源码:Bean生命周期(三)的更多相关文章
- Spring源码-Bean生命周期总览
- spring(二、bean生命周期、用到的设计模式、常用注解)
spring(二.bean生命周期.用到的设计模式.常用注解) Spring作为当前Java最流行.最强大的轻量级框架,受到了程序员的热烈欢迎.准确的了解Spring Bean的生命周期是非常必要的. ...
- Spring源码学习-容器BeanFactory(三) BeanDefinition的创建-解析Spring的默认标签
写在前面 上文Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作中Spring对XML解析后创建了对应的Docum ...
- spring源码-bean之增强初始化-3
一.ApplicationContext的中文意思是“应用上下文”,它继承自BeanFactory接口,除了包含BeanFactory的所有功能之外,在国际化支持.资源访问(如URL和文件).事件传播 ...
- 【Spring系列】- Bean生命周期底层原理
Bean生命周期底层原理 生命不息,写作不止 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 一个有梦有戏的人 @怒放吧德德 分享学习心得,欢迎指正,大家一起学习成长! 前言 上次学到动 ...
- Spring事务,Bean生命周期
一.事务相关: 1.Spring事务基于Spring AOP切面编程: 2.AOP基于代理模式,得到需要开启事务的代码的代理对象: 3.而没有开启事务的Service方法里调用了开启事务 @Trans ...
- Spring(四)之Bean生命周期、BeanPost处理
一.Bean 生命周期 Spring bean的生命周期很容易理解.当bean被实例化时,可能需要执行一些初始化以使其进入可用状态.类似地,当不再需要bean并从容器中移除bean时,可能需要进行一些 ...
- TOMCAT源码分析——生命周期管理
前言 从server.xml文件解析出来的各个对象都是容器,比如:Server.Service.Connector等.这些容器都具有新建.初始化完成.启动.停止.失败.销毁等状态.tomcat的实现提 ...
- Spring源码--Bean的管理总结(一)
前奏 最近看了一系列解析spring管理Bean的源码的文章,在这里总结下,方便日后复盘.文章地址https://www.cnblogs.com/CodeBear/p/10336704.html sp ...
- spring容器对bean生命周期的管理三中方式
spring容器对bean的生命周期管理主要在两个时间点:bean的初始化完成(包括属性值被完全注入),bean的销毁(程序结束,或者引用结束)方式一:使用springXML配置中的init-meth ...
随机推荐
- C/C++ 异常处理机制(例:文件拷贝)
异常是一种程序控制机制,与函数机制互补. 函数是一种以栈结构展开的上下函数衔接的程序控制系统,异常是另一种控制结构,它可以在出现"意外"时中断当前函数,并以某种机制(类型匹配)回馈 ...
- jenkins启动失败,查看状态提示active(exited)
chown -R jenkins:jenkins /var/lib/jenkins chown -R jenkins:jenkins /var/cache/jenkins chown -R jenki ...
- 转发:前端组件化之Monorepo方案实战
前言 在上一篇的前端组件化方案探究中,我们研究了什么是组件化以及我们为什么需要组件化.也调研和测试了一些开源项目,并且在使用.学习.研究.对比之后最终确定了以 pnpm + workspace + c ...
- 3---java中的集合
集合是什么:表示一组元素的对象,有的是有序的,有的是无序的,有的是可重复的,有的是不可重复的. 首先根是:Collection 1:Set 没有重复元素 SortedSet 有序的Set 2:Lis ...
- unidbgrid显示图片
column设置imageoptions属性,visible=true,设置width
- Linux shell $相关的变量
代码示例1 点击查看代码 user="yaya" echo ${user} #在已定义过的变量前加$符号便可使用该变量,{}是可选的 yaya #输出结果 代码示例2 点击查看代码 ...
- AI来实现代码转换!Python转Java,Java转Go不在话下?
今天看到个有趣的网站,给大家分享一下. 该网站的功能很神奇,可以实现编程语言的转化.感觉在一些场景之下还是有点作用的,比如你原来跟我一样是做Java的,因为工作需要突然转Go.这个时候用你Java的经 ...
- Python练习-3.12
1.给文章中的手机号打上马赛克 也就是在文章中发现手机号之后,用*或者#等这一类无法将手机号直接识别出来的符号代替 # 文章中手机号的马赛克形式化 import re content="白日 ...
- Gym 101490K Safe Racing (dp转换, 超超超级详细,包你看懂)
题意:给你一个长为L的圆形跑道,让你放置警示牌,相邻两个警示牌相隔距离不能超过S,让你求有多少种方案数放置.数据L,S都是1e6. 来个例子:L = 13, S = 5.一个圈表示长度为1. 思路:因 ...
- Yolov5——训练目标检测模型
项目的克隆 打开yolov5官网(GitHub - ultralytics/yolov5 at v5.0),下载yolov5的项目: 环境的安装(免额外安装CUDA和cudnn) 打开anaconda ...