深入理解SPI机制

it2023-05-10  85

一、什么是

目录

一、什么是SPI

二、源码分析

三、java的SPI的缺点:


SPI

SPI ,全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。

这一机制为很多框架扩展提供了可能,比如在Dubbo、JDBC中都使用到了SPI机制。我们先通过一个很简单的例子来看下它是怎么用的。

代码示例:

// 1、定义接口 package com.liang.spi; /** * SPI接口 * @author Administrator */ public interface SPIServiceInterface { void handle(String req); } // 2、两个实现类 package com.liang.spi; public class SPIServiceA implements SPIServiceInterface { @Override public void handle(String req) { System.out.println("加载SPIServiceA处理逻辑,req:"+req); } } package com.liang.spi; public class SPIServiceB implements SPIServiceInterface{ @Override public void handle(String req) { System.out.println("加载SPIServiceB处理逻辑,req:"+req); } } // 3、spi配置文件 resources文件夹下新增META-INF/services文件夹,新建文件 文件名:com.liang.spi.SPIServiceInterface 文件内容: com.liang.spi.SPIServiceA com.liang.spi.SPIServiceB // 4、测试SPI 加载执行 package com.liang.spi; import sun.misc.Service; import java.time.LocalDateTime; import java.util.Iterator; import java.util.ServiceLoader; /** * 我们通过ServiceLoader.load或者Service.providers方法拿到实现类的实例。 * 其中,Service.providers包位于sun.misc.Service, * 而ServiceLoader.load包位于java.util.ServiceLoader。 */ public class TestSPI { public static void main(String[] args) { /** * 方式1 */ Iterator<SPIServiceInterface> providers = Service.providers(SPIServiceInterface.class); while (providers.hasNext()) { SPIServiceInterface spiServiceInterface = providers.next(); spiServiceInterface.handle(LocalDateTime.now().toString()); } System.out.println("====@@===="); /** * 方式2 */ ServiceLoader<SPIServiceInterface> load = ServiceLoader.load(SPIServiceInterface.class); Iterator<SPIServiceInterface> iterator = load.iterator(); while (iterator.hasNext()) { SPIServiceInterface spiServiceInterface = iterator.next(); spiServiceInterface.handle(LocalDateTime.now().toString()); } } }

二、源码分析

上面的spi测试类,通过两种方式来实现spi,这两种不同的方式分别是sun.misc包,java.util包;这里先进行util包下ServiceLoader源码分析,

1、首先来看ServiceLoader类,

/** * 一个简单的服务提供者加载工具。 * A simple service-provider loading facility. */ public final class ServiceLoader<S> implements Iterable<S> { // spi配置文件目录 private static final String PREFIX = "META-INF/services/"; // 表示被加载的服务的类或接口 // The class or interface representing the service being loaded private final Class<S> service; // 用于定位、装入和实例化提供程序的类装入器 // The class loader used to locate, load, and instantiate providers private final ClassLoader loader; // 创建ServiceLoader时采取的访问控制上下文 // The access control context taken when the ServiceLoader is created private final AccessControlContext acc; // 缓存的提供程序,按实例化顺序 // Cached providers, in instantiation order private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); // 当前的延迟查找迭代器 // The current lazy-lookup iterator private LazyIterator lookupIterator; ... }

2、分析具体的加载、解析和实例化步骤,这期间需要用到重要的内部类 LazyIterator,

// 实现完全延迟提供程序查找的私有内部类 // Private inner class implementing fully-lazy provider lookup private class LazyIterator implements Iterator<S> { Class<S> service; ClassLoader loader; Enumeration<URL> configs = null; Iterator<String> pending = null; String nextName = null; private LazyIterator(Class<S> service, ClassLoader loader) { this.service = service; this.loader = loader; } private boolean hasNextService() { // 遍历存在的实现类 if (nextName != null) { return true; } if (configs == null) { try { // spi接口的全名 = "META-INF/services/"+"com.liang.spi.SPIServiceInterface" String fullName = PREFIX + service.getName(); if (loader == null) configs = ClassLoader.getSystemResources(fullName); else // spi配置文件加载到configs对象 configs = loader.getResources(fullName); } catch (IOException x) { fail(service, "Error locating configuration files", x); } } // 处理spi配置文件,通过configs对象解析出配置的实现类 while ((pending == null) || !pending.hasNext()) { if (!configs.hasMoreElements()) { return false; } pending = parse(service, configs.nextElement()); } nextName = pending.next(); return true; } private S nextService() { if (!hasNextService()) throw new NoSuchElementException(); // spi接口实现类的全限定名 String cn = nextName; nextName = null; Class<?> c = null; try { // 反射加载cn类对象 : com.liang.spi.SPIServiceA 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 { // 通过newInstance进行对象实例化为p接口的实例 S p = service.cast(c.newInstance()); // 存入provider容器 providers.put(cn, p); return p; } catch (Throwable x) { fail(service, "Provider " + cn + " could not be instantiated", x); } throw new Error(); // This cannot happen } public boolean hasNext() { if (acc == null) { return hasNextService(); } else { PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() { public Boolean run() { return hasNextService(); } }; return AccessController.doPrivileged(action, acc); } } public S next() { if (acc == null) { return nextService(); } else { PrivilegedAction<S> action = new PrivilegedAction<S>() { public S run() { return nextService(); } }; return AccessController.doPrivileged(action, acc); } } public void remove() { throw new UnsupportedOperationException(); } }

几个常用的场景:Dubbo【对JDK的SPI进行了改造】和JDBC、配置中心apollo等

三、Java的SPI的缺点:

  1、每次都要遍历所有的配置实现,不够灵活。

  2、加载所有的实现类,即使不需要的也会加载,浪费资源。

Dubbo框架就解决了Java的以上缺点,可以自定义加载对应的SPI类。

 

 

-------------欢迎各位留言交流,如有不正确的地方,请予以指正。【Q:981233589】

最新回复(0)