Spring Framework 组件注册 之 FactoryBean

前言

前两篇文章介绍了如何使用@Component@Import注解来向spring容器中注册组件(javaBean),本文将介绍通过FactoryBean接口继续向spring容器中注册组件。可能第一印象是spring中BeanFactory接口,但是这里确实说的是FactoryBean

推荐阅读

FactoryBean 与 BeanFactory

根据接口名称,我们也可以简单看出两者的区别

  • FactoryBean:它是spring中的一个Bean,只不过它是一个特殊的Bean(工厂Bean),我们可以通过它来自定义生产需要的普通JavaBean
  • BeanFactory:它是spring的Bean工厂,是spring最为重要的接口之一,spring通过此接口获取,管理容器中的各个Bean

接下来将进入本文正题,如何通过FactoryBean接口向spring容器中注册组件

FactoryBean简单使用

正如前面说的,FactoryBean也是spring中的一个Bean,但是它又是一个特殊的Bean,它的存在是为了生产其他的JavaBean。首先我们看看FactoryBean自身的接口定义

public interface FactoryBean<T> {
/**
* 从Spring容器中获取Bean时会调用此方法,返回一个T对象
*/
@Nullable
T getObject() throws Exception; /**
* 此工厂Bean返回对象的类型
*/
@Nullable
Class<?> getObjectType(); /**
* 工厂Bean创建的对象是否为单例,
* 如果返回false,说明getObject方法的实例对象不是单例的,
* Spring每次从容器中获取T对象时,都调用getObject方法创建一个对象
*/
default boolean isSingleton() {
//spring 5 接口默认返回true(单例)
return true;
}
}

FactoryBean接口定义简单明了,就是用来获取一个Bean的基本信息,下面我们自己实现该接口,来生产一个javaBean

/**
* 产生 Bike 对象的工厂Bean
*/
@Component
public class BikeFactoryBean implements FactoryBean<Bike> { public Bike getObject() throws Exception {
System.out.println("......开始创建Bike对象......");
return new Bike();
} public Class<?> getObjectType() {
return Bike.class;
} public boolean isSingleton() {
return true;
}
}

自定义的一个JavaBean类

/**
* 待注册的自定义组件
*/
@Data
public class Bike {
private String id = "by FactoryBean";
}

添加spring容器启动的引导类

/**
* spring 容器启动引导类,测试 FactoryBean 功能
*/
@ComponentScan("com.spring.study.ioc.factorybean")
public class TestFactoryBeanBootstrap { public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext(TestFactoryBeanBootstrap.class);
//获取工厂Bean本身的Id
String[] beanNames = applicationContext.getBeanNamesForType(BikeFactoryBean.class);
System.out.println("BikeFactoryBean names:" + Arrays.asList(beanNames));
//获取工厂Bean产生的Bean的Id
beanNames = applicationContext.getBeanNamesForType(Bike.class);
System.out.println("Bike bean names:" + Arrays.asList(beanNames));
Object bean = applicationContext.getBean("bikeFactoryBean");
System.out.println(bean);
bean = applicationContext.getBean(Bike.class);
System.out.println(bean);
// 获取工厂Bean 本身的实例对象
bean = applicationContext.getBean(BeanFactory.FACTORY_BEAN_PREFIX + "bikeFactoryBean");
System.out.println(bean);
applicationContext.close();
}
}

启动spring容器,控制台打印结果:

BikeFactoryBean names:[&bikeFactoryBean]

Bike bean names:[bikeFactoryBean]

......开始创建Bike对象......

Bike(id=by FactoryBean)

Bike(id=by FactoryBean)

com.spring.study.ioc.factorybean.BikeFactoryBean@4eb7f003

