前言
深入dubbo源碼前最好先了解下java spi(service provider interface)機制, 簡單來說, spi可以幫我們加載指定文件中描述的接口實現類. 嗯…就這? 是不是太簡單了, 雖然我是個菜瓜, 那我也知道Class.forName呀~ 那我們來研究下~
java spi
demo
儘管千篇一律, 還是給出一個可運行demo
// 首先你需要一個對外接口
public interface GreetOrBye {
String say(String name);
}
// 以及兩個實現類
public class Bye implements GreetOrBye {
@Override
public String say(String name) {
return "bye " + name;
}
}
public class Greet implements GreetOrBye {
@Override
public String say(String name) {
return "hi " + name;
}
}
//然後是執行類
public class Launcher {
public static void say(String name) {
ServiceLoader<GreetOrBye> greetOrByeServiceLoader = ServiceLoader.load(GreetOrBye.class);
Iterator<GreetOrBye> iterator = greetOrByeServiceLoader.iterator();
while (iterator.hasNext()) {
GreetOrBye greetOrBye = iterator.next();
System.out.println(greetOrBye.say(name));
}
}
public static void main(String[] args) {
say("wahahah");
}
}
下面是需要指定的文件, 目錄名稱固定META-INF下的services, 文件名稱爲接口全限定名
文件內容是實現類的全限定名
運行結果
hi wahahah
bye wahahah
平平無奇~(古天樂?)
雖然還沒有看ServiceLoader的代碼, 但是以我40年代碼經驗來看, 裏面必定就是一個讀文件, 反射創建對象的過程. 那麼好吧~ 接下來證明自己
ServiceLoader
菜瓜慣例, 先看註釋, 再看屬性.
註釋總結
註釋中先定義了兩個名詞
service 指暴露的接口或者類, 通常是抽象類, 也可以是具體類, 但是不建議.
service provider 指實現類, 通常是一個代理類, 內容決定具體的實現類
然後提了幾點要求
**service provider 必須提供無參構造方法 **
service provider 在META-INF/services中定義, 文件名稱是service的全限定名稱, 比如com.togo.spi.helloworld.GreetOrBye, 文件內容是每一行都是service provider的全限定名稱, 比如com.togo.spi.helloworld.impl.Greet
如果一個service provider出現在多個文件或者在一個文件中出現多次, service loader會去重.
service provider和配置文件不一定要在一個jar中, 但是service provider必須可以被加載配置文件的loader訪問到(這個我們到時候關注下)
service provider都是懶加載(按需加載)
ServiceLoader是線程不安全的
屬性
ServiceLoader實現了Iterable接口, 屬性如下.
// 文件路徑
private static final String PREFIX = "META-INF/services/";
// 暴露的接口類型
private final Class<S> service;
// 類加載器
private final ClassLoader loader;
// 安全相關
private final AccessControlContext acc;
// 緩存加載的類
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// 懶加載迭代器
private LazyIterator lookupIterator;
看名字和註釋大家應該能猜出各個屬性的作用, 我們通過源碼來進一步瞭解下.
源碼
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader){
return new ServiceLoader<>(service, loader);
}
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();
}
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
load方法其實就是創建一個新的ServiceLoader對象(因爲構造方法私有~), 使用的就是當前線程的類加載器, 整個構造過程就是一些賦值操作. 在reload方法中會清除map中緩存的對象, 並重新創建一個LazyIterator, LazyIterator構造方法中就只是賦值了. 既然說是懶加載了, 那麼重要的操作當然都在使用的時候了.
public Iterator<S> iterator() {
return new Iterator<S>() {
// 已經加載的對象
Iterator<Map.Entry<String,S>> knownProviders
= providers.entrySet().iterator();
// 先從已經加載的對象中找, 沒有再從lookupIterator中找
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();
}
};
}
public boolean hasNext() {
if (acc == null) { // 默認走這裏, AccessControlContext作者沒關注~~
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
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;
}
// 解析文件, 並將文件中的字符串存儲到list中, 返回該list的iterator
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
next()方法也比較簡單, 就是獲取到類全限定名稱後做了反射創建對象的操作, 跟我們開始的預測是一樣.
總結
至此java spi分析到這, 代碼很簡單, 重點還是學習思想-面向接口編程, 比如我們經常使用不同的數據庫驅動代碼, 在DriverManager中就有ServiceLoader的身影. 下一篇研究下dubbo增強版spi~