SPI机制


一、什么是SPI

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

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

1、小例子

首先,我们需要定义一个接口,SPIService

package com.viewscenes.netsupervisor.spi; public interface SPIService { void execute(); } 

然后,定义两个实现类,没别的意思,只输入一句话。

package com.viewscenes.netsupervisor.spi; public class SpiImpl1 implements SPIService{ public void execute() { System.out.println("SpiImpl1.execute()"); } } ----------------------我是乖巧的分割线---------------------- package com.viewscenes.netsupervisor.spi; public class SpiImpl2 implements SPIService{ public void execute() { System.out.println("SpiImpl2.execute()"); } } 

最后呢,要在ClassPath路径下配置添加一个文件。文件名字是接口的全限定类名,内容是实现类的全限定类名,多个实现类用换行符分隔。
文件路径如下:


 
SPI配置文件位置

内容就是实现类的全限定类名:

com.viewscenes.netsupervisor.spi.SpiImpl1
com.viewscenes.netsupervisor.spi.SpiImpl2

2、测试

然后我们就可以通过ServiceLoader.load或者Service.providers方法拿到实现类的实例。其中,Service.providers包位于sun.misc.Service,而ServiceLoader.load包位于java.util.ServiceLoader

public class Test { public static void main(String[] args) { Iterator<SPIService> providers = Service.providers(SPIService.class); ServiceLoader<SPIService> load = ServiceLoader.load(SPIService.class); while(providers.hasNext()) { SPIService ser = providers.next(); ser.execute(); } System.out.println("--------------------------------"); Iterator<SPIService> iterator = load.iterator(); while(iterator.hasNext()) { SPIService ser = iterator.next(); ser.execute(); } } } 

两种方式的输出结果是一致的:

SpiImpl1.execute() SpiImpl2.execute() -------------------------------- SpiImpl1.execute() SpiImpl2.execute() 

二、源码分析

我们看到一个位于sun.misc包,一个位于java.util包,sun包下的源码看不到。我们就以ServiceLoader.load为例,通过源码看看它里面到底怎么做的。

1、ServiceLoader

首先,我们先来了解下ServiceLoader,看看它的类结构。

public final class ServiceLoader<S> implements Iterable<S> //配置文件的路径 private static final String PREFIX = "META-INF/services/"; //加载的服务类或接口 private final Class<S> service; //已加载的服务类集合 private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); //类加载器 private final ClassLoader loader; //内部类,真正加载服务类 private LazyIterator lookupIterator; } 

2、Load

load方法创建了一些属性,重要的是实例化了内部类,LazyIterator。最后返回ServiceLoader的实例。

public final class ServiceLoader<S> implements Iterable<S> 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; //先清空 providers.clear(); //实例化内部类 LazyIterator lookupIterator = new LazyIterator(service, loader); } } 

3、查找实现类

查找实现类和创建实现类的过程,都在LazyIterator完成。当我们调用iterator.hasNext和iterator.next方法的时候,实际上调用的都是LazyIterator的相应方法。

public Iterator<S> iterator() { return new Iterator<S>() { public boolean hasNext() { return lookupIterator.hasNext(); } public S next() { return lookupIterator.next(); } ....... }; } 

所以,我们重点关注lookupIterator.hasNext()方法,它最终会调用到hasNextService。

private class LazyIterator implements Iterator<S>{ Class<S> service; ClassLoader loader; Enumeration<URL> configs = null; Iterator<String> pending = null; String nextName = null; private boolean hasNextService() { //第二次调用的时候,已经解析完成了,直接返回 if (nextName != null) { return true; } if (configs == null) { //META-INF/services/ 加上接口的全限定类名,就是文件服务类的文件 //META-INF/services/com.viewscenes.netsupervisor.spi.SPIService String fullName = PREFIX + service.getName(); //将文件路径转成URL对象 configs = loader.getResources(fullName); } while ((pending == null) || !pending.hasNext()) { //解析URL文件对象,读取内容,最后返回 pending = parse(service, configs.nextElement()); } //拿到第一个实现类的类名 nextName = pending.next(); return true; } } 

4、创建实例

当然,调用next方法的时候,实际调用到的是,lookupIterator.nextService。它通过反射的方式,创建实现类的实例并返回。

private class LazyIterator implements Iterator<S>{ private S nextService() { //全限定类名 String cn = nextName; nextName = null; //创建类的Class对象 Class<?> c = Class.forName(cn, false, loader); //通过newInstance实例化 S p = service.cast(c.newInstance()); //放入集合,返回实例 providers.put(cn, p); return p; } }

看到这儿,我想已经很清楚了。获取到类的实例,我们自然就可以对它为所欲为了!

原文链接:https://www.jianshu.com/p/3a3edbcd8f24


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM