java spi 扩展_【扩展和解耦】JAVA原生SPI实现插件扩展
Java极客 | 作者 / 铿然一叶
这是Java极客的第 81 篇原创文章
相关阅读:
1. 什么是插件
通俗的讲插件有以下特征:
1.增加或者替换已有能力
2.不影响原有功能
3.对原有系统无侵入
例如替换电脑中的内存条和显卡,属于替换原有能力,Intellij Idea增加各种代码检查插件属于增加能力。
2. 实现扩展性的方式和插件的应用场景
插件提供了扩展性,实现扩展性的方式还有很多,例如:
1.模板方法设计模式通过子类实现父类的抽象方法实现扩展
2.方法参数通过传入接口实现扩展
插件和其他扩展方式的差别是:
插件是在已有的软件系统/框架上扩展,引入插件后,系统还是原来的系统,例如Intellij Idea增加了代码检查的插件,还是在使用Intellij Idea这个软件,而上述的其他两种方式则不是在原有系统扩展,而是增加或者改变了某个组件的能力引入到自己的软件系统中。
因此,插件的应用场景为:
1.产品研发团队进行产品化开发,提供通用的产品能力,并提供插件化机制
2.定制团队根据需要扩展插件
3. SPI插件扩展
先看下SPI扩展机制的实现方式:
1.产品研发需要开发一个接口(这里也可以是类),并预埋在系统中
2.产品研发在系统中通过ServiceLoader加载预埋的接口类并调用
3.定制研发开发实现类
4.定制研发在classpath路径的META-INF/services目录下生成一个和接口类的同名文件,同名文件的内容只有一行,为具体的实现类名
在满足以上几点后,如果在运行期发现接口类有子类,则子类会被调用,这样就实现了插件的插入。
4. 代码样例
4.1. 代码结构
原生的SPI机制会查找插件,查找不到则不会处理,在实际项目落地中需要支持默认的插件实现,因此得到如下的结构图:
类职责SServiceLoader负责加载插件类,先通过SPI机制加载定制插件,如果找不到则通过ServiceRegistry获取默认插件
ServiceRegistry负责默认插件注册,和默认插件的查询
DefaultImplClass默认插件
4.2. 代码
4.2.1. ServiceRegistry.java
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @ClassName ServiceRegistry
* @Description
* @Author 铿然一叶
* @Date 2021/2/13 18:37
* @Version 1.0
* 掘金:https://juejin.im/user/5d48557d6fb9a06ae071e9b0
**/
public final class ServiceRegistry{
private static Map serviceMap = new ConcurrentHashMap<>();
// 注册默认实现类
static {
register(ICat.class, Ragdoll.class); // 默认值,没有定制
register(IBird.class, Sparrow.class); // 默认值,将被parrot替代
}
// 提供给外部注册用
public static void register(Class interfaceClass, Class defaultImplClass){
serviceMap.put(interfaceClass, defaultImplClass);
}
// 包级访问权限,不需要提供给外部调用
static Class getImplClass(Class interfaceClass) throws Exception{
if (!serviceMap.containsKey(interfaceClass)) {
throw new Exception("没有找到实现类, " + interfaceClass.getName());
}
return serviceMap.get(interfaceClass);
}
}
复制代码
4.2.2. SServiceLoader.java
import java.util.ServiceLoader;
/**
* @ClassName SServiceLoader
* @Description
* @Author 铿然一叶
* @Date 2021/2/13 18:42
* @Version 1.0
* 掘金:https://juejin.im/user/5d48557d6fb9a06ae071e9b0
**/
public class SServiceLoader{
private S s;
// 私有构造器,不需要外部实例化
private SServiceLoader(S s){
this.s = s;
}
public S getService(){
return s;
}
public static SServiceLoader load(Class service) throws Exception{
SServiceLoader ssServiceLoader = null;
ServiceLoader sServiceLoader = ServiceLoader.load(service);
try {
if (sServiceLoader.iterator().hasNext()) {
ssServiceLoader = new SServiceLoader(sServiceLoader.iterator().next());
} else {
Class clazz = ServiceRegistry.getImplClass(service);
ssServiceLoader = new SServiceLoader(clazz.newInstance());
}
} catch (Throwable e) {
throw e;
}
return ssServiceLoader;
}
}
复制代码
4.2.3. 插件相关类
4.2.3.1. IBird.java
public interface IBird{
void say();
}
复制代码
4.2.3.2. Sparrow.java
public class Sparrow implements IBird{
@Override
public void say(){
System.out.println("I am sparrow.");
}
}
复制代码
4.2.3.3. Sparrow.java
public class Parrot implements IBird{
@Override
public void say(){
System.out.println("I am parrot.");
}
}
复制代码
4.2.3.4. ICat.java
public interface ICat{
void say();
}
复制代码
4.2.3.5. Ragdoll.java
public class Ragdoll implements ICat{
@Override
public void say(){
System.out.println("I am ragdoll.");
}
}
复制代码
4.2.3.6. IDog.java
public interface IDog{
void say();
}
复制代码
4.2.3.7. Labrador.java
public class Labrador implements IDog{
@Override
public void say(){
System.out.println("I am labrador.");
}
}
复制代码
4.2.3.8. IFish.java
public interface IFish{
void say();
}
复制代码
4.2.4. 测试类
public class ServiceTest{
public static void main(String[] args) throws Exception{
// 通过SPI扩展
IDog iDog = createIDog();
iDog.say();
// 检查是否单例
IDog iDog1 = createIDog();
System.out.println("iDog: " + iDog.toString());
System.out.println("iDog1: " + iDog1.toString());
// 没有定制,使用默认值
SServiceLoader catServiceLoader = SServiceLoader.load(ICat.class);
ICat iCat = catServiceLoader.getService();
iCat.say();
// 使用定制值覆盖默认值
SServiceLoader birdServiceLoader = SServiceLoader.load(IBird.class);
IBird iBird = birdServiceLoader.getService();
iBird.say();
// 找不到定制和默认实现,抛出异常
SServiceLoader fishServiceLoader = SServiceLoader.load(IFish.class);
IFish iFish = fishServiceLoader.getService();
iFish.say();
}
private static IDog createIDog() throws Exception{
SServiceLoader sServiceLoader = SServiceLoader.load(IDog.class);
return sServiceLoader.getService();
}
}
复制代码
4.2.5. 插件配置文件
插件配置文件需在classpath中的META-INFO/services目录下:
4.2.5.1. com.javageektour.spi.IBird
com.javageektour.spi.Parrot
复制代码
4.2.5.2. com.javageektour.spi.IDog
com.javageektour.spi.Labrador
复制代码
4.2.6. 输出结果
I am labrador.
iDog: com.javageektour.spi.Labrador@4554617c
iDog1: com.javageektour.spi.Labrador@74a14482
I am ragdoll.
I am parrot.
Exception in thread "main" java.lang.Exception: 没有找到实现类, com.javageektour.spi.IFish
at com.javageektour.spi.ServiceRegistry.getImplClass(ServiceRegistry.java:31)
at com.javageektour.spi.SServiceLoader.load(SServiceLoader.java:33)
at com.javageektour.spi.ServiceTest.main(ServiceTest.java:32)
复制代码
5. 总结
1.SPI是Java原生实现的插件机制,原理是通过配置文件动态加载类,在实际落地时,自行实现也可以
2.插件提供了扩展性,是在已有系统中增加或者替换某个能力
3.插件机制对已有系统无侵入
3.插件机制适用于产品团队和定制团队协作开发
java spi 扩展_【扩展和解耦】JAVA原生SPI实现插件扩展相关推荐
- java python算法_用Python,Java和C ++示例解释的排序算法
java python算法 什么是排序算法? (What is a Sorting Algorithm?) Sorting algorithms are a set of instructions t ...
- java实现报表_用存储过程和 JAVA 写报表数据源有什么弊端?
用存储过程和 JAVA 写报表数据源有什么弊端?跟着小编一起来一看一下吧! 我们在报表开发中经常会使用存储过程准备数据,存储过程支持分步计算,可以实现非常复杂的计算逻辑,为报表开发带来便利.所以,报表 ...
- java必读书籍_最佳5本Java性能调优书籍–精选,必读
java必读书籍 为什么Java开发人员应该阅读有关性能调优的书? 当我很久以前第一次面对这个问题时,我以为以后会做,但是我很长一段时间都没有回过头来. 仅当我在用Java编写的任务关键型服务器端财务 ...
- java架构师_成为一名Java高级架构师究竟要学哪些东西??
Java架构师,应该算是一些Java程序员们的一个职业目标了吧.很多码农码了五六年的代码也没能成为架构师.那成为Java架构师要掌握哪些技术呢,总体来说呢,有两方面,一个是基础技术,另一个就是组织能力 ...
- java 冻结对象_模式匹配时冻结 - java
我的程序遇到一个小问题.它似乎冻结了,很可能是由于while循环引起的. 我正在尝试做的是拾取并替换Java注释.因此,在键入块注释时,您将使用/*打开该注释.如果没有结束符(*/),该程序将在5-6 ...
- java 7 反射_【7】java 反射详解
[7]java 反射详解 获取Class对象的方式: 1. Class.forName("全类名"); 将字节码加载进内存,返回Class对象,多用于配置文件,将类名定义在配置文件 ...
- python比java容易学_是不是Python比Java更容易学,更容易就业?
首先,从编程语言本身的语法结构,Java语言更为困难.对初学者来说,学习Java语言的时间成本较高,并且由于Java语言本身有一个相对高度的抽象,你必须想很多实验需要形成自己的Java编程思想完成.J ...
- java 判断类型_如何快速入门Java编程学习(干货)
一.初识Java 1.生活中的程序: 从起床到教室上课的过程 穿衣打扮>起床>洗漱>出宿舍>>吃早餐>到教室 按照特定的顺序去完成某一件事的过程我们叫做生活中的程序 ...
- 支持java虚拟主机_为何缺乏支持Java的虚拟主机
现在很多站长们,都青睐于香港虚拟主机建站.现在常见的是asp和PHP的程序空间很多,但是Java的少之又少,下面小编我给大家聊一聊! 一.香港Java虚拟主机成本高 使用Java程序建站,就必须要用支 ...
- 做Java头发少_这35个Java代码优化细节,你用了吗
链接:https://www.jianshu.com/p/6e472304b5ac 前言 代码 优化 ,一个很重要的课题.可能有些人觉得没用,一些细小的地方有什么好修改的,改与不改对于代码的运行效率有 ...
最新文章
- careercup-中等难度 17.5
- 算法训练 字符串的展开
- 20个常用的Python小技巧
- Java随机生成长宽的矩形_java – 将正方形或矩形分解为大量随机大小的正方形或矩形...
- 【记录】jenkins 安装及环境配置(二)
- python不同时间周期k线_请问期货不同时间级别的k线呈现相反形态怎么判断买卖点?...
- 只要沾上婚恋焦虑,她们就王者变青铜
- 使得最右边的元素右边框为0
- wing ide 3.x 中文设置
- 剑指 Offer II 028. 数组中出现次数超过一半的数字
- Linux执行fastqc报错Exception in thread “main“ java.awt.HeadlessException: No X11 DISPLAY variable was s
- 一图助你搞明白Spring应用上下文初始化流程!
- 86版五笔字根表(JPG版)
- redis实现原理和应用(redis读书笔记)
- UVM 事务级建模TLM 单向/多向通信 端口 FIFO通信
- [荐]硕博经验——科研论文阅读与写作实战技巧
- unity设置中文版
- VFP开发微信、支付宝扫码支付
- “富勒”官网软件感染网银木马 360独家拦截 - 卫星杂谈 - 360官方论坛
- DecisionTreeClassifier决策树