JAVA SPI && java.util.ServiceLoader

一锨天、SPI 簡介:

? ?SPI 全稱為 (Service Provider Interface) ,是JDK(1.5開始)內(nèi)置的一種服務(wù)提供發(fā)現(xiàn)機(jī)制。

二墅诡、SPI場景和產(chǎn)生原因:

我們系統(tǒng)里抽象的各個(gè)模塊成榜,往往有很多不同的實(shí)現(xiàn)方案,比如日志模塊的方案意荤,xml解析模塊啊片、jdbc模塊的方案等。面向的對(duì)象的設(shè)計(jì)里玖像,我們一般推薦模塊之間基于接口編程紫谷,模塊之間不對(duì)實(shí)現(xiàn)類進(jìn)行硬編碼齐饮。一旦代碼里涉及具體的實(shí)現(xiàn)類,就違反了可拔插的原則笤昨,如果需要替換一種實(shí)現(xiàn)祖驱,就需要修改代碼。

為了實(shí)現(xiàn)在模塊裝配的時(shí)候能不在程序里動(dòng)態(tài)指明瞒窒,這就需要一種服務(wù)發(fā)現(xiàn)機(jī)制捺僻。java spi就是提供這樣的一個(gè)機(jī)制:為某個(gè)接口尋找服務(wù)實(shí)現(xiàn)的機(jī)制。有點(diǎn)類似IOC的思想崇裁,就是將裝配的控制權(quán)移到程序之外匕坯,在模塊化設(shè)計(jì)中這個(gè)機(jī)制尤其重要。


三拔稳、使用spi需要遵循的規(guī)范:


四葛峻、具體使用

第一步:提供一個(gè)接口和它的若干個(gè)實(shí)現(xiàn):

有一個(gè)接口

package com.xihe.api;

public interface XiheInterface {

? ? public void sayHi();

}

該接口有兩個(gè)實(shí)現(xiàn)

package com.xihe.api;

public class XiheBJ implements XiheInterface {

? ? public void sayHi() {

? ? ? ? System.out.println("xihe in beijing ");

? ? }

}

public class XiheZZ implements XiheInterface {

? ? public void sayHi() {

? ? ? ? System.out.println("xihe in zhengzhou");

? ? }

}

第二步: 在src下創(chuàng)建META-INF/services/目錄中創(chuàng)建一個(gè)名為com.xihe.api.XiheInterface 的文件,文件的內(nèi)容是實(shí)現(xiàn)類的全名。

如果該Service有多個(gè)服務(wù)實(shí)現(xiàn)巴比,則每一行寫一個(gè)服務(wù)實(shí)現(xiàn)术奖,如:

com.xihe.api.XiheZZ

com.xihe.api.XiheBJ

第三步:加載

public class Demo { public static void main(String[] args) { ServiceLoaderserviceLoader = ServiceLoader.load(XiheInterface.class); Iterator it = serviceLoader.iterator();

? ? ? ? while (it!=null && it.hasNext()) {

? ? ? ? ? ? XiheInterface demoService = it.next();

? ? ? ? ? ? System.out.println("class:"+demoService.getClass().getName());

? ? ? ? ? ? demoService.sayHi();

? ? ? ? }

? ? }

}

運(yùn)行結(jié)果:

class:com.xihe.api.XiheZZ

xihe in zhengzhou

class:com.xihe.api.XiheBJ

xihe in beijing


五、源碼分析

import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStreamReader;

import java.security.AccessControlContext;

import java.security.AccessController;

import java.security.PrivilegedAction;

import java.util.*;

//ServiceLoader實(shí)現(xiàn)了Iterable接口轻绞,可以遍歷所有的服務(wù)實(shí)現(xiàn)者

public final class ServiceLoader

implements Iterable

{

//查找配置文件的目錄

? ? private static final StringPREFIX ="META-INF/services/";

? ? //表示要被加載的服務(wù)的類或接口

? ? private final Classservice;

? ? //這個(gè)ClassLoader用來定位采记,加載,實(shí)例化服務(wù)提供者

? ? private final ClassLoaderloader;

? ? // 訪問控制上下文

? ? private final AccessControlContextacc;

? ? // 緩存已經(jīng)被實(shí)例化的服務(wù)提供者政勃,按照實(shí)例化的順序存儲(chǔ)

? ? private LinkedHashMapproviders =new LinkedHashMap<>();

? ? // 迭代器

? ? private LazyIteratorlookupIterator;

? ? //重新加載挺庞,就相當(dāng)于重新創(chuàng)建ServiceLoader了,用于新的服務(wù)提供者安裝到正在運(yùn)行的Java虛擬機(jī)中的情況稼病。

? ? public void reload() {

//清空緩存中所有已實(shí)例化的服務(wù)提供者

? ? ? ? providers.clear();

? ? ? ? //新建一個(gè)迭代器,該迭代器會(huì)從頭查找和實(shí)例化服務(wù)提供者

? ? ? ? lookupIterator =new LazyIterator(service, loader);

? ? }

//私有構(gòu)造器

//使用指定的類加載器和服務(wù)創(chuàng)建服務(wù)加載器

//如果沒有指定類加載器掖鱼,使用系統(tǒng)類加載器然走,就是應(yīng)用類加載器。

? ? private ServiceLoader(Class 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();

? ? }

//解析失敗處理的方法

? ? private static void fail(Class service, String msg, Throwable cause)

throws ServiceConfigurationError

{

throw new ServiceConfigurationError(service.getName() +": " + msg,

? ? ? ? ? ? ? ? cause);

? ? }

private static void fail(Class service, String msg)

throws ServiceConfigurationError

{

throw new ServiceConfigurationError(service.getName() +": " + msg);

? ? }

private static void fail(Class service, URL u, int line, String msg)

throws ServiceConfigurationError

{

fail(service, u +":" + line +": " + msg);

? ? }

//解析服務(wù)提供者配置文件中的一行

//首先去掉注釋校驗(yàn)戏挡,然后保存

//返回下一行行號(hào)

//重復(fù)的配置項(xiàng)和已經(jīng)被實(shí)例化的配置項(xiàng)不會(huì)被保存

? ? private int parseLine(Class service, URL u, BufferedReader r, int lc,

? ? ? ? ? ? ? ? ? ? ? ? ? List names)

throws IOException, ServiceConfigurationError

{

//讀取一行

? ? ? ? String ln = r.readLine();

? ? ? ? if (ln ==null) {

return -1;

? ? ? ? }

//#號(hào)代表注釋行

? ? ? ? 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;

? ? }

//解析配置文件芍瑞,解析指定的url配置文件

//使用parseLine方法進(jìn)行解析,未被實(shí)例化的服務(wù)提供者會(huì)被保存到緩存中去

? ? private Iteratorparse(Class service, URL u)

throws ServiceConfigurationError

{

InputStream in =null;

? ? ? ? BufferedReader r =null;

? ? ? ? ArrayList 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);

? ? ? ? }

return names.iterator();

? ? }

//服務(wù)提供者查找的迭代器

? ? private class LazyIterator

implements Iterator

{

Classservice;//服務(wù)提供者接口

? ? ? ? ClassLoaderloader;//類加載器

? ? ? ? Enumerationconfigs =null;//保存實(shí)現(xiàn)類的url

? ? ? ? Iteratorpending =null;//保存實(shí)現(xiàn)類的全名

? ? ? ? StringnextName =null;//迭代器中下一個(gè)實(shí)現(xiàn)類的全名

? ? ? ? private LazyIterator(Class 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);

? ? ? ? ? ? ? ? }

}

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);

? ? ? ? ? ? }

if (!service.isAssignableFrom(c)) {

fail(service, "Provider " + cn? +" not a subtype");

? ? ? ? ? ? }

try {

S p =service.cast(c.newInstance());

? ? ? ? ? ? ? ? providers.put(cn, p);

? ? ? ? ? ? ? ? return p;

? ? ? ? ? ? }

}

public boolean hasNext() {

if (acc ==null) {

return hasNextService();

? ? ? ? ? ? }else {

PrivilegedAction action =new PrivilegedAction() {

public Booleanrun() {return hasNextService(); }

};

? ? ? ? ? ? ? ? return AccessController.doPrivileged(action, acc);

? ? ? ? ? ? }

}

public S next() {

if (acc ==null) {

return nextService();

? ? ? ? ? ? }else {

PrivilegedAction action =new PrivilegedAction() {

public S run() {return nextService(); }

};

? ? ? ? ? ? ? ? return AccessController.doPrivileged(action, acc);

? ? ? ? ? ? }

}

public void remove() {

throw new UnsupportedOperationException();

? ? ? ? }

}

//獲取迭代器

//返回遍歷服務(wù)提供者的迭代器

//以懶加載的方式加載可用的服務(wù)提供者

//懶加載的實(shí)現(xiàn)是:解析配置文件和實(shí)例化服務(wù)提供者的工作由迭代器本身完成

? ? public Iteratoriterator() {

return new Iterator() {

//按照實(shí)例化順序返回已經(jīng)緩存的服務(wù)提供者實(shí)例

? ? ? ? ? ? Iterator>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();

? ? ? ? ? ? }

};

? ? }

//為指定的服務(wù)使用指定的類加載器來創(chuàng)建一個(gè)ServiceLoader

? ? public static ServiceLoaderload(Class service,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ClassLoader loader)

{

return new ServiceLoader<>(service, loader);

? ? }

//使用線程上下文的類加載器來創(chuàng)建ServiceLoader

? ? public static ServiceLoaderload(Class service) {

ClassLoader cl = Thread.currentThread().getContextClassLoader();

? ? ? ? return ServiceLoader.load(service, cl);

? ? }

