彻底搞懂JAVA SPI

之前做项目的时候,用到了LiteFlow规则引擎,LiteFlow中支持了标准关系型结构化数据库的配置源,使用后发现基于这种方式启动项目时LiteFlow会通过以下固定的SQL去加载相关组件

不难发现,这两个SQL加载了同一租户的所有数据,做开发的都知道,我们更多的情况是去加载有效数据(非逻辑删除的数据),虽然在后续的版本中作者也增加了相应的字段来过滤有效数据,但是有的公司数据库通用字段都是统一的(如逻辑删除标志位、创建人信息等等),不满足我们公司的场景,通过阅读源码后发现作者提供了解析器SPI插件接口,然后通过重写了SQL解析器,可以看到现在的SQL满足了要求

通过该案例,主要是为了引出分享的主要内容:Java SPI机制

大家平时开发的时候有留意的话,就会发现我们使用的框架中有大量使用了java SPI技术(JDBC加载不同类型的数据库驱动、SLF4J加载不同提供商的日志实现类、Spring、Dubbo等等)

SPI (Service Provider Interface)是JDK内置的一种服务提供发现机制,这种机制通常用于插件架构或模块化系统,使得开发者可以轻松地添加或替换系统中的组件。简单来说,接口的定义(调用方提供)与具体实现是隔离的(服务提供方提供),使用接口的实现类需要依赖某种服务发现机制

这里我们不得不提一下API

API (Application Programming Interface) 服务提供方提供的接口与其实现方法

JAVA SPI机制实现

我们平时上下班通过打卡机的时候,打卡机可能是指纹打卡、人脸识别打卡等等。

  • 定义接口
java 复制代码
public interface Clock {

    public void clock();
    
}
  • 指纹打卡
java 复制代码
public class FingerprintClock implements Clock{

    @Override
    public void clock() {

        System.out.println("//// 指纹打卡");

    }
}
  • 人脸识别打卡
java 复制代码
public class FaceRecognitionClock implements Clock{
    @Override
    public void clock() {
    
        System.out.println("//// 人脸识别打卡");
        
    }
}
  • resources下新建META-INF/services/目录能放到别的目录下吗?,新建接口全限定名的文件:com.tx.general.spi.Clock,里面加上需要用到的实现类(我这里配了指纹打卡实现类)
  • 使用ServiceLoader.load()加载接口
java 复制代码
/**
 * 通过ServiceLoader获取所有实现了Clock接口的时钟对象,并依次调用它们的clock方法
 */
public static void main(String[] args) {
    // 创建ServiceLoader类对象
    ServiceLoader<Clock> serviceLoaders = ServiceLoader.load(Clock.class);
    // 返回LazyIterator迭代器的匿名迭代器对象
    Iterator<Clock> it = serviceLoaders.iterator();
    while (it.hasNext()){
        Clock clock = it.next();
        clock.clock();
    }
}

可以看到输出的结果是我们预期的,但是调用方式和平时我们调用方法有所不同,通过ServiceLoader类使用Java的类加载器机制从META-INF/services/目录下加载和实例化服务提供者,完成了对方法的调用。

JAVA SPI机制原理

前面提到了通过ServiceLoader类使用Java的类加载器机制从META-INF/services/目录下加载和实例化服务提供者,我们主要分析ServiceLoader类

load(Class<S> svc, ClassLoader cl)方法

java 复制代码
// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();


public static <S> ServiceLoader<S> load(Class<S> service,
                                        ClassLoader loader)
{
    // 创建一个ServiceLoader对象
    return new ServiceLoader<>(service, loader);
}

private ServiceLoader(Class<S> svc, ClassLoader cl) {
    service = Objects.requireNonNull(svc, "Service interface cannot be null");
    // loader如果为空,那么就使用ClassLoader.getSystemClassLoader(),即系统类加载器
    loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
    acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
    reload();
}

public void reload() {
    // providers是一个Map结构的链表LinkedHashMap,clear()清空了缓存,这里是什么时候有值的呢?
    providers.clear();
    // 创建一个Lazy懒加载形式的迭代器
    lookupIterator = new LazyIterator(service, loader);
}

该方法创建一个可加载接口服务提供者实例的ServiceLoader类对象,其内部创建一个具有延迟加载功能的迭代器LazyIterator(实现了Iterator迭代器接口

iterator()方法

java 复制代码
    /**
     * 返回一个迭代器
     */
    public Iterator<S> iterator() {
        return new Iterator<S>() {
            
            Iterator<Map.Entry<String, S>> knownProviders
                = providers.entrySet().iterator();

            /**
             * 判断是否有下一个元素。
             */
            public boolean hasNext() {
                // 判断缓存是否为空
                if (knownProviders.hasNext())
                    return true;
                // lookupIterator,前文创建的LazyIterator迭代器对象的引用
                return lookupIterator.hasNext();
            }

            /**
             * 获取下一个元素。
             */
            public S next() {
                // 判断缓存是否为空
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                // lookupIterator,前文创建的LazyIterator迭代器对象的引用
                return lookupIterator.next();
            }
        };
    }

该方法创建一个实现了Iterator接口的匿名内部类实例对象,并返回该实例对象作为一个迭代器。 hasNext()方法调用LazyIterator.hasNextService()方法,next()方法调用LazyIterator.nextService()方法

LazyIterator.hasNextService()方法

java 复制代码
private static final String PREFIX = "META-INF/services/";

private boolean hasNextService() {
    if (nextName != null) {
        return true;
    }
    if (configs == null) {
        try {
        
            // 拼接文件路径
            String fullName = PREFIX + service.getName();
            if (loader == null)
                configs = ClassLoader.getSystemResources(fullName);
            else
                 // 通过类加载器读取该文件资源
                configs = loader.getResources(fullName);
        } catch (IOException x) {
            fail(service, "Error locating configuration files", x);
        }
    }
    while ((pending == null) || !pending.hasNext()) {
        if (!configs.hasMoreElements()) {
            return false;
        }
         // 解析读取配置文件类名,将读取到的类名存储到ArrayList,包装成iterator返回
        pending = parse(service, configs.nextElement());
    }
    nextName = pending.next();
    return true;
}

可以看到在这方法里面我们去读取了META-INF/services/下的文件,并将service实现类的名字列表返回

LazyIterator.nextService()方法

java 复制代码
        /**
         * 获取下一个服务提供者
         * 如果没有下一个服务提供者,则抛出`NoSuchElementException`异常
         * 
         * @return 下一个服务提供者
         */
        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            
            // 获取下一个服务提供者的类名
            String cn = nextName;
            nextName = null;
            
            Class<?> c = null;
            try {
                // 根据类名获取Class对象
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                // 类名未找到,抛出异常
                fail(service, "Provider " + cn + " not found");
            }
            
            // 检查服务提供者是否是所指定的服务的子类型
            if (!service.isAssignableFrom(c)) {
                fail(service, "Provider " + cn + " not a subtype");
            }
            
            try {
                // 根据Class对象创建服务提供者的实例
                S p = service.cast(c.newInstance());
                // 将实例添加到缓存中
                providers.put(cn, p);
                // 返回服务提供者实例
                return p;
            } catch (Throwable x) {
                // 创建实例失败,抛出异常
                fail(service, "Provider " + cn + " could not be instantiated", x);
            }
            
            // 不应该执行到这里,抛出`Error`异常
            throw new Error();  
        }

通过反射方法Class.forName()加载类对象,并用newInstance()方法将类实例化,把实例化后的类缓存到providers对象中,到此,我们就搞懂了java SPI的原理

总结

SPI 将服务接口和具体的服务实现分离开来,将服务调用方和服务实现者解耦,能够提升程序的扩展性、可维护性。但是我们也看到了通过ServiceLoader类将接口的实现类全部加载并实例化一遍。

相关推荐
LightOfNight10 分钟前
Redis设计与实现第14章 -- 服务器 总结(命令执行器 serverCron函数 初始化)
服务器·数据库·redis·分布式·后端·缓存·中间件
刽子手发艺19 分钟前
云服务器部署springboot项目、云服务器配置JDK、Tomcat
java·后端·部署
北漂编程小王子22 分钟前
maven <scope>import</scope>配置作用
java·maven·maven import
BIGSHU092322 分钟前
java接口对接标准
java
m0_5474866632 分钟前
数据结构试题库1
java·数据结构·算法
多多*33 分钟前
后端并发编程操作简述 Java高并发程序设计 六类并发容器 七种线程池 四种阻塞队列
java·开发语言·前端·数据结构·算法·状态模式
_im.m.z35 分钟前
Mac配置和启动 Tomcat
java·macos·tomcat·ssm框架
白初&1 小时前
文件上传代码分析
java·c++·python·php·代码审计
sssuperMario1 小时前
IDEA无法创建java8、11项目创建出的pom.xml为空
xml·java·intellij-idea
豪宇刘1 小时前
Spring MVC
java·spring·mvc