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.插件机制适用于产品团队和定制团队协作开发