什么是SPI
SPI全称Service Provider Interface,字面意思是提供服务的接口,再解释具体一下就是Java提供的一套用来被第三方实现或扩大的接口,实现了接口的动静扩大,让第三方的实现类能像插件一样嵌入到零碎中。
咦。。。
这个解释感觉还是有点绕口。
那就说一下它的实质。
将接口的实现类的全限定名配置在文件中(文件名是接口的全限定名),由服务加载器读取配置文件,加载实现类。实现了运行时动静为接口替换实现类。
SPI示例
还是举例说明吧。
咱们创立一个我的项目,而后创立一个module叫spi-interface。
在这个module中咱们定义一个接口:
/** * @author jimoer **/public interface SpiInterfaceService { /** * 打印参数 * @param parameter 参数 */ void printParameter(String parameter);}
再定义一个module,名字叫spi-service-one,pom.xml中依赖spi-interface。
在spi-service-one中定义一个实现类,实现SpiInterfaceService 接口。
package com.jimoer.spi.service.one;import com.jimoer.spi.app.SpiInterfaceService;/** * @author jimoer **/public class SpiOneService implements SpiInterfaceService { /** * 打印参数 * * @param parameter 参数 */ @Override public void printParameter(String parameter) { System.out.println("我是SpiOneService:"+parameter); }}
而后在spi-service-one的resources目录下创立目录META-INF/services,在此目录下创立一个文件名称为SpiInterfaceService接口的全限定名称,文件内容写入SpiOneService这个实现类的全限定名称。
成果如下:
再创立一个module,名称为:spi-service-one,也是依赖spi-interface,并且定义一个实现类SpiTwoService 来实现SpiInterfaceService 接口。
package com.jimoer.spi.service.two;import com.jimoer.spi.app.SpiInterfaceService;/** * @author jimoer **/public class SpiTwoService implements SpiInterfaceService { /** * 打印参数 * * @param parameter 参数 */ @Override public void printParameter(String parameter) { System.out.println("我是SpiTwoService:"+parameter); }}
目录构造如下:
上面再创立一个用来测试的module,名为:spi-app。
pom.xml中依赖spi-service-one和spi-service-two
<dependencies> <dependency> <groupId>com.jimoer.spi</groupId> <artifactId>spi-service-one</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>com.jimoer.spi</groupId> <artifactId>spi-service-two</artifactId> <version>1.0-SNAPSHOT</version> </dependency></dependencies>
创立测试类
/** * @author jimoer **/public class SpiService { public static void main(String[] args) { ServiceLoader<SpiInterfaceService> spiInterfaceServices = ServiceLoader.load(SpiInterfaceService.class); Iterator<SpiInterfaceService> iterator = spiInterfaceServices.iterator(); while (iterator.hasNext()){ SpiInterfaceService sip = iterator.next(); sip.printParameter("参数"); } }}
执行后果:
我是SpiTwoService:参数我是SpiOneService:参数
通过运行后果咱们能够看到,曾经将SpiInterfaceService接口的所有实现都加载到了以后我的项目中,并且执行了调用。
这整个代码构造咱们能够看出SPI机制将模块的拆卸放到了程序里面,就是说,接口的实现能够在程序里面,只须要在应用的时候指定具体的实现。并且动静的加载到本人的我的项目中。
SPI机制的次要目标:
一是为理解耦,将接口和具体实现拆散开来;
二是进步框架的扩展性。以前写程序的时候,接口和实现都写在一起,调用方在应用的时候依赖接口来进行调用,无权抉择应用具体的实现类。
SPI的实现
那么咱们来看一下SPI具体是如何实现的呢?
通过下面的例子,咱们能够看到,SPI机制的外围代码是上面这段:
ServiceLoader<SpiInterfaceService> spiInterfaceServices = ServiceLoader.load(SpiInterfaceService.class);
那么咱们来看一下ServiceLoader.load()办法的源码:
public static <S> ServiceLoader<S> load(Class<S> service) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl);}
看到Thread.currentThread().getContextClassLoader();我就明确是怎么回事了,这个就是线程上下文类加载器,因为线程上下文类加载器就是为了做类加载双亲委派模型的逆序而创立的。
应用这个线程上下文类加载器去加载所需的SPI服务代码,这是一种父类加载器去申请子类加载器实现类加载的行为,这种行为实际上是买通了,双亲委派模型的层次结构来逆向应用类加载器,曾经违反了双亲委派模型的一般性准则,但也是无可奈何的事件。
《深刻了解Java虚拟机(第三版)》
尽管晓得了它是毁坏双亲委派的了,然而具体实现,还是须要具体往下看的。
在ServiceLoader里找到具体实现hasNext()的办法了,那么持续来看这个办法的实现。
hasNext()办法又次要调用了hasNextService()办法。
// 固定门路private static final String PREFIX = "META-INF/services/";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()); } // 前面next()办法中判断以后类是否曾经呈现化的时候要用 nextName = pending.next(); return true; }
次要就是去加载META-INF/services/门路下的接口全限定名称的文件而后去外面找到实现类的类门路将实现类进行类加载。
持续看迭代器是如何取出每一个实现对象的。那就要看ServiceLoader中实现了迭代器的next()办法了。
next()办法次要是nextService()实现的,那么持续看nextService()办法。
private S nextService() { if (!hasNextService()) throw new NoSuchElementException(); String cn = nextName; nextName = null; Class<?> c = null; try { // 间接加载类,无需初始化(因为下面hasNext()曾经初始化了)。 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()将接口的实现类进行加载并判断是否存在接口的实现类,而后在next()办法中将实现类进实例化。
Java中应用SPI机制的性能其实有很多,像JDBC、JNDI、以及Spring中也有应用,甚至RPC框架(Dubbo)中也有应用SPI机制来实现性能。