其他分享
首页 > 其他分享> > 什么是SPI

什么是SPI

作者:互联网

什么是SPI

SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。 SPI的作用就是为这些被扩展的API寻找服务实现。

整体机制图如下:

 

 Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。

系统设计的各个抽象,往往有很多不同的实现方案,在面向的对象的设计里,一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。
Java SPI就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。所以SPI的核心思想就是解耦。

使用场景

概括地说,适用于:调用者根据实际使用需要,启用、扩展、或者替换框架的实现策略

比较常见的例子:

使用介绍

要使用Java SPI,需要遵循如下约定:

示例代码

step1. 定义接口

package org.ray.spi;

public interface Human {

    public void speak();
}

step2. 定义实现类

package org.ray.spi;

public class Chinese implements Human{

    @Override
    public void speak() {
        System.out.println("哈喽 我的");
    }
}
package org.ray.spi;

public class English implements Human{

    @Override
    public void speak() {
        System.out.println("hello world");
    }
}

step3. 定义配置文件

在classpath(src/main/resources)下创建META-INF/resources目录,创建以接口名字org.ray.spi.Human命名的文件,内容写入接口实现类的全限定类名,如果有多个需换行

org.ray.spi.English
org.ray.spi.Chinese

step4. 执行ServiceLoader

    public static void main(String[] args) throws IOException {

        ServiceLoader<Human> load = ServiceLoader.load(Human.class);
        for (Human human : load) {
            human.speak();
        }
    }

step5. 查看输出

hello world
哈喽 我的

源码分析

ServiceLoader在这里没有核心操作,主要负责对外提供load()方法用于获取SPI接口和实例化懒加载迭代器LazyIterator

public final class ServiceLoader<S> implements Iterable<S>{
    
    #SPI规则固定加载文件地址前缀
    private static final String PREFIX = "META-INF/services/";
       #SPI的接口
    private final Class<S> service;
    #类加载器,使用的是当前线程的类加载器(Thread.currentThread().getContextClassLoader())
    private final ClassLoader loader;
    #默认是null, 创建ServiceLoader时采用的访问控制上下文
    private final AccessControlContext acc;
    #缓存加载成功的类
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    #当前的迭代器,默认初始化为LazyIterator,注意这里是懒加载的,只有使用的时候才去迭代加载SPI文件
    private LazyIterator lookupIterator;
    
    #SPI执行使用方法,不指定ClassLoader
       public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }
    #SPI执行使用方法,指定ClassLoader
    public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader){
        return new ServiceLoader<>(service, loader);
    }
    #构造方法中保存SPI接口,初始化懒加载迭代器
    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }
    #初始化懒加载迭代器
    public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }
}

LazyIterator是懒加载,实例化后什么也不干,只保存了SPI接口

    private class LazyIterator implements Iterator<S> {
        private LazyIterator(Class<S> service, ClassLoader loader) {
            this.service = service;
            this.loader = loader;
        }
    }

ServiceLoader的iterator()方法被调用,开始执行核心逻辑LazyIterator懒加载SPI文件

    private class LazyIterator implements Iterator<S> {
        
        private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    #拼装SPI文件完整地址META-INF/services+SPI全限定类名
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        #加载SPI文件
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                #解析SPI文件,获取实现类全限定类名
                pending = parse(service, configs.nextElement());
            }
            #赋值实现类全限定类名
            nextName = pending.next();
            return true;
        }

        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);
            }
            throw new Error();          // This cannot happen
        }
    }

总结

优点:
使用Java SPI机制的优势是实现解耦,使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离,而不是耦合在一起。应用程序可以根据实际业务情况启用框架扩展或替换框架组件。

相比使用提供接口jar包,供第三方服务模块实现接口的方式,SPI的方式使得源框架不必关心接口的实现类的路径,可以不用通过下面的方式获取接口实现类:

代码硬编码import 导入实现类

指定类全路径反射获取:例如在JDBC4.0之前,JDBC中获取数据库驱动类需要通过Class.forName(“com.mysql.jdbc.Driver”),类似语句先动态加载数据库相关的驱动,然后再进行获取连接等的操作

第三方服务模块把接口实现类实例注册到指定地方,源框架从该处访问实例

通过SPI的方式,第三方服务模块实现接口后,在第三方的项目代码的META-INF/services目录下的配置文件指定实现类的全路径名,源码框架即可找到实现类

缺点:

虽然ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。获取某个实现类的方式不够灵活,只能通过Iterator形式获取,不能根据某个参数来获取对应的实现类。

多个并发多线程使用ServiceLoader类的实例是不安全的。

转载:https://blog.csdn.net/dcr782195101/article/details/122004685

 

 

TRANSLATE with x English
Arabic Hebrew Polish
Bulgarian Hindi Portuguese
Catalan Hmong Daw Romanian
Chinese Simplified Hungarian Russian
Chinese Traditional Indonesian Slovak
Czech Italian Slovenian
Danish Japanese Spanish
Dutch Klingon Swedish
English Korean Thai
Estonian Latvian Turkish
Finnish Lithuanian Ukrainian
French Malay Urdu
German Maltese Vietnamese
Greek Norwegian Welsh
Haitian Creole Persian  
  TRANSLATE with COPY THE URL BELOW Back EMBED THE SNIPPET BELOW IN YOUR SITE Enable collaborative features and customize widget: Bing Webmaster Portal Back

标签:service,实现,什么,接口,SPI,ServiceLoader,加载
来源: https://www.cnblogs.com/cainiao-Shun666/p/16144368.html