由结果可以看出

  • 虽然代码中只在BikeFactoryBean类上加了@Component注解,但是从spring容器仍然可以获取到Bike类的信息
  • 工厂Bean的Id与实际产生的Bean的Id仅差了一个&符,也就是说,工厂Bean定义的Id实际为getObject()方法返回Bean的Id,而工厂Bean本身的Id被添加了一个前缀&
  • 工厂Bean的isSingleton()方法返回了true,所以通过spring容器多次获取实际的Bean时,getObject()方法也是执行一次
  • 根据工厂Bean的Id可以看出,要想从spring容器中获取工厂Bean本身,则需要在注册的Id前面添加一个&符,而此前缀在BeanFactory接口中已经定义了FACTORY_BEAN_PREFIX

如果将BikeFactoryBeanisSingleton()方法返回了false

 public boolean isSingleton() {
return false;
}

重新启动spring容器,可以看如下结果:

BikeFactoryBean names:[&bikeFactoryBean]

Bike bean names:[bikeFactoryBean]

......开始创建Bike对象......

Bike(id=by FactoryBean)

......开始创建Bike对象......

Bike(id=by FactoryBean)

com.spring.study.ioc.factorybean.BikeFactoryBean@4eb7f003

由结果可以看出,唯一的变化在于从spring容器中多次获取实际Bean时,工厂Bean的getObject()方法被多次进行了调用。这与spring容器中被标识为原型的普通Bean相同,每次从spring中获取Bean时都会被实例化。

FactoryBean 执行过程

要想了解FactoryBean的执行过程,就需要结合Spring容器启动的过程来进行分析。而spring容器的启动过程经过了纷繁复杂的步骤。为了尽可能少的入坑和挖坑,下面仅结合FactoryBean相关的源码进行说明。

在开始入坑之旅之前,结合前面的例子做几点说明,方便后续讲解

前面定义的 BikeFactoryBean 类上面直接添加了@Component注解,这样spring会默认以类名首字母小写(bikeFactoryBean)作为beanName;如果使用@Bean进行注册时,spring默认会以方法名作为beanName,下面继续以“BikeFactoryBean”为例。

  1. spring容器启动过程中,在执行完所有的BeanFactoryPostProcessorBeanPostProcessor以及注册Listener后会执行org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization()方法,此方法会对剩下所有的非Abstract非LazyInit的单实例Bean进行实例化,以下为部分代码片段。
@Override
public void preInstantiateSingletons() throws BeansException {
...省略代码...
// 1. 拷贝一份副本:spring容器中的所有的Bean名称
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
// 2. 遍历每一个beanName,尝试通过getBean()方法进行实例化
// 在getBean()方法内部会先尝试从容器singletonObjects中获取Bean,如果没有才会进行实例化操作
for (String beanName : beanNames) {
// 3. 通过beanName获取Bean定义信息 BeanDefinition
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
// 4. 根据BeanDefinition判断该Bean是否不是抽象的,单例的,非懒加载的
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
// 5. 满足上面的条件后,在根据beanName判断此Bean是否是一个工厂Bean(实现了FactoryBean接口)
if (isFactoryBean(beanName)) {
// 6. 如果是一个工厂Bean,则在此处进行工厂Bean本身的实例化
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
...省略代码...
}
else {
// 如果不是工厂bean,也是调用getBean()方法进行实例化
getBean(beanName);
}
}
}
...省略代码...
}

preInstantiateSingletons()是在finishBeanFactoryInitialization()方法内部调用的

