Spring官网阅读(七)容器的扩展点(二)FactoryBean_factorybean创建的对象在单例池中吗-程序员宅基地

技术标签: spring  Spring官网读书笔记  factoryBean  

在上篇文章中我们已经对容器的第一个扩展点(BeanFactoryPostProcessor)做了一系列的介绍。其中主要介绍了Spring容器中BeanFactoryPostProcessor的执行流程。已经Spring自身利用了BeanFactoryPostProcessor完成了什么功能,对于一些细节问题可能说的不够仔细,但是在当前阶段我想要做的主要是为我们以后学习源码打下基础。所以对于这些问题我们暂且不去过多纠结,待到源码学习阶段我们会进行更加细致的分析。

在本篇文章中,我们将要学习的是容器的另一个扩展点(FactoryBean),对于FactoryBean官网上的介绍甚短,但是如果我们对Spring的源码有一定了解,可以发现Spring在很多地方都对这种特殊的Bean做了处理。话不多说,我们开始进入正文。


我们还是先看看官网上是怎么说的:

官网介绍

在这里插入图片描述

从上面这段文字我们可以得出以下几个信息:

  1. FactoryBean主要用来定制化Bean的创建逻辑
  2. 当我们实例化一个Bean的逻辑很复杂的时候,使用FactoryBean是很必要的,这样可以规避我们去使用冗长的XML配置
  3. FactoryBean接口提供了以下三个方法:
  • Object getObject(): 返回这个FactoryBean所创建的对象。
  • boolean isSingleton(): 返回FactoryBean所创建的对象是否为单例,默认返回true。
  • Class getObjectType(): 返回这个FactoryBean所创建的对象的类型,如果我们能确认返回对象的类型的话,我们应该正常对这个方法做出实现,而不是返回null。
  1. Spring自身大量使用了FactoryBean这个概念,至少有50个FactoryBean的实现类存在于Spring容器中
  2. 假设我们定义了一个FactoryBean,名为myFactoryBean,当我们调用getBean("myFactoryBean")方法时返回的并不是这个FactoryBean,而是这个FactoryBean所创建的Bean,如果我们想获取到这个FactoryBean需要在名字前面拼接"&",行如这种形式:getBean("&myFactoryBean")

上面这些概念可能刚刚说的时候大家不是很明白,下面我们通过FactoryBean的一些应用来进一步体会这个接口的作用。

FactoryBean的应用

我们来看下面这个Demo:

public class MyFactoryBean implements FactoryBean {
    
	@Override
	public Object getObject() throws Exception {
    
		System.out.println("执行了一段复杂的创建Bean的逻辑");
		return new TestBean();
	}

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

	@Override
	public boolean isSingleton() {
    
		return true;
	}
}

public class TestBean {
    
	public TestBean(){
    
		System.out.println("TestBean被创建出来了");
	}
}
// 测试类
public class Main {
    
	public static void main(String[] args) {
    
		AnnotationConfigApplicationContext ac=
				new AnnotationConfigApplicationContext(Config.class);
		System.out.println("直接调用getBean(\"myFactoryBean\")返回:"+ac.getBean("myFactoryBean"));
		System.out.println("调用getBean(\"&myFactoryBean\")返回:"+ac.getBean("&myFactoryBean"));
	}
}

运行后结果如下:


执行了一段复杂的创建Bean的逻辑
TestBean被创建出来了
直接调用getBean(“myFactoryBean”)返回:com.dmz.official.extension.factorybean.TestBean@28f67ac7
调用getBean("&myFactoryBean")返回:com.dmz.official.extension.factorybean.MyFactoryBean@256216b3


我们虽然没有直接将TestBean放入Spring容器中,但是通过FactoryBean也完成了这一操作。同时当我们直接调用getBean("FactoryBean的名称")获取到的是FactoryBean创建的Bean,但是添加了“&”后可以获取到FactoryBean本身。

FactoryBean相关源码分析

我们先看下下面这张图:

在这里插入图片描述

涉及到FactoryBean主要在3-11-6这一步中,我们主要关注下面这段代码:

// .....省略无关代码.......

