SPI服務(wù)發(fā)現(xiàn)機(jī)制
SPI是Java JDK內(nèi)部提供的一種服務(wù)發(fā)現(xiàn)機(jī)制而咆。
SPI->Service Provider Interface,服務(wù)提供接口榴芳,是Java JDK內(nèi)置的一種服務(wù)發(fā)現(xiàn)機(jī)制
通過在ClassPath路徑下的META-INF/services文件夾查找文件锈麸,自動(dòng)加載文件里所定義的類
[??注意事項(xiàng)]:
面向?qū)ο蟮脑O(shè)計(jì)里,一般推薦模塊之間基于接口編程,模塊之間不對實(shí)現(xiàn)類進(jìn)行編碼蝙斜。如果涉及實(shí)現(xiàn)類就會(huì)違反可插拔的原則经磅,針對于模塊裝配泌绣,Java SPI提供了為某個(gè)接口尋找服務(wù)的實(shí)現(xiàn)機(jī)制。
SPI規(guī)范
- 使用約定:
[1].編寫服務(wù)提供接口预厌,可以是抽象接口和函數(shù)接口阿迈,JDK1.8之后推薦使用函數(shù)接口
[2].在jar包的META-INF/services/目錄里創(chuàng)建一個(gè)以服務(wù)接口命名的文件。其實(shí)就是實(shí)現(xiàn)該服務(wù)接口的具體實(shí)現(xiàn)類轧叽。
提供一個(gè)目錄:
META-INF/services/
放到ClassPath下面
[3].當(dāng)外部程序裝配這個(gè)模塊的時(shí)候苗沧,就能通過該Jar包META-INF/services/配置文件找到具體的實(shí)現(xiàn)類名,并裝載實(shí)例化炭晒,完成模塊注入待逞。
目錄下放置一個(gè)配置文件:
文件名是需要拓展的接口全限定名稱
文件內(nèi)部為要實(shí)現(xiàn)的接口實(shí)現(xiàn)類
文件必須為UTF-8編碼
[4].尋找服務(wù)接口實(shí)現(xiàn),不用在代碼中提供网严,而是利用JDK提供服務(wù)查找工具類:java.util.ServiceLoader類來加載使用:
ServiceLoader.load(xxx.class)
ServiceLoader<XXXInterface> loads = ServiceLoader.load(xxx.class)
SPI源碼分析
[1].ServiceLoader源碼:
package java.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.AccessController;
import java.security.AccessControlContext;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
public final class ServiceLoader<S> implements Iterable<S>
{
//[1].初始化定義全局配置文件路徑Path
private static final String PREFIX = "META-INF/services/";
//[2].初始化定義加載的服務(wù)類或接口
private final Class<S> service;
//[3].初始化定義類加載器
private final ClassLoader loader;
//[4].初始化定義訪問控制上下文
private final AccessControlContext acc;
//[5].初始化定義加載服務(wù)類的緩存集合
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
//[6].初始化定義私有內(nèi)部LazyIterator類识樱,真正加載服務(wù)類的實(shí)現(xiàn)類
private LazyIterator lookupIterator;
//私有化有參構(gòu)造-> ServiceLoader(Class<S> svc, ClassLoader cl)
private ServiceLoader(Class<S> svc, ClassLoader cl) { //[1].實(shí)例化服務(wù)接口->Class<S>
service = Objects.requireNonNull(svc, "Service interface cannot be null");
//[2].實(shí)例化類加載器->ClassLoader
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
//[3].實(shí)例化訪問控制上下文->AccessControlContext
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
//[4].回調(diào)函數(shù)->reload
reload();
}
public void reload() {
//[1].清空緩存實(shí)例集合
providers.clear();
//[2].實(shí)例化私有內(nèi)部LazyIterator類->LazyIterator
lookupIterator = new LazyIterator(service, loader);
}
public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
}
2.LazyIterator源碼:
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;
}
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());
}
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);
} 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);
}
throw new Error(); // This cannot happen
}
public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction<Boolean> action =
new PrivilegedAction<Boolean>() {
public Boolean run() {
return hasNextService();
}
};
return AccessController.doPrivileged(action, acc);
}
}
public S next() {
if (acc == null) {
return nextService();
} else {
PrivilegedAction<S> action =
new PrivilegedAction<S>() {
public S run() {
return nextService();
}
};
return AccessController.doPrivileged(action, acc);
}
}
public void remove() {
throw new UnsupportedOperationException();
}
}
使用舉例
[1].Dubbo SPI 機(jī)制:
META-INF/dubbo.internal/xxx=接口全限定名
Dubbo 并未使用 Java SPI,而是重新實(shí)現(xiàn)了一套功能更強(qiáng)的 SPI 機(jī)制。
Dubbo SPI 的相關(guān)邏輯被封裝在了 ExtensionLoader 類中牺荠,通過 ExtensionLoader翁巍,我們可以加載指定的實(shí)現(xiàn)類。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路徑下休雌。
與Java SPI 實(shí)現(xiàn)類配置不同灶壶,Dubbo SPI 是通過鍵值對的方式進(jìn)行配置,這樣我們可以按需加載指定的實(shí)現(xiàn)類杈曲。另外驰凛,在測試 Dubbo SPI 時(shí),需要在 Robot 接口上標(biāo)注 @SPI 注解担扑。
[2].Cache SPI 機(jī)制:
META-INF/service/javax.cache.spi.CachingProvider=xxx
[3]Spring SPI 機(jī)制:
META-INF/services/org.apache.commons.logging.LogFactory=xxx
[4].SpringBoot SPI機(jī)制:
META-INF/spring.factories/org.springframework.boot.autoconfigure.EnableAutoConfiguration=xxx
在springboot的自動(dòng)裝配過程中恰响,最終會(huì)加載META-INF/spring.factories文件,而加載的過程是由SpringFactoriesLoader加載的涌献。從CLASSPATH下的每個(gè)Jar包中搜尋所有META-INF/spring.factories配置文件胚宦,然后將解析properties文件,找到指定名稱的配置后返回
源碼:
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
// spring.factories文件的格式為:key=value1,value2,value3
// 從所有的jar包中找到META-INF/spring.factories文件
// 然后從文件中解析出key=factoryClass類名稱的所有value值
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
// 取得資源文件的URL
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
// 遍歷所有的URL
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
// 根據(jù)資源文件URL解析properties文件燕垃,得到對應(yīng)的一組@Configuration類
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
// 組裝數(shù)據(jù)枢劝,并返回
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}
[5].自定義序列化實(shí)現(xiàn)SPI:META-INF/services/xxx=接口全限定名
參考學(xué)習(xí)Java SPI 和Dubbo SPI機(jī)制源碼,自己動(dòng)手實(shí)現(xiàn)序列化工具類等
版權(quán)聲明:本文為博主原創(chuàng)文章,遵循相關(guān)版權(quán)協(xié)議卜壕,如若轉(zhuǎn)載或者分享請附上原文出處鏈接和鏈接來源您旁。