Spring中FactoryBean接口详解

380 阅读4分钟

本文中Spring源码,来自5.3.x分支,也可访问gitee仓库:侠客行/spring-framework

FactoryBean是Spring框架中的一个重要接口,通过实现这个接口,开发人员可以定制实例化Bean的逻辑。

该接口的作用有:

  1. 隐藏复杂Bean的实例化细节:对于一些复杂的Bean,通过实现FactoryBean接口,开发人员可以隐藏复杂的实例化细节,给上层应用带来便利。
  2. 提供自定义的Bean实例化逻辑:通过实现FactoryBean接口的getObject()方法,开发人员来自定义Bean的实例化逻辑。
  3. 支持复杂对象的创建:对于一些无法通过简单配置或注解来创建的对象,例如数据库连接池、线程池等,可以通过实现FactoryBean接口来创建。

FactoryBean接口有3个方法:

  1. getObject,返回一个该工厂创建的bean;
  2. getObjectType,返回此FactoryBean创建的对象类型,如果事先不知道,则返回null;
  3. isSingleton,这个工厂管理的对象是单例的吗?默认为true。

一 使用示例

package com.xiakexing.service;

import org.springframework.beans.factory.FactoryBean;
import org.springframework.stereotype.Component;

@Component
public class MyFactoryBean implements FactoryBean {

    @Override
    public Object getObject() throws Exception {
        return new UserService();
    }

    @Override
    public Class<?> getObjectType() {
        return UserService.class;
    }
}
package com.xiakexing.service;

public class UserService {
}
package com.xiakexing;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(value = "com.xiakexing")
public class AppConfig {
}
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(context.getBean("myFactoryBean"));

注意,最终获得的是UserService对象,并不是MyFactoryBean类型。

二 实现原理

当调用context.getBean("myFactoryBean")时,会走到AbstractBeanFactory#doGetBean。

此时,单例池中myFactoryBean的类型是MyFactoryBean。

在AbstractBeanFactory#getObjectForBeanInstance中,如果当前bean是FactoryBean类型,则从factoryBeanObjectCache中获取FactoryBean.getObject()所创建的对象。

如果factoryBeanObjectCache中没有,则执行FactoryBeanRegistrySupport#getObjectFromFactoryBean,

在FactoryBeanRegistrySupport#doGetObjectFromFactoryBean中,调用factory.getObject()创建UserService对象。

FactoryBean.isSingleton()返回true时,最后将UserService对象放入factoryBeanObjectCache中,key就是工厂Bean的名称myFactoryBean。

流程图如下

三 如何获取工厂Bean对象

通过context.getBean("myFactoryBean"),可以获得被管理的bean对象。如何获得工厂Bean对象呢? 使用context.getBean("&myFactoryBean")即可。 来看源码:

  1. 将getBean(name)的入参,会去掉工厂引用前缀即&,得到beanName;
  2. 从单例池中获取beanName映射的bean,没有则创建;
  3. 如果name以&开头,要求当前bean必须是FactoryBean类型,否则抛出BeanIsNotAFactoryException;BeanFactory.FACTORY_BEAN_PREFIX就是"&"
  4. name以&开头时,直接返回当前name映射的单例bean,即工厂Bean。

四 与Bean注解的区别

使用场景不同

  • FactoryBean更适用于创建复杂的、需要额外配置或依赖注入的Bean。
  • @Bean注解更适用于创建简单的、可以通过配置或注解来创建的Bean。

返回对象不同

  • 通过FactoryBean获取的Bean实例是由getObject()方法返回的。
  • 通过@Bean注解获取的Bean实例是方法直接返回的。

生命周期不同:

  • @Bean定义的Bean是会经过完整的Bean生命周期,
  • 而FactoryBean所管理的bean,只会经历BeanPostProcessor.postProcessAfterInitialization这一周期(因为 aop 实现),因为实例化和初始化被自定义了

五 与BeanFactory接口的区别

BeanFactory:

  • 是Spring框架的核心接口之一,用于管理和获取应用程序中的Bean实例。
  • 负责创建、配置和管理Bean对象,是Spring IoC容器的基础。

FactoryBean:

  • 是一个特殊的Bean,用于创建和管理其他Bean的实例。
  • 通过实现FactoryBean接口,开发人员可以自定义Bean的实例化逻辑和配置方式。

六 SmartFactoryBean接口

SmartFactoryBean是FactoryBean接口的扩展。这个接口是一个特殊用途的接口,主要用于框架内部和协作框架内部使用。 通常,应用程序自定义的FactoryBean,应该简单地实现普通的FactoryBean接口即可。

有两个方法声明:

  • isPrototype:getObject()总是返回一个独立的实例吗?
  • isEagerInit:这个FactoryBean是否期望急切地初始化所管理的Bean?

通常,getObject()只会在实际访问时调用。当isEagerInit返回true时,表明应该急切地调用getObject()来创建Bean。

isEagerInit的源码实现

在AbstractApplicationContext#refresh中,调用finishBeanFactoryInitialization方法创建所有非lazy-init的单例bean。最终执行DefaultListableBeanFactory#preInstantiateSingletons:

其中会判断当前bean是SmartFactoryBean类型时,且isEagerInit返回true时,立即调用getBean(beanName)来创建所管理的bean。

注意:上图中调用getBean(beanName)后,没有获取返回值。当FactoryBean.isSingleton()返回false时,本次创建的bean被丢弃了(因为是prototype的,不会放入factoryBeanObjectCache中)。