SPI的全稱是Service Provider Interface,它是為了實(shí)現(xiàn)在模塊裝配的時候能不在程序里動態(tài)指明,這就需要一種服務(wù)發(fā)現(xiàn)機(jī)制。 JAVA SPI就是提供這樣的一個機(jī)制,為某個接口尋找服務(wù)實(shí)現(xiàn)的機(jī)制。
我們舉一個小例子加以說明
首先我有一個接口IHello.java
public interface IHello {
void sayHello();
}
有兩個接口的實(shí)現(xiàn)類HelloImpl1,HelloImpl2
public class HelloImpl1 implements IHello {
@Override
public void sayHello() {
System.out.println("我是Impl1");
}
}
public class HelloImpl2 implements IHello {
@Override
public void sayHello() {
System.out.println("我是Impl2");
}
}
在MATE-INF下面的services文件夾中有一個以接口的全限定名命名的文件,里面的內(nèi)容就是實(shí)現(xiàn)類的全限定名。
使用時使用幫助類ServiceLoader,下面是我們具體的使用方法
public static void main(String[] args){
ServiceLoader<IHello> s = ServiceLoader.load(IHello.class);
Iterator<IHello> iHelloIterator = s.iterator();
while (iHelloIterator.hasNext()) {
IHello iHello = iHelloIterator.next();
iHello.sayHello();
}
}
我們大體解釋一下,搜索IHello類型的實(shí)例,之后遍歷執(zhí)行,我們并沒有指明是具體的哪個IHello的實(shí)現(xiàn)類進(jìn)行操作,那么是誰指定的呢?就是META-INF/services 下面的那個文件內(nèi)容決定的,如果內(nèi)容是:
com.test.demo.impl.HelloImpl2
那么就由Impl2執(zhí)行,如果是
com.test.demo.impl.HelloImpl1
com.test.demo.impl.HelloImpl2
就是兩個都執(zhí)行。在我們不需要改動代碼的情況下可以動態(tài)的切換執(zhí)行的實(shí)現(xiàn)類。
ServiceLoader是怎樣執(zhí)行的呢?
首先創(chuàng)建這個類型的ServiceLoader
public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
當(dāng)需要的屬性都準(zhǔn)備完畢,開始reload
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;
reload();
}
關(guān)注這個LazyIterator的操作,查詢實(shí)現(xiàn)類都是通過這個類進(jìn)行的。
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
這個方法是LazyIterator判斷有沒有下一個服務(wù)的方法,我們可以先看一下
這個fullName就是前綴(META-INF/services/)加上要搜索服務(wù)的name組成,所以我們的文件的路徑及名稱都已經(jīng)確定了,只能這么寫,否則搜索不到,我們再來看一下parse方法,就是解析文件中的內(nèi)容放到Iterator中
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;
}
下面是獲取service的過程,經(jīng)過上一步,基本上都解析出來實(shí)現(xiàn)類的名稱了,所以要通過Class.forName反射加載這個類的Class對象,然后實(shí)例化(newInstance),最后返回。
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
}
上面就是這個ServiceLoader執(zhí)行的流程。這個SPI大多數(shù)開發(fā)人員可能不熟悉,因?yàn)檫@個是針對廠商或者插件的,現(xiàn)在很多軟件都已采用或者擴(kuò)展了這種。比如說dubbo,jdbc等,還是有必要了解一下的。