首页 > 编程语言 >Spring源码分析(七)容器的扩展点(FactoryBean)

Spring源码分析(七)容器的扩展点(FactoryBean)

时间:2023-09-11 14:02:59浏览次数:50  
标签:Spring Bean bean 源码 创建 FactoryBean public

在上篇文章中我已经对容器的第一个扩展点(BeanFactoryPostProcessor)做了一系列的介绍。其中主要介绍了 Spring 容器中 BeanFactoryPostProcessor 的执行流程,以及 Spring 自身利用了 BeanFactoryPostProcessor 完成了什么功能,对于一些细节问题可能说的不够仔细,但是当前阶段我想要做的主要是为了以后学习源码打下基础,所以对于这些问题我暂且不去过多纠结,待到源码学习会进行更加细致的分析。在本篇文章中,我们将要学习的是容器的另一个扩展点(FactoryBean),对于 FactoryBean 官网的介绍很短,但是如果我们对 Spring 的源码有一定了解可以发现,Spring 在很多地方都对这个特殊的 Bean 做了处理。。。

先看看官网上是怎么说的:

Spring源码分析(七)容器的扩展点(FactoryBean)_懒加载

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

  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:

@Component
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;
    } 
}

@Configuration
@ComponentScan("com.wxx.service")
public class Appconfig {

}

