首页 > 其他分享 >浅谈使用实现FactoryBean接口的方式定义Bean

浅谈使用实现FactoryBean接口的方式定义Bean

时间:2023-01-19 11:07:39浏览次数:54  
标签:浅谈 beanName getObject Bean FactoryBean 方法 teacher


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

文章目录

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



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

Bean定义代码:

@Component
public class Teacher implements FactoryBean {
@Override
public Object getObject() throws Exception {
return new Teacher();
}

@Override
public 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 {
@Override
public Object getObject() throws Exception {
return new User();
}

@Override
public 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"));
}
}

浅谈使用实现FactoryBean接口的方式定义Bean_java



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

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

对应的Spring源代码如下:

DefaultListableBeanFactory类的preInstantiateSingletons方法中

浅谈使用实现FactoryBean接口的方式定义Bean_实例化_02


根据以上代码流程(实例化所有非懒加载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

浅谈使用实现FactoryBean接口的方式定义Bean_缓存_03


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

后面有详细的说明

浅谈使用实现FactoryBean接口的方式定义Bean_缓存_04

这是一个比较关键的方法

该方法的参数为:

  • 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,否则false
if (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中含有对应的名字的BeanDefinition
if (mbd == null && containsBeanDefinition(beanName)) {
mbd = getMergedLocalBeanDefinition(beanName);
}
boolean synthetic = (mbd != null && mbd.isSynthetic());
//调用对应的方法:单例池中的实例,经过处理的beanName
object = getObjectFromFactoryBean(factory, beanName, !synthetic);
}
return object;
}


  1. 调用方法内部的doGetObjectFromFactoryBean方法,完成真正的操作获取

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

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

浅谈使用实现FactoryBean接口的方式定义Bean_spring boot_05


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

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

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

浅谈使用实现FactoryBean接口的方式定义Bean_缓存_06


总结几个要点:

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 {
@Override
public Object getObject() throws Exception {
return new User();
}

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

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

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

浅谈使用实现FactoryBean接口的方式定义Bean_spring boot_07


标签:浅谈,beanName,getObject,Bean,FactoryBean,方法,teacher
From: https://blog.51cto.com/u_15942107/6019927

相关文章

  • 浅谈Spring如何利用三个缓存Map解决循环依赖
    写在最前面,在写这篇文章之前,我也参考了很多别人对于Spring循环依赖的讲解,大部分人都是按照先使用二级缓存,再使用三级缓存的思路去讲解。在阅读了Spring源码中关于循环依赖的......
  • 浅谈BeanDefinition、BeanDefinitionMap、RootBeanDefintion三者的关系
    文章目录​​一、BeanDefinition​​​​1、具体实现子类​​​​2、手动创建子类​​​​3、beanClass成员变量的含义​​​​二、BeanDefinitionMap​​​​三、RootBeanD......
  • 通过BeanDefinitionReader获取BeanDefinition的三种不同方式
    文章目录​​一、方式一​​​​二、方式二​​​​三、方式三​​​​四、总结​​在Spring源码中,Spring会先将类扫描为一个一个的BeanDefinition,用于存放Bean的一些基本......
  • 浅谈InnoDB存储引擎的MVCC机制
    文章目录​​一、数据库事务隔离级别​​​​1、事务原则​​​​2、4种隔离级别​​​​3、会出现的3种问题​​​​二、MVCC概念​​​​1、基本结构​​​​2、字段介绍......
  • 浅谈Spring中Bean的生命周期
    文章目录​​1、基本概念​​​​2、生命周期流程图​​​​3、源码与功能分析​​​​3.1、实例化前​​​​3.2、实例化​​​​3.3、实例化后​​​​3.4、初始化前前​......
  • 浅谈Netty中ServerBootstrap服务端源码(含bind全流程)
    文章目录​​一、梳理Java中NIO代码​​​​二、Netty服务端代码​​​​1、newNioEventLoopGroup()​​​​2、group​​​​3、channel​​​​4、NioServerSocketChanne......
  • 浅谈Redisson底层源码
    Redisson源码分析​​一、加锁时使用lua表达式,执行添加key并设置过期时间​​​​二、加锁成功之后给锁添加对应的事件​​​​三、加锁完成,看门狗自动续命未处理完的线程​......
  • 浅谈三种使用Redis实现MQ的方式
    文章目录​​一、消息队列​​​​二、基于List的消息队列​​​​三、基于PubSub的消息队列​​​​四、基于Stream的消息队列​​​​1、基本命令​​​​2、简单使用​​......
  • 浅谈如何使用Redis实现分布式锁
    文章目录​​一、基础版(含自动释放锁)​​​​二、改良版(含过期时间)​​​​三、进阶版(含唯一性验证)​​​​四、单节点版(含Redisson)​​​​五、多节点版(含RedLock)​​写在......
  • 浅谈Redis底层数据结构(sdshdr-redisObject)
    最近看了点Redis底层的源码分析,特作此记录前提共识:Redis是一个默认为16个数据库的key-value内存数据库Redis底层是由C语言实现文章目录​​C语言源码流程​​​​1、server.......