Spring 如何从 IoC 容器中获取对象?

2023-11-11

前情回顾

前面几篇文章主要分析了 Spring IoC 容器如何初始化,以及解析和注册我们定义的 bean 信息。

其中,「Spring 中的 IoC 容器」对 Spring 中的容器做了一个概述,「Spring IoC 容器初始化」和「Spring IoC 容器初始化(2)」分析了 Spring 如何初始化 IoC 容器,「Spring 是如何解析 <bean> 标签的?」分析了 Spring 如何解析 <bean> 标签及其子标签,并注册到 BeanFactory。

主要流程如下:

IoC 容器已经建立,而且把我们定义的 bean 信息放入了容器,那么如何从容器中获取对象呢?

本文继续分析。

配置及测试代码

为便于查看,这里再贴一下 bean 配置文件和测试代码。

  • 配置文件 application-ioc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="person" class="com.jaxer.spring.ioc.Person">
        <property name="pet" ref="dog"/>
    </bean>

    <bean id="dog" class="com.jaxer.spring.ioc.Dog">
        <property name="age" value="1"/>
        <property name="owner" ref="person"/>
    </bean>

</beans>
  • 测试代码

public class IocTests {
    @Test
    public void test01() {
        ApplicationContext context = new ClassPathXmlApplicationContext("application-ioc.xml");
        System.out.println(context.getBean("person"));
        System.out.println(context.getBean("dog"));
    }
}

/*
 * 输出结果:
 *  Person{id=12, name='Jack-12'}
 *  Dog{age=1}
 */

如何从容器获取对象?

从容器中获取对象是通过 BeanFactory#getBean 方法,它有多个重载的方法,但最终都是通过

AbstractBeanFactory#doGetBean 方法来实现的。doGetBean 方法代码如下:

public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
    // ...

    protected <T> T doGetBean(
            String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
            throws BeansException {

        String beanName = transformedBeanName(name);
        Object bean;

        // 从缓存中获取单例 bean 对象
        Object sharedInstance = getSingleton(beanName);
        if (sharedInstance != null && args == null) {
            // ...
            // 处理 FactoryBean 的场景
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
        }
        
        // 缓存中不存在 bean 对象
        else {
            if (isPrototypeCurrentlyInCreation(beanName)) {
                throw new BeanCurrentlyInCreationException(beanName);
            }

            // bean 对象在父容器中,则从父容器中获取 bean 对象
            BeanFactory parentBeanFactory = getParentBeanFactory();
            if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
                // Not found -> check parent.
                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);
                }
            }

            // 是否只做类型检查
            if (!typeCheckOnly) {
                markBeanAsCreated(beanName);
            }

            try {
                // 获取 BeanDefinition
                RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
                checkMergedBeanDefinition(mbd, beanName, args);

                // 获取依赖的 bean 对象
                // 若创建一个 bean 对象时依赖其他对象,则先创建被依赖对象
                String[] dependsOn = mbd.getDependsOn();
                if (dependsOn != null) {
                    for (String dep : dependsOn) {
                        if (isDependent(beanName, dep)) {
                            // ...
                        }
                        registerDependentBean(dep, beanName);
                        try {
                            getBean(dep);
                        }
                        catch (NoSuchBeanDefinitionException ex) {
                            // ...
                        }
                    }
                }

                // 创建 scope 为 singleton(单例)的对象
                if (mbd.isSingleton()) {
                    sharedInstance = getSingleton(beanName, () -> {
                        try {
                            return createBean(beanName, mbd, args);
                        }
                        catch (BeansException ex) {
                            // ...
                        }
                    });
                    // 处理 FactoryBean 的场景
                    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
                }

                // 创建 scope 为 prototype 的对象
                else 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);
                    }
                    // 处理 FactoryBean 的场景
                    bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
                }

                // 创建其他类型对象
                else {
                    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 {
                        Object scopedInstance = scope.get(beanName, () -> {
                            beforePrototypeCreation(beanName);
                            try {
                                return createBean(beanName, mbd, args);
                            }
                            finally {
                                afterPrototypeCreation(beanName);
                            }
                        });
                        // 处理 FactoryBean 的场景
                        bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
                    }
                    catch (IllegalStateException ex) {
                        // ...
                    }
                }
            }
            catch (BeansException ex) {
                cleanupAfterBeanCreationFailure(beanName);
                throw ex;
            }
        }

        // 类型检查
        if (requiredType != null && !requiredType.isInstance(bean)) {
            try {
                T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
                if (convertedBean == null) {
                    throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
                }
                return convertedBean;
            }
            catch (TypeMismatchException ex) {
                // ...
            }
        }
        return (T) bean;
    }
}

