本文首發於個人微信公衆號《andyqian》, 期待你的關注!
前言
在之前的文章《Seata 之 config 模塊源碼解讀》中提到了Microkernel + Plugin 的架構模式。如果對它還不熟悉的話,沒關係。我們可以簡單的將其理解爲一個接口的多個實現,與設計模式中的策略模式極爲相似。其中調用組裝這些 Plugin 的地方稱之爲 Microkernel,而接口的實現則可以理解爲 Plugin。從 JDK 1.6 版本開始,Java 在 JDK 中提供了一種名爲 SPI (Service Provider Interface) 的服務提供機制,可以理解成 Microkernel + Plugin 模式的 JDK 實現。
舉栗子
在解讀源碼之前,我們先來看看 JDK 中的 SPI 是如何使用的?通過以下例子進行演示:
接口:
package io.andyqian.service;
public interface UserService {
void printlnName();
void printlnAge();
}
實現:
package io.andyqian.service.impl;
import io.andyqian.service.UserService;
public class UserServiceImpl implements UserService {
@Override
public void printlnName() {
System.out.println("andyqian");
}
@Override
public void printlnAge() {
System.out.println("18");
}
}
配置:
- 在 resource 文件夾下創建子文件夾 META-INF.services,並新增名爲: io.andyqian.service.UserService 的文件,內容如下所示:
io.andyqian.service.impl.UserServiceImpl
如下圖所示:
測試:
public class UserServiceTest {
@Test
public void testLoaderClass(){
ServiceLoader<UserService> serviceLoader = ServiceLoader.load(UserService.class);
for(UserService userService : serviceLoader) {
userService.printlnAge();
userService.printlnName();
}
}
}
運行結果:
18
andyqian
通過上述代碼發現,我們並沒有手工實例化 UserServiceImpl 對象,但會執行其實現,甚是神奇,帶着這樣的疑問,我們繼續往下看。
源碼解析
在進行源碼分析之前,我們先來看看JDK中 SPI 中的一些規範。如下列表所示:
- 配置文件必須在指定文件夾 META-INF/services/ 內。
- 配置文件編碼格式必須爲:
UTF-8。 - 配置文件名必須是 類的全限定名。
- 接口如果有多個實現,在配置文件中以 # 作爲分隔符。
緊接着,我們以 ServiceLoader 類的 load 方法作爲入口,分析源碼。代碼如下所示:
// 重載函數, 默認爲 當前線程的 ContextClassLoader
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
//返回 ServiceLoader 實例
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {
return new ServiceLoader<>(service, loader);
}
ServiceLoader 構造函數,源碼如下所示:
// 校驗參數以及初始化 loader ,初始化 accessController。
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();
}
其中 reload 方法,源碼如下:
// 構造 LazyIterator
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
這裏使用了自定義的 LazyIterator 類,其源碼如下所示:
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;
}
// 判斷是否有下一個 Service,
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;
}
// 通過config文件路徑加載服務
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
// 獲取下一個service
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 {
// 目標類實例轉換爲響應的類,並將其加載到 providers 列表中
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
}
// 實現 hasNext 方法,底層調用 hasNextService 方法
public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
// 實現 Iterator 類中的方法,底層調用 nextService()。
public S next() {
if (acc == null) {
return nextService();
} else {
PrivilegedAction<S> action = new PrivilegedAction<S>() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
public void remove() {
throw new UnsupportedOperationException();
}
}
parse 方法源碼:
//其中service 表示需要加載的類對象, u 表示 META-INF.services 下文件 在系統中的全路徑。
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();
//這裏驗證配置文件中編碼爲 utf8.
r = new BufferedReader(new InputStreamReader(in, "utf-8"));
int lc = 1;
// 循環解析配置文件中的每一行,當 lc >=0時繼續讀取,<0 時 則中斷
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();
}
parseLine 方法源碼如下:
private int parseLine(Class<?> service, URL u, BufferedReader r, int lc,
List<String> names)
throws IOException, ServiceConfigurationError
{
// 讀取到空行,則返回 -1
String ln = r.readLine();
if (ln == null) {
return -1;
}
// 此處驗證多個實現以 # 進行分割。
int ci = ln.indexOf('#');
if (ci >= 0) ln = ln.substring(0, ci);
// 去除空白字符
ln = ln.trim();
int n = ln.length();
// 校驗 配置文件中的文件格式是否正確
if (n != 0) {
if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
fail(service, u, lc, "Illegal configuration-file syntax");
int cp = ln.codePointAt(0);
if (!Character.isJavaIdentifierStart(cp))
fail(service, u, lc, "Illegal provider-class name: " + ln);
for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
cp = ln.codePointAt(i);
if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
fail(service, u, lc, "Illegal provider-class name: " + ln);
}
// service 去重,如果不存在,則添加到服務列表中。
if (!providers.containsKey(ln) && !names.contains(ln))
names.add(ln);
}
return lc + 1;
}
上面使用 Iterator 接口的實現類LazyIterator,將服務加載的這個動作延遲到使用服務時。這樣做的好處是:只有在真正使用時才加載,避免造成沒必要的加載。但未解決,未使用擴展加載的問題。
通過上面的源碼,其實還是有疑問的,它們到底怎麼運行起來的呢?其中就不乏ServiceLoader 類 中 Iterable 接口的iterator 方法的功勞,代碼如下所示:
public Iterator<S> iterator() {
//實例Iterator 對象
return new Iterator<S>() {
// 遍歷 providers 列表
Iterator<Map.Entry<String,S>> knownProviders
= providers.entrySet().iterator();
//是否有下一個對象
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
//直接獲取下一個對象
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
到這裏,已經比較清楚了。但還有一個比較重要的點,在Java中是不存在真正的for each 語法的。之所以我們可以使用,其實是藉助語法糖提供的。而上述的以下代碼:
public class UserServiceTest {
@Test
public void testLoaderClass(){
ServiceLoader<UserService> serviceLoader = ServiceLoader.load(UserService.class);
for(UserService userService : serviceLoader) {
userService.printlnAge();
userService.printlnName();
}
}
}
對應可翻譯爲:
public class UserServiceTest {
@Test
public void testLoaderClass(){
ServiceLoader<UserService> serviceLoader = ServiceLoader.load(UserService.class);
for(java.util.Iterator i$ = serviceLoader.iterator(); i$.hasNext();) {
UserService userService = (UserService) i$.next();
userService.printlnAge();
userService.printlnName();
}
}
}
其中 serviceLoader.iterator()方法就是 ServiceLoader 類中實現的 iterator方法。至此,JDK中的SPI 的核心代碼就連起來了。
小結
在很多框架中,參考或增強了JDK 的SPI,使得其在最大程度的解耦,也給框架擴展提供了豐富的想象。其中就包括:JDBC,Dubbo,Seata 等等。下次我們一起來分析分析。
相關閱讀: