《系列二》-- 3、FactoryBean 的使用

系列,factorybean,使用 · 浏览次数 : 9

小编点评

**工厂模式** 工厂模式是一种基于工厂的注入方式,它可以帮助我们简化依赖注入的过程,并提供更简洁的代码。 **工厂模式的应用** 工厂模式可以应用于以下场景: *当我们需要创建多个相同类型的对象时,工厂模式可以帮助我们简化依赖注入的过程。 *当我们需要创建多个不同类型的对象时,工厂模式可以帮助我们创建不同的对象。 *当我们需要创建复杂对象的依赖对象时,工厂模式可以帮助我们创建更复杂的依赖对象。 **工厂模式的优点** *简化依赖注入 *提供更简洁的代码 *允许我们创建更复杂的依赖对象 **工厂模式的缺点** *可能导致代码复杂性增加 *可能导致工厂模式依赖注入的复杂性增加 **工厂模式的补充** 工厂模式可以与其他注入模式一起使用,例如单模式、工厂模式和依赖注入模式。 **工厂模式的例子** 以下是一个工厂模式的例子: ```xml User Car ``` 在这个例子中,我们创建一个名为 `myFactory` 的工厂,它包含两个名为 `user` 和 `car` 的对象。每个对象都由工厂的 `createUser` 和 `createCar` 方法创建。

正文

阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内容需要看官自己去源码中验证。全系列文章基于 spring 源码 5.x 版本。

写在开始前的话:

阅读spring 源码实在是一件庞大的工作,不说全部内容,单就最基本核心部分包含的东西就需要很长时间去消化了:

  • beans
  • core
  • context

实际上我在博客里贴出来的还只是一部分内容,更多的内容,我放在了个人,fork自 spring 官方源码仓了; 而且对源码的学习,必须是要跟着实际代码层层递进的,不然只是干巴巴的文字味同嚼蜡。

https://gitee.com/bokerr/spring-framework-5.0.x-study

这个仓设置的公共仓,可以直接拉取。



Spring源码阅读系列--全局目录.md



FactoryBean 解决的问题

我们已知,spring 创建bean 利用的是反射机制, 当我们想要从容器中获取某个Bean,只需要使用配置的Class信息,通过反射即可得到Bean的实例对象。

上边所述的是一般场景下的bean 创建,只有一个动作,反射拿到对象完事。但是考虑下,如果在真正的业务场景下,Bean 的创建可能还需要包括一些属性的初始化,下边举个栗子:

假如我们现在需要对代码进行安全整改,数据库口令、静态token等信息禁止通过配置文件配置,必须在运行时动态从配置中心获取,我们有一个管理所有配置的单例bean, 那么当应用启动过程中,打开数据库链接前,就必须完成这个配置管理bean的初始化。


@Setter
@Getter
public class AppConfigManager {
    private Object mysqlPassword;
    private Object redisPassword;
    private Object other;
}

/**
 * 通过自定义方法,配置初始化,这里可能涉及一些无法通过配置 bean.xml 来达成的事情。 
 */
public class ConfigManagerUtil {
    private static AppConfigManager initConfig(Object anyInfo) {
        AppConfigManager appConfigManager = new AppConfigManager();
        appConfigManager.mysqlPassword = ConfigManagerUtil.getRemoteMysqlConfig();
        appConfigManager.redisPassword = ConfigManagerUtil.getRemoteRedisConfig();
        appConfigManager.other = ConfigManagerUtil.getRemoteOther();
    }
    private static Object getRemoteMysqlConfig() {
        // do something ,that maybe can`t configuration by bean.xml
    }
    private static Object getRemoteRedisConfig() {
        // do something ,that maybe can`t configuration by bean.xml
    }
    private static Object getRemoteOther() {
        // do something ,that maybe can`t configuration by bean.xml
    }
}


上文定义了两个伪代码类:一个时配置管理对象类,另一个是一个工具类,它负责配置管理类的初始化

  • AppConfigManager
  • ConfigManagerUtil

如果我们想要简单的按照如下方式,向容器注入配置管理类肯定是不可能的,AppConfigManager 必须经过额外的操作来初始化。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "
	https://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
	<bean id="action" class="com.bokerr.AppConfigManager"/>
</beans>

FactoryBean 接口初识

Spring 官方引入了一个接口: FactoryBean 没错它是个泛型。

就像上边我们需要通过 ConfigManagerUtil 去管理 AppConfigManager那样,我们可以认为:FactoryBean 管理的泛型对象就是它维护的 Bean。

factoryBean_struct

我们可以看下边的接口代码:

public interface FactoryBean<T> {
    /***
     * 返回 FactoryBean 管理的泛型对象- Class 对象
     */
    @Nullable
	T getObject() throws Exception;

    /***
     * 返回 FactoryBean 管理的泛型对象- Class 对象
     */
	@Nullable
	Class<?> getObjectType();

	default boolean isSingleton() {
	    // 默认单例
		return true;
	}
}

那么,接下来我们就只需要在, ConfigManagerUtil 工具上实现 FactoryBean 接口。