获取 bean 对象主要就是通过这个 doGetBean 方法实现的。

该方法虽然看起来稍微有点长,但是呢,它内部的实现更长、更复杂。不过也是有迹可循的,莫慌。

本文先看下这个方法的整体流程,内部逻辑后面再慢慢研究。先上流程图:

代码虽然有点长,但梳理下来其实也没那么复杂了。

这个方法主要做了什么呢?

当从容器中获取 bean 对象时,首先从缓存中获取。如果缓存中存在,处理 FactoryBean 的场景。

BeanFactory 和 FactoryBean,这哥俩长得很像,也有个别面试题可能会问到。

嗯……以后有机会单独分析?

如果缓存中没有,先去父容器获取,前面创建 BeanFactory 时可以指定 parent 参数,就是那个。

不在父容器中,若 bean 对象依赖了其他对象,则先创建被依赖的 bean 对象,再根据 <bean> 标签的 scope 属性去创建相应的 bean 对象。

是不是有点像我们平时写查询接口时、先从缓存查询,缓存中没的话再查询 DB?

道理是一样的,空间换时间。

小结

先整体,后细节。

本文先从整体上分析了如何从 Spring IoC 容器中获取 bean 对象,内容不多,后文再详细分解吧。

休息会~

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring 如何从 IoC 容器中获取对象? 的相关文章

