阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内容需要看官自己去源码中验证。全系列文章基于 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。

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

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 的使用的更多相关文章

  1. 前端构建大法 Gulp 系列 (二):为什么选择gulp

    系列目录 前端构建大法 Gulp 系列 (一):为什么需要前端构建 前端构建大法 Gulp 系列 (二):为什么选择gulp 前端构建大法 Gulp 系列 (三):gulp的4个API 让你成为gul ...

  2. WPF入门教程系列二十三——DataGrid示例(三)

    DataGrid的选择模式 默认情况下,DataGrid 的选择模式为“全行选择”,并且可以同时选择多行(如下图所示),我们可以通过SelectionMode 和SelectionUnit 属性来修改 ...

  3. Web 开发人员和设计师必读文章推荐【系列二十九】

    <Web 前端开发精华文章推荐>2014年第8期(总第29期)和大家见面了.梦想天空博客关注 前端开发 技术,分享各类能够提升网站用户体验的优秀 jQuery 插件,展示前沿的 HTML5 ...

  4. Web 前端开发人员和设计师必读文章推荐【系列二十八】

    <Web 前端开发精华文章推荐>2014年第7期(总第28期)和大家见面了.梦想天空博客关注 前端开发 技术,分享各类能够提升网站用户体验的优秀 jQuery 插件,展示前沿的 HTML5 ...

  5. Web 开发精华文章集锦(jQuery、HTML5、CSS3)【系列二十七】

    <Web 前端开发精华文章推荐>2014年第6期(总第27期)和大家见面了.梦想天空博客关注 前端开发 技术,分享各类能够提升网站用户体验的优秀 jQuery 插件,展示前沿的 HTML5 ...

  6. Web 前端开发人员和设计师必读精华文章【系列二十六】

    <Web 前端开发精华文章推荐>2014年第5期(总第26期)和大家见面了.梦想天空博客关注 前端开发 技术,分享各类能够提升网站用户体验的优秀 jQuery 插件,展示前沿的 HTML5 ...

  7. Web 前端开发精华文章推荐(HTML5、CSS3、jQuery)【系列二十三】

    <Web 前端开发精华文章推荐>2014年第2期(总第23期)和大家见面了.梦想天空博客关注 前端开发 技术,分享各类能够提升网站用户体验的优秀 jQuery 插件,展示前沿的 HTML5 ...

  8. Web 前端开发精华文章推荐(HTML5、CSS3、jQuery)【系列二十二】

    <Web 前端开发精华文章推荐>2014年第一期(总第二十二期)和大家见面了.梦想天空博客关注 前端开发 技术,分享各类能够提升网站用户体验的优秀 jQuery 插件,展示前沿的 HTML ...

  9. 【圣诞特献】Web 前端开发精华文章推荐【系列二十一】

    <Web 前端开发精华文章推荐>2013年第九期(总第二十一期)和大家见面了.梦想天空博客关注 前端开发 技术,分享各种增强网站用户体验的 jQuery 插件,展示前沿的 HTML5 和  ...

  10. Web 前端开发精华文章集锦(jQuery、HTML5、CSS3)【系列二十】

    <Web 前端开发精华文章推荐>2013年第八期(总第二十期)和大家见面了.梦想天空博客关注 前端开发 技术,分享各种增强网站用户体验的 jQuery 插件,展示前沿的 HTML5 和 C ...

随机推荐

  1. [转帖]docker exec 失败问题排查之旅

    https://plpan.github.io/docker-exec-%E5%A4%B1%E8%B4%A5%E9%97%AE%E9%A2%98%E6%8E%92%E6%9F%A5%E4%B9%8B% ...

  2. [转帖]GRUB2 配置文件详解

    https://www.cnblogs.com/fluidog/p/15176726.html 1. GRUB2配置文件 GRUB2 的配置文件通常为 /boot/grub2/grub.cfg,虽然此 ...

  3. [转帖]一次ORA-3136的处理

    https://oracleblog.org/working-case/deal-with-ora3136/ 最近收到一个告警,用户说数据库无法连接,但是从监控上看,oracle的后台进程已经侦听进程 ...

  4. [转帖]看看 Jmeter 是如何玩转 redis 数据库的

    柠檬小欧 2021-08-31 20:06420 Jmeter 作为当前非常受欢迎的接口测试和性能测试的工具,在企业中得到非常广泛的使用,而 Redis 作为缓存数据库,也在企业中得到普遍使用,那如何 ...

  5. 【转帖】nginx变量使用方法详解-4

    https://www.diewufeiyang.com/post/578.html 在设置了"取处理程序"的情况下,Nginx 变量也可以选择将其值容器用作缓存,这样在多次读取变 ...

  6. [转帖]ldconfig命令

    https://linux265.com/course/linux-command-ldconfig.html ldconfig命令的作用主要是在默认搜寻目录/lib和/usr/lib以及动态库配置文 ...

  7. [转帖]ansible 安装 K8S

    作者:山河已无恙链接:https://www.zhihu.com/question/315497851/answer/2898814729来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业 ...

  8. 【记录一个问题】VictoriaMetrics的vmstorage因为慢查询导致大量写入失败

    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 见上图. 一直以为vmstorage中的查询协程会让位于写 ...

  9. 【JS 逆向百例】Ether Rock 空投接口 AES256 加密分析

    关注微信公众号:K哥爬虫,持续分享爬虫进阶.JS/安卓逆向等技术干货! 声明 本文章中所有内容仅供学习交流,抓包内容.敏感网址.数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后 ...

  10. Go 跟踪函数调用链,理解代码更直观

    Go 跟踪函数调用链,理解代码更直观 目录 Go 跟踪函数调用链,理解代码更直观 一.引入 二.自动获取所跟踪函数的函数名 三.增加 Goroutine 标识 四.让输出的跟踪信息更具层次感 五.利用 ...