1.SPI的概念
瞭解ServiceLoader,需要先了解 SPI(Service Provider Interface)
SPI的簡單來說就是在程式設計時將一個功能服務的介面與實現分離,在程式執行時通過JVM機制自動找到服務介面的實現類並建立,以達到解耦的目的,提高程式的可擴充性; 比如JDBC
2.ServiceLoader
ServiceLoader就是 Java平臺提供的一個簡單的 Service Provder Framework。使用ServiceLoader有簡單的以下幾個步驟
- 建立服務介面
- 在服務介面的實現模組中,建立一個實現類實現對應的服務介面,並通過在專案的resource/META-INF/services資料夾下面建立一個對應該服務介面全限定名的文字檔案,在該文字檔案寫入該服務介面實現類的全限定名,以此達到一個註冊服務的作用(專案打包後在jar檔案裡也得存在該檔案)
- 服務呼叫方(需求方)通過ServiceLoader類的load方法載入服務並得到服務的實現類
2.1 一個簡單ServiceLoader場景例項
這裡以一個簡單虛擬支付場景為例。
有一個業務模組目前需要使用支付服務,所以我們首先建立了一個PaymenService抽象介面表示 支付服務,介面類中有一個抽象方法**pay(String productName,double price)**表示支付某個商品
###建立服務實現模組
package com.knight.serviceimpl;
import com.knight.PaymentService;
public class PaymentServiceImpl implements PaymentService {
@Override
public void pay(String productName, double price) {
System.out.println("支付模組:購買產品 "+productName +",價格"+price);
}
}
複製程式碼
在IDEA中的結構如下
建立服務介面類
通過ServiceLoader獲取服務
業務模組中直接通過ServiceLoader類及PaymentService介面獲取服務例項並實現業務邏輯(業務模組一般是不包含服務的實現模組的)
package com.knight.business;
import com.knight.PaymentService;
import java.util.Iterator;
import java.util.ServiceLoader;
public class BusinessModule {
public static void run(){
Iterator<PaymentService> serviceIterator = ServiceLoader.load(PaymentService.class).iterator();
if (serviceIterator.hasNext()){
PaymentService paymentService = serviceIterator.next();
paymentService.pay("Q幣充值",100.00);
}else {
System.out.println("未找到支付模組");
}
}
}
複製程式碼
以上的核心程式碼是 通過 ServiceLoader的load方法,傳入PaymentService介面類,會返回一個ServiceLoader的例項物件,通過該物件的**iterator()**方法會返回一個 Iterator 的迭代器,可以通過這個迭代器得到所有PaymentService的實現物件。
最後 我們再建立一個app模組執行業務程式碼邏輯,app模組包含service、service-impl、business、3個模組。
以上所有程式碼已上傳 git
ServiceLoader 核心原始碼簡單解析
ServiceLoader內部細節
1.首先通過靜態方法load獲取對應服務介面的ServiceLoader例項;
2.ServiceLoader類繼承了Iterabale介面,內部實現了一個服務例項懶載入的迭代器;迭代器內部通過classLoader讀 取對應META-INF/service/資料夾下的服務配置檔案獲取到所有的實現類類名稱,當通過iterator()方法獲取迭代器後,就可以依次例項化service的實現並將實現物件加入到快取中。
3.解析配置檔案的過程就是按行讀取,每一行的文字都是一個服務實現類的全限定名,獲取到類名就可以通過反射例項化物件了
public final class ServiceLoader<S>
implements Iterable<S>
{
//Service配置檔案的資源路徑
private static final String PREFIX = "META-INF/services/";
// The class or interface representing the service being loaded
private final Class<S> service;
// 負責service配置資源載入,例項化service
private final ClassLoader loader;
// 服務例項的快取,已被迭代被建立過的會加到這個cache中
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
reload();
}
//過載load, 刪除快取並重新例項化iterator
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
//懶載入的service迭代器
private class LazyIterator
implements Iterator<S>
{
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
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
}
public boolean hasNext() {
return hasNextService();
}
public S next() {
return nextService();
}
}
//按行讀取檔案,每一行都是服務實現類的介面名
private Iterator<String> parse(Class<?> service, URL u)throws ServiceConfigurationError
{
InputStream in = null;
BufferedReader r = null;
ArrayList<String> names = new ArrayList<>();
try {
in = u.openStream();
r = new BufferedReader(new InputStreamReader(in, "utf-8"));
int lc = 1;
while ((lc = parseLine(service, u, r, lc, names)) >= 0);
} catch (IOException x) {
fail(service, "Error reading configuration file", x);
} finally {
try {
if (r != null) r.close();
if (in != null) in.close();
} catch (IOException y) {
fail(service, "Error closing configuration file", y);
}
}
return names.iterator();
}
}
複製程式碼
Google autoService
以上 當註冊服務實現時如果需要手動建立檔案並寫入服務實現類名稱 難免有些繁瑣,我們可以使用谷歌提供的 AutoService 庫簡化這一過程
使用方式
- gradle 引入autoService
dependencies {
compileOnly `com.google.auto.service:auto-service:1.0-rc2`
annotationProcessor `com.google.auto.service:auto-service:1.0-rc2`
}
複製程式碼
- 服務實現類上加上@AutoService註解,引數為服務抽象類
package com.knight.serviceimpl;
import com.google.auto.service.AutoService;
import com.knight.PaymentService;
@AutoService(PaymentService.class)
public class PaymentServiceImpl implements PaymentService {
@Override
public void pay(String productName, double price) {
System.out.println("支付模組:購買產品 "+productName +",價格"+price);
}
}
複製程式碼
3.專案編譯觸發auto-service註解處理過程後自動生成了配置檔案
其他
該例項原始碼已上傳 git倉庫