ServiceLoader詳解


系統中用到了ServiceLoader,查了一下:

ServiceLoader與ClassLoader是Java中2個即相互區別又相互聯系的加載器.JVM利用ClassLoader將類載入內存,這是一個類聲明周期的第一步(一個java類的完整的生命周期會經歷加載、連接、初始化、使用、和卸載五個階段,當然也有在加載或者連接之后沒有被初始化就直接被使用的情況)。詳情請參閱:詳解Java類的生命周期

那ServiceLoader又是什么呢?ServiceLoader:一個簡單的服務提供者加載設施。服務 是一個熟知的接口和類(通常為抽象類)集合。服務提供者 是服務的特定實現。提供者中的類通常實現接口,並子類化在服務本身中定義的子類。服務提供者可以以擴展的形式安裝在 Java 平台的實現中,也就是將 jar 文件放入任意常用的擴展目錄中。也可通過將提供者加入應用程序類路徑,或者通過其他某些特定於平台的方式使其可用。……唯一強制要求的是,提供者類必須具有不帶參數的構造方法,以便它們可以在加載中被實例化。

通過在資源目錄META-INF/services中放置提供者配置文件 來標識服務提供者。文件名稱是服務類型的完全限定二進制名稱。該文件包含一個具體提供者類的完全限定二進制名稱列表,每行一個。忽略各名稱周圍的空格、制表符和空行。注釋字符為'#'('\u0023', NUMBER SIGN);忽略每行第一個注釋字符后面的所有字符。文件必須使用 UTF-8 編碼。 

以延遲方式查找和實例化提供者,也就是說根據需要進行。服務加載器維護到目前為止已經加載的提供者緩存。每次調用 iterator 方法返回一個迭代器,它首先按照實例化順序生成緩存的所有元素,然后以延遲方式查找和實例化所有剩余的提供者,依次將每個提供者添加到緩存。可以通過 reload 方法清除緩存。

……

以上來源於Java API里的說明,也許說的很專業,讓我們有點暈頭轉向,我們可以簡單的認為:ServiceLoader也像ClassLoader一樣,能裝載類文件,但是使用時有區別,具體區別如下:(1) ServiceLoader裝載的是一系列有某種共同特征的實現類,而ClassLoader是個萬能加載器;(2)ServiceLoader裝載時需要特殊的配置,使用時也與ClassLoader有所區別;(3)ServiceLoader還實現了Iterator接口。[如有錯誤或不到的地方敬請指出,互相學習:)]

下面是關於ServiceLoader的簡單的例子,僅供參考

(1)基礎服務:IService

1
2
3
4
5
package com.service;
public interface IService {
     String sayHello();
     String getScheme();
}

 

(2)具體服務實現1:HDFSService

1
2
3
4
5
6
7
8
9
10
11
12
package com.impl;
import com.service.IService;
public class HDFSService implements IService {
     @Override
     public String sayHello() {
         return "Hello HDFSService" ;
     }
     @Override
     public String getScheme() {
         return "hdfs" ;
     }
}

 

(3)具體服務實現2:LocalService

 

1
2
3
4
5
6
7
8
9
10
11
12
package com.impl;
import com.service.IService;
public class LocalService  implements IService {
     @Override
     public String sayHello() {
         return "Hello LocalService" ;
     }
     @Override
     public String getScheme() {
         return "local" ;
     }
}
(4)配置:META-INF/services/com.service.IService

 

 

1
2
com.impl.HDFSService
com.impl.LocalService
(5)測試類
1
2
3
4
5
6
7
8
9
10
11
package com.test;
import java.util.ServiceLoader;
import com.service.IService;
public class Test {
     public static void main(String[] args) {
         ServiceLoader<IService> serviceLoader  = ServiceLoader.load(IService. class );
         for (IService service : serviceLoader) {
             System.out.println(service.getScheme()+ "=" +service.sayHello());
         }
     }
}

 

結果:

hdfs=Hello HDFSService
local=Hello LocalService

可以看到ServiceLoader可以根據IService把定義的兩個實現類找出來,返回一個ServiceLoader的實現,而ServiceLoader實現了Iterable接口,所以可以通過ServiceLoader來遍歷所有在配置文件中定義的類的實例。

 

ServiceLoader的應用

(1)Hadoop FileSystem

Hadoop FileSystem就是通過這個機制來根據不同文件的scheme來返回不同的FileSystem。

