FactoryBean和BeanFactory的区别

news2025/1/13 2:42:57

1. 前言

“BeanFactory和FactoryBean的区别是什么???”
这是Spring非常高频的一道面试题,BeanFactory是Spring bean容器的顶级接口,负责创建和维护容器内所有的bean对象。而FactoryBean是用来创建一类bean的接口,通过实现FactoryBean接口,重写FactoryBean#getObject()方法来生成bean对象。可以这么说,原本由Spring负责的创建bean的过程,通过实现FactoryBean接口就可以将创建bean对象的过程交给开发者自己来完成。
比如MyBatis在整合Spring的时候,Mapper接口是无法被实例化的,因此就算把Mapper注册到Spring容器,Spring也无法实例化Mapper对应的bean对象。MyBatis的做法是就是通过实现FactoryBean接口来手动生成Mapper接口的代理对象,对应的类是MapperFactoryBean。

2. FactoryBean

FactoryBean接口定义很简单,getObjectType()返回bean的类型,getObject()用来生成bean对象。

public interface FactoryBean<T> {
    @Nullable
    T getObject() throws Exception;

    @Nullable
    Class<?> getObjectType();

    default boolean isSingleton() {
        return true;
    }
}

FactoryBean本身也会被当成一个特殊的bean注册到Spring容器中,为了区分普通bean和FactoryBean,Spring的作法是给FactoryBean的beanName前面拼接一个固定的&字符。
例如现在有一个用来产生Person对象的FactoryBean,那么在容器内就会有两个bean。名称为"person"对应的是Person这个bean对象,名称为"&person"对应的是PersonFactoryBean对象。

@Component("person")
public class PersonFactoryBean implements FactoryBean<Person> {

    public PersonFactoryBean() {
        System.err.println("PersonFactoryBean");
    }

    @Override
    public Person getObject() throws Exception {
        return new Person();
    }

    @Override
    public Class<?> getObjectType() {
        return Person.class;
    }
}

通过FactoryBean接口来生成bean的话,还有一个特殊的点需要注意:Spring容器启动时,默认会实例化FactoryBean对象,但是getObject()方法只有在需要用到bean时才会被调用。

3. 源码探究

Spring启动时默认会调用DefaultListableBeanFactory#preInstantiateSingletons()方法来实例化容器内所有非Lazy的单例bean,方式很简单,就是遍历容器内所有非Lazy的单例beanName,然后依次调用getBean()方法来创建bean。
这里Spring会进行判断,如果bean是FactoryBean的话,不会直接去创建bean本身,而仅仅是创建FactoryBean。
image.png
getBean("&person")时,由于容器内不存在"&person"所以会通过createBean()方法来创建bean,注意这里创建的仅仅是PersonFactoryBean对象,还没有创建Person。此时,Spring一级缓存里的bean还是:

[singletonObjects]
	"person" -> PersonFactoryBean

getBean("&person")由于beanName有&前缀,所以Spring认为我们仅仅是要获取FactoryBean对象,而不是要获取真正的bean,所以会直接返回FactoryBean,而不会去调用getObject()方法创建bean。
image.png
当我们要获取Person这个bean时,只需要取消beanName的&前缀,或者直接根据类型获取即可。

context.getBean(Person.class);
context.getBean("person");

此时,Spring会去一级缓存里拿"person"对应的bean,也就是PersonFactoryBean。但是PersonFactoryBean并不是我们要的啊,别着急,Spring会通过方法AbstractBeanFactory#getObjectForBeanInstance()来判断到底是要给你FactoryBean还是真正的bean对象。

protected Object getObjectForBeanInstance(
        Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {
    // 是否是FactoryBeanName? &前缀
    if (BeanFactoryUtils.isFactoryDereference(name)) {
        if (beanInstance instanceof NullBean) {
            return beanInstance;
        }
        // &前缀,但不是FactoryBean类型,抛异常
        if (!(beanInstance instanceof FactoryBean)) {
            throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass());
        }
    }
    /**
     * 到这一步,beanInstance 要么是普通Bean,要么是FactoryBean
     * 1.如果想获取FactoryBean对象,请在name前加&前缀,这里会直接返回
     * 2.name没有&前缀,但是beanInstance是FactoryBean,则会走后续流程,基于FactoryBean生成Bean
     */
    if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
        return beanInstance;
    }
    // 基于FactoryBean生成Bean
    Object object = null;
    if (mbd == null) {
        //从Bean工厂缓存中获取给定名称的Bean实例对象
        object = getCachedObjectForFactoryBean(beanName);
    }
    //让Bean工厂生产给定名称的Bean对象实例
    if (object == null) {
        // Return bean instance from factory.
        // 到这里已经明确知道beanInstance一定是FactoryBean类型
        FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
        // Caches object obtained from FactoryBean if it is a singleton.
        // 如果是单例对象,则缓存从FactoryBean获得的对象。
        // containsBeanDefinition(beanName) 检测beanDefinitionMap中 也就是在所有已经加载的类中检测是否定义beanName
        if (mbd == null && containsBeanDefinition(beanName)) {
            // 将存储XML配置文件的GenericBeanDefinition转换为RootBeanDefinition
            //从容器中获取指定名称的Bean定义,如果继承基类,则合并基类相关属性
            mbd = getMergedLocalBeanDefinition(beanName);
        }
        //如果从容器得到Bean定义信息,并且Bean定义信息是用户定义的而不是应用程序本身定义的,则让FactoryBean生产Bean实例对象
        boolean synthetic = (mbd != null && mbd.isSynthetic());
        //调用FactoryBeanRegistrySupport类的getObjectFromFactoryBean方法,实现工厂Bean生产Bean对象实例的过程
        object = getObjectFromFactoryBean(factory, beanName, !synthetic);
    }
    return object;
}