//使用擴(kuò)展類加載器為指定的服務(wù)創(chuàng)建ServiceLoader

//只能找到并加載已經(jīng)安裝到當(dāng)前Java虛擬機(jī)中的服務(wù)提供者褐墅,應(yīng)用程序類路徑中的服務(wù)提供者將被忽略

? ? public static ServiceLoaderloadInstalled(Class service) {

ClassLoader cl = ClassLoader.getSystemClassLoader();

? ? ? ? ClassLoader prev =null;

? ? ? ? while (cl !=null) {

prev = cl;

? ? ? ? ? ? cl = cl.getParent();

? ? ? ? }

return ServiceLoader.load(service, prev);

? ? }

public StringtoString() {

return "java.util.ServiceLoader[" +service.getName() +"]";

? ? }

}

ServiceLoader不是實(shí)例化以后拆檬,就去讀取配置文件中的具體實(shí)現(xiàn),并進(jìn)行實(shí)例化妥凳。而是等到使用迭代器去遍歷的時(shí)候竟贯,才會(huì)加載對(duì)應(yīng)的配置文件去解析,調(diào)用hasNext方法的時(shí)候會(huì)去加載配置文件進(jìn)行解析逝钥,調(diào)用next方法的時(shí)候進(jìn)行實(shí)例化并緩存屑那。

所有的配置文件只會(huì)加載一次,服務(wù)提供者也只會(huì)被實(shí)例化一次,重新加載配置文件可使用reload方法持际。

六沃琅、SPI缺點(diǎn)

通過上面的解析,可以發(fā)現(xiàn)蜘欲,我們使用SPI查找具體的實(shí)現(xiàn)的時(shí)候益眉,需要遍歷所有的實(shí)現(xiàn),并實(shí)例化姥份,然后我們?cè)谘h(huán)中才能找到我們需要實(shí)現(xiàn)郭脂。這應(yīng)該也是最大的缺點(diǎn),需要把所有的實(shí)現(xiàn)都實(shí)例化了殿衰,即便我們不需要朱庆,也都給實(shí)例化了。

有關(guān)SPI的東西暫先了解到這里闷祥,有深入的以后再添加娱颊。

參考:

http://www.reibang.com/p/32d3e108f30a

http://cxis.me/2017/04/17/Java%E4%B8%ADSPI%E6%9C%BA%E5%88%B6%E6%B7%B1%E5%85%A5%E5%8F%8A%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市凯砍,隨后出現(xiàn)的幾起案子箱硕,更是在濱河造成了極大的恐慌,老刑警劉巖悟衩,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件剧罩,死亡現(xiàn)場離奇詭異,居然都是意外死亡座泳,警方通過查閱死者的電腦和手機(jī)惠昔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來挑势,“玉大人镇防,你說我怎么就攤上這事〕北ィ” “怎么了来氧?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長香拉。 經(jīng)常有香客問我啦扬,道長,這世上最難降的妖魔是什么凫碌? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任扑毡,我火速辦了婚禮,結(jié)果婚禮上证鸥,老公的妹妹穿的比我還像新娘僚楞。我一直安慰自己勤晚,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布泉褐。 她就那樣靜靜地躺著赐写,像睡著了一般。 火紅的嫁衣襯著肌膚如雪膜赃。 梳的紋絲不亂的頭發(fā)上挺邀,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音跳座,去河邊找鬼端铛。 笑死,一個(gè)胖子當(dāng)著我的面吹牛疲眷,可吹牛的內(nèi)容都是我干的禾蚕。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼狂丝,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼换淆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起几颜,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤倍试,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后蛋哭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體县习,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年谆趾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了躁愿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡沪蓬,死狀恐怖攘已,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情怜跑,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布吠勘,位于F島的核電站性芬,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏剧防。R本人自食惡果不足惜植锉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望峭拘。 院中可真熱鬧俊庇,春花似錦狮暑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至彭沼,卻和暖如春缔逛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背姓惑。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來泰國打工褐奴, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人于毙。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓敦冬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親唯沮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子脖旱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)烂翰,斷路器夯缺,智...
    卡卡羅2017閱讀 134,657評(píng)論 18 139
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法甘耿,內(nèi)部類的語法踊兜,繼承相關(guān)的語法,異常的語法佳恬,線程的語...
    子非魚_t_閱讀 31,631評(píng)論 18 399
  • 1 基本信息 每個(gè)開發(fā)人員對(duì)java.lang.ClassNotFoundExcetpion這個(gè)異衬缶常肯定都不陌生,...
    java小菜鳥閱讀 2,609評(píng)論 0 15
  • 一. Java基礎(chǔ)部分.................................................
    wy_sure閱讀 3,811評(píng)論 0 11
  • 聽君一席話
    薔薇颯閱讀 254評(píng)論 0 0