java spi 扩展_【扩展和解耦】JAVA原生SPI实现插件扩展

4352409b0afea9d5071c581732946e02.png

Java极客  |  作者  /  铿然一叶

这是Java极客的第 81 篇原创文章

相关阅读:

1. 什么是插件

通俗的讲插件有以下特征:

1.增加或者替换已有能力

2.不影响原有功能

3.对原有系统无侵入

例如替换电脑中的内存条和显卡,属于替换原有能力,Intellij Idea增加各种代码检查插件属于增加能力。

2. 实现扩展性的方式和插件的应用场景

插件提供了扩展性,实现扩展性的方式还有很多,例如:

1.模板方法设计模式通过子类实现父类的抽象方法实现扩展

2.方法参数通过传入接口实现扩展

插件和其他扩展方式的差别是:

插件是在已有的软件系统/框架上扩展,引入插件后,系统还是原来的系统,例如Intellij Idea增加了代码检查的插件,还是在使用Intellij Idea这个软件,而上述的其他两种方式则不是在原有系统扩展,而是增加或者改变了某个组件的能力引入到自己的软件系统中。

因此,插件的应用场景为:

1.产品研发团队进行产品化开发,提供通用的产品能力,并提供插件化机制

2.定制团队根据需要扩展插件

3. SPI插件扩展

先看下SPI扩展机制的实现方式:

7ee2a757883c85ab0e8eaeff78c687af.png

1.产品研发需要开发一个接口(这里也可以是类),并预埋在系统中

2.产品研发在系统中通过ServiceLoader加载预埋的接口类并调用

3.定制研发开发实现类

4.定制研发在classpath路径的META-INF/services目录下生成一个和接口类的同名文件,同名文件的内容只有一行,为具体的实现类名

在满足以上几点后,如果在运行期发现接口类有子类,则子类会被调用,这样就实现了插件的插入。

4. 代码样例

4.1. 代码结构

原生的SPI机制会查找插件,查找不到则不会处理,在实际项目落地中需要支持默认的插件实现,因此得到如下的结构图:

2baa566c4b3584c875b92d1c29c84c59.png

类职责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目录下:

da02e8acb60a16c11aac7b8bb4789a4a.png

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

91e6b901dc21a5af46549847a4d47d50.gif

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值