如果name有&前缀就返回FactoryBean,否则通过FactoryBeanRegistrySupport#doGetObjectFromFactoryBean()方法去调用FactoryBean对象的getObject()方法来获取真正的bean对象,为了确保单例语义,FactoryBean#getObject()只能被调用一次,所以Spring会把首次生成的bean对象缓存到factoryBeanObjectCacheMap容器中,后续再获取bean时直接从缓存里返回即可。
image.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/156549.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数字新基建之数据云

自2021年“新基建”概念火爆以来&#xff0c;相关的政策和技术都不断跟进和发展&#xff0c;由于“新基建”本质上是基础设施向数字化、智能化、网络化方向发展&#xff0c;因此更多的科技领域从业者和投资者都将其称为“数字新基建”。而数据库、数据仓库、大数据平台和数据云…

C语言:整数的存储方式

整数的存储方式 char类型在存储时是按照ASCII码值进行存储&#xff0c;存储方式与整型一致 有符号数与无符号数 char一个字节signed charunsigned char int四个字节signed intunsigned int 各种类型数据均分为有符号和无符号类型&#xff0c;当定义一个int类型或char类型的数…

备库为什么会延迟好几个小时?

在上一篇文章中,我和你介绍了几种可能导致备库延迟的原因。你会发现,这些场景里,不论是偶发性的查询压力,还是备份,对备库延迟的影响一般是分钟级的,而且在备库恢复正常以后都能够追上来。 但是,如果备库执行日志的速度持续低于主库生成日志的速度,那这个延迟就有可能…

百度搜索留痕推广资源整理如何收录排名的?

每日分享&#xff1a;百度对图文类内容的优质标准 &#xff08;1&#xff09;文字的字体、字号与间距需要适配网页&#xff0c;文档分段合理&#xff0c;结构有序&#xff0c;阅读体验舒适。 &#xff08;2&#xff09;在文章中使用小标题准确概括段意&#xff0c;通过加粗、…

vue3 setup语法糖父子组件传值,让女友看得明明白白

前言 最近在想做个cloud项目,gitee上找了个模板项目&#xff0c;前端使用到vue3 typeScript&#xff0c;最近使用到vue3 的父子组件之间的传值&#xff0c;顺便学习一下&#xff0c;在此总结一下&#xff0c;若有不足之处&#xff0c;望大佬们可以指出。 vue3官网&#xff1a…

栈--专题讲解

文章目录基本概念模拟栈数据结构-栈&#xff1a;stack头文件定义基本操作实例&#xff1a;火车进栈题目大意解题思路AC代码基本概念 栈的定义 栈(stack)是限定仅在表尾进行插入或者删除的线性表。对于栈来说&#xff0c;表尾端称为栈顶&#xff08;top&#xff09;&#xff0c…

web服务器----基于http协议搭建的静态网站详解

一&#xff0c;WWW的简介 1、什么是 www www 是 world wide web 的缩写&#xff0c;也就是全球信息广播的意思。通常说的上网就是使用 www 来查询用户所需要的信息。www 可以结合文字、图形、影像以及声音等多媒体&#xff0c;并通过可以让鼠标单击超链接的方式将信息以 Inter…

Docker容器搭建及基本使用

一、安装环境 操作系统&#xff1a;CentOS 7&#xff08;建议用7或以上&#xff0c;因为6版本有部分功能不兼容&#xff09; 二、Docker安装 1、卸载旧版本 yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrota…

linux修改密码报错‘Authentication token manipulation error‘

