Spring系列之FactoryBean(一)_servicelocationfactorybean-程序员宅基地

技术标签: 转载  

本文转载https://conan.blog.csdn.net/article/details/79572387#comments_13341441

在我们的开发工作中应该都见过或使用过FactoryBean这个类,也许你会看成了BeanFactory这个类。FactoryBean和BeanFactory虽然长的很像,但是他们的作用确实完全不像。这里你可以想象一下,你会在什么样的场景下使用FactoryBean这个接口?FactoryBean是一个工厂Bean,可以生成某一个类型Bean实例,它最大的一个作用是:可以让我们自定义Bean的创建过程。BeanFactory是Spring容器中的一个基本类也是很重要的一个类,在BeanFactory中可以创建和管理Spring容器中的Bean,它对于Bean的创建有一个统一的流程。下面我们先看一下FactoryBean中有什么东西:

public interface FactoryBean<T> {

    //返回的对象实例
    T getObject() throws Exception;
    //Bean的类型
    Class<?> getObjectType();
    //true是单例,false是非单例  在Spring5.0中此方法利用了JDK1.8的新特性变成了default方法,返回true
    boolean isSingleton();
}

从上面的代码中我们发现在FactoryBean中定义了一个Spring Bean的很重要的三个特性:是否单例、Bean类型、Bean实例,这也应该是我们关于Spring中的一个Bean最直观的感受。虽然没有返回BeanName的值,但是我们也知道BeanName的值。下面我们来写一个关于FactoryBean的小例子,看看我们是怎么使用FactoryBean的,然后再从源码的角度看看Spring是怎么解析FactoryBean中的Bean的。

//FactoryBean接口的实现类
@Component
public class FactoryBeanLearn implements FactoryBean {

    @Override
    public Object getObject() throws Exception {
        //这个Bean是我们自己new的,这里我们就可以控制Bean的创建过程了
        return new FactoryBeanServiceImpl();
    }

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

    @Override
    public boolean isSingleton() {
        return true;
    }
}
//接口
public interface FactoryBeanService {

    /**
     * 测试FactoryBean
     */
    void testFactoryBean();
}
//实现类
public class FactoryBeanServiceImpl implements FactoryBeanService {
    /**
     * 测试FactoryBean
     */
    @Override
    public void testFactoryBean() {
        System.out.println("我是FactoryBean的一个测试类。。。。");
    }
}
//单测
@Test
public void test() {
        ClassPathXmlApplicationContext cac = new ClassPathXmlApplicationContext("classpath:com/zkn/spring/learn/base/applicationContext.xml");
        FactoryBeanService beanService = cac.getBean(FactoryBeanService.class);
        beanService.testFactoryBean();
    }

我们的输出结果如下:
FactoryBean
从上面的代码中我们可以看到我们从Spring容器中获取了FactoryBeanService类型的Bean。那么这个获取Bean的过程Spring是怎么处理的呢?它是怎么从FactoryBean中获取我们自己创建的Bean实例的呢?我们先从getBean这个方法看起,因为在Spring的AbstractApplicationContext中有很多重载的getBean方法,这里我们调用的是根据Type(指Class类型)来获取的Bean信息。我们传入的type是FactoryBeanService类型。

getBean

AbstractApplicationContext#getBean(java.lang.Class)

    @Override
    public <T> T getBean(Class<T> requiredType) throws BeansException {
        //检测BeanFactory的激活状态
        assertBeanFactoryActive();
        //getBeanFactory()获取到的是一个DefaultListableBeanFactory的实例
        //所以我们去DefaultListableBeanFactory中看一下getBean这个方法
        return getBeanFactory().getBean(requiredType);
    }

DefaultListableBeanFactory#getBean(java.lang.Class)

    @Override
    public <T> T getBean(Class<T> requiredType) throws BeansException {
        return getBean(requiredType, (Object[]) null);
    }
    @Override
    public <T> T getBean(Class<T> requiredType, Object... args) throws BeansException {
        //解析Bean
        NamedBeanHolder<T> namedBean = resolveNamedBean(requiredType, args);
        if (namedBean != null) {
            return namedBean.getBeanInstance();
        }
        //如果当前Spring容器中没有获取到相应的Bean信息,则从父容器中获取
        //SpringMVC是一个很典型的父子容器
        BeanFactory parent = getParentBeanFactory();
        if (parent != null) {
            //一个重复的调用过程,只不过BeanFactory的实例变了
            return parent.getBean(requiredType, args);
        }
        //如果都没有获取到,则抛出异常
        throw new NoSuchBeanDefinitionException(requiredType);
    }

