平時API卻是聽得不少?SPI又是啥.別急咱們來先看看面向接口編程的調用關係,來了解一下,API和SPI的類似和不一樣之處。html
先來一段官話的介紹:SPI 全稱爲 (Service Provider Interface) ,是JDK內置的一種服務提供發現機制.(聽了一臉懵逼)好的,咱們結合圖片來理解一下。 java
SPI在框架中其實有不少普遍的應用,這裏列舉幾個例子: 1.Mysql驅動的選擇driverManager根據配置來肯定要使用的驅動;
2.dubbo框架中的擴展機制(dubbo官網連接)git
看完上面的簡介和SPI在框架中的應用,想必對SPI在讀者的大腦中已經產生了一個雛形,talk is cheap!show me the code.說了這麼多,咱們具體寫一個簡單的例子來看看效果,驗證一下SPI.github
1.首先定義一個接口,忍者服務接口sql
public interface NinjaService {
void performTask();
}
複製代碼
2.接下來寫兩個實現類,ForbearanceServiceImpl(上忍),ShinobuServiceImpl(下忍)apache
public class ForbearanceServiceImpl implements NinjaService {
@Override
public void performTask() {
System.out.println("上忍在執行A級任務");
}
}
複製代碼
public class ShinobuServiceImpl implements NinjaService {
@Override
public void performTask() {
System.out.println("下忍在執行D級任務");
}
}
複製代碼
3.接下來咱們在main/resources/下建立META-INF/services目錄,而且在services目錄下建立一個com.scott.java.task.spi.NinjaService(忍者服務類的全限定名)的文件.編程
4.建立一個Client場景類來調用看看結果 api
5.最後貼一下目錄結構 緩存
1.先看下核心類ServiceLoader的定義和屬性app
// 繼承了Iterable類 遍歷的時候使用
public final class ServiceLoader<S> implements Iterable<S> {
// 這就是爲啥須要在META-INF/services/目錄下建立服務類的文件
private static final String PREFIX = "META-INF/services/";
// 被加載的服務
private final Class<S> service;
// 類加載器
private final ClassLoader loader;
// 訪問控制類
private final AccessControlContext acc;
// 實現類的緩存 根據初始化的順序 也就是在/services/文件中的定義順序來定義的加載順序
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// 懶加載iterator
private LazyIterator lookupIterator;
複製代碼
2.而後從client開始,而後依次debug進去
ServiceLoader<NinjaService> ninjaServices = ServiceLoader.load(NinjaService.class);
複製代碼
public static <S> ServiceLoader<S> load(Class<S> service) {
// 獲取當前的類加載器 也就是AppClassLoader
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);
}
複製代碼
後面的就省略了,由於這裏僅僅就是根據NinjaService初始化的事項,沒有什麼很難理解的點.
3.咱們在看看具體的調用過程,這裏使用的是client對應的class文件,由於增長for(foreach)在java中是個語法糖,實際上編譯後是這樣的內容
public static void main(String[] args) {
ServiceLoader<NinjaService> ninjaServices = ServiceLoader.load(NinjaService.class);
// 這裏一下其實就是增長for解糖後的代碼 有興趣能夠去了解下java的語法糖
Iterator var2 = ninjaServices.iterator();
while(var2.hasNext()) {
NinjaService item = (NinjaService)var2.next();
item.performTask();
}
}
複製代碼
4.隨着斷點繼續走,咱們進入到var2.hasNext()的方法
public boolean hasNext() {
// knownProviders尚未加載過provider 走下面的分支
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
複製代碼
這裏lookupIterator上面ServiceLoader的屬性介紹過,它實際上是ServiceLoader中的一個Iterator的內部類。而後調用了內部類Iterator的hasNext()方法。
public boolean hasNext() {
// acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
// ServiceLoader初始化沒有設置過securityManager,因此acc是null,進入hasNextService()
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
複製代碼
5.hasNextService()分析
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
// 這裏加載了META-INF/services下的文件 也就是含有兩個實現類全限定名的文件
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
// 由於loader是不爲null 的AppClassLoader
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;
}
複製代碼
6.繼續看到parse方法,這裏最後返回的是含有兩個全限定類名的Iterator,其實就是把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();
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();
}
複製代碼
附帶的說一下parseLine(service, u, r, lc, names),檢查類名是否符合規範,符合的話添加到Iterator中,到這裏var2 .hasNext()執行完畢,結果是加載了services下的文件內容
private int parseLine(Class<?> service, URL u, BufferedReader r, int lc, List<String> names) throws IOException, ServiceConfigurationError {
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);
}
if (!providers.containsKey(ln) && !names.contains(ln))
names.add(ln);
}
return lc + 1;
}
複製代碼
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());
}
// 這裏將下一個實現類的名字賦值給了LazyIterator的屬性nextName
nextName = pending.next();
return true;
}
複製代碼
7.接下來執行的是 NinjaService item = (NinjaService)var2.next()的next(方法),而後繼續debug進去,這裏我省略了一些方法的調用,只展現出有用的方法這個是ServiceLoader的內部類LazyIterator的nextService()方法.
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
// 這裏nextName在上面已經賦值過了 因此反射建立實例
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());
// 將類添加到ServiceLoader的providers屬性中 而後返回
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
複製代碼
8.到這裏子類的實現類返回,分析就結束了.
1.瞭解了什麼是SPI;
2.SPI和API的簡單區別和聯繫;
3.學習了怎麼使用SPI來擴展服務;
4.分析了ServiceLoader的源碼加載過程,這裏扯一句,簡單的就是META-INF/services定義好要實現的接口(文件名)和實現類(文件內容), ServiceLoader加載的時候沒有實例化實現類,而是在Iterator遍歷的時候去用反射建立了實例.
以爲寫得還行的能夠點個贊,關注一波,後面會繼續寫更好的文章~ XD
1.cr.openjdk.java.net/~mr/jigsaw/… 2.www.cnblogs.com/happyframew… 3.dubbo.apache.org/zh-cn/blog/… 4.www.cnblogs.com/googlemeoft… 5.zhuanlan.zhihu.com/p/28909673