文章目錄
一、JNDI與ServiceLoder
JNDI更復。ServiceLoder是個簡單實現,更常用。
1.1 JNDI
提供可插拔式的服務—能夠掃描目錄或者遠程類的資源,加載到內存中,並執行。
API
Application Programming Interface
應用程序能夠能夠使用的接口,用來操作第三方服務SPI
Service Provider Interface
服務提供商,按照一定規範實現服務,然後被解析加載。
一般是分爲服務實現
+配置信息
。首先掃描配置信息,然後加載服務實現
,然後被使用
1.2 ServiceLoder
位置
openjdk14
module | package | class |
---|---|---|
java.base | java.util | serviceLoder |
基於SPI思想 jdk自帶加載類的方式。分爲
服務實現
+配置信息
,通過掃描配置信息
加載服務實現
。用途
用於想不使用依賴,在代碼中僅僅使用服務功能的API
,而靠讀取目錄下的jar包信息,加載業務實現
二、ServiceLoder的SPI
2.1 SPI的基本流程
服務的接口約定
比如解碼器,加載一個PNG圖片,依賴PNG的解碼器。但是開發時僅僅需要定義接口 getEncoder和getDecoder即可。
package com.example;
public interface CodecFactory {
Encoder getEncoder(String encodingName);
Decoder getDecoder(String encodingName);
}
服務實現
PNG解碼器真正的實現,實現CodecFactory
,完成對PNG的處理邏輯服務註冊
往往需要掃描配置文件
,來獲得實現類的信息。所以依賴一個
配置文件com.corn.javalib.PNGCodec
中配置
PNG=com.corn.PNGCodec
服務發現與使用
依賴Service的lode函數,通過掃描配置在指定路徑下的配置文件
,加載解碼器,實現實現注入
。
ServiceLoader<CodecFactory> loader = ServiceLoader.load(CodecFactory.class);
for (CodecFactory factory : loader) {
Encoder enc = factory.getEncoder("PNG");
if (enc != null)
... use enc to encode a PNG file
break;
}
2.2 使用示例
便於理解概念
多服務的示例
三個類做成了三個包,HelloService是接口,其他是實現
package com.test.loader;
public interface HelloService {
public void sayHello();
}
package com.test.loader;
public class Dog implements HelloService {
@Override
public void sayHello() {
System.out.println("bark bark bark...");
}
}
package com.test.loader;
public class Sheep implements HelloService {
@Override
public void sayHello() {
System.out.println("bleat bleat bleat...");
}
}
使用形如
public static void inTheClassLoader() throws MalformedURLException {
ClassLoader serviceCL = new URLClassLoader(
new URL[] { new URL("file:" + "D:/Dog.jar"), new URL("file:" + "D:/Sheep.jar") },
TestServiceLoader.class.getClassLoader().getParent());
/* 實現類在指定的ClassLoader,所以可以掃描META-INF/services/com.test.loader.HelloService */
ServiceLoader<HelloService> helloServices = ServiceLoader.load(HelloService.class, serviceCL);
Iterator<HelloService> it = helloServices.iterator();
while (it.hasNext()) {
HelloService service = it.next();
service.sayHello();
}
}
- ServiceLoader helloServices = ServiceLoader.load(HelloService.class, serviceCL);
找到給定classloader下serviceCL
,並實例化。 - HelloService service = it.next();
拿到實例,並使用
三、源碼解析
3.1 Iterable 接口
示例中我們使用到迭代器HelloService service = it.next()
Iterator<HelloService> it = helloServices.iterator();
while (it.hasNext()) {
HelloService service = it.next();
service.sayHello();
}
}
加載後,所有搜索到的目標接口都在ServiceLoader<HelloService>
中,使用迭代器遍歷,而且it.next()
會觸發實例的創建。
3.2 內部類provider
作爲實現類定義的載體
僅僅具備兩個方法,它的實現providerImpl
中的S get()
方法會被it.next();
調用到,並創建實例。
3.3 核心方法Load
首先ServiceLoad.load
會創建ServiceLoad
對象一個對象,最終被開發者用來得到目標實例。
有兩種serviceload主要是classloader的區別,要知道classloader決定了搜索路徑。
3.3.1 static ServiceLoader load(Class service, ClassLoader loader, Module callerModule)
- 參數
Class<S> service
形如ServiceLoader.load(HelloService.class, serviceCL)
,就是代表了被搜索的類 - 參數
Classloader
這load函數支持,傳入一個classloader,這個是用戶自定義的,應該包含需要的類。 - Module
jdk9的概念,被package更高一層的隔離
3.3.2 static ServiceLoader load(Class service)
源碼
@CallerSensitive
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return new ServiceLoader<>(Reflection.getCallerClass(), service, cl);
}
- @CallerSensitive
當檢查權限時Reflection.getCallerClass()
會跳過ServiceLoader
類,直接獲取原始調用者的對象,進行權限判斷。
比如線程1--->反射1--->反射2
,當反射2
檢查權限時,會檢查到反射1
,而不是真正的線程1
的調用權限。
這個是解決黑客利用雙反射
來繞過權限,獲得資源。所以後面JVM對標註@CallerSensitive
的類會跳過,來到沒有這個註解類的位置,來獲取真正的權限。 Thread.currentThread().getContextClassLoader
沒有loader傳入,就會返回線程的ContextClassLoader
,每當創建一個線程時,就會繼承父線程classLoader,創建一個ContextClassLoader
,需要被顯示的調用,否則永遠不會被使用。main線程
的getContextClassLoader
就是SystemClassloader