在上面的代码中,我们重点关注的是resolveNamedBean这个方法:

在上面的代码中我们说我们传入的type是com.zkn.spring.learn.service.FactoryBeanService类型,但是在我们的Spring容器中却没有FactoryBeanService类型的Bean,那么我们是怎么从getBeanNamesForType获取到beanName的呢?
getBeanNamesForType的分析

    public String[] getBeanNamesForType(Class<?> type, boolean includeNonSingletons, boolean allowEagerInit) {
        if (!isConfigurationFrozen() || type == null || !allowEagerInit) {
            return doGetBeanNamesForType(ResolvableType.forRawClass(type), includeNonSingletons, allowEagerInit);
        }
        //先从缓存中获取
        Map<Class<?>, String[]> cache =
                (includeNonSingletons ? this.allBeanNamesByType : this.singletonBeanNamesByType);
        String[] resolvedBeanNames = cache.get(type);
        if (resolvedBeanNames != null) {
            return resolvedBeanNames;
        }
        //调用doGetBeanNamesForType方法获取beanName
        resolvedBeanNames = doGetBeanNamesForType(ResolvableType.forRawClass(type), includeNonSingletons, true);
        //所传入的类能不能被当前类加载加载
        if (ClassUtils.isCacheSafe(type, getBeanClassLoader())) {
            //放入到缓存中,解析一次以后从缓存中获取
            //这里对应到我们这里 key是FactoryBeanService Value是beanFactoryLearn
            cache.put(type, resolvedBeanNames);
        }
        return resolvedBeanNames;
    }

doGetBeanNamesForType的分析