// 1.判断是不是一个FactoryBean
if (isFactoryBean(beanName)) {
    
    // 2.如果是一个FactoryBean那么在getBean时,添加前缀“&”,获取这个FactoryBean
    Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
    if (bean instanceof FactoryBean) {
    
        final FactoryBean<?> factory = (FactoryBean<?>) bean;
        boolean isEagerInit;
        // 3.做权限校验,判断是否是一个SmartFactoryBean,并且不是懒加载的
        if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
    
            isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
                                                        ((SmartFactoryBean<?>) factory)::isEagerInit,
                                                        getAccessControlContext());
        }
        else {
    
            // 3.判断是否是一个SmartFactoryBean,并且不是懒加载的
            isEagerInit = (factory instanceof SmartFactoryBean &&
                           ((SmartFactoryBean<?>) factory).isEagerInit());
        }
        if (isEagerInit) {
    
            // 4.如果是一个SmartFactoryBean并且不是懒加载的,那么创建这个FactoryBean创建的Bean
            getBean(beanName);
        }
    }
}
else {
    
    // 不是一个FactoryBean,直接创建这个Bean
    getBean(beanName);
}
// ...省略无关代码.....

我们按照顺序一步步分析,首先看第一步:

  1. 判断是不是一个FactoryBean,对应源码如下:
public boolean isFactoryBean(String name) throws NoSuchBeanDefinitionException {
    
    String beanName = transformedBeanName(name);
    // 直接从单例池中获取这个Bean,然后进行判断,看是否是一个FactoryBean
    Object beanInstance = getSingleton(beanName, false);
    if (beanInstance != null) {
    
        return (beanInstance instanceof FactoryBean);
    }
    // 查找不到这个BeanDefinition,那么从父容器中再次确认是否是一个FactoryBean
    if (!containsBeanDefinition(beanName) && getParentBeanFactory() instanceof ConfigurableBeanFactory) {
    
        // No bean definition found in this factory -> delegate to parent.
        return ((ConfigurableBeanFactory) getParentBeanFactory()).isFactoryBean(name);
    }
    // 从当前容器中,根据BeanDefinition判断是否是一个FactoryBean
    return isFactoryBean(beanName, getMergedLocalBeanDefinition(beanName));
}
  1. 如果是一个FactoryBean那么在getBean时,添加前缀“&”,获取这个FactoryBean
  2. 判断是否是一个SmartFactoryBean,并且不是懒加载的

这里涉及到一个概念,就是SmartFactoryBean,实际上这个接口继承了FactoryBean接口,并且SmartFactoryBeanFactoryBean的唯一子接口,它扩展了FactoryBean多提供了两个方法如下:

// 是否为原型,默认不是原型
default boolean isPrototype() {
    
    return false;
}

// 是否为懒加载,默认为懒加载
default boolean isEagerInit() {
    
    return false;
}

从上面的代码中可以看出,我们当当实现一个FactoryBean接口,Spring并不会在启动时就将这个FactoryBean所创建的Bean创建出来,为了避免这种情况,我们有两种办法:

  • 实现SmartFactoryBean,并重写isEagerInit方法,将返回值设置为true
  • 我们也可以在一个不是懒加载的Bean中注入这个FactoryBean所创建的Bean,Spring在解决依赖关系也会帮我们将这个Bean创建出来

实际上我们可以发现,当我们仅仅实现FactoryBean时,其getObject()方法所产生的Bean,我们可以当前是懒加载的。

  1. 如果是一个SmartFactoryBean并且不是懒加载的,那么创建这个FactoryBean创建的Bean。这里需要注意的是此时创建的不是这个FactoryBean,以为在getBean时并没有加一个前缀“&”,所以获取到的是其getObject()方法所产生的Bean。

在上面的代码分析完后,在3-6-11-2中也有两行FactoryBean相关的代码,如下:

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
                          @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
    
	
    // 1.获取bean名称
    final String beanName = transformedBeanName(name);
    Object bean;
	
    //...省略无关代码...,这里主要根据beanName创建对应的Bean
	
    // 2.调用getObject对象创建Bean
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    }
  1. 获取bean名称
protected String transformedBeanName(String name) {
    
    // 這個方法主要用來解析別名,如果是別名的話,获取真实的BeanName
    return canonicalName(BeanFactoryUtils.transformedBeanName(name));
}

 // 处理FactoryBean
public static String transformedBeanName(String name) {
    
    Assert.notNull(name, "'name' must not be null");
    // 没有带“&”,直接返回
    if (!name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
    
        return name;
    }
    // 去除所有的“&”,防止这种写法getBean("&&&&beanName")
    return transformedBeanNameCache.computeIfAbsent(name, beanName -> {
    
        do {
    
            beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
        }
        while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));
        return beanName;
    });
}
  1. 如果是一个FactoryBean,将会调用其getObject()方法,如果不是直接返回。

我们可以看到,在调用getObjectForBeanInstance(sharedInstance, name, beanName, null);传入了一个参数—name,也就是还没有经过transformedBeanName方法处理的bean的名称,可能会带有“&”符号,Spring通过这个参数判断这个Bean是不是一个FactoryBean,如果是的话,会调用其getObject()创建Bean。**被创建的Bean不会存放于单例池中,而是放在一个名为factoryBeanObjectCache的缓存中。**具体的代码因为比较复杂,在这里我们就暂且不分析了,大家可以先留个印象,源码阶段我会做详细的分析。

Spring中FactoryBean概念的汇总(纯粹个人观点)

除了我们在上文中说到的实现了FactoryBean或者SmartFactoryBean接口的Bean可以称之为一个”FactoryBean“,不知道大家对BeanDefinition中的一个属性是否还有印象。BeanDefinition有属性如下(实际上这个属性存在于AbstractBeanDefinition中):

@Nullable
private String factoryBeanName;
@Nullable
private String factoryMethodName;

对于这个属性跟我们这篇文章中介绍的FactoryBean有什么关系呢?

首先,我们看看什么情况下bd中会存在这个属性,主要分为以下两种情况:

第一种情况:

@Configuration
public class Config {
    
	@Bean
	public B b(){
    
		return new B();
	}
}

我们通过@Bean的方式来创建一个Bean,那么在B的BeanDefinition会记录factoryBeanName这个属性,同时还会记录是这个Bean中的哪个方法来创建B的。在上面的例子中,factoryBeanName=configfactoryMethodName=b。

第二种情况:

<bean id="factoryBean" class="com.dmz.official.extension.factorybean.C"/>

<bean id="b" class="com.dmz.official.extension.factorybean.B" factory-bean="factoryBean" factory-method="b"/>

通过XML的方式进行配置,此时B的BeanDefinitionfactoryBeanName=factoryBeanfactoryMethodName=b。

上面两种情况,BeanDefinition中的factoryBeanName这个属性均不会为空,但是请注意此时记录的这个名字所以对于的Bean并不是一个实现了FactoryBean接口的Bean。

综上,我们可以将Spring中的FactoryBean的概念泛化,也就是说所有生产对象的Bean我们都将其称为FactoryBean,那么可以总结画图如下:

在这里插入图片描述

这是个人观点哈,没有在官网找到什么文档,只是这种比较学习更加能加深印象,所以我把他们做了一个总结,大家面试的时候不用这么说在这里插入图片描述

跟FactoryBean相关常见的面试题

1、FactoryBean跟BeanFactory的区别

FactoryBean就如我们标题所说,是Spring提供的一个扩展点,适用于复杂的Bean的创建。mybatis在跟Spring做整合时就用到了这个扩展点。并且FactoryBean所创建的Bean跟普通的Bean不一样。我们可以说FactoryBean是Spring创建Bean的另外一种手段。

BeanFactory是什么呢?BeanFactorySpring IOC容器的顶级接口,其实现类有XMLBeanFactoryDefaultListableBeanFactory以及AnnotationConfigApplicationContext等。BeanFactory为Spring管理Bean提供了一套通用的规范。接口中提供的一些方法如下:

boolean containsBean(String beanName)

Object getBean(String)

Object getBean(String, Class)

Class getType(String name)

boolean isSingleton(String)

String[] getAliases(String name)

