读完这篇文章你将会收获到

  • getBean 方法中, Spring 处理别名以及 factoryBeanname
  • Spring 如何从多级缓存中根据 beanName 获取 bean
  • Spring 如何处理用户获取普通 beanfactoryBean

引言

Spring 容器的初始化 中,我们了解到 Spring 是如何将 XML 文件转换为 BeanDefinition 并注册到 BeanDefinitionRegstry

今天我们一起继续学习 Springbean 加载

public static void main(String[] args) {
Resource resource = new ClassPathResource("coderLi.xml");
DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(defaultListableBeanFactory);
xmlBeanDefinitionReader.loadBeanDefinitions(resource);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
<bean class="com.demo.data.Person">
<description>
微信搜一搜:CoderLi
</description>
</bean>
</beans>

熟悉的味道、熟悉的配方

源码分析

我们可以在上面的 Java 代码中加入以下的代码

System.out.println(defaultListableBeanFactory.getBean("com.demo.data.Person#0"));

我们根据默认的 beanName 从 Spring 容器中获取 Person 这个 bean 对象,当然我们可以使用默认的别名获取

System.out.println(defaultListableBeanFactory.getBean("com.demo.data.Person"));

对 Spring 别名不熟悉的朋友可以先看下我的这一篇文章 Spring-AliasRegistry

我们直接进入到 AbstractBeanFactory#getBean(String) 方法中, AbstractBeanFactoryDefaultListableBeanFactory 的父类

@Override
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}

可以看到 do 开头的才是真正干活的老大 , AbstractBeanFactory#doGetBean

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { // 找到这个参数的 bean name
final String beanName = transformedBeanName(name);
Object bean; // Eagerly check singleton cache for manually registered singletons.
// 检查缓冲中是否有这个bean、spring中只是保存单例的bean
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
// 这里被我删除了一些spring 的log
// 处理一下 factory bean 的情况、包括从 factory beans 的缓存中获取、或者重新调用 factory bean 的 get bean 方法 包括一些回调
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
..........
...........

因为这个方法实在太长了所以截取一部分、我们一步步来分析

transformedBeanName(name) 这个方法就是将我们的 name 转换为真正的 beanName,因为我们传进来的参数可能是一个 alias 或者可能是一个 factoryBean 的 beanName (前缀为&),而我们在 Spring 中存放的 factoryBean 的 beanName 是没有 & 前缀的,所以需要处理掉这个前缀

protected String transformedBeanName(String name) {
return canonicalName(BeanFactoryUtils.transformedBeanName(name));
}
public static String transformedBeanName(String name) {
Assert.notNull(name, "'name' must not be null");
// 是否是一个 factory bean 、如果不是的话就直接返回
if (!name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
return name;
}
// 如果是的话就将其前缀 & 去掉
return transformedBeanNameCache.computeIfAbsent(name, beanName -> {
do {
beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
}
while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));
return beanName;
});
}
/**
* 找到这个别名的最终的 bean Name、如果没有的话(
* 也就是说参数中的name 就是人家的 bean name 那么就直接返回这个 参数就行了)
*
*/
public String canonicalName(String name) {
String canonicalName = name;
// Handle aliasing...
String resolvedName;
do {
resolvedName = this.aliasMap.get(canonicalName);
if (resolvedName != null) {
canonicalName = resolvedName;
}
}
while (resolvedName != null);
return canonicalName;
}

让我们再看看下一个方法 DefaultSingletonBeanRegistry#getSingleton(String)

public Object getSingleton(String beanName) {
// allowEarlyReference 允许早期依赖
return getSingleton(beanName, true);
}
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName);
// 这个bean 正处于 创建阶段
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 并发控制
synchronized (this.singletonObjects) {
// 单例缓存是否存在
singletonObject = this.earlySingletonObjects.get(beanName);
// 是否运行获取 bean factory 创建出的 bean
if (singletonObject == null && allowEarlyReference) {
// 获取缓存中的 ObjectFactory
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
// 将对象缓存到 earlySingletonObject中
this.earlySingletonObjects.put(beanName, singletonObject);
// 从工厂缓冲中移除
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}

上面的代码就是 Spring 尝试从缓存中加载单例。单例在 Spring 的同一个容器中只会被创建一次,后续再获取 bean,就直接从缓存中取了。

在介绍这个方法之前、我们先认识下 DefaultSingletonBeanRegistry 这个类里面的成员变量吧

  • Map<String, Object> singletonObjects 这个很好理解、 key 就是 beanName ,value 就是 bean 实例
  • Map<String, ObjectFactory<?>> singletonFactories key 为 beanName,value 为创建 bean 的工厂
  • Map<String, Object> earlySingletonObjects key 为 beanName ,value 为 bean。但是和 singletonObjects 不同的是,bean 被加入到 earlySingletonObjects 的时候、这个 bean 还是处于一种创建中的状态,目的也很简单、Spring 用来解决某些场景下的循环依赖

我们再回到代码中、分析一下它的逻辑

  1. 先从 singletonObjects 中尝试获取 bean,这里存放的是已经创建好的 bean 了、如果在这里能知道、那当然是最好啦
  2. 如果在这里找不到的话、那么我们就要判断下这个 beanName 对应的 bean 是否正在创建中
  3. 如果是的话,那么我们再看看这个正在创建的 bean 是否已经曝光出来、如果没有的话、那么就要看看我们的参数是否允许依赖早期的 bean 了、
  4. 如果允许早期依赖、那么我们就尝试冲 ObjectFactory 中获取到对应的 bean、并将它放入到 earlySingletonObjects 中、并从 singletonFactories 中移除

类似多级缓存的设计

在上面的方法中我们看到 isSingletonCurrentlyInCreation(beanName) 这个方法、

public boolean isSingletonCurrentlyInCreation(String beanName) {
return this.singletonsCurrentlyInCreation.contains(beanName);
}

singletonsCurrentlyInCreation 这个 Set 中,当创建一个 bean 之前会将其 对应的 beanName 放置到这个 Set 中、后面的分析会涉及到、这里先提一嘴

我们第一次获取这个 bean 、返回为 null 是正常的

那假如我们在代码中 getBean 了两次

defaultListableBeanFactory.getBean("com.demo.data.Person#0")
defaultListableBeanFactory.getBean("com.demo.data.Person#0")

那么针对第二次的调用、返回的值就不是为 null 了

 Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
// 处理一下 factory bean 的情况、包括从 factory beans 的缓存中获取、或者重新调用 factory bean 的 get bean 方法 包括一些回调
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}

我们先假设 sharedInstance 不为 null 也就是我们第二次调用 getBean ,我们进入到 getObjectForBeanInstance 方法中

protected Object getObjectForBeanInstance(
Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) { // 我王大锤就是想要一个 factory bean
if (BeanFactoryUtils.isFactoryDereference(name)) { // 如果这个是 NullBean 类型、表示这是一个 null 的 instance、直接返回
if (beanInstance instanceof NullBean) {
return beanInstance;
}
// 获取到的 beanInstance 不是一个 factory、但是你tm name 又带有这个 & 很迷惑啊兄弟
if (!(beanInstance instanceof FactoryBean)) {
throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass());
} if (mbd != null) {
mbd.isFactoryBean = true;
}
return beanInstance;
} // 王大锤不想要factory bean、并且spring 也帮他找到了一个普通的 bean、直接返回
if (!(beanInstance instanceof FactoryBean)) {
return beanInstance;
} // 王大锤要的是一个普通的bean 、但是spring 给他找到了一个 factory的bean、那么spring 是不是要做一些额外的处理 给王大锤返回一个普通的bean
Object object = null; if (mbd != null) {
mbd.isFactoryBean = true;
} else {
// 从缓存中 看看有没有
object = getCachedObjectForFactoryBean(beanName);
} // 如果 bean factory 中还是没有
if (object == null) {
// Return bean instance from factory.
FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
// 从 缓存中拿到 bean definition
if (mbd == null && containsBeanDefinition(beanName)) {
mbd = getMergedLocalBeanDefinition(beanName);
}
// 是否是用户定义的还是程序本身需要创建的bean
boolean synthetic = (mbd != null && mbd.isSynthetic()); object = getObjectFromFactoryBean(factory, beanName, !synthetic);
}
return object;
}