然后就可以继续通过 bean.xml 向容器注入 AppConfigManager对象了?

改造结果

接下来,结合: FactoryBean 我们改写下之前的代码。

ConfigManagerUtil 重命名为: ConfigManagerFactoryBean


/**
 * 通过自定义方法,配置初始化,这里可能涉及一些无法通过配置 bean.xml 来达成的事情。 
 */
public class ConfigManagerFactoryBean implements FactoryBean<AppConfigManager> {

    private Boolean isInit = new Boolean("false"); // sys
    
    private AppConfigManager singleTone;

    @Nullable
    T getObject() throws Exception {
        // 双重锁保证单例, 这里我没仔细深究 getObject() 有没有从外部实现过单例控制,疑罪从无原则,为了保证可靠性,我自己又实现了一次。
        // 【根据 FactoryBean 的默认方法 isSingleton() 来看大概率spring 容器已经帮我们完成了这里的单例控制。】 
        if (!isInit){
            isInit = new Boolean("true");
            synchronized (isInit) {
                Object params = defaultInfo();
                return initConfig(params);
            }
        }
        ConfigManagerFactoryBean.updateConif();
        return singleTone;
    }

    @Nullable
    Class<AppConfigManager> getObjectType() {
        AppConfigManager.class;
    }

    /**
     * 初始化配置
     */
    private static AppConfigManager initConfig(Object anyInfo) {
        AppConfigManager appConfigManager = new AppConfigManager();
        appConfigManager.mysqlPassword = ConfigManagerFactoryBean.getRemoteMysqlConfig();
        appConfigManager.redisPassword = ConfigManagerFactoryBean.getRemoteRedisConfig();
        appConfigManager.other = ConfigManagerFactoryBean.getRemoteOther();
    }

    /**
     * 配置更新
     */
    private static void updateConif() {
        singleTone.mysqlPassword = ConfigManagerUtil.getRemoteMysqlConfig();
        singleTone.redisPassword = ConfigManagerUtil.getRemoteRedisConfig();
        singleTone.other = ConfigManagerUtil.getRemoteOther();
    }

    private static Object getRemoteMysqlConfig() {
        // do something ,that maybe can`t configuration by bean.xml
    }

    private static Object getRemoteRedisConfig() {
        // do something ,that maybe can`t configuration by bean.xml
    }

    private static Object getRemoteOther() {
        // do something ,that maybe can`t configuration by bean.xml
    }
}

完成上述的改造后,我们只需要配置如下的 bean.xml 即可:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "
	https://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
    <!-- 虽然这里配置的Class是ConfigManagerFactoryBean, 但是通过beanName = "configBean" 获取的bean 对象实例会是:AppConfigManager -->
	<bean id="configBean" class="com.bokerr.ConfigManagerFactoryBean" scope="singleton"/>
</beans>

在容器初始化的过程中会判断 [ beans>.bean>.class ] 配置的类的类型:

  • 如果没有实现接口:FactoryBean,那么容器直接根据 [class] 配置值,反射出对象实例后,直接返回即可

  • 如果发现它实现了 FactoryBean 接口,那么获取bean 的时候就不是直接返回反射得到的对象,而是会去调用: [ 根据 Class 反射出来的 FactoryBean 对象的 getObject() 方法 ]

我想通过本文你已经简单的认识了 FactoryBean 接口了。




这里遗留了一个问题:FactoryBean 的 getObject()方法的调用过程中,spring 容器自身是否提供了该方法返回值的单例控制实现,不再需要我们从 getObject() 方法内部来实现单例?

[学习 bean创建知识后可以得知,Spring容器自身完成了单例控制,我们只需要在 FactoryBean.isSingleton() 中设置好 bean属性即可;并不需要我们自行在 FactoryBean.getObject() 方法中是实现双重锁保证单例。]

最后的补充

你可能在 spring原生的 xml 中看到如下配置:

  • factory-bean="xxxx"
  • factory-method="zzzz"

并且它们还并不是成对出现的,事实上它们的应用很接近 FactoryBean; 如果你了解工厂模式应该就会很容易理解接下来的内容。

回顾下 FactoryBean 的应用

这里看似和普通的bean配置没什么区别,在 spring容器创建bean 时会判断 class指向的bean的类型:

  • com.bokerr.ConfigManagerFactoryBean
    因为这里配置的时 FactoryBean 所以会被特殊对待。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "
	https://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
    <!-- 虽然这里配置的Class是ConfigManagerFactoryBean, 但是通过beanName = "configBean" 获取的bean 对象实例会是:AppConfigManager -->
	<bean id="configBean" class="com.bokerr.ConfigManagerFactoryBean" scope="singleton"/>
</beans>

这里我们通过实现 FactoryBean 接口实现了一个简单的,工厂模式的应用;它有一个短板,一个工厂只能有一种"产品"

但是呢,假如我们原有的代码已经实现了工厂呢? 假如我们的工厂负责多种产品呢?

这时候 factory-bean 和 factory-method 就需要登场了。

factory-method 和 factory-bean 的应用

import demo.self.User;
public class MySimpleBeanFactory {
    private static User staticUser = new User();
    private static Car staticCar = new Car();
    public User createUser() {
        return new User();
    }
    public static User createStaticUser() {
        return staticUser;
    }
    public Car createCar() {
        return new Car();
    }
    public static Car createStaticCar() {
        return staticCar;
    }
}

参考如下的xml 配置,相信你会理解 它们的用法

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "
	https://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
    <!-- 非静态注入: 同时需要应用factory-bean、factory-method -->
    <bean id="myFactory" class="demo.self.MySimpleBeanFactory"/>
    <bean id="user" factory-bean="myFactory" factory-method="createUser" />
    <bean id="car" factory-bean="myFactory" factory-method="createCar" />

    <!-- 静态注入,只需要应用factory-method即可 -->
    <bean id="staticUser" factory-method="createStaticCar" />
    <bean id="staticCar" factory-method="createStaticCar" />
    
</beans>

经过上述例子,我们在没有实现 FactoryBean 接口的前提下,把我们自己自定义的工厂注入到了容器中。

所以在我的理解中,factory-bean 和 factory-method 更像是 FactoryBean 注入方式的补充。

与《系列二》-- 3、FactoryBean 的使用相似的内容:

《系列二》-- 3、FactoryBean 的使用

[TOC] > 阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内容需要看官自己去源码中验证。全系列文章基于 spring 源码 5.x 版本。 写在开始前的话: 阅读spring 源码实在是一件庞大的工作,不说全部内容,单就最基本核心部分包含的东西就需要很长时间去消化了

[转帖]Java命令学习系列(二)——Jstack

2015-04-18 分类:Java 阅读(42792) 评论(3) GitHub 24k Star 的Java工程师成神之路,不来了解一下吗! jstack是java虚拟机自带的一种堆栈跟踪工具。 功能 jstack用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程

Velero 系列文章(二):使用 Helm 安装 Velero

概述 本文是通过 Helm 3 来安装 Velero, 只做最基本的安装。并计划将 YAML (只备份 YAML, 不备份 Volume) 备份到腾讯云的 COS(兼容 S3, 所以可以通过 AWS S3 插件来实现) 需要安装: velero AWS S3 插件 不安装: CSI VolumeSn

[转帖]Django系列3-Django常用命令

文章目录 一. Django常用命令概述二. Django常用命令实例2.1 help命令2.2 version2.3 check2.4 startproject2.5 startapp2.6 runserver2.7 shell2.8 migrations2.8.1 makemigrations2

机器学习算法(一):1. numpy从零实现线性回归

系列文章目录 机器学习算法(一):1. numpy从零实现线性回归 机器学习算法(一):2. 线性回归之多项式回归(特征选取) @目录系列文章目录前言一、理论介绍二、代码实现1、导入库2、准备数据集3、定义预测函数(predict)4 代价(损失)函数5 计算参数梯度6 批量梯度下降7 训练8 可视

保姆教程系列:小白也能看懂的 Linux 挂载磁盘实操

!!!是的没错,胖友们,保姆教程系列又更新了!!! @目录前言简介一、磁盘分区二、文件系统三、实际操作1. 使用lsblk命令查看新加入的磁盘信息2. 使用fdisk或者cfdisk分区新磁盘,并将分区标记为Linux文件系统类型(83)3. 格式化新分区,使用mkfs命令4. 创建挂载目录,使用m

[转帖]Centos下使用containerd管理容器:5分钟从docker转型到containerd

https://www.cnblogs.com/renshengdezheli/p/16684175.html 目录 一.系统环境 二.前言 三.containerd 四.部署containerd 4.1 安装containerd 4.2 containerd配置文件 4.3 配置container

[Linux] 安装JDK

一、将linux的jdk安装包上传到linux系统中 二、解压后放到安装路径下 tar -zxvf jdk-8u121-linux-x64.tar 三、配置JDK环境变量 1、vim /etc/profile 2、按 i 进入编辑状态 3、添加环境变量 #java environment expor

[转帖]银河麒麟高级服务器操作系统V10SP1 - ISO镜像定制 + KickStart自动化安装

文章目录 一、基础环境搭建1. 原始ISO获取2. 系统安装 二、ISO镜像定制1. 准备工作2. 修改ISO镜像3. 生成ISO镜像; 三、ISO镜像验证 一、基础环境搭建 1. 原始ISO获取 OS版本:银河麒麟高级服务器操作系统V10SP1(X86_64) 百度网盘:https://pan.b

[转帖]Linux下strace调试系统应用参数总结(附实例操作讲解)

文章目录 一、简介二、常用参数详解三、实例详解3.1 跟踪具体进程3.2 监控具体程序执行过程 四、其他相关知识链接 一、简介 strace命令是一个集诊断、调试、统计与一体的Linux 用户空间跟踪器,我们可以使用strace对应用的系统调用、信号传递和进程状态变更的监控结果来对应用进行分析,以达