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

Posted 默辨

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈使用实现FactoryBean接口的方式定义Bean相关的知识,希望对你有一定的参考价值。

在定义一个Bean的时候,我们可以直接实现FactoryBean接口,然后重写对应的getXxx方法,就能够完成一个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"));
	}
}






三、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,否则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:上一步已经返回




  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 {
	@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揭秘》---- 工厂方法与FactoryBean

通过FactoryBean方式来配置bean

Spring之BeanFactory和FactoryBean接口的区别

Spring中@Bean和FactoryBean作用和区别

Spring中的BeanFactory和FactoryBean的区别

Spring中BeanFactory与FactoryBean的区别