最近在看一个rpc框架源码的时候,发现它可以获取到某一个接口的实现类列表。感觉很厉害,究竟是怎么做到的。查看了源码,原来是使用了jdk自带的spi机制。
什么是SPI
spi(Service Provider Interface) 是JDK提供的一种解耦方法。spi规定以接口全路径名为文件名,以实现类全路径名为文件内容。使用JDK提供的 java.util.ServiceLoader 类即可获得实现类的实例。这样就可以以配置的形式来决定在运行时的具体实现。SPI的解耦思路其实和springIOC是一样的,简单来说就是把硬编码,改成了配置形式。
如何使用SPI
使用spi的步骤如下:
- 在resource目录下新建一个 META-INF/services 目录
- 在 META-INF/services 目录下新建一个文件,文件名是接口的全路径名
- 文件的内容是接口实现类的全路径名
- 调用 java.util.ServiceLoader.load( interface.class) 来获取文件中的对象实例
demo
文件结构如下:-- src |- test |- ITestService |- impl |- TestServiceImpl |- TestService2Impl -- resource |- META-INF |- services |- test.ITestService
test.ITestService文件中的内容如下:
test.impl.TestServiceImpl test.impl.TestService2Impl
分析一下:
- ITestService接口的路径为test.ITestService,所以SPI的配置文件名就叫test.ITestService。
- 两个实现类的包路径是 test.impl, 所以配置文件中的内容为
test.impl.TestServiceImpl
test.impl.TestService2Impl
调用如下:
ServiceLoader<ITestService> services = ServiceLoader.load(ITestService.class); //可以遍历得到文件中所有的实现类实例,即TestServiceImpl和TestService2Impl对象 for (ITestService service: services){ //可以调用service的方法 //service.XXX(); }
源码实现
现在我们知道了spi的作用,但是他是怎么实现的呢?可以看到调用的代码非常简洁,load 之后就可以直接循环获取实例了。我们看下他是如何实现的。
调用 load 方法:
将class对象保存在ServiceLoader的内部。并初始化一个迭代器。
遍历ServiceLoader:
读取保存的class对象,获取class的全路径名(这就是配置文件名)。读取 /META-INF/services 路径下的配置文件(class的全路径名)。每次迭代都会去读取下一行的内容并转换成对象返回。
SPI的核心逻辑在这个迭代器中,源码如下:
private class LazyIterator implements Iterator<S> { //load方法传入的class 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 { //根据传入的接口的全路径名,得到配置文件 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 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 } //遍历的时候会先调用hasnext 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); } } //如果hasnext返回true,然后会调用next方法 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(); } }
相关链接:官方文档