在定义一个Bean的时候,我们可以直接实现FactoryBean接口,然后重写对应的getXxx方法,就能够完成一个Bean的定义工作

文章目录

  • 一、使用实现接口的方式定义Bean
  • 二、其实是生成了两个Bean
  • 三、Spring容器对应这两个类的实例化方式
  • 四、传入&teacher和teacher名字时,获取Bean对象流程

一、使用实现接口的方式定义Bean

Bean定义代码:

@Component
public class Teacher implements FactoryBean {@Overridepublic Object getObject() throws Exception {return new Teacher();}@Overridepublic Class<?> getObjectType() {return Teacher.class;}
}

配置类代码:

@ComponentScan(basePackages = {"pers.mobian.springfifth"})
public class ConfigBean {}

测试类代码:

public class Test3 {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigBean.class);System.out.println(context.getBean("teacher"));}
}

二、其实是生成了两个Bean

我们不难发现对应的getXxx方法是可以自定义返回类型的,如果我们getObject和getObjectType方法返回的类型设置的和类本身不一样,会发生什么?

根据Spring的机制,使用实现FactoryBean接口的Bean定义方式会生成两个Bean,一个是类本身的Bean,一个是getXxx方法返回的Bean,对应的测试代码如下

修改实现接口的Bean代码:

@Component
public class Teacher implements FactoryBean {@Overridepublic Object getObject() throws Exception {return new User();}@Overridepublic Class<?> getObjectType() {return User.class;}
}

修改对应的测试类:

public class Test3 {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigBean.class);// 获取的是getObject方法返回的对象System.out.println(context.getBean("teacher"));// 获取的是getObject方法所属类的对象System.out.println(context.getBean("&teacher"));}
}


三、Spring容器对应这两个类的实例化方式

由于Spring容器的初始化机制,我们首先需要扫描出符合要求的类,然后将这些类注册为一个BeanDefinition,对于BeanDefinition不太熟悉的小伙伴可以参考这篇文章:浅谈BeanDefinition、BeanDefinitionMap、RootBeanDefintion三者的关系,然后再将所有的非懒加载的Bean进行统一的实例化(即完成Bean的生命周期)。

对应的Spring源代码如下:

DefaultListableBeanFactory类的preInstantiateSingletons方法中

根据以上代码流程(实例化所有非懒加载Bean的第一步,遍历beanDefinitionNames集合,近似理解为BeanDefinitionMap,只是前者存的只有名字,后者是名字和BeanDefinition的键值对)我们分析:

  1. 遍历List集合中所有的名字
  2. 剔除是抽象类、不是单例、是懒加载的Bean
  3. 最终调用getBean方法(Spring的设计机制,getBean可以间接的理解为createBean)

通过以上描述我们不难发现,在容器的初始化阶段只会完成一个Bean的实例化,即类上带有@Component注解的类,也就是我们上面的Teacher类,那么

  1. 另一个User类在什么地方呢?
  2. 按照上面的分析,List集合中beanName是teacher,那么我们测试代码中获取Spring容器中名字叫teacher的Bean的时候,获取到的为什么是User类的Bean呢?
  3. 获取到Teacher类的时候又为什么需要在teacher字符串名字前面加&,才能获取到呢?

四、传入&teacher和teacher名字时,获取Bean对象流程

Spring源代码级别:

1.紧接上面的getBean方法,方法会调用doGetBean方法。根据Bean的名字去单例池中获取,如果获取到就调用对应的getObjectFromBeanInstance方法

由于Spring容器实例化已经完成,即容器中(单例池Map)是含有Teacher类对应的Bean

  • 传入teacher:beanName是teacher,单例池中Teacher类对应的Bean
  • 传入&teacher:beanName是teacher,单例池中Teacher类对应的Bean

  1. 仅针对本文的情况,该方法会调用内部的getObjectFromFactoryBean方法

后面有详细的说明

这是一个比较关键的方法

该方法的参数为:

  • beanInstance(beanName对应的单例池中的Bean信息)
  • name(外部调用get方法的字符串)
  • beanName(对name字符串的处理,如果name是&XX,beanName就是XX,如果name是XX,则beanName相同)
  • mbd(对应的beanDefinition信息)

传入teacher:调用getObjectFromFactoryBean方法进一步处理

传入&teacher:直接返回单例池中的对象

