點(diǎn)贊關(guān)注,不再迷路,你的支持對(duì)我意義重大!
?? Hi,我是丑丑。本文 「Java 路線」| 導(dǎo)讀 —— 他山之石,可以攻玉 已收錄,這里有 Android 進(jìn)階成長路線筆記 & 博客,歡迎跟著彭丑丑一起成長。(聯(lián)系方式在 GitHub)
目錄
1. 前置知識(shí)
這篇文章的內(nèi)容會(huì)涉及以下前置 / 相關(guān)知識(shí),貼心的我都幫你準(zhǔn)備好了,請(qǐng)享用~
系統(tǒng)屬性: 「Java 路線」| System.getProperty(...) 獲取系統(tǒng)屬性
類加載: 「Java 路線」類加載機(jī)制
2. 什么是服務(wù)發(fā)現(xiàn)?
服務(wù)發(fā)現(xiàn)(Service Provider Interface,SPI)是一個(gè)服務(wù)的注冊(cè)與發(fā)現(xiàn)機(jī)制,通過 解耦服務(wù)提供者與服務(wù)使用者,實(shí)現(xiàn)了服務(wù)創(chuàng)建 & 服務(wù)使用的關(guān)注點(diǎn)分離。
服務(wù)發(fā)現(xiàn)可以理解為控制反轉(zhuǎn)的一種實(shí)現(xiàn)形式,即:當(dāng)「調(diào)用方對(duì)象」需要使用「依賴項(xiàng)」時(shí),不再直接「構(gòu)造對(duì)象」,取而代之的是由外部 IoC 容器來創(chuàng)建并注入調(diào)用方。 IoC 可以認(rèn)為是一種設(shè)計(jì)模式,但是由于理論成熟的時(shí)間相對(duì)較晚,所以沒有包含在《設(shè)計(jì)模式·GoF》之中。
控制反轉(zhuǎn)的實(shí)現(xiàn)方式主要有兩種:
- 1、服務(wù)提供模式:從外部服務(wù)容器抓取依賴對(duì)象
- 2、依賴注入:并以參數(shù)的形式注入依賴對(duì)象
兩者的本質(zhì)區(qū)別是:使用服務(wù)提供模式時(shí),調(diào)用方可以控制請(qǐng)求依賴對(duì)象的時(shí)機(jī);而使用依賴項(xiàng)注入方式時(shí),一般由外部主動(dòng)注入所需對(duì)象。
服務(wù)提供模式可以為我們帶來以下好處:
- 1、在外部注入或配置依賴項(xiàng),因此我們可以重用這些組件。當(dāng)我們需要修改依賴項(xiàng)的實(shí)現(xiàn)時(shí),不需要大量修改很多處代碼,只需要修改一小部分代碼;
- 2、可以注入依賴項(xiàng)的模擬實(shí)現(xiàn),讓代碼測(cè)試更加容易。
3. ServiceLoader 使用步驟
在分析 ServiceLoader 的使用原理之前,我們先來介紹下 ServiceLoader 的使用步驟。我們直接以 JDBC 作為例子,具體如下:
我們都知道 JDBC 編程有五大基本步驟:
- 1、(非必須)執(zhí)行數(shù)據(jù)庫驅(qū)動(dòng)類加載:
Class.forName("com.mysql.jdbc.driver")
- 2、連接數(shù)據(jù)庫:
DriverManager.getConnection(url, user, password)
- 3、創(chuàng)建SQL語句:
Connection#.creatstatement();
- 4、執(zhí)行SQL語句并處理結(jié)果集:
Statement#executeQuery()
- 5、釋放資源:
ResultSet#close()
Statement#close()
Connection#close()
其中「2、連接數(shù)據(jù)庫」內(nèi)部就是用了 ServiceLoader。為什么連接數(shù)據(jù)庫需要使用 SPI 設(shè)計(jì)思想呢?因?yàn)椴僮鲾?shù)據(jù)庫需要使用廠商提供的數(shù)據(jù)庫驅(qū)動(dòng)程序,如果直接使用廠商的驅(qū)動(dòng)耦合太強(qiáng)了,而使用 SPI 設(shè)計(jì)就能夠?qū)崿F(xiàn)服務(wù)提供者與服務(wù)使用者解耦。
下面,我們一步步手寫 JDBC 中關(guān)于 ServiceLoader 的相關(guān)源碼:
步驟1:定義服務(wù)接口
抽象出一個(gè)服務(wù)接口,這個(gè)接口將由數(shù)據(jù)庫驅(qū)動(dòng)實(shí)現(xiàn)類實(shí)現(xiàn):
public interface Driver {
創(chuàng)建數(shù)據(jù)庫連接
Connection connect(String url, java.util.Properties info);
...
}
步驟2:實(shí)現(xiàn)服務(wù)接口
數(shù)據(jù)庫廠商提供一個(gè)或多個(gè)實(shí)現(xiàn) Driver 接口的驅(qū)動(dòng)實(shí)現(xiàn)類,以 mysql 和 oracle 為例:
-
mysql:
com.mysql.cj.jdbc.Driver.java
已簡化
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
static {
注冊(cè)驅(qū)動(dòng)
java.sql.DriverManager.registerDriver(new Driver());
}
...
}
-
oracle:
oracle.jdbc.driver.OracleDriver.java
已簡化
public class OracleDriver implements Driver {
private static OracleDriver defaultDriver = null;
static {
if (defaultDriver == null) {
1、單例
defaultDriver = new OracleDriver();
注冊(cè)驅(qū)動(dòng)
DriverManager.registerDriver(defaultDriver);
}
}
...
}
步驟3:注冊(cè)實(shí)現(xiàn)類到配置文件
在工程目錄 java 的同級(jí)目錄中新建目錄resources/META-INF/services
,新建一個(gè)配置文件java.sql.Driver
(文件名為服務(wù)接口的全限定名),文件中每一行是實(shí)現(xiàn)類的全限定名,例如:
com.mysql.cj.jdbc.Driver
我們可以解壓mysql-connector-java-8.0.19.jar
包,找到對(duì)應(yīng)的 META-INF 文件夾。
步驟4:(使用方)加載服務(wù)
DriverManaer.java
已簡化
static {
loadInitialDrivers();
}
private static void loadInitialDrivers() {
...
1、讀取 "jdbc.drivers" 屬性
String drivers = System.getProperty("jdbc.drivers");
1、使用ServiceLoader遍歷實(shí)現(xiàn)類
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
2、獲得迭代器
Iterator<Driver> driversIterator = loadedDrivers.iterator();
3、迭代
while(driversIterator.hasNext()) {
driversIterator.next();
}
return null;
...
}
可以看到,DriverManager 被類加載時(shí)(static{})會(huì)調(diào)用 loadInitialDrivers()。這個(gè)方法內(nèi)部通過 ServiceLoader 提供的迭代器 Iterator<Driver> 遍歷了所有驅(qū)動(dòng)實(shí)現(xiàn)類。
那么,ServiceLoader 是如何實(shí)例化 Driver 接口的實(shí)現(xiàn)類的呢?下一節(jié),我們會(huì)深入 ServiceLoader 的源碼來解答這個(gè)問題。
4. ServiceLoader 源碼解析
4.1 入口方法
ServiceLoader 提供了三個(gè)靜態(tài)泛型工廠方法,內(nèi)部最終將調(diào)用ServiceLoader.load(Class,ClassLoader)
:
方法 1:
public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
使用 SystemClassLoader 類加載器
ClassLoader cl = ClassLoader.getSystemClassLoader();
ClassLoader prev = null;
while (cl != null) {
prev = cl;
cl = cl.getParent();
}
return ServiceLoader.load(service, prev);
}
方法 2:
public static <S> ServiceLoader<S> load(Class<S> service) {
使用線程上下文類加載器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
方法 3:
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader){
return new ServiceLoader<>(service, loader);
}
可以看到,三個(gè)方法僅在傳入的類加載器不同。
4.2 構(gòu)造方法
已簡化
private final Class<S> service;
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
private ServiceLoader(Class<S> svc, ClassLoader cl) {
1、類加載器
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
2、清空 providers
providers.clear();
3、實(shí)例化 LazyIterator
lookupIterator = new LazyIterator(service, loader);
}
可以看到,ServiceLoader 的構(gòu)造器中實(shí)例化了一個(gè) LazyIterator 迭代器的實(shí)例,這是一個(gè)「懶加載」的迭代器。這個(gè)迭代器里做了什么呢?
4.3 LazyIterator 迭代器
3、實(shí)例化 LazyIterator
private static final String PREFIX = "META-INF/services/";
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;
}
@Override
public boolean hasNext() {
return hasNextService();
}
@Override
public S next() {
return nextService();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
// configs 未初始化才執(zhí)行
3.1 配置文件:META-INF/services/服務(wù)接口的全限定名
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
}
3.2 解析配置文件資源的迭代器
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
3.3 下一個(gè)實(shí)現(xiàn)類的全限定名
nextName = pending.next();
return true;
}
private S nextService() {
if (!hasNextService()) throw new NoSuchElementException();
String cn = nextName;
nextName = null;
4.1 使用類加載器loader加載(不執(zhí)行初始化)
Class<?> c = Class.forName(cn, false, loader);
if (!service.isAssignableFrom(c)) { 檢查是否實(shí)現(xiàn) S 接口
ClassCastException cce = new ClassCastException(service.getCanonicalName() + " is not assignable from " + c.getCanonicalName());
fail(service, "Provider " + cn + " not a subtype", cce);
}
4.2 使用反射創(chuàng)建服務(wù)類實(shí)例
S p = service.cast(c.newInstance());
4.3 服務(wù)實(shí)現(xiàn)類緩存到 providers
providers.put(cn, p);
return p;
}
}
-> 3.2 解析配置文件資源的迭代器
private Iterator<String> parse(Class<?> service, URL u) throws ServiceConfigurationError {
3.2.1 使用 UTF-8 編碼輸入配置文件資源
InputStream in = u.openStream();
BufferedReader r = new BufferedReader(new InputStreamReader(in, "utf-8"));
ArrayList<String> names = new ArrayList<>();
int lc = 1;
while ((lc = parseLine(service, u, r, lc, names)) >= 0);
return names.iterator();
}
以上代碼已經(jīng)非常簡化了,LazyIterator 的要點(diǎn)如下:
-
hasNext() 判斷邏輯:
- 3.1 配置文件:「META-INF/services/服務(wù)接口的全限定名」;
- 3.2 解析配置文件資源的迭代器;
- 3.3 下一個(gè)實(shí)現(xiàn)類的全限定名。
-
next() 邏輯:
- 4.1 使用類加載器 loader 加載(不執(zhí)行初始化);
- 4.2 使用反射創(chuàng)建服務(wù)類實(shí)例;
- 4.3 服務(wù)實(shí)現(xiàn)類緩存到 providers。
小結(jié)一下:
LazyInterator 會(huì)解析「META-INF/services/服務(wù)接口的全限定名」配置,遍歷每個(gè)服務(wù)實(shí)現(xiàn)類全限定類名,執(zhí)行類加載(未初始化),最后將服務(wù)實(shí)現(xiàn)類緩存到 providers。
這個(gè)迭代器在哪里使用的呢?繼續(xù)往下看~
4.4 包裝迭代器
其實(shí) ServiceLoader 本身就是實(shí)現(xiàn) Iterable 接口的:
public final class ServiceLoader<S> implements Iterable<S>
讓我們來看看 ServiceLoader 中的 Iterable#iterator()
是如何實(shí)現(xiàn)的:
private LazyIterator lookupIterator;
4、返回一個(gè)新的迭代器,包裝了providers和lookupIterator
public Iterator<S> iterator() {
return new Iterator<S>() {
Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();
@Override
public boolean hasNext() {
4.1 優(yōu)先從knownProviders取
if (knownProviders.hasNext()) return true;
return lookupIterator.hasNext();
}
@Override
public S next() {
4.2 優(yōu)先從knownProviders取
if (knownProviders.hasNext()) return knownProviders.next().getValue();
return lookupIterator.next();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
可以看到,ServiceLoader 里有一個(gè)泛型方法 Iterator<S> iterator(),它包裝了 providers 集合迭代器和 lookupIterator 兩個(gè)迭代器,迭代過程中優(yōu)先從 providers 獲取元素。providers 就是存放 LazyLoader 中存放類的地方。
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
5. ServiceLoader 源碼小結(jié)
理解 ServiceLoader 源碼之后,我們總結(jié)要點(diǎn)如下:
5.1 約束
1、服務(wù)實(shí)現(xiàn)類必須實(shí)現(xiàn)服務(wù)接口 S(if (!service.isAssignableFrom(c))
);
2、服務(wù)實(shí)現(xiàn)類需包含無參的構(gòu)造器,LazyInterator 是反射創(chuàng)建實(shí)現(xiàn)類市里的(S p = service.cast(c.newInstance())
);
3、配置文件需要使用 UTF-8 編碼(new BufferedReader(new InputStreamReader(in, "utf-8"))
)。
5.2 懶加載
ServiceLoader 使用「懶加載」的方式創(chuàng)建服務(wù)實(shí)現(xiàn)類實(shí)例,只有在迭代器推進(jìn)的時(shí)候才會(huì)創(chuàng)建實(shí)例(nextService()
)。
5.3 內(nèi)存緩存
ServiceLoader 使用 LinkedHashMap 緩存創(chuàng)建的服務(wù)實(shí)現(xiàn)類實(shí)例。
提示: LinkedHashMap 在迭代時(shí)會(huì)按照 Map#put 執(zhí)行順序遍歷。
5.4 沒有服務(wù)注銷機(jī)制
服務(wù)實(shí)現(xiàn)類實(shí)例被創(chuàng)建后,它的垃圾回收的行為與 Java 中的其他對(duì)象一樣,只有這個(gè)對(duì)象沒有到 GC Root 的強(qiáng)引用,才能作為垃圾回收。而 ServiceLoader 內(nèi)部只有一個(gè)方法來清楚 provices 內(nèi)存緩存。
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
6. 服務(wù)實(shí)現(xiàn)的選擇
當(dāng)存在多個(gè)提供者時(shí),不一定使用全部的服務(wù)實(shí)現(xiàn)類,而是需要根據(jù)某些特性篩選一種最佳實(shí)現(xiàn)。ServiceLoader 機(jī)制只能在遍歷整個(gè)迭代器的過程中,從發(fā)現(xiàn)的實(shí)現(xiàn)類中決策出一個(gè)最佳實(shí)現(xiàn)。
舉個(gè)例子,我們可以使用字符集的表示符號(hào)來獲得一個(gè)對(duì)應(yīng)的 Charset 對(duì)象:Charset.forName(String)
,這個(gè)方法里面就只會(huì)選擇匹配的 Charaset 對(duì)象。
CharsetProvider.java
服務(wù)接口
public abstract class CharsetProvider {
public abstract Charset charsetForName(String charsetName);
// 省略其他方法...
}
Charset.java
public static Charset forName(String charsetName) {
以下只摘要與ServiceLoader有關(guān)的邏輯...
ServiceLoader<CharsetProvider> sl = ServiceLoader.load(CharsetProvider.class, cl);
Iterator<CharsetProvider> i = sl.iterator();
for (Iterator<CharsetProvider> i = providers(); i.hasNext();) {
CharsetProvider cp = i.next();
滿足匹配條件,return
Charset cs = cp.charsetForName(charsetName);
if (cs != null)
return cs;
}
}
7. 總結(jié)
服務(wù)發(fā)現(xiàn) SPI 是控制反轉(zhuǎn) IoC 的實(shí)現(xiàn)方式之一,而 ServiceLoader 是 JDK 中實(shí)現(xiàn)的 SPI 框架;
ServiceLoader 本身就是一個(gè) Iterable 接口,迭代時(shí)會(huì)從 META-INF/services 配置中解析接口實(shí)現(xiàn)類的全限定類名,使用反射創(chuàng)建服務(wù)實(shí)現(xiàn)類對(duì)象;
SPI 是一個(gè)相對(duì)簡易的框架,為了滿足復(fù)雜業(yè)務(wù)的需要,一般會(huì)使用其他第三方框架,例如后臺(tái)的 Dubbo、客戶端的 ARouter 與 WMRouter等。
在后面的文章中,我將與你探討 Android 組件化實(shí)踐,并附帶阿里 ARouter 源碼分析。點(diǎn)擊關(guān)注,趕緊上車~ 「Android 路線」| 導(dǎo)讀 —— 從零到無窮大
創(chuàng)作不易,你的「三連」是丑丑最大的動(dòng)力,我們下次見!