在上面的代码中,我们可以知道的是我们的FactoryBeanLearn是一个FactoryBean类型的类。所以在上面的代码中会调用isTypeMatch这个方法来判断FactoryBeanLearn是不是和我们传入的类型相匹配。这里是值:FactoryBeanService类。我们去isTypeMatch方法中去看一下它是怎么进行类型匹配判断的:由于isTypeMatch方法很长,这里我们只看和我们这次分析相关的一部分代码

    public boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException {
        //转换beanName   这里我们可以知道我们的beanName为factoryBeanLearn 因为上面是循环了Spring容器中的所有的Bean
        String beanName = transformedBeanName(name);
        //因为我们这里是用的AbstractApplicationContext的子类来从Spring容器中获取Bean
        //获取beanName为factoryBeanLearn的Bean实例 这里是可以获取到Bean实例的
        //这里有一个问题:使用AbstractApplicationContext的子类从Spring容器中获取Bean和
        //使用BeanFactory的子类从容器中获取Bean有什么区别?这个可以思考一下
        Object beanInstance = getSingleton(beanName, false);
        if (beanInstance != null) {
            //factoryBeanLearn是FactoryBean的一个实现类
            if (beanInstance instanceof FactoryBean) {
                //这里判断beanName是不是以&开头  这里明显不是 这里可以想一下什么情况下会有&开头的Bean
                if (!BeanFactoryUtils.isFactoryDereference(name)) {
                    //这里就是从factoryBeanLearn中获type类型 我们在下面会分析一下这个类
                    Class<?> type = getTypeForFactoryBean((FactoryBean<?>) beanInstance);
                    //从factoryBeanLearn中获取到的type类型和我们传入的类型是不是同一种类型 是的话直接返回
                    return (type != null && typeToMatch.isAssignableFrom(type));
                }
                else {
                    return typeToMatch.isInstance(beanInstance);
                }
            }

getTypeForFactoryBean

    protected Class<?> getTypeForFactoryBean(final FactoryBean<?> factoryBean) {
        try {
            if (System.getSecurityManager() != null) {
                return AccessController.doPrivileged(new PrivilegedAction<Class<?>>() {
                    @Override
                    public Class<?> run() {
                        return factoryBean.getObjectType();
                    }
                }, getAccessControlContext());
            }
            else {
                //看到这里是不是很熟悉了 调用FactoryBean实例的getObjectType()方法
                return factoryBean.getObjectType();
            }
        }
    }

我们在调用factoryBeanLearn的getObjectType方法的时候,获取到的值为:com.zkn.spring.learn.service.FactoryBeanService和我们传入的type是一样的类型。所以这里返回true,根据我们上面说的如果isTypeMatch返回true的话,我们返回的beanName为factoryBeanLearn
上面的分析总结起来是:我们调用getBean(Class requiredType)方法根据类型来获取容器中的bean的时候,对应我们的例子就是:根据类型com.zkn.spring.learn.service.FactoryBeanService来从Spring容器中获取Bean(首先明确的一点是在Spring容器中没有FactoryBeanService类型的BeanDefinition。但是却有一个Bean和FactoryBeanService这个类型有一些关系)。Spring在根据type去获取Bean的时候,会先获取到beanName。获取beanName的过程是:先循环Spring容器中的所有的beanName,然后根据beanName获取对应的BeanDefinition,如果当前bean是FactoryBean的类型,则会从Spring容器中根据beanName获取对应的Bean实例,接着调用获取到的Bean实例的getObjectType方法获取到Class类型,判断此Class类型和我们传入的Class是否是同一类型。如果是则返回测beanName,对应到我们这里就是:根据factoryBeanLearn获取到FactoryBeanLearn实例,调用FactoryBeanLearn的getObjectType方法获取到返回值FactoryBeanService.class。和我们传入的类型一致,所以这里获取的beanName为factoryBeanLearn。换句话说这里我们把factoryBeanLearn这个beanName映射为了:FactoryBeanService类型。即FactoryBeanService类型对应的beanName为factoryBeanLearn这是很重要的一点。在这里我们也看到了FactoryBean中三个方法中的

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

智能推荐

【转】多核处理器的工作原理及优缺点_多核处理器怎么工作-程序员宅基地

文章浏览阅读8.3k次,点赞3次,收藏19次。【转】多核处理器的工作原理及优缺点《处理器关于多核概念与区别 多核处理器工作原理及优缺点》原文传送门  摘要:目前关于处理器的单核、双核和多核已经得到了普遍的运用,今天我们主要说说关于多核处理器的一些相关概念,它的工作与那里以及优缺点而展开的分析。1、多核处理器  多核处理器是指在一枚处理器中集成两个或多个完整的计算引擎(内核),此时处理器能支持系统总线上的多个处理器,由总..._多核处理器怎么工作

个人小结---eclipse/myeclipse配置lombok_eclispe每次运行个新项目都需要重新配置lombok吗-程序员宅基地

文章浏览阅读306次。1. eclipse配置lombok 拷贝lombok.jar到eclipse.ini同级文件夹下,编辑eclipse.ini文件,添加: -javaagent:lombok.jar2. myeclipse配置lombok myeclipse像eclipse配置后,定义对象后,直接访问方法,可能会出现飘红的报错。 如果出现报错,可按照以下方式解决。 ..._eclispe每次运行个新项目都需要重新配置lombok吗

【最新实用版】Python批量将pdf文本提取并存储到txt文件中_python批量读取文字并批量保存-程序员宅基地

文章浏览阅读1.2w次,点赞31次,收藏126次。#注意:笔者在2021/11/11当天调试过这个代码是可用的,由于pdfminer版本的更新,网络上大多数的语法没有更新,我也是找了好久的文章才修正了我的代码,仅供学习参考。1、把pdf文件移动到本代码文件的同一个目录下,笔者是在pycharm里面运行的项目,下图中的x1文件夹存储了我需要转换成文本文件的所有pdf文件。然后要在此目录下创建一个存放转换后的txt文件的文件夹,如图中的txt文件夹。2、编写代码 (1)导入所需库# coding:utf-8import ..._python批量读取文字并批量保存

【C语言初阶】内存管理(内存的分配方式、常见的内存错误即对策、杜绝”野指针“)_函数造成的内存错误怎么解决-程序员宅基地

文章浏览阅读646次。内存的分配方式、常见的内存错误即对策、指针与数组的对比、free、杜绝“野指针“、内存耗尽怎么办、malloc/free 的使用要点_函数造成的内存错误怎么解决

6.UE4的多维数组_ue 二维数组-程序员宅基地

文章浏览阅读4.2k次。一. 多维数组的概念1. 一维像线,array[0],索引2. 二维像表格(面),array[0][0],行列3. 三维像立方体,array[0][0][0],行列厚度二. 三维数组在虚拟漫游中的应用使用三维数组表示一栋楼的数据设定结构体 RoomInfo属性 变量名称 数据类型面积 RoomAreaF 单价 Price_ue 二维数组

读取csv(excel类相关文件)常见bug,及解决办法统计__csv.error: line contains nul-程序员宅基地

文章浏览阅读2.5k次,点赞2次,收藏2次。bug1:UnicodeDecodeError: ‘gbk’ codec can’t decode byte 0xb1 in position 5: illegal multibyte sequence解决办法1:上面报错的意思就是,默认以gbk的方式读取数据,但是文本数据是utf-8类型的,这是需要用到另一个参数encoding,也就是把它编码成与文本一样类型的格式,下面的代码encoding="utf-8"就是要修改的地方,如果不写编码格式,默认是encoding="gbk"的#open(“.__csv.error: line contains nul

随便推点

#程序员因薪资不满拒绝offer,HR:你只能是个程序员了_因薪资问题拒绝offer-程序员宅基地

文章浏览阅读1w次,点赞21次,收藏8次。职场上,公司与求职者是处于一种平等关系,你看重我能力,而我看重的是薪资和发展平台。对于双方来说,接受与拒绝都很正常,被拒绝也应该理性看待。但是就有一名程序员在求职过程中,公司看重他的技能,让HR和他谈薪资,而该程序员对于该公司给出的薪资并不满意,从而礼貌拒绝了。可是没想到该HR却生气称:估计你一辈子就是个程序员。从网友曝光的聊天记录来看,HR刚开始给程序员开出15K的薪资,而程序员则表示太..._因薪资问题拒绝offer

NPOI 在 System.IO.FileNotFoundException 中第一次偶然出现的“mscorlib.dll”类型的异常_在system.io.filenotfund 第一次偶现的 mscorlib-程序员宅基地

文章浏览阅读6.1k次。 本人环境是:VS2008 64位系统 。报错的版本是NPOI 2.1.3 binary(2.1.3之前都不可以) 。之后换上NPOI binary 2.1.3.1版本 错误消失,正常启动!NPOI官网: https://npoi.codeplex.com/..._在system.io.filenotfund 第一次偶现的 mscorlib

A survey of Large Lanuage models_a survey of large language models-程序员宅基地

文章浏览阅读720次。Reddit link:高赞链接,知乎或者贴吧类似的;2.预训练数据的数量,随着大语言模型参数规模的增加,需要增加更多数据,chinchilla表明,现有许多大语言模型由于缺乏足够的预训练数据而受到次优训练的影响,llama表明,随着更多数据和更长时间的训练,较小的模型可以实现良好的性能。LLM,语言建模目标是通过单词预测对模型参数进行预训练,但缺乏对人类价值观和偏好的考虑,提出人类对齐,使大语言模型行为能够符合人类的期望,但是与初始的预训练和适应微调不同,语言模型的对齐需要考虑不同的标准。_a survey of large language models

Flume入门-程序员宅基地

文章浏览阅读715次,点赞30次,收藏22次。Flume基础架构、安装部署、入门案例、断点续传源码修改

Android -- 每日一问:介绍一下你经常浏览的 Android 技术网站_android 英文技术网站-程序员宅基地

文章浏览阅读1.3k次。每日一问:介绍一下你经常浏览的 Android 技术网站_android 英文技术网站

文本推理_CVPR 2020 | 细粒度文本视频跨模态检索-程序员宅基地

文章浏览阅读826次。本文介绍的是CVPR 2020的论文《Fine-grained Video-Text Retrieval with Hierarchical Graph Reasoning》(已开源),文章作者是中国人民大学博士生陈师哲同学,这项工作是陈师哲同学在澳大利亚阿德莱德大学吴琦老师组访问时所完成。作者|陈师哲编辑 | 贾 伟论文链接:http://arxiv.org/abs/2003...._细粒度跨模态检索