随机推荐

  • 如何看懂别人的项目代码

    1 先运行别人的demo 然后再去看文档 2 运行调试 新的 1 了解项目 要知道这个项目是用来干嘛的 有什么样的功能 2 获取源代码 3 运行 4 一定要亲自运行一下 玩一玩 看看都有什么功能 都能完成什么事情 要想对项目源码了解 首先必
  • 基于python的种子搜索网站,你懂得!

    该项目是基于python的web类库django开发的一套web网站 给师弟做的毕业设计 本人的研究方向是一项关于搜索的研究项目 在该项目中 笔者开发了一个简单版的搜索网站 实现了对数据库数据的检索和更新 通过开发该项目 笔者学习和巩固了p
  • css自适应布局,缩放保持图片比例

    场景一 适合商品类展示型布局 后台的数据呈现列表数据返回 窗口宽度变化 图片比例不变 假设我们的图片宽高固定比例16 9 效果图满屏 效果图缩放时 移动端 直接看代码 里面会注释详细说明 div class parentBox div cl
  • Matter Project 入门 – Matter开发环境设置

    如果您尚未在计算机上本地运行 Ubuntu 您将需要一个替代方案来开发 Matter 设备 你需要什么 用于构建 Matter 终端设备固件的 Linux 环境 我们将为这个项目使用 Ubuntu 21 10 服务器 但任何 Ubuntu
  • 软考之运筹学计算-车床问题

    软考之运筹学计算 车床问题 一 真题示例 二 题意分析 三 解答 一 真题示例 某车间需要用一台车床和一台铣床加工A B C D四个零件 每个零件都需要先用车床加工 再用铣床加工 车床与铣床加工每个零件所需的工时 包括加工前的准备时间以及加
  • AndroidStudio新建项目报错build failed

    AndroidStudio新建项目报错build failed 报错信息 org gradle initialization ReportedException org gradle internal exceptions Location
  • VC6提示番茄助手过期

    VC6提示番茄助手过期 无法打开 在控制面板中卸载即可
  • FlinkCDC第二部分-搭建Flink单机服务,ctrl就完事~

    Flink版本 1 16 环境 Linux CentOS 7 0 jdk1 8 基础文件 flink 1 16 2 bin scala 2 12 tgz flink connector jdbc 3 0 0 1 16 jar flink s
  • string字符插入及截取操作

    1 string满足 运算符的重载 可以理解为拼接 s s 3 加在末尾 s 4 s 加在头部 2 append函数 插入整个字符串s2 s1 append s2 插入s2部分 如第四个字符到第七个字符 s1 append s3 3 6 插
  • linux使用读写锁pthread_rwlock_t

    使用读写锁 配置读写锁的属性之后 即可初始化读写锁 以下函数用于初始化或销毁读写锁 锁定或解除锁定读写锁或尝试锁定读写锁 下表列出了本节中讨论的用来处理读写锁的函数 表 4 9 处理读写锁的例程 操作 相关函数说明 初始化读写锁 pthre
  • Liunx下使用docker搭建ftp服务

    1 检索最新镜像 docker search vsftpd 2 拉取FTP镜像 docker pull fauria vsftpd 3 查看本机ip ifconfig 4 运行并创建container 方法1 运行时创建用户名和密码 doc
  • 计算机是仿生学,一种基于人体仿生学的计算机键盘的制作方法

    本实用新型属于计算机键盘技术领域 具体涉及一种基于人体仿生学的计算机键盘 背景技术 键盘是最常用也是最主要的输入设备 通过键盘 可以将英文字母 数字和标点符号等输入到计算机中 从而向计算机发出命令和输入数据等 键盘 由一组按阵列方式装配在一
  • 什么是软件测试、生命周期、软件开发模型、测试模型

    目录 1 什么是软件测试 2 软件测试和软件软件开发的区别 3 软件测试 Testing 和软件调试 Debug 的区别 4 什么是需求 5 需求是测试人员开展软件测试工作的依据 6 测试人员如何深入了解需求 7 测试用例 Test Cas
  • mysql 导入超大sql文件

    mysql u root p 登录mysql命令 可以登陆mysql服务器使用source命令导入 会快很多 我这里导入500M 大概用了5分钟 1 liunx登陆mysql mysql u 用户名 p 数据库名 然后输入密码 登陆mysq
  • Blender 雕刻

    文章目录 简介 基本操作 进入雕刻 雕刻工作区 强度 笔刷 纹理 笔画 衰减 动态拓扑 动态拓扑属性 重构网格 物体数据属性重构网格 雕刻自带的重构网格 镜像 同一个模型的不同网格 对其中一个雕刻 不影响其他的网格 快捷键 笔刷 自由线 显
  • 知识图谱——机器大脑中的知识库

    Published by liuzy on July 6 2015 作者 刘知远 清华大学 整理 林颖 RPI 版权所有 转载请注明出处 知识就是力量 英 弗兰西斯 培根 1 什么是知识图谱 在互联网时代 搜索引擎是人们在线获取信息和知识的
  • 计算机2.0培训心得,信息2.0培训心得体会

    今年7月份学校召开了关于学校信息化教育的会议 由于刚接触 我们听得一头雾水 于是就在摸索中开始了我们的信息化2 0工程培训学习 由于我组的化学科目特殊性 除了选择基础的A1技术支持的学前分析和A3演示文稿设计与制作外 我们还选择了B6技术支
  • IDEA报错解决:Cannot resolve com.oracle:ojdbc7:12.1.0.2或‘com.oracle:ojdbc7:12.1.0.2‘ not found

    1 到oracle官网下载ojdbc7 jar 附博主的ojdbc7 jar包下载地址 也可进入我的资源中下载 存入本地 不要存入maven仓库 2 打开cmd 切换到ojdbc7 jar所在文件夹 3 执行命令mvn install in
  • Vue(五)——调试

    一 console输出到控制台 如果Vue项目中 使用了eslint loader 代码规范 空格 缩进 console等测试语句 的话 就没法使用console 一定需要使用console可以尝试关闭eslint loader 在vue
  • Spring 如何从 IoC 容器中获取对象?

    前情回顾 前面几篇文章主要分析了 Spring IoC 容器如何初始化 以及解析和注册我们定义的 bean 信息 其中 Spring 中的 IoC 容器 对 Spring 中的容器做了一个概述 Spring IoC 容器初始化 和 Spri