通过这些方法,可以方便地获取bean,对Bean进行操作和判断。

2、如何把一个对象交给Spring管理

首先,我们要弄明白一点,这个问题是说,怎么把一个对象交給Spring管理,“对象”要划重点,我们通常采用的注解如@Compent或者XML配置这种类似的操作并不能将一个对象交给Spring管理,而是让Spring根据我们的配置信息及类信息创建并管理了这个对象,形成了Spring中一个Bean。把一个对象交给Spring管理主要有两种方式

  • 就是用我们这篇文章中的主角,FactoryBean,我们直接在FactoryBeangetObject方法直接返回需要被管理的对象即可
  • @Bean注解,同样通过@Bean注解标注的方法直接返回需要被管理的对象即可。

总结

在本文中我们完成了对FactoryBean的学习,最重要的是我们需要明白一点,FactoryBean是Spring中特殊的一个Bean,Spring利用它提供了另一种创建Bean的方式,FactoryBean整体的体系比较复杂,FactoryBean是如何创建一个Bean的一些细节我们还没有涉及到,不过不要急,在源码学习阶段我们还会接触到它,并会对其的整个流程做进一步的分析。目前容器的扩展点我们还剩最后一个部分,即BeanPostProcessorBeanPostProcessor贯穿了整个Bean的生命周期,学习的难度更大。希望大家跟我一步步走下去,认认真真学习完Spring,加油!

扫描下方二维码,关注我的公众号,更多精彩文章在等您!~~

公众号

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_41907991/article/details/103867036

智能推荐

请你回答一下malloc的原理,另外brk系统调用和mmap系统调用的作用分别是什么?_malloc 和brk-程序员宅基地

文章浏览阅读1.7k次,点赞2次,收藏3次。Malloc函数用于动态分配内存。为了减少内存碎片和系统调用的开销,malloc其采用内存池的方式,先申请大块内存作为堆区,然后将堆区分为多个内存块,以块作为内存管理的基本单位。当用户申请内存时,直接从堆区分配一块合适的空闲块。Malloc采用隐式链表结构将堆区分成连续的、大小不一的块,包含已分配块和未分配块;同时malloc采用显示链表结构来管理所有的空闲块,即使用一个双向链表将空闲块连..._malloc 和brk

Python3自学--2环境搭建_/root/training/python-3.6.5/lib/python3.6/site-pac-程序员宅基地

文章浏览阅读168次。笔者开发环境:[root@BigData11 /]# cat /etc/redhat-release Red Hat Enterprise Linux Server release 7.4 (Maipo)安装Python3:以Python3.6.1 为例:1.访问https://www.python.org/downloads/source/,选择源码压缩包2.上传到..._/root/training/python-3.6.5/lib/python3.6/site-packages/pandas/compat/__init

JavaScript-超大文件上传-如何上传文件-大文件上传_js如何上传大文件-程序员宅基地

文章浏览阅读191次。一、概述所谓断点续传,其实只是指下载,也就是要从文件已经下载的地方开始继续下载。在以前版本的HTTP协议是不支持断点的,HTTP/1.1开始就支持了。一般断点下载时才用到Range和Content-Range实体头。HTTP协议本身不支持断点上传,需要自己实现。二、Range用于请求头中,指定第一个字节的位置和最后一个字节的位置,一般格式: Rang..._js如何上传大文件

YARN的发展背景及基本原理_yarn框架历史-程序员宅基地

文章浏览阅读2.1k次。Yarn产生的历史背景Hadoop是apache基金会的一个项目, 目的是开发一个开源软件,用于可靠的可扩展的,分布式的计算。 Hadoop不是一个软件,而是一个软件库,hadoop作为一个软件库,提供了一个框架,可以以分布式的方式,在集群的多台主机之间,使用简单的编程模型来处理大量的数据集。Hadoop被设计成可以从单主机扩展到数千台主机,并且支持本地计算和存储。 Hadoop本身被设计成可以_yarn框架历史

kubernetes杂谈之(二)静态Pod_静态pod和动态pod的主要区别-程序员宅基地

文章浏览阅读1.2k次。一 静态Pod --- '静体现在哪?' ---之前:前面Pod的'生命周期管理'都是通过像DaemonSet、StatefulSet、Deployment'上层'这种方式创建管理的静态Pod是由kubelet进行管理'仅存在于特定Node上'的Pod -->'可以理解为定向调度' -->'如何定向调度涉及到创建方式'它们'不能通过API Server'进行管理,无法与'ReplicationController.._静态pod和动态pod的主要区别

eclipse里面配置spring,提示java.lang.ClassNotFoundException: org.springframework.web.servlet.Dispatcher_eclipse java.lang.classnotfoundexception: org.spri-程序员宅基地

文章浏览阅读7.5k次。在eclipse里面创建了一个Dynamic 项目,用到Spring,一直提示java.lang.ClassNotFoundException: org.springframework.web.servlet.Dispatcher 错误,后来又提示java.lang.NoClassDefFoundError: org/springframework/context/ApplicationConte_eclipse java.lang.classnotfoundexception: org.springframework.web.servlet.di

随便推点

IOC、AOP、以及Spring框架的面试整合题_springioc和aop的面试题-程序员宅基地

文章浏览阅读616次,点赞3次,收藏3次。spring是一个开放源代码的设计层面框架,它解决的是业务逻辑层和其他各层的松耦合问题,是一个分层的javaEE一站式轻量级开源框架。_springioc和aop的面试题

蚂蚁开发者工具(支付宝小程序开发工具)v0.32.3官方版_支付宝小程序开发者工具linux版本-程序员宅基地

文章浏览阅读3k次。蚂蚁开发者工具也就是支付宝小程序开发工具,支付宝小程序已经进入公测阶段,目前公测主要面向的是企业级开发者,蚂蚁开发者工具是支付宝小程序必备的工具!小程序介绍:支付宝小程序是手机应用嵌入支付宝客户端的一种方法,有以下特点:基于 Web 技术,学习成本低一套代码,同时支持 iOS 和 Android,接近原生体验提供丰富的组件和 API(比如获取用户信息、本地存储、支付功能等)基..._支付宝小程序开发者工具linux版本

csp 详解碰撞的小球(C++实现)_小球与客户边区边界碰撞动画怎样用c++执行-程序员宅基地

文章浏览阅读1.4k次。题目什么的就不写了,觉得大家都看过,直接上思路!解题思路碰撞的小球一题和下面链接里的题目类似:POJ的题目Ants可惜还是有点区别,如果使用和上题一样的思路,会带来不必要的麻烦!(我使用上题的思路没有做出来,大家可以试试)POJ的题目Ants这道题的解题思路还是挺新颖的,建议大家看看! 在我抛弃上述思路的做法后,很快就写出了答案:一、首先定义了一个结构体:typedef struct Ball ball;struct Ball{ int position;//表示小球位置 _小球与客户边区边界碰撞动画怎样用c++执行

位运算解决“一个数组中,只有一个数字出现n次,其他数字出现k次”问题_一个整形数组中,除了一个数字外,其他数字都出现了n次,找出这个不重复的数字-程序员宅基地

文章浏览阅读1.7k次。在学习完位操作后,经常会遇到一类关于查找缺失整数的问题。_一个整形数组中,除了一个数字外,其他数字都出现了n次,找出这个不重复的数字

MobaXterm连接远程服务器_mobaxterm迁移连接与密码-程序员宅基地

文章浏览阅读2.6k次。下载一个MobaXterm,选择左上角Session,弹出界面中选择SSH,输入ip和port,登陆后输入用户名和密码,完成连接前提是服务器开启了ssh:sudo apt-get updatesudo apt-get install openssh-serversudp ps -e | grep ssh ..._mobaxterm迁移连接与密码

Ext.form.field.File示例-程序员宅基地

文章浏览阅读7.1k次。Ext.onReady(function(){ Ext.QuickTips.init(); var uploadForm = Ext.create('Ext.form.Panel', { title: 'Ext.form.field.File示例', bodyStyle: 'padding:5 5 5 5',//表单边距 width: 300, fram_ext.form.field.file

推荐文章

热门文章

相关标签