本次事故使用操作系统为centos7 1、报错起因&#xff1a; 利用chage设置root用户密码定期更换后&#xff0c;到期之后登录系统&#xff0c;输入密码之后&#xff0c;提示要改密码&#xff0c;输入新密码之后&#xff0c;报错 ‘Authentication token manipulation error’ &a…

【k8s系列】gvisor安装与containerd集成

文章目录安装与containerd集成下发runtimeclass资源修改containerd配置文件准备pod的yaml文件参考资料author: ningan123date: ‘2023-01-11 21:23’updated: ‘2023-01-11 21:31’安装 安装地址&#xff1a;Installation - gVisor ARCH$(uname -m)URLhttps://storage.googlea…

Gotify <2.2.3 存在反射型 XSS 漏洞(MPS-2023-0815)

漏洞描述 Gotify 是 Go 语言开发的开源组件&#xff0c;用作于发送和接收消息的服务器。 由于 2.2.3 之前版本的 Gotify 使用具有反射型 XSS 漏洞版本的 swagger-ui 生成文档&#xff0c;当用户访问 Gotify /docs 页面时存在反射型 XSS 漏洞。 攻击者可诱导 Gotify 用户点击…

【学习笔记】【Pytorch】四、torchvision中的数据集使用

【学习笔记】【Pytorch】四、torchvision中的数据集使用学习地址主要内容一、datasets模块介绍二、datasets.CIFAR10类的使用1.使用说明2.代码实现学习地址 PyTorch深度学习快速入门教程【小土堆】. 主要内容 一、datasets模块介绍 介绍&#xff1a;一些加载数据的函数及常用…

P6:DataLoader的使用

1、准备数据集&#xff08;测试集&#xff09; import torchvisiontest_data torchvision.datasets.CIFAR10(./dataset, trainFalse, transformtorchvision.transforms.ToTensor()) 注意数据集中的图片是PIL的格式&#xff0c;需要格式转换。 2、使用DataLoader from torch…

HBase数据库总结(一)

1、 HBase的特点是什么&#xff1f;HBase是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统&#xff0c;HBase不同于一般的关系数据库&#xff0c;它是一个适合于非结构化数据存储的数据库。1&#xff09;大&#xff1a;一个表可以有数十亿行&#xff0c;上百万列2&…

测试开发基础|一文搞定计算机网络(一)

计算机网络知识对测试人员来说是非常重要的基础技能。无论是在平时测试工作中&#xff08;比如接口测试&#xff09;&#xff0c;还是测试技术面试时&#xff0c;都会经常涉猎。很多基础薄弱的同学靠临时抱佛脚突击搜索学习&#xff0c;对系统知识和重点难点的理解总是不够透彻…

认证授权-SpringSecurity

认证授权-SpringSecurity 1. 认证授权概述 1.1 认证授权概念 1.1.1 认证 在互联网中&#xff0c;我们每天都会使用到各种各样的 APP 和网站&#xff0c;在使用过程中通常还会遇到需要注册登录的情况&#xff0c;输入你的用户名和密码才能正常使用&#xff0c;也就是说成为这…

uniapp引入vantweapp踩坑笔记

vue-cli创建uniapp项目引入vantweapp踩坑笔记 uni-app中引入vantweappvue-cli创建uniapp项目引入vantweapp踩坑笔记一、环境准备二、项目搭建三、引入vant第一种方式第二种方式一、环境准备 我用的环境分别是 软件名称版本号下载命令\链接检查是否成功卸载命令Vue-clivue/cli…

JavaScript中的重要概念

JavaScript中的重要概念 1、标识符 标识符&#xff08;Identifier&#xff09;就是名字。JavaScript 中的标识符包括变量名、函数名、参数名、属性名、类名等。 合法的标识符应该注意以下强制规则&#xff1a; 第一个字符必须是字母、下划线&#xff08;_&#xff09;或美元…

发表计算机SCI论文,需要经历哪些阶段? - 易智编译EaseEditing

想在SCI期刊上发表一篇属于自己的论文一定是要经历四个阶段&#xff1a;论文选题、论文写作、期刊投稿和修稿反馈。 1.论文选题&#xff1a;前面已经说过在SCI期刊上发表论文是比较有难度的&#xff0c;是因为SCI期刊通常要求所接收稿件的选题具有较高的创新性和新颖性。 所以…

马蹄集 单位矩阵

单位矩阵 难度&#xff1a;白银 0时间限制&#xff1a;1秒 巴占用内存&#xff1a;64M 输入3X3的整型矩阵A,判断是否为单位矩阵&#xff0c;输出YES或者NO。 格式 输入格式&#xff1a;输入矩阵&#xff0c;空格分隔 输出格式&#xff1a;输出YES或者NO #include<bits/stdc.…