根据上面的代码流程,在第6步时,会对BikeFactoryBean类本身进行实例化,并且可以看出传递的beanName为初始注册的name前添加了&符前缀即&bikeFactoryBean,用于在genBean()方法内部标识它是一个工厂Bean。但是在跟踪源码后发现,在getBean()方法内部,会先将传入的beanName(&bikeFactoryBean)开头的&符去除,并且最终实例化Bean后,在容器中保存的beanName还是不带&符前缀的名称即bikeFactoryBean

  1. 根据第一步的结果,spring容器在启动后,工厂Bean会像普通Bean一样在spring容器中会保留一条自身的单实例Bean(spring容器中保存的数据为:<bikeFactoryBean, BikeFactoryBean>),既然spring容器中只保存了BikeFactoryBean本身,那么后续获取Bike类的beanName和Bean实例时,又是怎么获取到的呢?带着疑问,我们继续看后面的代码。首先,上面的例子中调用了applicationContext.getBeanNamesForType(Bike.class)方法来获取Bike类的beanName。所以继续跟踪此方法看看到底发生了什么。
    // getBeanNamesForType()方法内部最终调用了此方法,可断点跟踪至此
private String[] doGetBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit) {
// 用来保存符合条件的结果(beanName集合)
List<String> result = new ArrayList<>();
// 与上面的代码相似,遍历spring容器中注册的所有的beanNames
for (String beanName : this.beanDefinitionNames) {
if (!isAlias(beanName)) {
try {
// 根据beanName获取Bean的定义信息 BeanDefinition
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
// 根据BeanDefinition 进行检查
if (!mbd.isAbstract() && (allowEagerInit ||
(mbd.hasBeanClass() || !mbd.isLazyInit() || isAllowEagerClassLoading()) &&
!requiresEagerInitForType(mbd.getFactoryBeanName()))) {
// 根据beanName和Bean的定义信息判断是否是工厂Bean
boolean isFactoryBean = isFactoryBean(beanName, mbd);
BeanDefinitionHolder dbd = mbd.getDecoratedDefinition();
boolean matchFound =
(allowEagerInit || !isFactoryBean ||
(dbd != null && !mbd.isLazyInit()) || containsSingleton(beanName)) &&
(includeNonSingletons ||
(dbd != null ? mbd.isSingleton() : isSingleton(beanName))) &&
// 根据Bean的定义信息判断完后,在此方法中判断此beanName对应的Bean实例是否与传入的类型相匹配
isTypeMatch(beanName, type);
// 如果根据beanName获得的是一个工厂Bean,并且与传入的类型不匹配,则满足条件,将beanName添加 & 符前缀
if (!matchFound && isFactoryBean) {
// 对于工厂Bean,接下来尝试匹配工厂Bean实例本身
beanName = FACTORY_BEAN_PREFIX + beanName;
matchFound = (includeNonSingletons || mbd.isSingleton()) && isTypeMatch(beanName, type);
}
// 如果获取的Bean实例与传入的类型匹配,将beanName添加到结果集合中
if (matchFound) {
result.add(beanName);
}
}
}
catch (CannotLoadBeanClassException ex) {
// ...省略代码...
}
catch (BeanDefinitionStoreException ex) {
// ...省略代码...
}
}
}
// ...省略代码... return StringUtils.toStringArray(result);
}

isTypeMatch()方法中的部分代码

public boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException {
// 对beanName进行处理,将开头的 & 符过滤
String beanName = transformedBeanName(name);
// 从spring容器中获取单实例Bean,由于spring容器启动时已经将单实例Bean进行了实例化,
// 所以此时可以直接在容器中得到Bean实例
Object beanInstance = getSingleton(beanName, false);
if (beanInstance != null && beanInstance.getClass() != NullBean.class) {
// 获取到Bean实例后,判断是否为工厂Bean
if (beanInstance instanceof FactoryBean) {
// 如果是工厂Bean,并且获取的beanName不是以&符开头
if (!BeanFactoryUtils.isFactoryDereference(name)) {
// 将实例强转为 FactoryBean 并调用 FactoryBean接口的getObjectType()方法,
// 获取工厂Bean所生产的实例类型
Class<?> type = getTypeForFactoryBean((FactoryBean<?>) beanInstance);
// 判断工厂Bean生产的实例类型与传入的类型是否匹配
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((PrivilegedAction<Class<?>>)
factoryBean::getObjectType, getAccessControlContext());
}
else {
// 直接调用 FactoryBean 接口的 getObjectType()方法,获取生产的类型
return factoryBean.getObjectType();
}
}
catch (Throwable ex) {
// Thrown from the FactoryBean's getObjectType implementation.
logger.info("FactoryBean threw exception from getObjectType, despite the contract saying " +
"that it should return null if the type of its object cannot be determined yet", ex);
return null;
}
}

以上便是getBeanNamesForType()方法经过的部分重要代码

由此可以看出,当我们想要获取BikeFactoryBean本身的beanName时,doGetBeanNamesForType方法内部将bikeFactoryBean前添加了&符前缀,于是便获取到了&bikeFactoryBean

当我们想要获取Bike类型的beanName时,spring会通过容器遍历已经注册的所有的beanNames,然后根据beanName及对应的Bean定义信息BeanDefinition进行判断过滤,并且对于所有的工厂Bean,会获取spring容器中已经实例化的Bean对象,调用 FactoryBean 接口的 getObjectType()方法,得到工厂Bean所生产的实例类型,然后与Bike.class相比较,如果匹配,则将此beanName保存到结果集中,最后返回。所以,当我们想要获取Bike类型的beanName时,从spring容器中便可以找到bikeFactoryBean

  1. 从spring容器中获取到beanName后,我们继续获取Bike实例

    从前文中引导类的代码可以看出,获取Bike实例有两种方式,跟踪源码可以发现,根据Bike类型获取实例时,spring实际是通过第二步获取到beanName后再最终调用doGetBean方法获取实例对象。下面看看部分源码
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
// 对传入的beanName进行过滤,去除&符前缀
final String beanName = transformedBeanName(name);
Object bean; // 从spring容器中获取实例,由于spring容器启动时已经将单实例Bean进行实例化,所以此时可以直接获得
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
if (logger.isTraceEnabled()) {
if (isSingletonCurrentlyInCreation(beanName)) {
logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
"' that is not fully initialized yet - a consequence of a circular reference");
}
else {
logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
}
}
// 获取指定的Bean实例,如果是工厂bean,则为Bean实例本身或其创建的对象。
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
// ...省略代码...
return (T) bean;
}

从上面的代码可以看出,获取Bike实例的具体代码还在getObjectForBeanInstanc()方法内部,我们继续查看

protected Object getObjectForBeanInstance(
Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) { // 判断beanName是否是以&符开头的
if (BeanFactoryUtils.isFactoryDereference(name)) {
if (beanInstance instanceof NullBean) {
return beanInstance;
}
if (!(beanInstance instanceof FactoryBean)) {
throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass());
}
} // 根据beanName从spring容器中获取的Bean实例如果不是工厂Bean,或者beanName是以&符开头,就直接返回这个Bean实例
// 当我们获取Bike类型的实例时,beanName为“bikeFactoryBean”,
// beanInstance为“BikeFactoryBean”类型,是一个工厂Bean,所以条件不满足,继续向下走
if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
return beanInstance;
} Object object = null;
if (mbd == null) {
// 根据beanName从缓存中获取Bean实例,第一次来获取Bike实例时为空,
// factoryBeanObjectCache.get(beanName);
// 后续再获取时,便可以在此获得到,然后返回
object = getCachedObjectForFactoryBean(beanName);
}
if (object == null) {
// 将获取的工厂Bean强转为 FactoryBean 类型,以便下面调用其getObject()方法获取对象
FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
// 获取bean定义信息
if (mbd == null && containsBeanDefinition(beanName)) {
mbd = getMergedLocalBeanDefinition(beanName);
}
boolean synthetic = (mbd != null && mbd.isSynthetic());
//在此方法内部调用 FactoryBean 接口的 getObject()方法获取对象
object = getObjectFromFactoryBean(factory, beanName, !synthetic);
}
return object;
}

距离真相还差两步了,坚持就是胜利,我们继续看getObjectFromFactoryBean()的源码

protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
// 此处调用 FactoryBean 的isSingleton()方法,判断是否是一个单列
// 如果是单例的,走if内部,获取到对象后,会保存到factoryBeanObjectCache缓存中,以便后续使用
if (factory.isSingleton() && containsSingleton(beanName)) {
synchronized (getSingletonMutex()) {
// 检查缓存中是否已经存在
Object object = this.factoryBeanObjectCache.get(beanName);
if (object == null) {
// 调用最后一个方法,执行FactoryBean 的 getObject()方法获取对象
object = doGetObjectFromFactoryBean(factory, beanName);
// 再次检查缓存
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
if (alreadyThere != null) {
object = alreadyThere;
}
else {
// ... 省略代码 ...
if (containsSingleton(beanName)) {
// 将获取的对象放入factoryBeanObjectCache缓存中,以便后续使用
this.factoryBeanObjectCache.put(beanName, object);
}
}
}
return object;
}
}
// 如果不是单例的,每次获取的对象直接返回,不会放入缓存中,所以每次都会调用getObject()方法
else {
Object object = doGetObjectFromFactoryBean(factory, beanName);
if (shouldPostProcess) {
try {
object = postProcessObjectFromFactoryBean(object, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", ex);
}
}
return object;
}
}

根据上面的流程,终于来到了最后一步

private Object doGetObjectFromFactoryBean(final FactoryBean<?> factory, final String beanName)
throws BeanCreationException {
Object object;
try {
if (System.getSecurityManager() != null) {
AccessControlContext acc = getAccessControlContext();
try {
object = AccessController.doPrivileged((PrivilegedExceptionAction<Object>) factory::getObject, acc);
} catch (PrivilegedActionException pae) {
throw pae.getException();
}
}
else {
// 直接调用 FactoryBean 接口的 getObject()方法获取实例对象
object = factory.getObject();
}
}
catch (FactoryBeanNotInitializedException ex) {
throw new BeanCurrentlyInCreationException(beanName, ex.toString());
}
// ... 省略代码 ...
return object;
}

经过如此多的代码,spring终于帮我们获取到Bike对象实例

通过BikeFactoryBean来获取Bike类的实例时,spring先获取Bike类型对应的beanName(bikeFactoryBean),然后根据beanName获取到工厂Bean实例本身(BikeFactoryBean),最终spring会调用BikeFactoryBean 的 getObject()方法来获取Bike对象实例。并且根据 BikeFactoryBean 实例的 isSingleton() 方法来判断Bike类型的实例是否时单例的,依此来决定要不要将获取的Bike对象放入到缓存中,以便后续使用。

总结

本文主要讲解了如何通过 FactoryBean接口向spring容器中注入组件,通过简单的案例进行模拟,并根据案例对源码的执行过程进行跟踪,分析了FactoryBean接口的执行过程。

另外,在每一次跟踪spring源码时,都会有新的收获。在spring庞大的体系下,只有定位好自己的目标,明确自己的需求,才不会被spring无限的代码所淹没。

学习永远都不是一件简单的事情,可以有迷茫,可以懒惰,但是前进的脚步永远都不能停止。

不积跬步,无以至千里;不积小流,无以成江海;

Spring Framework 组件注册 之 FactoryBean的更多相关文章

  1. Spring Framework 组件注册 之 @Import

    Spring Framework 组件注册 之 @Import 写在前面 向spring中注册组件或者叫javaBean是使用spring的功能的前提条件.而且spring也提供了很多种方式,让我们可 ...

  2. Spring Framework 组件注册 之 @Component

    Spring Framework 组件注册 之 @Component 写在前面 在spring大行其道的今天,对于spring的使用和掌握乃是不可缺少的必备技能.但是spring的整个体系尤为庞大,对 ...

  3. 11、组件注册-使用FactoryBean注册组件

    11.组件注册-使用FactoryBean注册组件 package org.springframework.beans.factory; import org.springframework.lang ...

  4. spring注解-组件注册

    一.@Configuration+@Bean @Configuration:配置类==配置文件 @Bean:给容器中注册一个Bean:类型为返回值的类型,默认是用方法名作为id @Bean(" ...

  5. Spring笔记 - 组件注册

    @Bean:类注入容器 xml方式: <bean id="person" class="com.hrh.bean.Person"> <prop ...

  6. 一、Spring之组件注册-@Configuration&@Bean给容器中注册组件

    xml配置方式 首先我们创建一个实体类Person public class Person { private String name; private Integer age; private St ...

  7. Spring Framework 条件装配 之 @Conditional

    Spring Framework 条件装配 之 @Conditional 前言 了解SpringBoot的小伙伴对Conditional注解一定不会陌生,在SpringBoot项目中,Conditio ...

  8. Difference between BeanFactory and FactoryBean in Spring Framework (Spring BeanFactory与Factory区别)

    参见原文:http://www.geekabyte.io/2014/11/difference-between-beanfactory-and.html geekAbyte Codes and Ran ...

  9. 向Spring容器中注册组件的方法汇总小结

    1.通过xml定义 <bean class=""> <property name="" value=""></ ...

随机推荐

  1. HDU 4357 String change 法冠军

    意甲冠军: 鉴于a串b串,问我们能否a变b串 办法:自选a的2快报,ascil+=1 然后交换位置,能够操作自如倍. 3个月3以上就能T^T 2法官将着眼于暴力 #include <cstdio ...

  2. React实现checkbox group多组选项和标签组显示的联动

    实现功能:勾选checkbox项,确定后,已勾选的checkbox项以tag标签的形式展示,tag标签可快捷删除. 实现过程: 使用React. 使用Ant Design的Checkbox.Tag组件 ...

  3. Python 推断素数

    a = raw_input() #输入数字 a = int(a) #铸造成int b=True #的标记 for i in range(2,a): #从2开始循环本身 if a%i==0: #除了自己 ...

  4. 设置oracle密码不过期,修改用户密码

    1. 查看用户名使用的profile select username,profile from dba_usersSELECT * FROM dba_profiles WHERE profile='D ...

  5. delphi判断线程状态函数(使用GetExitCodeThread API函数去判断线程的句柄)

    //判断线程是否释放//返回值:0-已释放:1-正在运行:2-已终止但未释放://3-未建立或不存在 function CheckThreadFreed(aThread: TThread): Byte ...

  6. Windows 10开发基础——XML和JSON (一)

    主要内容: JSON的序列化与反序列化 XML的序列化与反序列化 1.JSON的序列化与反序列化     JSON(JavaScript Object Notation)是一种轻量级的数据交换语言,它 ...

  7. 应用ImageJ对荧光图片进行半定量分析

    原文 应用ImageJ对荧光图片进行半定量分析 前言ImageJ是个好东西……(省略1000字)总地来说对我们的好处是:1.免费2.多功能,基本功能就很多,加上插件可以说得上是无限多(前提是你找得到, ...

  8. PHPEXCEL 不能输出中文内容,只显示空白

    以他带的示例文件为例 01simple-download-xls.php // Add some data $objPHPExcel->setActiveSheetIndex(0)        ...

  9. 发布ActiveX控件

    最近我们正在研究ActiveX技术.我们使用Delphi 5创建了一个具有ActiveForm的ActiveX控件应用程序.这个控件产生一个.OCX文件.现在,我们需要把这个控件部署在服务器端,在用户 ...

  10. 微信小程序把玩(三十五)Video API

    原文:微信小程序把玩(三十五)Video API 电脑端不能测试拍摄功能只能测试选择视频功能,好像只支持mp4格式,值得注意的是成功之后返回的临时文件路径是个列表tempFilePaths而不是tem ...