今天看啥  ›  专栏  ›  Java极客

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

Java极客  · 掘金  ·  · 2021-02-14 11:40
阅读 47

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

Java极客  |  作者  /  铿然一叶 这是Java极客的第 81 篇原创文章

相关阅读:

JAVA编程思想(一)通过依赖注入增加扩展性
JAVA编程思想(二)如何面向接口编程
JAVA编程思想(三)去掉别扭的if,自注册策略模式优雅满足开闭原则
JAVA编程思想(四)Builder模式经典范式以及和工厂模式如何选?
Java编程思想(五)事件通知模式解耦过程
Java编程思想(六)事件通知模式解耦过程
Java编程思想(七)使用组合和继承的场景
JAVA基础(一)简单、透彻理解内部类和静态内部类
JAVA基础(二)内存优化-使用Java引用做缓存
JAVA基础(三)ClassLoader实现热加载
JAVA基础(四)枚举(enum)和常量定义,工厂类使用对比
JAVA基础(五)函数式接口-复用,解耦之利刃
HikariPool源码(二)设计思想借鉴
【极客源码】JetCache源码(一)开篇
【极客源码】JetCache源码(二)顶层视图
人在职场(一)IT大厂生存法则


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<Class, Class> 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<S> {

    private S s;

    // 私有构造器,不需要外部实例化
    private SServiceLoader(S s) {
        this.s = s;
    }

    public S getService() {
        return s;
    }

    public static <S> SServiceLoader<S> load(Class<S> service) throws Exception {
        SServiceLoader<S> ssServiceLoader = null;
        ServiceLoader<S> 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<ICat> catServiceLoader = SServiceLoader.load(ICat.class);
        ICat iCat = catServiceLoader.getService();
        iCat.say();

        // 使用定制值覆盖默认值
        SServiceLoader<IBird> birdServiceLoader = SServiceLoader.load(IBird.class);
        IBird iBird = birdServiceLoader.getService();
        iBird.say();

        // 找不到定制和默认实现,抛出异常
        SServiceLoader<IFish> fishServiceLoader = SServiceLoader.load(IFish.class);
        IFish iFish = fishServiceLoader.getService();
        iFish.say();
    }

    private static IDog createIDog() throws Exception {
        SServiceLoader<IDog> 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.插件机制适用于产品团队和定制团队协作开发


<--阅过留痕,左边点赞!





原文地址:访问原文地址
快照地址: 访问文章快照