概述
Java SPI機制指的是java來定義接口,然後由不同的廠商去實作這個接口,比如資料庫的驅動程式就是由不同廠商實作的,MySQL的驅動和Oracle的驅動是不同的,但是它們都實作了java.sql.Driver接口。下面就來探索程式中如何去擷取這個具體實作以及類加載器的雙親委派機制在其中的作用。
Java SPI示例
1、實作服務
假設
java.lang.Runnable
是一個服務,需要各個廠商去實作這個服務,這裡有廠商A和廠商B,廠商A的實作如下
package cn.cjc.hh;
/**
* @author debo
* @date 2020-01-28
*/
public class HanHongSpiService implements Runnable {
@Override
public void run() {
System.out.println("韓紅開始唱歌了...");
}
}
廠商A在實作這個接口後,将這個實作類打成jar包,同時在jar包中還有一個檔案夾,名字為
META-INF/services
,裡面有個檔案,名字為
java.lang.Runnable
,該檔案中隻有一行内容,是cn.cjc.hh.HanHongSpiService,如圖所示:
廠商B的實作如下
package cn.cjc.sn;
/**
* @author debo
* @date 2020-01-28
*/
public class SunNanSpiService implements Runnable {
@Override
public void run() {
System.out.println("孫楠開始唱歌了...");
}
}
廠商B在實作這個接口後,将這個實作類打成jar包,同時在jar包中還有一個檔案夾,名字為
META-INF/services
,裡面有個檔案,名字為
java.lang.Runnable
,該檔案中隻有一行内容,是cn.cjc.sn.SunNanSpiService。
2、使用服務
使用服務的代碼如下:
import java.util.Iterator;
import java.util.ServiceLoader;
/**
* @author debo
* @date 2020-01-28
*/
public class SpiTest {
public static void main(String[] args) {
ServiceLoader<Runnable> services = ServiceLoader.load(Runnable.class);
Iterator<Runnable> iterator = services.iterator();
while (iterator.hasNext()) {
Runnable service = iterator.next();
System.out.println(service.getClass());
System.out.println(service.getClass().getClassLoader());
service.run();
}
}
}
輸出如下:
class cn.cjc.hh.HanHongSpiService
[email protected]
韓紅開始唱歌了...
class cn.cjc.sn.SunNanSpiService
[email protected]
孫楠開始唱歌了...
3、ClassLoader 雙親委派機制的作用
由輸出結果可知,接口的實作類都由 AppClassLoader 加載的,而接口 Runnable 肯定由根類加載器加載,那麼接口和實作類由不同的類加載器加載是怎麼做到的呢?答案在 ServiceLoader.load() 方法中,此方法的源碼如下
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
可以知道,加載實作類的類加載器用的是目前線程上綁定的類加載器,如果你沒有給目前線程設定自定義的類加載器,那麼類加載器預設将會是 AppClassLoader, AppClassLoader 先委托它的上級,也就是ExtClassLoader來加載接口實作類,ExtClassLoader 會委托它的上級,也就是根類加載器來做這個事。無論是 ExtClassLoader 還是根類加載器都無法找到接口的實作類,是以最終接口的實作類由 AppClassLoader 加載。