Java--SPI機(jī)制
SPI全稱為Service Provider Interface,是JDK內(nèi)置的一種服務(wù)提供發(fā)現(xiàn)機(jī)制。簡單來說,它就是一種動態(tài)替換發(fā)現(xiàn)機(jī)制。例如:有個接口想在運(yùn)行時才發(fā)現(xiàn)具體的實(shí)現(xiàn)類,那么你只需要在程序運(yùn)行前添加一個實(shí)現(xiàn)即可,并把新加的實(shí)現(xiàn)描述給JDK即可。此外,在程序的運(yùn)行過程中,也可以隨時對該描述進(jìn)行修改,完成具體實(shí)現(xiàn)的替換。
Java提供了很多服務(wù)提供者接口(Service Provider Interface,SPI),允許第三方為這些接口提供實(shí)現(xiàn)。常見的SPI有JDBC、JCE、JNDI、JAXP和JBI等。
這些SPI的接口是由Java核心庫來提供,而SPI的實(shí)現(xiàn)則是作為Java應(yīng)用所依賴的jar包被包含進(jìn)類路徑(CLASSPATH)中。例如:JDBC的實(shí)現(xiàn)mysql就是通過maven被依賴進(jìn)來。
那么問題來了,SPI的接口是Java核心庫的一部分,是由引導(dǎo)類加載器(Bootstrap Classloader)來加載的。SPI的實(shí)現(xiàn)類是由系統(tǒng)類加載器(System ClassLoader)來加載的。
引導(dǎo)類加載器在加載時是無法找到SPI的實(shí)現(xiàn)類的,因?yàn)殡p親委派模型中規(guī)定,引導(dǎo)類加載器BootstrapClassloader無法委派系統(tǒng)類加載器AppClassLoader來加載。這時候,該如何解決此問題?
線程上下文類加載由此誕生,它的出現(xiàn)也破壞了類加載器的雙親委派模型,使得程序可以進(jìn)行逆向類加載。
下面,我們就用具體的代碼來說明逆向類加載。不過,在距離之前,還是想對spi的使用進(jìn)行一個簡單的說明。
spi使用
首先,通過一張圖來看,完成spi的實(shí)現(xiàn),需要哪些操作,需要遵循哪些規(guī)范?
1.代碼編寫
既然是spi,那么就必須先定義好接口。其次,就是定義好接口的實(shí)現(xiàn)類。
2.創(chuàng)建一個文件夾
在項(xiàng)目的\src\main\resources\下創(chuàng)建\META-INF
\services目錄(筆者在網(wǎng)上找了很多文章,很多都沒有告知具體這個文件夾放在哪,放在其他位置下無法加載得到)
3.文件夾下增加配置文件
在上面META-INF
\services的目錄下再增加一個配置文件,這個文件必須以接口的全限定類名保持一致,例如:com.jiaboyan.test.HelloService
4.配置文件增加描述
上面介紹spi時說道,除了代碼上的接口實(shí)現(xiàn)之外,你還需要把該實(shí)現(xiàn)的描述提供給JDK。那么,此步驟就是在配置文件中撰寫接口實(shí)現(xiàn)描述。很簡單,就是在配置文件中寫入具體實(shí)現(xiàn)類的全限定類名,如有多個便換行寫入。
5.使用JDK來載入
編寫main()方法,輸出測試接口。使用JDK提供的ServiceLoader.load()來加載配置文件中的描述信息,完成類加載操作。
接口定義:
public interface HelloService {
void hello();
}
接口實(shí)現(xiàn):
public class HelloService1Impl implements HelloService {
@Override
public void hello() {
System.out.println("hello jiaboyan");
}
}
public class HelloService2Impl implements HelloService {
@Override
public void hello() {
System.out.println("hello world");
}
}
添加JDK描述,在META-INF\services目錄下:
com.jiaboyan.test.impl.HelloService1Impl
com.jiaboyan.test.impl.HelloService2Impl
編寫main()方法:
public class Test {
public static void main(String[] agrs) {
ServiceLoader<HelloService> loaders = ServiceLoader.load(HelloService.class);
for (HelloService helloService : loaders) {
helloService.hello();
}
}
}