Spring官网阅读(七)容器的扩展点(二)FactoryBean

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: Spring官网阅读(七)容器的扩展点(二)FactoryBean

官网介绍


微信图片_20221112130844.png

从上面这段文字我们可以得出以下几个信息:


  1. FactoryBean主要用来定制化Bean的创建逻辑
  2. 当我们实例化一个Bean的逻辑很复杂的时候,使用FactoryBean是很必要的,这样可以规避我们去使用冗长的XML配置
  3. FactoryBean接口提供了以下三个方法:
  • Object getObject(): 返回这个FactoryBean所创建的对象。
  • boolean isSingleton(): 返回FactoryBean所创建的对象是否为单例,默认返回true。
  • Class getObjectType(): 返回这个FactoryBean所创建的对象的类型,如果我们能确认返回对象的类型的话,我们应该正常对这个方法做出实现,而不是返回null。


1.Spring自身大量使用了FactoryBean这个概念,至少有50个FactoryBean的实现类存在于Spring容器中

2.假设我们定义了一个FactoryBean,名为myFactoryBean,当我们调用getBean("myFactoryBean")方法时返回的并不是这个FactoryBean,而是这个FactoryBean所创建的Bean,如果我们想获取到这个FactoryBean需要在名字前面拼接"&",行如这种形式:getBean("&myFactoryBean")


上面这些概念可能刚刚说的时候大家不是很明白,下面我们通过FactoryBean的一些应用来进一步体会这个接口的作用。


FactoryBean的应用


我们来看下面这个Demo:

public class MyFactoryBean implements FactoryBean {
  @Override
  public Object getObject() throws Exception {
    System.out.println("执行了一段复杂的创建Bean的逻辑");
    return new TestBean();
  }
  @Override
  public Class<?> getObjectType() {
    return TestBean.class;
  }
  @Override
  public boolean isSingleton() {
    return true;
  }
}
public class TestBean {
  public TestBean(){
    System.out.println("TestBean被创建出来了");
  }
}
// 测试类
public class Main {
  public static void main(String[] args) {
    AnnotationConfigApplicationContext ac=
        new AnnotationConfigApplicationContext(Config.class);
    System.out.println("直接调用getBean(\"myFactoryBean\")返回:"+ac.getBean("myFactoryBean"));
    System.out.println("调用getBean(\"&myFactoryBean\")返回:"+ac.getBean("&myFactoryBean"));
  }
}

运行后结果如下:


执行了一段复杂的创建Bean的逻辑

TestBean被创建出来了

直接调用getBean(“myFactoryBean”)返回:com.dmz.official.extension.factorybean.TestBean@28f67ac7

调用getBean("&myFactoryBean")返回:com.dmz.official.extension.factorybean.MyFactoryBean@256216b3


我们虽然没有直接将TestBean放入Spring容器中,但是通过FactoryBean也完成了这一操作。同时当我们直接调用getBean("FactoryBean的名称")获取到的是FactoryBean创建的Bean,但是添加了“&”后可以获取到FactoryBean本身。


FactoryBean相关源码分析


我们先看下下面这张图:


微信图片_20221112131203.png

涉及到FactoryBean主要在3-11-6这一步中,我们主要关注下面这段代码:

// .....省略无关代码.......
// 1.判断是不是一个FactoryBean
if (isFactoryBean(beanName)) {
    // 2.如果是一个FactoryBean那么在getBean时,添加前缀“&”,获取这个FactoryBean
    Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
    if (bean instanceof FactoryBean) {
        final FactoryBean<?> factory = (FactoryBean<?>) bean;
        boolean isEagerInit;
        // 3.做权限校验,判断是否是一个SmartFactoryBean,并且不是懒加载的
        if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
            isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
                                                        ((SmartFactoryBean<?>) factory)::isEagerInit,
                                                        getAccessControlContext());
        }
        else {
            // 3.判断是否是一个SmartFactoryBean,并且不是懒加载的
            isEagerInit = (factory instanceof SmartFactoryBean &&
                           ((SmartFactoryBean<?>) factory).isEagerInit());
        }
        if (isEagerInit) {
            // 4.如果是一个SmartFactoryBean并且不是懒加载的,那么创建这个FactoryBean创建的Bean
            getBean(beanName);
        }
    }
}
else {
    // 不是一个FactoryBean,直接创建这个Bean
    getBean(beanName);
}
// ...省略无关代码.....

我们按照顺序一步步分析,首先看第一步:

1.判断是不是一个FactoryBean,对应源码如下:

public boolean isFactoryBean(String name) throws NoSuchBeanDefinitionException {
    String beanName = transformedBeanName(name);
    // 直接从单例池中获取这个Bean,然后进行判断,看是否是一个FactoryBean
    Object beanInstance = getSingleton(beanName, false);
    if (beanInstance != null) {
        return (beanInstance instanceof FactoryBean);
    }
    // 查找不到这个BeanDefinition,那么从父容器中再次确认是否是一个FactoryBean
    if (!containsBeanDefinition(beanName) && getParentBeanFactory() instanceof ConfigurableBeanFactory) {
        // No bean definition found in this factory -> delegate to parent.
        return ((ConfigurableBeanFactory) getParentBeanFactory()).isFactoryBean(name);
    }
    // 从当前容器中,根据BeanDefinition判断是否是一个FactoryBean
    return isFactoryBean(beanName, getMergedLocalBeanDefinition(beanName));
}

1.如果是一个FactoryBean那么在getBean时,添加前缀“&”,获取这个FactoryBean

2.判断是否是一个SmartFactoryBean,并且不是懒加载的

这里涉及到一个概念,就是SmartFactoryBean,实际上这个接口继承了FactoryBean接口,并且SmartFactoryBean是FactoryBean的唯一子接口,它扩展了FactoryBean多提供了两个方法如下

// 是否为原型,默认不是原型
default boolean isPrototype() {
    return false;
}
// 是否为懒加载,默认为懒加载
default boolean isEagerInit() {
    return false;
}

从上面的代码中可以看出,我们当当实现一个FactoryBean接口,Spring并不会在启动时就将这个FactoryBean所创建的Bean创建出来,为了避免这种情况,我们有两种办法:


  • 实现SmartFactoryBean,并重写isEagerInit方法,将返回值设置为true
  • 我们也可以在一个不是懒加载的Bean中注入这个FactoryBean所创建的Bean,Spring在解决依赖关系也会帮我们将这个Bean创建出来

实际上我们可以发现,当我们仅仅实现FactoryBean时,其getObject()方法所产生的Bean,我们可以当前是懒加载的。


1.如果是一个SmartFactoryBean并且不是懒加载的,那么创建这个FactoryBean创建的Bean。这里需要注意的是此时创建的不是这个FactoryBean,以为在getBean时并没有加一个前缀“&”,所以获取到的是其getObject()方法所产生的Bean。

在上面的代码分析完后,在3-6-11-2中也有两行FactoryBean相关的代码,如下:

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
                          @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
    // 1.获取bean名称
    final String beanName = transformedBeanName(name);
    Object bean;
    //...省略无关代码...,这里主要根据beanName创建对应的Bean
    // 2.调用getObject对象创建Bean
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    }

2.获取bean名称

protected String transformedBeanName(String name) {
    // 這個方法主要用來解析別名,如果是別名的話,获取真实的BeanName
    return canonicalName(BeanFactoryUtils.transformedBeanName(name));
}
 // 处理FactoryBean
public static String transformedBeanName(String name) {
    Assert.notNull(name, "'name' must not be null");
    // 没有带“&”,直接返回
    if (!name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
        return name;
    }
    // 去除所有的“&”,防止这种写法getBean("&&&&beanName")
    return transformedBeanNameCache.computeIfAbsent(name, beanName -> {
        do {
            beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
        }
        while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));
        return beanName;
    });
}

如果是一个FactoryBean,将会调用其getObject()方法,如果不是直接返回。

我们可以看到,在调用getObjectForBeanInstance(sharedInstance, name, beanName, null);传入了一个参数—name,也就是还没有经过transformedBeanName方法处理的bean的名称,可能会带有“&”符号,Spring通过这个参数判断这个Bean是不是一个FactoryBean,如果是的话,会调用其getObject()创建Bean。**被创建的Bean不会存放于单例池中,而是放在一个名为factoryBeanObjectCache的缓存中。**具体的代码因为比较复杂,在这里我们就暂且不分析了,大家可以先留个印象,源码阶段我会做详细的分析。


Spring中FactoryBean概念的汇总(纯粹个人观点)


除了我们在上文中说到的实现了FactoryBean或者SmartFactoryBean接口的Bean可以称之为一个”FactoryBean“,不知道大家对BeanDefinition中的一个属性是否还有印象。BeanDefinition有属性如下(实际上这个属性存在于AbstractBeanDefinition中):

@Nullable
private String factoryBeanName;
@Nullable
private String factoryMethodName;

对于这个属性跟我们这篇文章中介绍的FactoryBean有什么关系呢?

首先,我们看看什么情况下bd中会存在这个属性,主要分为以下两种情况:

第一种情况:

@Configuration
public class Config {
  @Bean
  public B b(){
    return new B();
  }
}

我们通过@Bean的方式来创建一个Bean,那么在B的BeanDefinition会记录factoryBeanName这个属性,同时还会记录是这个Bean中的哪个方法来创建B的。在上面的例子中,factoryBeanName=config,factoryMethodName=b。


第二种情况:

<bean id="factoryBean" class="com.dmz.official.extension.factorybean.C"/>
<bean id="b" class="com.dmz.official.extension.factorybean.B" factory-bean="factoryBean" factory-method="b"/>

通过XML的方式进行配置,此时B的BeanDefinition中factoryBeanName=factoryBean,factoryMethodName=b。


上面两种情况,BeanDefinition中的factoryBeanName这个属性均不会为空,但是请注意此时记录的这个名字所以对于的Bean并不是一个实现了FactoryBean接口的Bean。


综上,我们可以将Spring中的FactoryBean的概念泛化,也就是说所有生产对象的Bean我们都将其称为FactoryBean,那么可以总结画图如下:

微信图片_20221112131907.png

这是个人观点哈,没有在官网找到什么文档,只是这种比较学习更加能加深印象,所以我把他们做了一个总结,大家面试的时候不用这么说


跟FactoryBean相关常见的面试题


1、FactoryBean跟BeanFactory的区别


FactoryBean就如我们标题所说,是Spring提供的一个扩展点,适用于复杂的Bean的创建。mybatis在跟Spring做整合时就用到了这个扩展点。并且FactoryBean所创建的Bean跟普通的Bean不一样。我们可以说FactoryBean是Spring创建Bean的另外一种手段。


而BeanFactory是什么呢?BeanFactory是Spring IOC容器的顶级接口,其实现类有XMLBeanFactory,DefaultListableBeanFactory以及AnnotationConfigApplicationContext等。BeanFactory为Spring管理Bean提供了一套通用的规范。接口中提供的一些方法如下:

boolean containsBean(String beanName)
Object getBean(String)
Object getBean(String, Class)
Class getType(String name)
boolean isSingleton(String)
String[] getAliases(String name)

通过这些方法,可以方便地获取bean,对Bean进行操作和判断。


2、如何把一个对象交给Spring管理


首先,我们要弄明白一点,这个问题是说,怎么把一个对象交給Spring管理,“对象”要划重点,我们通常采用的注解如@Compent或者XML配置这种类似的操作并不能将一个对象交给Spring管理,而是让Spring根据我们的配置信息及类信息创建并管理了这个对象,形成了Spring中一个Bean。把一个对象交给Spring管理主要有两种方式


就是用我们这篇文章中的主角,FactoryBean,我们直接在FactoryBean的getObject方法直接返回需要被管理的对象即可

@Bean注解,同样通过@Bean注解标注的方法直接返回需要被管理的对象即可。


总结


在本文中我们完成了对FactoryBean的学习,最重要的是我们需要明白一点,FactoryBean是Spring中特殊的一个Bean,Spring利用它提供了另一种创建Bean的方式,FactoryBean整体的体系比较复杂,FactoryBean是如何创建一个Bean的一些细节我们还没有涉及到,不过不要急,在源码学习阶段我们还会接触到它,并会对其的整个流程做进一步的分析。目前容器的扩展点我们还剩最后一个部分,即BeanPostProcessor。BeanPostProcessor贯穿了整个Bean的生命周期,学习的难度更大。希望大家跟我一步步走下去,认认真真学习完Spring,加油!


相关文章
|
3月前
|
XML Java 数据格式
Spring5入门到实战------7、IOC容器-Bean管理XML方式(外部属性文件)
这篇文章是Spring5框架的实战教程,主要介绍了如何在Spring的IOC容器中通过XML配置方式使用外部属性文件来管理Bean,特别是数据库连接池的配置。文章详细讲解了创建属性文件、引入属性文件到Spring配置、以及如何使用属性占位符来引用属性文件中的值。
Spring5入门到实战------7、IOC容器-Bean管理XML方式(外部属性文件)
|
1月前
|
缓存 JavaScript Java
Spring之FactoryBean的处理底层源码分析
本文介绍了Spring框架中FactoryBean的重要作用及其使用方法。通过一个简单的示例展示了如何通过FactoryBean返回一个User对象,并解释了在调用`getBean()`方法时,传入名称前添加`&`符号会改变返回对象类型的原因。进一步深入源码分析,详细说明了`getBean()`方法内部对FactoryBean的处理逻辑,解释了为何添加`&`符号会导致不同的行为。最后,通过具体代码片段展示了这一过程的关键步骤。
Spring之FactoryBean的处理底层源码分析
|
30天前
|
Java 测试技术 Windows
咦!Spring容器里为什么没有我需要的Bean?
【10月更文挑战第11天】项目经理给小菜分配了一个紧急需求,小菜迅速搭建了一个SpringBoot项目并完成了开发。然而,启动测试时发现接口404,原因是控制器包不在默认扫描路径下。通过配置`@ComponentScan`的`basePackages`字段,解决了问题。总结:`@SpringBootApplication`默认只扫描当前包下的组件,需要扫描其他包时需配置`@ComponentScan`。
|
1月前
|
XML Java 数据格式
Spring IOC容器的深度解析及实战应用
【10月更文挑战第14天】在软件工程中,随着系统规模的扩大,对象间的依赖关系变得越来越复杂,这导致了系统的高耦合度,增加了开发和维护的难度。为解决这一问题,Michael Mattson在1996年提出了IOC(Inversion of Control,控制反转)理论,旨在降低对象间的耦合度,提高系统的灵活性和可维护性。Spring框架正是基于这一理论,通过IOC容器实现了对象间的依赖注入和生命周期管理。
65 0
|
2月前
|
XML Java 开发者
经典面试---spring IOC容器的核心实现原理
作为一名拥有十年研发经验的工程师,对Spring框架尤其是其IOC(Inversion of Control,控制反转)容器的核心实现原理有着深入的理解。
120 3
|
1月前
|
XML 缓存 Java
Spring FactoryBean 的常见使用场景总结
FactoryBean 是 Spring 框架中的一个重要接口,用于自定义 Bean 的创建逻辑。常见使用场景包括: 1. **复杂 Bean 的创建**:如数据源配置。 2. **延迟实例化**:按需创建资源密集型对象。 3. **动态代理**:为 Bean 创建 AOP 代理。 4. **自定义配置**:根据特定配置创建 Bean。 5. **第三方库集成**:利用 FactoryBean 封装外部库的创建过程。
|
2月前
|
运维 Cloud Native Devops
云原生架构的崛起与实践云原生架构是一种通过容器化、微服务和DevOps等技术手段,帮助应用系统实现敏捷部署、弹性扩展和高效运维的技术理念。本文将探讨云原生的概念、核心技术以及其在企业中的应用实践,揭示云原生如何成为现代软件开发和运营的主流方式。##
云原生架构是现代IT领域的一场革命,它依托于容器化、微服务和DevOps等核心技术,旨在解决传统架构在应对复杂业务需求时的不足。通过采用云原生方法,企业可以实现敏捷部署、弹性扩展和高效运维,从而大幅提升开发效率和系统可靠性。本文详细阐述了云原生的核心概念、主要技术和实际应用案例,并探讨了企业在实施云原生过程中的挑战与解决方案。无论是正在转型的传统企业,还是寻求创新的互联网企业,云原生都提供了一条实现高效能、高灵活性和高可靠性的技术路径。 ##
198 3
|
3月前
|
XML Java 数据格式
Spring5入门到实战------3、IOC容器-Bean管理XML方式(一)
这篇文章详细介绍了Spring框架中IOC容器的Bean管理,特别是基于XML配置方式的实现。文章涵盖了Bean的定义、属性注入、使用set方法和构造函数注入,以及如何注入不同类型的属性,包括null值、特殊字符和外部bean。此外,还探讨了内部bean的概念及其与外部bean的比较,并提供了相应的示例代码和测试结果。
Spring5入门到实战------3、IOC容器-Bean管理XML方式(一)
|
3月前
|
XML Java 数据格式
Spring5入门到实战------5、IOC容器-Bean管理(三)
这篇文章深入探讨了Spring5框架中IOC容器的高级Bean管理,包括FactoryBean的使用、Bean作用域的设置、Bean生命周期的详细解释以及Bean后置处理器的实现和应用。
Spring5入门到实战------5、IOC容器-Bean管理(三)
|
3月前
|
XML Java 数据格式
Spring5入门到实战------4、IOC容器-Bean管理XML方式、集合的注入(二)
这篇文章是Spring5框架的实战教程,主题是IOC容器中Bean的集合属性注入,通过XML配置方式。文章详细讲解了如何在Spring中注入数组、List、Map和Set类型的集合属性,并提供了相应的XML配置示例和Java类定义。此外,还介绍了如何在集合中注入对象类型值,以及如何使用Spring的util命名空间来实现集合的复用。最后,通过测试代码和结果展示了注入效果。
Spring5入门到实战------4、IOC容器-Bean管理XML方式、集合的注入(二)