SPI(Service Provider Interface),服务提供者接口,或称服务提供者模式,是一种被广泛使用的用于解耦的技术。常见的xml、jdbc、nio selector,甚至是dubbo都能看到它的身影。当然,我们知道SPI主要目的是解耦,但是它又是怎样去实现解耦这个目的的呢?
就拿nio的java.nio.channels.spi.SelectorProvider来说,java.nio.channels.spi.SelectorProvider本身是一个抽象类,它必然要有一个具体的类才能让程序跑起来。首先我们看到java.nio.channels.spi.SelectorProvider的一个叫provider的方法:
public static SelectorProvider provider() {
synchronized (lock) {
if (provider != null)
return provider;
return AccessController.doPrivileged(
new PrivilegedAction<SelectorProvider>() {
public SelectorProvider run() {
if (loadProviderFromProperty()) //(1)先从系统参数获取SelectorProvider的实现类
return provider;
if (loadProviderAsService()) //(2)找不到的话,尝试从META-INF/services/目录下查找对应的java.nio.channels.spi.SelectorProvider文件
return provider;
provider = sun.nio.ch.DefaultSelectorProvider.create();// (3)使用默认的实现sun.nio.ch.DefaultSelectorProvider
return provider;
}
});
}
}
windows下打开对应的sun.nio.ch.DefaultSelectorProvider源码:
public class DefaultSelectorProvider {
public static SelectorProvider create() {
return new WindowsSelectorProvider();
}
}
所以,很容易看出,SPI机制就是要想办法让接口定义和对应实现做的完全解耦,甚至可插拔化。不管使用的系统参数,还是META-INF/service/目录定义的方式,目的都是为了尽量避免在代码里面直接对着接口new出具体的实现类,这样也方便后续维护以及替换对应的实现类,做到对上层代码无感知。当然,java.nio.channels.spi.SelectorProvider这里组合了三种方式来实现SPI,其实还是稍微复杂的,大部分时候,其实我们只需单独使用第二种就可以了。
private static boolean loadProviderAsService() {
ServiceLoader<SelectorProvider> sl =
ServiceLoader.load(SelectorProvider.class,
ClassLoader.getSystemClassLoader());
Iterator<SelectorProvider> i = sl.iterator();
for (;;) {
try {
if (!i.hasNext())
return false;
provider = i.next();
return true;
} catch (ServiceConfigurationError sce) {
if (sce.getCause() instanceof SecurityException) {
// Ignore the security exception, try the next provider
continue;
}
throw sce;
}
}
}
这里可以看到,最最核心的一句代码是:
ServiceLoader<SelectorProvider> sl =
ServiceLoader.load(SelectorProvider.class,
ClassLoader.getSystemClassLoader());
没错,这里的ServiceLoader.load方法就是来帮助我们找到一个接口对应的实现类的一种机制。通过这种方式,我们也可以很方便地在我们的业务代码里面用上SPI。下面,我们也自己动手来试试:
首先,新建一个Java interface,这里我起名MyInterface,然后又一个action的方法:
public interface MyInterface {
void action();
}
然后,新建两个MyInterface的实现类,分别为MyInterface1和MyInterface2:
public class MyInterface1 implements MyInterface {
@Override
public void action() {
System.out.println("hehe1");
}
}
public class MyInterface2 implements MyInterface {
@Override
public void action() {
System.out.println("hehe2");
}
}
最后,编写一段测试代码:
public class TestSpi {
/**
* @param args
*/
public static void main(String[] args) {
ServiceLoader<MyInterface> load = ServiceLoader.load(MyInterface.class);
Iterator<MyInterface> iterator = load.iterator();
while (iterator.hasNext()) {
MyInterface myInterface = iterator.next();
myInterface.action();
}
}
}
直接运行,你会发现啥也没输出。正常的,因为我们还没给MyInterface的接口配上具体的实现类。配置方式是:在项目工程下新建一个META-INF/services/[interface全类目]的目录结构,其中[interface全类目]部分则替换成我们的接口全名,比如我自己的如下:
然后,这个cn.seedeed.test.spi.MyInterface的文件的内容修改为具体实现类的全类目,就好了。比如,我们修改为cn.seedeed.test.spi.MyInterface1,就表示具体实现类是cn.seedeed.test.spi.MyInterface1,运行就会输出hehe1。如果我们把内容修改为cn.seedeed.test.spi.MyInterface2,就表示接口的实现类是cn.seedeed.test.spi.MyInterface2,运行则会输出hehe2了。
这样一来,你就会发现,我们的测试代码没有给cn.seedeed.test.spi.MyInterface这个接口耦合任何具体类的信息,具体类完全是配置出来的,这样就达到了SPI接口与实现解耦的目的了。