我们按步骤分析下上面的代码

  1. 我们调用 getBean(name) 中的 name 如果包含前缀 & ,表面我们是想要从 Spring 中获取一个 FactoryBean ,那么我们就要判断我们从缓存中获取的 beanInstance 是否是 一个 FactoryBean 、如果是的话就直接返回不是的话就要抛出异常了
  2. 我们想要的是一个非 factoryBean 并且 在 spring 容器中找到了非 factoryBean 的 bean、那么就直接返回
  3. 我们想要的是一个 非 factoryBean 但是在 spring 容器中找到了一个 factoryBean 的 bean、那么就要进入到 getObjectFromFactoryBean 方法中了
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
// 为单例模式且缓存中存在
if (factory.isSingleton() && containsSingleton(beanName)) { synchronized (getSingletonMutex()) {
// 从缓存中获取指定的 bean(这个bean 是从 factory bean 创建出来的)
Object object = this.factoryBeanObjectCache.get(beanName); if (object == null) {
// 为空则从 factory bean 中获取对象
object = doGetObjectFromFactoryBean(factory, beanName);
// 从缓存中获取
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
if (alreadyThere != null) {
// 已经存放到 缓存中了、后续的操作就不需要了
object = alreadyThere;
} else {
// 需要做一些后置处理
if (shouldPostProcess) {
// 如果这个bean正在创建中、
if (isSingletonCurrentlyInCreation(beanName)) {
return object;
}
// 前置处理 主要是将这个bean 加入到正在创建中的队列 singletonsCurrentlyInCreation
beforeSingletonCreation(beanName);
try {
// 对 从 factoryBean 获取的对象进行后处理
// 生成对象将暴露给 bean 引用 并回调 beanPostProcessor
object = postProcessObjectFromFactoryBean(object, beanName);
} catch (Throwable ex) {
throw new BeanCreationException(beanName,
"Post-processing of FactoryBean's singleton object failed", ex);
} finally {
// 后置处理 将其从 singletonsCurrentlyInCreation 移除
afterSingletonCreation(beanName);
}
}
// 他的 factory bean 已经存在 缓存中了、那么这个 factory bean 产生的bean 应该也要缓存一下
if (containsSingleton(beanName)) {
this.factoryBeanObjectCache.put(beanName, object);
}
}
} return object;
}
} else {
// 非单例
Object object = doGetObjectFromFactoryBean(factory, beanName); if (shouldPostProcess) {
try {
//
object = postProcessObjectFromFactoryBean(object, beanName);
} catch (Throwable ex) {
throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", ex);
}
}
return object;
}
}

啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊、代码很长长长..........我们一点点来分析

  1. 第一步就是判断这个 factoryBean 是否是单例、如果不是的话,并且是用户自己定义的 bean、那么就需要调用 postProcessObjectFromFactoryBean 方法去做一个后续的处理

    1. 这里面最终回调的就是我们常用的一个接口 BeanPostProcessor
    public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
    throws BeansException { Object result = existingBean;
    for (BeanPostProcessor processor : getBeanPostProcessors()) {
    Object current = processor.postProcessAfterInitialization(result, beanName);
    if (current == null) {
    return result;
    }
    result = current;
    }
    return result;
    }
  2. 如果这 beanFactory 是一个单例,那我们就看看 factoryBeanObjectCache ( key 是 beanName,value 是 beanFactory 产生出来的 object 也是我们正要获取的 bean ) 这个 Map 中是否存在这个 beanName 这个 bean

  3. 如果存在的话、就直接返回、如果不存在的话、那就 doGetObjectFromFactoryBean ,从这个方法中使用 FactoryBean#getObject 产生 bean

  4. 其实下面这段代码确实让人看不懂哦

    Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
    if (alreadyThere != null) {
    // 已经存放到 缓存中了、后续的操作就不需要了
    object = alreadyThere;
    }
  5. 然后我们看到 beforeSingletonCreation 这个方法、就是上面 getSingletonisSingletonCurrentlyInCreation 判断一个 bean 是否处于正在创建中

    protected void beforeSingletonCreation(String beanName) {
    if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
    throw new BeanCurrentlyInCreationException(beanName);
    }
    }
    public boolean isSingletonCurrentlyInCreation(String beanName) {
    return this.singletonsCurrentlyInCreation.contains(beanName);
    }
  6. 然后又调用到 postProcessObjectFromFactoryBean 方法、最终回调的就是我们常用的一个接口 BeanPostProcessor

  7. 最好调用 afterSingletonCreation(beanName) 方法、将其从 正在创建中的 bean 的集合中移除、最后的最后、将其加入到 factoryBeanObjectCache 集合中

今天我们就先分析到这里、后续的话我们在后面的文章继续探讨。今天我们大致分析了 getBean 里面的这三个方法

总结

  • 根据参数中的 name 找出对应的 beanName、无论这个 name 是别名或者是一个 factoryBean 的 beanName
  • 查看缓存中是否包含这个 beanName 对象
    • 先从一级缓存 singletonObjects 中看看有没有
    • 然后从二级缓存 earlySingletonObjects
    • 都没有的话再从三级缓存 singletonFactories 中看看有没有
  • 如果缓存中有 bean、那么我们还是需要处理一下这个 bean
    • 如果 Spring 缓存中返回的 bean 是 factoryBean、而用户也想要的是一个 beanFactory (参数 name 中的前缀是 & )、那么我们直接返回
    • 如果 Spring 缓存中返回的 bean 是普通的 bean、而用户也想要的是一个普通的 bean 、那么就直接返回
    • 如果 Spring 缓存中返回的 bean 是一个 factoryBean、而用户想要的是一个普通的 bean 、那么我们就要从 factoryBean 中获取这个 bean
    • 而从 factoryBean 中获取这个 bean的过程中、需要调用到前置处理、后置处理和我们常用的接口回调 BeanPostProcessor

上面的三个方法大致流程就是这样、希望对各位有帮助

有兴趣进入群聊、一起交流一起划水

Spring 获取单例流程(一)的更多相关文章

  1. Spring 获取单例流程(三)

    读完这篇文章你将会收获到 Spring 何时将 bean 加入到第三级缓存和第一级缓存中 Spring 何时回调各种 Aware 接口.BeanPostProcessor .InitializingB ...

  2. Spring 获取单例流程(二)

    读完这篇文章你将会收获到 Spring 中 prototype 类型的 bean 如何做循环依赖检测 Spring 中 singleton 类型的 bean 如何做循环依赖检测 前言 继上一篇文章 S ...

  3. Spring IOC 容器源码分析 - 获取单例 bean

    1. 简介 为了写 Spring IOC 容器源码分析系列的文章,我特地写了一篇 Spring IOC 容器的导读文章.在导读一文中,我介绍了 Spring 的一些特性以及阅读 Spring 源码的一 ...

  4. Spring源码分析(十五)获取单例

    本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 之前我们讲解了从缓存中获取单例的过程,那么,如果缓存中不存在已经加载的单例be ...

  5. 5.2:缓存中获取单例bean

    5.2  缓存中获取单例bean 介绍过FactoryBean的用法后,我们就可以了解bean加载的过程了.前面已经提到过,单例在Spring的同一个容器内只会被创建一次,后续再获取bean直接从单例 ...

  6. Spring源码分析(十三)缓存中获取单例bean

    摘要:本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 介绍过FactoryBean的用法后,我们就可以了解bean加载的过程了 ...

  7. 【转】Spring Bean单例与线程安全

    一.Spring单例模式及线程安全 Spring框架中的Bean,或者说组件,获取实例的时候都是默认单例模式,这是在多线程开发的时候需要尤其注意的地方. 单例模式的意思是只有一个实例,例如在Sprin ...

  8. Spring Bean单例与线程安全

    一.Spring单例模式及线程安全 Spring框架中的Bean,或者说组件,获取实例的时候都是默认单例模式,这是在多线程开发的时候需要尤其注意的地方. 单例模式的意思是只有一个实例,例如在Sprin ...

  9. Spring的单例实现原理-登记式单例

    单例模式有饿汉模式.懒汉模式.静态内部类.枚举等方式实现,但由于以上模式的构造方法是私有的,不可继承,Spring为实现单例类可继承,使用的是单例注册表的方式(登记式单例). 什么是单例注册表呢, 登 ...

随机推荐

  1. Java实现 蓝桥杯VIP 算法提高 士兵排队问题

    算法提高 士兵排队问题 时间限制:1.0s 内存限制:256.0MB 试题 有N个士兵(1≤N≤26),编号依次为A,B,C,-,队列训练时,指挥官要把一些士兵从高到矮一次排成一行,但现在指挥官不能直 ...

  2. 第六届蓝桥杯JavaC组国(决)赛真题

    解题代码部分来自网友,如果有不对的地方,欢迎各位大佬评论 题目1.机器人数目 少年宫新近邮购了小机器人配件,共有3类,其中, A类含有:8个轮子,1个传感器 B类含有: 6个轮子,3个传感器 C类含有 ...

  3. Java实现 蓝桥杯 算法训练 Multithreading

    问题描述 现有如下一个算法: repeat ni times yi := y y := yi+1 end repeat 令n[1]为你需要算加法的第一个数字,n[2]为第二个,-n[N]为第N个数字( ...

  4. Spring基本介绍

    一:Spring是什么 Spring是分层的 Java SE/EE应用 full-stack 轻量级开源框架,它以IOC控制反转和AOP面向切面编程为核心,提供了展现层 Spring MVC 和持久层 ...

  5. STL常用序列容器

    这里简要的记述一下STL常用容器的实现原理,要点等内容. vector vector是比较常用的stl容器,用法与数组是非类似,其内部实现是连续空间分配,与数组的不同之处在于可弹性增加空间,而arra ...

  6. Java基础(九)

    一.总述 多线程程序在较低的层次上扩展了多任务的概念:一个程序同时执行多个任务.通常,每一个任务称为一个线程,它是线程控制的简称.可以同时运行一个以上线程的程序称为多线程程序. 多进程与多线程的区别: ...

  7. 曹工说JDK源码(3)--ConcurrentHashMap,Hash算法优化、位运算揭秘

    hashcode,有点讲究 什么是好的hashcode,一般来说,一个hashcode,一般用int来表示,32位. 下面两个hashcode,大家觉得怎么样? 0111 1111 1111 1111 ...

  8. 【asp.net core 系列】8 实战之 利用 EF Core 完成数据操作层的实现

    0. 前言 通过前两篇,我们创建了一个项目,并规定了一个基本的数据层访问接口.这一篇,我们将以EF Core为例演示一下数据层访问接口如何实现,以及实现中需要注意的地方. 1. 添加EF Core 先 ...

  9. php的ts和nts选择

    TS(Thread-Safety)即线程安全,多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用.不会出现数据不一致或者数据 ...

  10. selenium获取图片验证码

    # encoding:utf-8 from PIL import Image from selenium import webdriver url = '网站地址' driver = webdrive ...