spi 动态加载、卸载_理解 ServiceLoader类与SPI机制
對于Java中的Service類和SPI機(jī)制的透徹理解,也算是對Java類加載模型的掌握的不錯(cuò)的一個(gè)反映。
了解一個(gè)不太熟悉的類,那么從使用案例出發(fā),讀懂源代碼以及代碼內(nèi)部執(zhí)行邏輯是一個(gè)不錯(cuò)的學(xué)習(xí)方式。
一、使用案例
通常情況下,使用ServiceLoader來實(shí)現(xiàn)SPI機(jī)制。 SPI 全稱為 (Service Provider Interface) ,是JDK內(nèi)置的一種服務(wù)提供發(fā)現(xiàn)機(jī)制。SPI是一種動(dòng)態(tài)替換發(fā)現(xiàn)的機(jī)制, 比如有個(gè)接口,想運(yùn)行時(shí)動(dòng)態(tài)的給它添加實(shí)現(xiàn),你只需要添加一個(gè)實(shí)現(xiàn)。
SPI機(jī)制可以歸納為如下的圖:
起始這樣說起來還是比較抽象,那么下面舉一個(gè)具體的例子,案例為JDBC的調(diào)用例子:
案例如下:
JDBC中的接口即為:java.sql.Driver
SPI機(jī)制的實(shí)現(xiàn)核心類為:java.util.ServiceLoader
Provider則為:com.mysql.jdbc.Driver
外層調(diào)用則是我們進(jìn)行增刪改查JDBC操作所在的代碼塊,但是對于那些現(xiàn)在還沒有學(xué)過JDBC的小伙伴來說(不難學(xué)~),這可能會(huì)有點(diǎn)難理理解,所以我這里就舉一個(gè)使用案例:
按照上圖的SPI執(zhí)行邏輯,我們需要寫一個(gè)接口、至少一個(gè)接口的實(shí)現(xiàn)類、以及外層調(diào)用的測試類。
但是要求以這樣的目錄書結(jié)構(gòu)來定義項(xiàng)目文件,否則SPI機(jī)制無法實(shí)現(xiàn)(類加載機(jī)制相關(guān),之后會(huì)講):
E:.│ MyTest.java│├─com│ └─fisherman│ └─spi│ │ HelloInterface.java│ ││ └─impl│ HelloJava.java│ HelloWorld.java│└─META-INF └─services com.fisherman.spi.HelloInterface123456789101112131415其中:
└─services
com.fisherman.spi.HelloInterface 為配置文件,負(fù)責(zé)類加載過程中的路徑值。
首先給出接口的邏輯:
public interface HelloInterface { void sayHello();}123其次,兩個(gè)實(shí)現(xiàn)類的代碼:
public class HelloJava implements HelloInterface { @Override public void sayHello() { System.out.println("HelloJava."); }}123456public class HelloWorld implements HelloInterface { @Override public void sayHello() { System.out.println("HelloWorld."); }}123456然后,配置文件:com.fisherman.spi.HelloInterface
com.fisherman.spi.impl.HelloWorldcom.fisherman.spi.impl.HelloJava12最后測試文件:
public class MyTest26 { public static void main(String[] args) { ServiceLoader loaders = ServiceLoader.load(HelloInterface.class); for (HelloInterface in : loaders) { in.sayHello(); } }}12345678910111213測試文件運(yùn)行后的控制臺(tái)輸出:
HelloWorld.HelloJava.12我們從控制臺(tái)的打印信息可知我們成功地實(shí)現(xiàn)了SPI機(jī)制,通過 ServiceLoader 類實(shí)現(xiàn)了等待實(shí)現(xiàn)的接口和實(shí)現(xiàn)其接口的類之間的聯(lián)系。
下面我們來深入探討以下,SPI機(jī)制的內(nèi)部實(shí)現(xiàn)邏輯。
二、ServiceLoader類的內(nèi)部實(shí)現(xiàn)邏輯
Service類的構(gòu)造方法是私有的,所以我們只能通過掉用靜態(tài)方法的方式來返回一個(gè)ServiceLoader的實(shí)例:
方法的參數(shù)為被實(shí)現(xiàn)結(jié)構(gòu)的Class對象。
ServiceLoader loaders = ServiceLoader.load(HelloInterface.class); 1其內(nèi)部實(shí)現(xiàn)邏輯如所示,不妨按調(diào)用步驟來分步講述:
1.上述load方法的源代碼:
public static ServiceLoader load(Class service) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl);}1234完成的工作:
2.被調(diào)用的另一個(gè)load重載方法的源代碼:
public static ServiceLoader load(Class service, ClassLoader loader) { return new ServiceLoader<>(service, loader); }123456完成的工作:
- 調(diào)用了類ServiceLoader的私有構(gòu)造器
3.私有構(gòu)造器的源代碼:
private ServiceLoader(Class 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();}123456完成的工作:
SercviceLoader的初始化跑完如上代碼就結(jié)束了。但是實(shí)際上聯(lián)系待實(shí)現(xiàn)接口和實(shí)現(xiàn)接口的類之間的關(guān)系并不只是在構(gòu)造ServiceLoader類的過程中完成的,而是在迭代器的方法hasNext()中實(shí)現(xiàn)的。
這個(gè)聯(lián)系通過動(dòng)態(tài)調(diào)用的方式實(shí)現(xiàn),其代碼分析就見下一節(jié)吧:
三、動(dòng)態(tài)調(diào)用的實(shí)現(xiàn)
在使用案例中寫的forEach語句內(nèi)部邏輯就是迭代器,迭代器的重要方法就是hasNext():
ServiceLoader是一個(gè)實(shí)現(xiàn)了接口Iterable接口的類。
hasNext()方法的源代碼:
public boolean hasNext() { if (acc == null) { return hasNextService(); } else { PrivilegedAction action = new PrivilegedAction() { public Boolean run() { return hasNextService(); } }; return AccessController.doPrivileged(action, acc); }}12345678910拋出復(fù)雜的確保安全的操作,可以將上述代碼看作就是調(diào)用了方法:hasNextService.
hasNextService()方法的源代碼:
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;}123456789101112131415161718192021222324上述代碼中比較重要的代碼塊是:
String fullName = PREFIX + service.getName(); if (loader == null) configs = ClassLoader.getSystemResources(fullName);123此處PREFIX(前綴)是一個(gè)常量字符串(用于規(guī)定配置文件放置的目錄,使用相對路徑,說明其上層目錄為以項(xiàng)目名為名的文件夾):
private static final String PREFIX = "META-INF/services/";1那么fullName會(huì)被賦值為:"META-INF/services/com.fisherman.spi.HelloInterface"
然后調(diào)用方法getSystemResources或getResources將fullName參數(shù)視作為URL,返回配置文件的URL集合 。
pending = parse(service, configs.nextElement());1parse方法是憑借 參數(shù)1:接口的Class對象 和 參數(shù)2:配置文件的URL來解析配置文件,返回值是含有配置文件里面的內(nèi)容,也就是實(shí)現(xiàn)類的全名(包名+類名)字符串的迭代器;
最后調(diào)用下面的代碼,得到下面要加載的類的完成類路徑字符串,相對路徑。在使用案例中,此值就可以為:
com.fisherman.spi.impl.HelloWorld和com.fisherman.spi.impl.HelloJava
nextName = pending.next();1這僅僅是迭代器判斷是否還有下一個(gè)迭代元素的方法,而獲取每輪迭代元素的方法為:nextService()方法。
nextService()方法源碼:
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}123456789101112131415161718192021222324252627拋出一些負(fù)責(zé)安全以及處理異常的代碼,核心代碼為:
1.得到接口實(shí)現(xiàn)類的完整類路徑字符串:
String cn = nextName;12使用loader引用的類加載器來加載cn指向的接口實(shí)現(xiàn)類,并返回其Class對象(但是不初始化此類):
c = Class.forName(cn, false, loader);13.調(diào)用Class對象的newInstance()方法來調(diào)用無參構(gòu)造方法,返回Provider實(shí)例:
S p = service.cast(c.newInstance());1//cast方法只是在null和類型檢測通過的情況下進(jìn)行了簡單的強(qiáng)制類型轉(zhuǎn)換public T cast(Object obj) { if (obj != null && !isInstance(obj)) throw new ClassCastException(cannotCastMsg(obj)); return (T) obj;}1234564.將Provider實(shí)例放置于providers指向的HashMap中:
providers.put(cn, p);15.返回provider實(shí)例:
return p;1ServiceLoader類的小總結(jié):
四、總結(jié)與評(píng)價(jià)
總結(jié)
以上是生活随笔為你收集整理的spi 动态加载、卸载_理解 ServiceLoader类与SPI机制的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql锁表更新_Mysql Inno
- 下一篇: java springmvc搭建_【Ja