FactoryBean在Spring中算是一个比较有意思的存在了,虽然在日常的业务开发中,基本上不怎么会用到,但在某些场景下,如果用得好,却可以实现很多有意思的东西
本篇博文主要介绍如何通过FactoryBean来实现一个类SPI机制的微型应用框架
文章内涉及到的知识点
SPI机制
FactoryBean
JDK动态代理
I. 相关知识点
在看下面的内容之前,得知道一下什么是SPI,以及SPI的用处和JDK实现SPI的方式,对于这一块有兴趣了解的童鞋,可以看一下个人之前写的相关文章
SPI相关技术博文汇总—By一灰灰Blog: (https://blog.hhui.top/hexblog/categories/Quick%E7%B3%BB%E5%88%97/QuickSpi/)
1. demo背景说明
在开始之前,有必要了解一下,我们准备做的这个东西,到底适用于什么样的场景。
在电商中,有一个比较恰当的例子,商品详情页的展示。拿淘宝系的详情页作为背景来说明(没有在阿里工作过,下面的东西纯粹是为了说明应用场景而展开)
假设有这么三个详情页,我们设定一个大前提,底层的数据层提供方都是一套的,商品详情展示的服务完全可以做到复用,即三个性情页中,绝大多数的东西都一样,只是不同的详情页车重点不同而已。
如上图中,我们假定有细微区别的几个地方
2. SPI简述
根据上面的简单对比,其实只想表达一个意思,业务基本上一致,仅仅只有很少的一些东西不同,需要定制化,这个时候可以考虑用SPI来支持定制化的服务
a. 基本定义
SPI的全名为Service Provider Interface,简单的总结下java spi机制的思想。我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块的方案,xml解析模块、jdbc模块的方案等。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。 java spi就是提供这样的一个机制:为某个接口寻找服务实现的机制
@一灰灰Blog blog.hhui.top/hexblog/2017/05/26/SPI%E6%A1%86%E6%9E%B6%E5%AE%9E%E7%8E%B0%E4%B9%8B%E6%97%85%E4%B8%80%EF%BC%9A%E8%83%8C%E6%99%AF%E4%BB%8B%E7%BB%8D
上面是相对正视一点的介绍,简单一点,符合本文设计目标的介绍如下
接口方式引用
具体执行时,根据某些条件,选中实际的子类执行
通过上面的描述,可以发现一个最大的优点就是:
通过扩展接口的实现,就可以实现服务扩展;而不需要改原来的业务代码
b. demo辅助说明
一个简单的应用场景如下
这个报警系统中,对于使用者而言,通过 IAlarm#sendMsg(level, msg)
来执行报警发送的方式,然而这一行的具体执行者是(忽略,日志报警,邮件报警还是短信报警)不确定的,通过SPI的实现方式将是如下
如果level为1,则忽略报警内容
如果level为2,则采用日志报警的方式来报警
…
如果我们想新添加一种报警方式呢?那也很简单,新建一个报警的实现
level == 5, 则采用微信报警
然后对于使用者而言,其他的地方都不用改,只是在传入的level参数换成5就可以了
3. 代理模式简述
代理模式,在Spring中可以说是非常非常非常常见的一种设计模式了,大名鼎鼎的AOP就是这个实现的一个经典case,常见的代理有两种实现方式
JDK方式
CGLIB方式
简单说一下,代理模式的定义和说明如下
其实在现实生活中代理模式还是非常多得,这里引入一个代理商的概念来加以描述,本来一个水果园直接卖水果就好了,现在中间来了一个水果超市,水果园的代销商,对水果进行分类,包装,然后再卖给用户,这其实也算是一种代理
百科定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
@一灰灰Blog blog.csdn.net/liuyueyi25/article/details/77868204
II. 方案设计与实现
了解完上面的前提之后,我们可以考虑下如何实现一个Spring容器中的SPI工具包
1. 目标拆分
首先确定大的生态环境为Spring,我们针对Bean做SPI功能的扩展,即定义一个SPI的接口,然后可以有多个实现类,并且全部都声明为Bean;
SPI的一个重要特点就是可以选中不同的实现来执行具体的代码,那么放在这里,就会有两种方案
方案一:依赖注入时,直接根据选择条件,注入一个满足的实例,后续所有的SPI调用,都将走这个具体的实例调用执行
方案二:依赖注入时,不注入具体的实例,反而注册一个代理类,在代理类中,根据调用的参数来选择具体匹配的实例来执行,因此后续的调用具体选中的实例将与传入的参数有关
方案对比
对比上面的两个方案之后,选中第二个(当然主要原因是为了演示FactoryBean和代理实现SPI机制,如果选择方案一就没有这两个什么事情了)
选中方案之后,目标拆分就比较清晰了
定义SPI接口,以及SPI的使用姿势(前提)
一个生成代理类的FactoryBean (核心)
2. 方案设计
针对前面拆分的目标,进行方案设计,第一步就是接口相关的定义了
a. 接口定义
设计的SPI微型框架的核心为:在执行的时候,根据传入的参数来决定具体的实例来执行,因此我们的接口设计中,至少有一个根据传入的参数来判断是否选中这个实例的接口
|
|
看到上面的实现之后,就会有一个疑问,如果有多个子类都满足这个条件怎么办?因此可以加一个排序的接口,返回优先级最高的匹配者
|
|
接口定义之后,使用者应该怎么用呢?
b. 使用约束
spi实现的约束
基于JDK的代理模式,一个最大的前提就是,只能根据接口来生成代理类,因此在使用SPI的时候,我们希望使用者先定义一个接口来继承ISpi
,然后具体的SPI实现这个接口即可
其次就是在Spring的生态下,要求所有的SPI实现都是Bean,需要自动扫描或者配置注解方式声明,否者代理类就不太好获取所有的SPI实现了
spi使用的约束
在使用SPI接口时,通过接口的方式来引入,因为我们实际注入的会是代理类,因此不要写具体的实现类
单独看上面的说明,可能不太好理解,建议结合下面的实例演示对比
c. 代理类生成
这个属于最核心的地方了(虽说重要性为No1,但实现其实非常非常简单)
代理类主要目的就是在具体调用执行时,根据传入的参数来选中具体的执行者,执行后并返回对应的结果
获取所有的SPI实现类(
org.springframework.beans.factory.ListableBeanFactory#getBeansOfType(java.lang.Class<T>)
)通过jdk生成代理类,代理类中,遍历所有的SPI实现,根据传入的第一个参数作为条件进行匹配,找出首个命中的SPI实现类,执行
将上面的步骤具体实现,也就比较简单了
|
|
3. 实例演示
话说方案设计之后,应该就是实现了,然而因为实现过于简单,设计的过程中,也就顺手写了,就是上面的一个接口定义 ISpi
和一个用来生成动态代理类的SpiFactoryBean
接下来写一个简单的实例用于功能演示,定义一个IPrint
用于文本输出,并给两个实现,一个控制台输出,一个日志输出
|
|
具体的实现类如下,外部使用者通过execute
方法实现调用,其中level<=0
时选择控制台输出;否则选则日志文件方式输出
|
|
前面的步骤和一般的写法没有什么区别,使用的姿势又是怎样的呢?
|
|
看上面的Application
的构造方法,要求传入一个IPrint
参数,Spring会从容器中找到一个bean作为参数传入,而这个bean就是我们生成的代理类,这样才可以根据不同的参数来选中具体的实现类
所以问题就是如何声明这个代理类了,配置如下,通过FactoryBean的方式来声明Bean,并添加上@Primary
注解,这样就可以确保注入的是我们声明的代理类了
|
|
上面的使用逻辑,涉及到的知识点在前面的博文中分别有过介绍,更多详情可以参考
FactoryBean的使用姿势,参考:181009-SpringBoot基础篇Bean之基本定义与使用
配置类
Configuration
声明的方式,参考:181012-SpringBoot基础篇Bean之自动加载@Primary注解的使用,参考: 181022-SpringBoot基础篇Bean之多实例选择
接下来就是实际执行看下结果如何了
III. 其他
0. 项目相关
a. 更多博文
基础篇
181009-SpringBoot基础篇Bean之基本定义与使用
181012-SpringBoot基础篇Bean之自动加载
181013-SpringBoot基础篇Bean之动态注册
181018-SpringBoot基础篇Bean之条件注入@Condition使用姿势
181019-SpringBoot基础篇Bean之@ConditionalOnBean与@ConditionalOnClass
181019-SpringBoot基础篇Bean之条件注入@ConditionalOnProperty
181019-SpringBoot基础篇Bean之条件注入@ConditionalOnExpression
181022-SpringBoot基础篇Bean之多实例选择
应用篇
181017-SpringBoot应用篇Bean之注销与动态注册实现服务mock
181024-SpringBoot应用篇之FactoryBean及代理实现SPI机制的实例
b. 项目源码
工程:spring-boot-demo
module: 000-spi-factorybean
1. 一灰灰Blog
一灰灰Blog个人博客 https://blog.hhui.top
一灰灰Blog-Spring专题博客 http://spring.hhui.top
一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
2. 声明
尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激
微博地址: 小灰灰Blog
QQ: 一灰灰/3302797840
3. 扫描关注
一灰灰blog
知识星球