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. delphi xe 之路(14)使用FireMonkeyStyle(一共30篇)

    FireMonkey使用Style来控制控件的显示方式. 每个控件都有一个StyleLookup属性,FireMonkey就是通过控件的这个属性来在当前窗体的StyleBook控件中查找匹配的Styl ...

  2. HTML精确定位:scrollLeft,scrollWidth,clientWidth,offsetWidth完全详细的说明

      HTML:scrollLeft,scrollWidth,clientWidth,offsetWidth具体指完全解释究竟哪里的距离scrollHeight: 获取对象的高度滚动. scrollLe ...

  3. Entity States

    Added. The entity does not yet exist in the database. The SaveChanges method must issue an INSERT st ...

  4. 高性能mysql笔记 第一章 mysql架构

    1.1  mysql逻辑结构 第一层: 负责连接处理,授权认证,安全等事情 第二层:负责mysql的大部分核心功能 ,查询解析,分析,优化,缓存和所有的内置函数,所有跨存储引擎的功能都在这一层实现,, ...

  5. WPF后台生成datatemplate(TreeViewItem例子)

    public void loadCheckListDataTemplate(TreeViewItem tvi) { DataTemplate cdt = new DataTemplate(); Fra ...

  6. 利用WIX制作安装包(2)

    原文 利用WIX制作安装包(2) 这一篇文章将为大家介绍如何使用WIX自定义UI.上一篇文章我们讲过WIX为我们提供了五种安装界面.每种安装界面都是由不同的Dialog组成.在这里我们挑选一种比较常用 ...

  7. 【全面解禁!真正的Expression Blend实战开发技巧】序章

    原文:[全面解禁!真正的Expression Blend实战开发技巧]序章 从silverlight2开始我也和大家一样一直在跟随微软的脚步,从sl2~sl4一步一步过来,总结了不少心得体会.由于各种 ...

  8. 使用BGP的虚拟下一跳实现IGP的路由负载

    网络拓扑: XRV1 ============================================================== !hostname XRV1! interface ...

  9. Win8Metro(C#)数字图像处理--2.20图像垂直镜像

    原文:Win8Metro(C#)数字图像处理--2.20图像垂直镜像  [函数名称] 图像垂直镜像函数MirrorYProcess(WriteableBitmap src) [函数代码]      ...

  10. 零元学Expression Blend 4 - Chapter 47 超简单!运用StackPanel配合OpacityMask做出倒影效果

    原文:零元学Expression Blend 4 - Chapter 47 超简单!运用StackPanel配合OpacityMask做出倒影效果 有网友问我如何在Blend内制作出倒影效果 我提供了 ...