public class TestBean {
    public TestBean(){
        System.out.println("TestBean 被创建出来了");
    }
}
// 测试类
public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac=
                new AnnotationConfigApplicationContext(Appconfig.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("myFactoryBean")获取到的是 FactoryBean 创建的 Bean,但是添加了"&"后获取到的是 FactoryB 本身。

FactoryBean 相关源码分析

我们先看下面这图:

Spring源码分析(七)容器的扩展点(FactoryBean)_System_02

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

Spring源码分析(七)容器的扩展点(FactoryBean)_System_03

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

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

Spring源码分析(七)容器的扩展点(FactoryBean)_懒加载_04

  1. 如果是一个 FactoryBean,那么在 getBean 的时候,添加前缀"&",获取这个 FactoryBean
  2. 判断是不是一个 SmartFactoryBean,并且不是懒加载

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

Spring源码分析(七)容器的扩展点(FactoryBean)_System_05

从上面的代码中可以看出,当我们实现了一个 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 相关的代码:

Spring源码分析(七)容器的扩展点(FactoryBean)_ci_06

  1. 获取 bean 名称

Spring源码分析(七)容器的扩展点(FactoryBean)_System_07

Spring源码分析(七)容器的扩展点(FactoryBean)_System_08

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

我们可以看到,在调用 getOBjectForInstance(sharedInstance, name, beanName, null);传入了一个参数 name,也就是还没有经过 transformedBeanName 方法处理的 bean 的名称,可能会带有"&"符号,SPring 通过这个参数判断这个 bean 是不是一个 FactoryBean,如果是的话会调用 getObject 创建 bean,被创建的 bean 不会存放在单例池中,而是放在一个名为 factoryBeanObjectCache 的缓存中。具体的代码比较复杂,在这里就不分析了,先留个印象。

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

除了我们在上文说到的实现了 FactoryBean 或者 SmartFactoryBean 接口的 bean 可被称为一个 FactoryBean,不知道对 BeanDefinition 中的一个属性是否还有印象。实际上这个属性存在于 AbstractBeanDefinition 中:

Spring源码分析(七)容器的扩展点(FactoryBean)_懒加载_09

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

首先看看什么情况下 BeanDefinition 会存在这个属性,主要分为两种情况:

  1. 第一种情况:
@Configuration
public class Config {
    @Bean
    public B b(){
        return new B();
    }
}

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

  1. 第二种情况:
<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,可以总结画图如下:

Spring源码分析(七)容器的扩展点(FactoryBean)_懒加载_10

和 FactoryBean 相关的面试题

1、FactoryBean 和 BeanFactory 的区别

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

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

Spring源码分析(七)容器的扩展点(FactoryBean)_懒加载_11

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

  1. 如何把一个对象交给 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 贯穿了整改 Spring 的生命周期,学习难度更大。

最后,认认真真学习,加油,共勉!!!

标签:Spring,Bean,bean,源码,创建,FactoryBean,public
From: https://blog.51cto.com/u_15668812/7435669

相关文章

  • 【源码】Vue.js 官方脚手架 create-vue 是怎么实现的?
    Vue.js官方脚手架create-vue是怎么实现的?摘要本文共分为四个部分,系统解析了vue.js官方脚手架create-vue的实现细节。第一部分主要是一些准备工作,如源码下载、项目组织结构分析、依赖分析、功能点分析等;第二部分分析了create-vue脚手架是如何执行的,执行文件的生成细节......
  • 使用Spring Boot构建高性能的Java后端应用
    引言在现代应用程序开发中,构建高性能的后端服务是至关重要的。SpringBoot是一种流行的Java框架,可以帮助开发者快速构建高性能的后端应用。本博客将深入探讨如何使用SpringBoot来构建高性能的Java后端应用,并提供实际的代码示例。SpringBoot简介SpringBoot是SpringFramework......
  • 使用gradle的方式进行Springboot3的web开发(微服务版)
    简要:最近看了很多的Springboot3的项目,但是发现很多都是用maven来进行版本管理的,很少有用gradle来管理的,通过网上查找资料,看视频,终于自己写一个gradle管理的Springboot3的项目 第一步:创建项目注意:JDK的版本必须要在17或者以上。 第二步:设置gradle仓库 第三步:创建项目......
  • SpringBoot上传文件
    application.yaml配置上传路经,其实写在哪都行无所谓,就是引入配置文件,@Values注解赋值web:#绝对路经upload-path:D://test/springservlet:multipart:enabled:true#单个文件的最大上限max-file-size:1024MB#单个请求的文件总大......
  • SpringBoot跨域访问
    没有引入SpringSecuity的情况Christopher2021.10.23CORS后端跨域CORS是一种访问机制,Cross-OriginResourceSharing,跨域资源共享,通过在服务器端设置相应头,把发起跨域的原始域名添加到Access-Control-Allow-Origin中即可。何为跨域域,即域名,跨域,即从域名A申请访......
  • 关于spring的注解作用(springboot相较于spring 的不同)
      springboot的@Bean注解作用在方法上,它会将这个方法返回的类型实例注入spring容器。  <bean>标签代表一个实例(或对象),而不是一个类型。在Spring中,<bean>标签用于声明和配置一个bean实例。当我们在XML配置文件中使用<bean>标签时,我们定义的是一个具体的b......
  • ubuntu 20.04源码编译安装ros2 gazebo及turtlebot3
    ros2gazebo安装gazebo11sudoapt-getinstallgazebo11gazebo-version//验证gazebo安装gazebo_ros_pkgsmkdir-p~/ros2_gazebo_ws/srccd~/ros2_gazebo_wswgethttps://raw.githubusercontent.com/ros-simulation/gazebo_ros_pkgs/ros2/gazebo_ros_pkgs.repos//显......
  • 分享一个 SpringBoot + Redis 实现「查找附近的人」的小技巧
    前言SpringDataRedis提供了十分简单的地理位置定位的功能,今天我就用一小段代码告诉大家如何实现。正文1、引入依赖<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>2、更......
  • SpringBoot + 自定义注解,实现用户操作日志(支持SpEL表达式)
    背景一个成熟的系统,都会针对一些关键的操作,去创建用户操作日志。比如:XX人创建了一条订单,订单号:XXXXXXXXX因为操作人或者订单号是动态的,所以有些开发人员,不知道获取,就将这种操作日志和业务代码融在一起。我们当然要杜绝这种现象,一定会有更好的解决方案。当前项目除了......
  • Spring面试题
    谈谈SpringIOC的理解,原理与实现?控制反转:理论思想,原来的对象是由使用者来控制,有了Spring之后,可以把整个对象交给Spring来帮我们进行管理。DI:依赖注入,把对应的属性的值注入到具体的对象中。容器:存储对象,使用map结构来存储,在spring中一般存在三级缓存,singletonObjects存......