SPI
全名为Service Provider Interface
是JDK内置的一种服务提供发现机制,是Java提供的一套用来被第三方实现或者扩展的API,它能够用来启用框架扩展和替换组件。html
JAVA SPI
= 基于接口的编程+策略模式+配置文件 的动态加载机制java
Java SPI的具体约定以下:git
当服务的提供者,提供了服务接口的一种实现以后,在jar
包的META-INF/services/
目录里同时建立一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。github
而当外部程序装配这个模块的时候,就能经过该jar
包META-INF/services/
里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。数据库
根据SPI的规范咱们的服务实现类必须有一个无参构造方法
。编程
为何必定要在classes
中的META-INF/services
下呢?缓存
JDK提供服务实现查找的一个工具类:java.util.ServiceLoader
安全
在这个类里面已经写死bash
// 默认会去这里寻找相关信息 private static final String PREFIX = "META-INF/services/";
常见的使用场景:多线程
JDBC
加载不一样类型的数据库驱动SLF4J
加载不一样提供商的日志实现类Spring
中大量使用了SPI
,
servlet3.0
规范ServletContainerInitializer
的实现Type Conversion SPI(Converter SPI、Formatter SPI)
等Dubbo
里面有不少个组件,每一个组件在框架中都是以接口的造成抽象出来!具体的实现又分不少种,在程序执行时根据用户的配置来按需取接口的实现总体包结构以下
└─main ├─java │ └─com │ └─xinchen │ └─spi │ └─App.java │ └─IService.java │ └─ServiceImplA.java │ └─ServiceImplB.java └─resources └─META-INF └─services └─com.xinchen.spi.IService
SPI接口
public interface IService { void say(String word); }
具体实现类
public class ServiceImplA implements IService { @Override public void say(String word) { System.out.println(this.getClass().toString() + " say: " + word); } } public class ServiceImplB implements IService { @Override public void say(String word) { System.out.println(this.getClass().toString() + " say: " + word); } }
/resource/META-INF/services/com.xinchen.spi.IService
com.xinchen.spi.ServiceImplA com.xinchen.spi.ServiceImplB
Client类
public class App { static ServiceLoader<IService> services = ServiceLoader.load(IService.class); public static void main(String[] args) { for (IService service:services){ service.say("Hello World!"); } } } // 结果: // class com.xinchen.spi.ServiceImplA say: Hello World! // class com.xinchen.spi.ServiceImplB say: Hello World!
java.util.ServiceLoader
中的Fied区域
// 加载具体实现类信息的前缀 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; // 用于缓存已经加载的接口实现类,其中key为实现类的完整类名 // Cached providers, in instantiation order private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); // 用于延迟加载接口的实现类 // The current lazy-lookup iterator private LazyIterator lookupIterator;
从ServiceLoader.load(IService.class)
进入源码中
public static <S> ServiceLoader<S> load(Class<S> service) { // 获取当前线程上下文的类加载器 ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }
在ServiceLoader.load(service, cl)
中
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 = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; reload(); } public void reload() { // 清空已经缓存的加载的接口实现类 providers.clear(); // 建立新的延迟加载迭代器 lookupIterator = new LazyIterator(service, loader); } private LazyIterator(Class<S> service, ClassLoader loader) { // 指定this类中的 须要加载的接口service和类加载器loader this.service = service; this.loader = loader; }
当咱们经过迭代器获取对象实例的时候,首先在成员变量providers
中查找是否有缓存的实例对象
若是存在则直接返回,不然则调用lookupIterator
延迟加载迭代器进行加载
迭代器判断的代码以下
public Iterator<S> iterator() { // 返回迭代器 return new Iterator<S>() { // 查询缓存中是否存在实例对象 Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator(); public boolean hasNext() { // 若是缓存中已经存在返回true if (knownProviders.hasNext()) return true; // 若是不存在则使用延迟加载迭代器进行判断是否存在 return lookupIterator.hasNext(); } public S next() { // 若是缓存中存在则直接返回 if (knownProviders.hasNext()) return knownProviders.next().getValue(); // 调用延迟加载迭代器进行返回 return lookupIterator.next(); } public void remove() { throw new UnsupportedOperationException(); } }; }
LazyIterator的类加载
// 判断是否拥有下一个实例 private boolean hasNextService() { // 若是拥有直接返回true if (nextName != null) { return true; } // 具体实现类的全名 ,Enumeration<URL> config 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; } // 转换config中的元素,或者具体实现类的真实包结构 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 { // 加载类对象 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 { // 经过c.newInstance()实例化 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机制的优点是实现解耦,使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离,而不是耦合在一块儿。应用程序能够根据实际业务状况启用框架扩展或替换框架组件。
缺点
多个并发多线程使用ServiceLoader类的实例是不安全的
虽然ServiceLoader也算是使用的延迟加载,可是基本只能经过遍历所有获取,也就是接口的实现类所有加载并实例化一遍。