SPI 全稱 Service Provider Interface,是 Java 提供的一套用來被第三方實現或者擴展的 API,它能夠用來啓用框架擴展和替換組件。是「接口的編程+策略模式+配置文件」組合實現的動態加載機制java
流程架構圖: git
在 java 代碼中,咱們編寫接口實現類,每每是事先肯定的,在啓動時候加載類具體的實現類,一旦咱們須要變動選擇某一實現類,咱們就須要修改代碼。爲了實現這一個能夠動態的選擇實現的方式,就出現了 SPI 技術,簡單說:SPI 其實就是一種服務發現機制。其核心思想就是結偶github
- 日誌模塊之日誌門面,能夠選擇不一樣的實現進行加載
- 數據庫驅動加載接口實現類的加載 JDBC 加載不一樣類型數據庫的驅動
- Dubbo 中的服務發現機制
SPI 的應用分 4 步:數據庫
- 建立接口類
- 編寫接口實現類
- 編輯配置文件。
- 程序運行起來
全他媽廢話編程
public interface SayWord {
String saySomething();
}
複製代碼
public class SayChineseWord implements SayWord {
@Override
public String saySomething() {
return "你好啊";
}
}
複製代碼
public class SayEnglishWord implements SayWord {
@Override
public String saySomething() {
return "Hello";
}
}
複製代碼
com.pangxie.server.dubbo.spi.impl.SayChineseWord
com.pangxie.server.dubbo.spi.impl.SayEnglishWord
複製代碼
ServiceLoader<SayWord> sayWords=ServiceLoader.load(SayWord.class);
for(SayWord sayWord:sayWords){
System.out.println(sayWord.saySomething());
}
複製代碼
其實從代碼編寫中能夠明白,核心類是ServiceLoader,這個是一個加載服務的一個類,那麼具體是怎麼實現的呢?來讓咱們look一下 安全
成員組成: 大體分爲5個成員遍量,分別爲:service-接口class對象;loader-類加載器;acc-建立時候用來控制訪問權限的上下文;providers-服務實現類列表;lookupIterator-懶加載的迭代器bash
// The class or interface representing the service being loaded
private final Class<S> service;
// The class loader used to locate, load, and instantiate providers
private final ClassLoader loader;
// The access control context taken when the ServiceLoader is created
private final AccessControlContext acc;
// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// The current lazy-lookup iterator
private LazyIterator lookupIterator;
複製代碼
提供了惟一的一個靜態方法(使用都是它~,或者直接構造吧~):網絡
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
複製代碼
爲嘛路徑要是META-INF/services下?在看源碼就發現了路徑配置:多線程
private static final String PREFIX = "META-INF/services/";
複製代碼
load調用發生了啥?其實沒啥,就是構建了一個LazyIterator對象,而後就沒有而後了。因此構建的時候並無直接加載,只是存儲了基本信息。
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
複製代碼
只有在調用迭代器的時候,判斷是否有有配置調用hasNextService方法會獲取實例信息,可是這一步沒有加載。
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);
}
}
複製代碼
在next方法中判斷有實例信息後就利用Class.forName,而且實例化,後存儲在鏈表裏。
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 {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
複製代碼
爲了熟悉ServiceLoader的實現就隨便本身寫了一個,能夠經過網絡請求形式獲取配置,簡單的擴展下啦啦啦,再加個配置文件變動監聽,就能夠真的爲所欲爲了!!! 代碼鏈接
public class NewServiceLoader<S> {
private static final String PREFIX = "META-INF/services/";
private String prefix = "META-INF/services/";
/**
* 接口的class
*/
private final Class<S> service;
/**
* 類加載器
*/
private final ClassLoader loader;
/**
* 權限上下文
*/
private final AccessControlContext acc;
/**
* 提供者列表
*/
private LinkedHashMap<String, S> providers = new LinkedHashMap<>();
private HashSet<String> providersName = new HashSet<>();
private NewServiceLoader(Class<S> svc, String prefix, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
this.prefix = prefix;
reload();
}
public static <S> NewServiceLoader<S> load(Class<S> sClass, String urlFix) {
return new NewServiceLoader<S>(sClass, urlFix, null);
}
public static <S> NewServiceLoader<S> load(Class<S> sClass) {
return new NewServiceLoader<S>(sClass, PREFIX, null);
}
public static <S> NewServiceLoader<S> load(Class<S> sClass, ClassLoader classLoader) {
return new NewServiceLoader<S>(sClass, PREFIX, classLoader);
}
public LinkedHashMap<String, S> getProviders() {
//若是二者長度不一致,說明沒有加載全實例,須要加載實例
if (providers.size() != providersName.size()) {
instanceClass(providersName, providers, service);
}
return providers;
}
public void setProviders(LinkedHashMap<String, S> providers) {
this.providers = providers;
}
/**
* 從新加載
*/
private void reload() {
//清除一下,而後解析url文件
providers.clear();
providersName.clear();
parse();
}
/**
* 解析文件內容
*/
private void parse() {
//加載遠程的或者當前的url
BufferedReader bufferedReader = null;
try {
bufferedReader = new BufferedReader(new InputStreamReader(getUrlInfo()));
String line=null;
while ((line=bufferedReader.readLine())!=null) {
providersName.add(line);
}
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
/**
* 獲取路徑文件的資源
* @return
* @throws IOException
*/
private InputStream getUrlInfo() throws IOException {
//若是不是http開頭的,那麼是類文件路徑啦~
if (!prefix.startsWith("http")) {
return getClass().getClassLoader().getResource(prefix + service.getName()).openStream();
}
// TODO 區分本地機器文件
URL url = new URL(prefix + service.getName());
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5 * 1000);
InputStream inStream = conn.getInputStream();
return inStream;
}
/**
* 實例化變量
*
* @param providersName
* @param providers
* @param sClass
*/
private void instanceClass(HashSet<String> providersName, LinkedHashMap<String, S> providers, Class<S> sClass) {
for (String className : providersName) {
Class c = null;
Object instance = null;
try {
c = Class.forName(className);
instance = c.newInstance();
} catch (Throwable e) {
throw new RuntimeException(e);
}
//轉化類對象
S s = sClass.cast(instance);
providers.put(className, s);
}
}
}
複製代碼
public static void main(String[] args) {
NewServiceLoader<SayWord> sayWords=NewServiceLoader.load(SayWord.class);
LinkedHashMap<String,SayWord> linkedHashMap=sayWords.getProviders();
for(SayWord sayWord:linkedHashMap.values()){
System.out.println(sayWord.saySomething());
}
}
複製代碼