protected Object getObjectForBeanInstance(Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {// Don't let calling code try to dereference the factory if the bean isn't a factory.// name以&开头就返回true,否则falseif (BeanFactoryUtils.isFactoryDereference(name)) {// 如果单例池中Bean类型是NullBean类型,直接返回if (beanInstance instanceof NullBean) {return beanInstance;}// 如果你的单例池中的Bean不是FactoryBean类型,就抛异常// 因为前面已经判断,&开头的名字才能进入if,而我们希望通过&xx获取到的Bean对象一定是FactoryBean的实现类if (!(beanInstance instanceof FactoryBean)) {throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass());}}// Now we have the bean instance, which may be a normal bean or a FactoryBean.// If it's a FactoryBean, we use it to create a bean instance, unless the// caller actually wants a reference to the factory.// 没有实现接口,返回false,在取反为true。或者是name以&开头为true// 正常情况下的Bean,直接返回单例池中的对象// 传入字符串为&teacher时,后半部分为true,直接返回单例池中的对象if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {return beanInstance;}// 既实现了factoryBean接口,传入的名字又带有&。此时就认为是去调用getObject方法Object object = null;if (mbd == null) {// 去缓存中获取对应的对象object = getCachedObjectForFactoryBean(beanName);}// 缓存中没有获取到,如果缓存中有就直接返回(这个缓存是用来缓存getObject方法中的对象的)if (object == null) {// Return bean instance from factory.FactoryBean<?> factory = (FactoryBean<?>) beanInstance;// Caches object obtained from FactoryBean if it is a singleton.// BeanDefinitionMap中含有对应的名字的BeanDefinitionif (mbd == null && containsBeanDefinition(beanName)) {mbd = getMergedLocalBeanDefinition(beanName);}boolean synthetic = (mbd != null && mbd.isSynthetic());//调用对应的方法:单例池中的实例,经过处理的beanNameobject = getObjectFromFactoryBean(factory, beanName, !synthetic);}return object;
}
  1. 调用方法内部的doGetObjectFromFactoryBean方法,完成真正的操作获取

传入teacher:先去缓存中获取,获取不到就调用doGetObjectFromFactoryBean方法获取

传入&teacher:上一步已经返回

  1. 在该方法内部完成getObject方法的调用

传入teacher:未打开安全管理器的情况下,调用factory接口的getObject方法,至此调用创建完成。

传入&teacher:上上一步已经返回

总结几个要点:

1. 实现FactoryBean接口的类,在创建Bean的时候会创建两个Bean,一个是类本身的Bean,一个是getObject方法对应的Bean。
2. 两个Bean创建的时间不同,类本身的Bean,是在Spring容器实例化的时候被扫描到,然后实例化Bean并添加到单例池中;但是getObject对应的Bean是在getBean的时候完成初始化,最终添加到通过getObject方法创建的Bean的缓存池中。
3. getObject方法对应的Bean与其他Bean的区别是,getObject方法对应的Bean没有完整的生命周期,因为完整生命周期的Bean是在Spring容器初始化的时候创建的,而getObject方法对应的Bean是在getBean的时候创建的,此时生命周期只有初始化后(AOP功能)。

4. 想要在Spring容器实例化阶段就去创建Bean也能够左到,只是需要将FactoryBean修改为SmartFactoryBean接口,然后将其isEagerInit方法的返回值修改为true即可,代码如下:

@Component
public class Teacher implements SmartFactoryBean {@Overridepublic Object getObject() throws Exception {return new User();}@Overridepublic Class<?> getObjectType() {return User.class;}@Overridepublic boolean isEagerInit() {return true;}
}

第四点原因:因为在初始化Spring容器的时候,他会去判断是否实现了SmartFactoryBean接口,以及对应的返回值是否为true,如果条件满足,就会立即去getBean,即立即去调用getObject方法,而不是等到你最终去调用的时候再去调用getObject方法

浅谈使用实现FactoryBean接口的方式定义Bean相关推荐

  1. 浅谈 PHY 芯片 UTP 接口直连(不使用变压器)的设计

    浅谈 PHY 芯片 UTP 接口直连(不使用变压器)的设计 1.背景: 一个项目, 需要把IP101GR模块的UTP接口和交换机芯片 (RTL8305NB) 的 UTP 接口连在一起,设计的时候没有考 ...

  2. mysql declare与set的区别_浅谈MySQL存储过程中declare和set定义变量的区别

    在存储过程中常看到declare定义的变量和@set定义的变量.简单的来说,declare定义的类似是局部变量,@set定义的类似全局变量. 1.declare定义的变量类似java类中的局部变量,仅 ...

  3. mysql存储过程set什么意思_浅谈MySQL存储过程中declare和set定义变量的区别

    在存储过程中常看到declare定义的变量和@set定义的变量.简单的来说,declare定义的类似是局部变量,@set定义的类似全局变量. 1.declare定义的变量类似java类中的局部变量,仅 ...

  4. 浅谈Go语言(7) - 接口与指针

    文章目录 1. 写在前面 2. 接口类型的使用 (1) 定义 (2) 实现 (3) 使用 基本示例 iface (4) 扩展知识 接口变量值nil 接口间的组合 3. 指针 (1) 基本概念 (2) ...

  5. 浅谈Compartor和Comperable接口

    Comparable 和 Comparator 都是用来实现集合中元素的比较.排序的,接下来我们具体来谈谈两个接口. 注:Comparator位于java.util包下,Comparable位于jav ...

  6. python老是报参数未定义_浅谈Python程序的错误:变量未定义

    Python程序的错误种类 Python程序的错误分两种.一种是语法错误(syntax error).这种错误是语句的书写不符合Python语言的语法规定.第二种是逻辑错误(logic error). ...

  7. python未定义_浅谈Python程序的错误:变量未定义

    Python程序的错误种类 Python程序的错误分两种.一种是语法错误(syntax error).这种错误是语句的书写不符合Python语言的语法规定.第二种是逻辑错误(logic error). ...

  8. 浅谈python+requests实现接口自动化

    前言 今年2月调去支持项目接口测试,测试过程中使用过postman.jmeter工具,基本能满足使用,但是部分情况下使用较为麻烦. 比如:部分字段存在唯一性校验或字段间有业务性校验,每次请求均需手工修 ...

  9. api接口怎么分批传递数据_新手上路:浅谈什么是API接口 API定义是什么

    API应用已经越来越广泛,那你们你了解API吗?API接口是什么呢?本人就来聊聊什么是API接口.首先我们来看看API的定义: API:应用程序接口(API:Application Program I ...

  10. 综合前置接口报文规范_浅谈用HttpRunner进行接口自动化测试

    在参与我们测试平台开发的时候,结识了HttpRunne这个优秀的开源接口测试框架,初步研究发现HttpRunner可以非常方便.非常高效地实现接口自动化测试. 01HttpRunner和常用接口测试框 ...

最新文章

  1. 2010年南非世界杯乌拉圭和韩国八强赛观后感
  2. es6 混合commjs_es6的模块化文件mjs为什么无法运行呢 | 学步园
  3. php dingo和jwt,dingo配合laravel、JWT使用
  4. 2019年下半年《软件评测师》上午试卷及答案
  5. 容器编排技术 -- Kubernetes Pod 生命周期
  6. JAVA-求整数序列中出现次数最多的数
  7. Srs之HttpApi内部调用流程
  8. 动手学深度学习讲义批量下载
  9. python聊天程序程序代码_python聊天程序实例代码分享 -电脑资料
  10. 华三 h3c ospf配置
  11. 动态规划(dynamic programming)初步入门
  12. TI CC1310 sub1G的SDK开发之入门
  13. 【悟空云课堂】第七期:不安全的反射漏洞(CWE-470: Use of Externally-Controlled Input to Select Classes or Code)
  14. POI读取Excel(兼容Excel2003、Excel2007)
  15. UI设计(用户界面设计)的好处
  16. 2020 电赛陕西省赛
  17. 图书信息管理系统(三)
  18. Android面试题最全总结系列 (持续更新中...)
  19. 中华万年历 1.42版本已发布
  20. .net 5 windows 系统服务 workserver

热门文章

  1. 使用nrm管理npm源的切换
  2. 磁盘分区和目录的区别是什么
  3. FastDFS简单介绍
  4. oracle数据库英语,Oracle的数据库管理功能的学习英语
  5. php正弦函数图像,Go语言输出正弦函数(Sin)图像
  6. sql cast函数_数据分析面试必备——SQL你掌握的怎么样?
  7. asp if 显示按钮_ASP.NET Core 3.1入门教程(二)
  8. 计算机基础ppt_「考前秘笈」2020年3月份计算机二级MS-office考试重点
  9. 函数不可以直接调用其他函数内部定义的变量_JavaScript(4) 函数
  10. MVC Area领域处理以及T4MVC的使用