b java 之 serviceLoader詳解 & serviceLoader.load(XXX.class)

—> go to 總目錄

一、JNDI與ServiceLoder

JNDI更復。ServiceLoder是個簡單實現,更常用。

1.1 JNDI

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();
    拿到實例,並使用

三、源碼解析

基於openjdk14

在這裏插入圖片描述

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

四、引用

JNDI介紹

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章