SPI全称S而vice Provider Interface,是java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。
系统设计的各个抽象,一般有很多不同的实现方案,比如通过不同类型的配置文件加载配置信息,通过不同的序列化方案实现序列化。一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可插拔的原则。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。
SPI的核心思想就是 解耦 。
调用者根据实际需要替换框架的实现策略。
比如常见的例子:
实例代码
package com.djl.test.spi.api; /** * @ClassName Robot * @Description TODO * @Author djl * @Date 2020-03-18 19:01 * @Version 1.0 */ public interface Robot { void sayHello(); }
package com.djl.test.spi.java; import com.djl.test.spi.api.Robot; /** * @ClassName Bumblebee * @Description TODO * @Author djl * @Date 2020-03-18 19:03 * @Version 1.0 */ public class Bumblebee implements Robot { @Override public void sayHello() { System.out.println("Hello ,I am Bumble bee"); } }
package com.djl.test.spi.java; import com.djl.test.spi.api.Robot; /** * @ClassName OptimusPrime * @Description TODO * @Author djl * @Date 2020-03-18 19:02 * @Version 1.0 */ public class OptimusPrime implements Robot { @Override public void sayHello() { System.out.println("Hello,I am Optimus prime"); } }
com.djl.test.spi.java.Bumblebee com.djl.test.spi.java.OptimusPrime
import com.djl.test.spi.api.Robot; import com.djl.test.spi.api.RobotDubbo; import org.apache.dubbo.common.extension.ExtensionLoader; import org.junit.Test; import java.util.ServiceLoader; /** * @ClassName JavaSPITest * @Description TODO * @Author djl * @Date 2020-03-18 19:06 * @Version 1.0 */ public class JavaSPITest { @Test public void sayHello(){ ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class); System.out.println("java spi"); serviceLoader.forEach(Robot::sayHello); } }
我会将分析都写在代码注释中,大家可以打开自己的源码耐心的看一会。
接下来是重头戏了,知道了spi怎么用,那么内部是如何实现的呢?
我们直接从ServiceLoader类的load方法看起。
/**为给定的服务类型创建一个新的服务加载程序,使用 *当前线程的{@linkplain java.lang.Thread#getContextClassLoader *上下文类装入器}。 */ public static <S> ServiceLoader<S> load(Class<S> service) { //1.获取当前线程的类加载 ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) { //new一个serviceloader对象 return new ServiceLoader<>(service, loader); }
//构造函数 private ServiceLoader(Class<S> svc, ClassLoader cl) { //判断入参是否为null service = Objects.requireNonNull(svc, "Service interface cannot be null"); //2.加载器如果不存在,获取系统类加载器,通常是applicationLoader,应用程序加载器 loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; //3.获取访问控制器 acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; reload(); }
public void reload() { // 清空缓存 providers.clear(); // 初始化内部类,用于遍历提供者 lookupIterator = new LazyIterator(service, loader); }
看到这里,相信大家对于初始化的内容有了一定了解,这里面涉及到了一些属性,我们来总结下
private static final String PREFIX = "META-INF/services/"; // 要加载的类 private final Class<S> service; // 用于加载实现类的类加载器 private final ClassLoader loader; // 访问控制器 private final AccessControlContext acc; // 提供者的缓存 private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); // 一个内部类,用于遍历实现类 private LazyIterator lookupIterator;
现在我们发现重点就在于LazyIterator这个内部类上,我们获取实现类都看这个内部类了,我们继续来分析
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 { //获取META-INF/services下文件全称 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; } pending = parse(service, configs.nextElement()); } nextName = pending.next(); return true; }
private Iterator<String> parse(Class<?> service, URL u) throws ServiceConfigurationError { InputStream in = null; BufferedReader r = null; //存储配置文件中实现类的全限定名 ArrayList<String> names = new ArrayList<>(); try { in = u.openStream(); r = new BufferedReader(new InputStreamReader(in, "utf-8")); int lc = 1; //读取文件内容,这里不多说了,正常的流操作 while ((lc = parseLine(service, u, r, lc, names)) >= 0); } catch (IOException x) { fail(service, "Error reading configuration file", x); } finally { try { if (r != null) r.close(); if (in != null) in.close(); } catch (IOException y) { fail(service, "Error closing configuration file", y); } } return names.iterator(); }
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 { //这一行将实例化的类强转成所表示的类型 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 }
//这里是iterable循环遍历 default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } }
到这里整个链路就分析完成了。
感兴趣的小伙伴可以按照demo,自己跑一遍,有问题欢迎提问。