1
2
3
4
5
6
7
8
9
10
11
private static void loadFileSystems() { 
   synchronized (FileSystem. class ){ 
     if (!FILE_SYSTEMS_LOADED) { 
       ServiceLoader<FileSystem> serviceLoader = ServiceLoader.load(FileSystem. class ); 
       for (FileSystem fs : serviceLoader) { 
         SERVICE_FILE_SYSTEMS.put(fs.getScheme(),fs.getClass()); 
      
       FILE_SYSTEMS_LOADED= true
    
  
}
對應的配置文件:

 

1
2
3
4
5
6
7
org.apache.hadoop.fs.LocalFileSystem 
org.apache.hadoop.fs.viewfs.ViewFileSystem 
org.apache.hadoop.fs.s3.S3FileSystem 
org.apache.hadoop.fs.s3native.NativeS3FileSystem 
org.apache.hadoop.fs.kfs.KosmosFileSystem 
org.apache.hadoop.fs.ftp.FTPFileSystem 
org.apache.hadoop.fs.HarFileSystem
通過之前的測試類輸出對應的scheme和class如下: 
file=class org.apache.hadoop.fs.LocalFileSystem   
viewfs=class org.apache.hadoop.fs.viewfs.ViewFileSystem   
s3=class org.apache.hadoop.fs.s3.S3FileSystem   
s3n=class org.apache.hadoop.fs.s3native.NativeS3FileSystem   
kfs=class org.apache.hadoop.fs.kfs.KosmosFileSystem   
ftp=class org.apache.hadoop.fs.ftp.FTPFileSystem   
har=class org.apache.hadoop.fs.HarFileSystem   
hdfs=class org.apache.hadoop.hdfs.DistributedFileSystem   
hftp=class org.apache.hadoop.hdfs.HftpFileSystem   
hsftp=class org.apache.hadoop.hdfs.HsftpFileSystem   
webhdfs=class org.apache.hadoop.hdfs.web.WebHdfsFileSystem  

可以看到FileSystem會把所有的FileSystem的實現都以scheme和class來cache,之后就從這個cache中取相應的值。因此,以后可以通過ServiceLoader來實現一些類似的功能,而不用依賴像Spring這樣的第三方框架。

(2)責任鏈模式

責任鏈模式的定義:使多個對象都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關系。將這些對象連成一條鏈,並沿着這條鏈傳遞該請求,直到有一個對象處理它為止。

責任連模式可以使用ServiceLoader實現具體服務對象的迭代加載並處理,為了確保此模式的靈活性,建議判斷邏輯通過配置文件或數據庫的方式,具體實現方式見 參考鏈接(2) 消滅成堆的……

 

一、使用場景
一般使用接口的實現類都是靜態new一個實現類賦值給接口引用,如下:

HelloService service = new HelloImpl();

如果需要動態的獲取一個接口的實現類呢?全局掃描全部的Class,然后判斷是否實現了某個接口?代價太大,一般不會這么做。一種合適的方式就是使用配置文件,把實現類名配置在某個地方,然后讀取這個配置文件,獲取實現類名。JDK給我們提供的TestServiceLoader 就是這種方式。

二、使用方式
在實現類的jar包的META-INF下新建一個文件夾services,並在services下新建一個文件,以接口的全限定名為文件名,內容為實現類的全限定名。

通過以下的例子來分析實現原理.

1. 新建一個接口,2個實現類。

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...");
}
}
2. 分別把接口、2個實現類打成3個jar包,放在D盤下。

 

3. 在Dog.jar、Sheep.jar分別加上META-INF下新建一個文件夾services,並在services下新建一個文件,以接口的全限定名為文件名,內容為實現類的全限定名。如下:

 

4. 使用指定的ClassLoader不包含實現類

public static void notInTheClassLoader() throws MalformedURLException {
ClassLoader serviceCL = new URLClassLoader(new URL[] { new URL("file:" + "D:/HelloService.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();
}
}
結果不會打印任何信息。

5. 指定的ClassLoader包含實現類

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();
}
}
結果如下:

bark bark bark...

bleat bleat bleat...
6. 使用指定的ClassLoader加載接口類,不指定ClassLoader加載實現類。

public static void defaultClassLoader() throws MalformedURLException, ClassNotFoundException {
ClassLoader serviceCL = new URLClassLoader(new URL[] { new URL("file:" + "D:/HelloService.jar"),
new URL("file:" + "D:/Dog.jar"), new URL("file:" + "D:/Sheep.jar") },
TestServiceLoader.class.getClassLoader().getParent());

/* 默認會使用 ClassLoader.getSystemClassLoader() */
ServiceLoader<HelloService> helloServices = ServiceLoader
.load(((Class<HelloService>) (serviceCL.loadClass("com.test.loader.HelloService"))));

Iterator<HelloService> it = helloServices.iterator();
while (it.hasNext()) {
HelloService service = it.next();
service.sayHello();
}
}
結果不打印任何信息。

7. 把2個實現jar加到工程的Build Path里面,不指定ClassLoader。

 

public static void notSpecifyClassLoader() {
ServiceLoader<HelloService> helloServices = ServiceLoader.load(HelloService.class);

Iterator<HelloService> it = helloServices.iterator();
while (it.hasNext()) {
HelloService service = it.next();
service.sayHello();
}
}
結果如下:

bark bark bark...

bleat bleat bleat...
三、簡單解析ServiceLoader
1. 構造函數,如果不指定ClassLoader或者指定的為null,則使用ClassLoader.getSystemClassLoader() ,即AppClassLoader。

private ServiceLoader(Class<S> 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();

}
2. 遍歷有兩個方法

hasNext會調用hasNextService,如果指定的ClassLoader為空(一般不會為空,構造函數會初始化),則調用ClassLoader.getSystemClassLoader().getResources,否則調用指定的ClassLoader的getResources方法,獲取META-INF/services/接口的全限定名稱,如META-INF/services/com.test.loader.HelloService,從這個文件中找出實現類全限定名稱。

next會調用nextService,根據hasNextService獲取的實現類信息,使用指定的ClassLoader進行加載和實例化。 

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;

}



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

}
注:以上代碼基於JDK1.8.0_144。

 

參考:Java ServiceLoader使用和解析

 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM