SPI簡介
站在一個框架作者的角度來說,定義一個接口暴拄,自己默認給出幾個接口的實現(xiàn)類,同時 允許框架的使用者也能夠自定義接口的實現(xiàn)。現(xiàn)在一個簡單的問題就是:如何優(yōu)雅的根據一個接口來獲取該接口的所有實現(xiàn)類呢廊鸥?
JDK SPI 正是為了優(yōu)雅解決這個問題而生,SPI 全稱為 (Service Provider Interface)辖所,即服務提供商接口惰说,是JDK內置的一種服務提供發(fā)現(xiàn)機制。目前有不少框架用它來做服務的擴展發(fā)現(xiàn)缘回,簡單來說吆视,它就是一種動態(tài)替換發(fā)現(xiàn)服務實現(xiàn)者的機制。
JDK SPI
JDK為SPI的實現(xiàn)提供了工具類酥宴,即java.util.ServiceLoader啦吧,ServiceLoader中定義的SPI規(guī)范沒有什么特別之處,只需要有一個提供者配置文件(provider-configuration file)拙寡,該文件需要在resource目錄META-INF/services下授滓,文件名就是服務接口的全限定名
public class SpiMain {
public static void main(String[] args) {
ServiceLoader<IHello> loaders = ServiceLoader.load(IHello.class);
System.out.println(loaders);
for (IHello hello : loaders) {
System.out.println(hello.sayHello("mergades"));
}
}
}
ServiceLoader 源碼
讀取文件
private static final String PREFIX = "META-INF/services/";
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;
}
JDK SPI缺點
- 雖然ServiceLoader也算是使用的延遲加載,但是基本只能通過遍歷全部獲取肆糕,也就是接口的實現(xiàn)類全部加載并實例化一遍般堆。如果你并不想用某些實現(xiàn)類,它也被加載并實例化了诚啃,這就造成了浪費淮摔。
獲取某個實現(xiàn)類的方式不夠靈活,只能通過Iterator形式獲取始赎,不能根據某個參數(shù)來獲取對應的實現(xiàn)類和橙。 - 多個并發(fā)多線程使用ServiceLoader類的實例是不安全的
Dubbo SPI擴展
Dubbo通過ExtensionLoader 來加載自定義的SPI服務仔燕。
Dubbo為了應對各種場景,所有的內部組件都是通過SPI的方式來管理的胃碾,
擴展功能
- 方便獲取擴展實現(xiàn):JDK的SPI機制僅僅只能通過接口類名獲取所有實現(xiàn)涨享,而ExtensionLoader則通過接口類名和key值獲取一個實現(xiàn)。
- IOC依賴注入功能仆百。Adaptive實現(xiàn)厕隧,就是生成一個代理類,這樣子就根據實際調用參數(shù)動態(tài)決定要調用的類俄周。
- 使用裝飾器模式進行自動增強吁讨,自動包裝實現(xiàn)。
擴展源碼分析
ExtensinLoader初始化
/**
* Protocol. (API/SPI, Singleton, ThreadSafe)
*/
@SPI("dubbo")
public interface Protocol {
/**
* Get default port when user doesn't config the port.
*
* @return default port
*/
int getDefaultPort();
/**
* Export service for remote invocation: <br>
* 1. Protocol should record request source address after receive a request:
* RpcContext.getContext().setRemoteAddress();<br>
* 2. export() must be idempotent, that is, there's no difference between invoking once and invoking twice when
* export the same URL<br>
* 3. Invoker instance is passed in by the framework, protocol needs not to care <br>
*
* @param <T> Service type
* @param invoker Service invoker
* @return exporter reference for exported service, useful for unexport the service later
* @throws RpcException thrown when error occurs during export the service, for example: port is occupied
*/
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
/**
* Refer a remote service: <br>
* 1. When user calls `invoke()` method of `Invoker` object which's returned from `refer()` call, the protocol
* needs to correspondingly execute `invoke()` method of `Invoker` object <br>
* 2. It's protocol's responsibility to implement `Invoker` which's returned from `refer()`. Generally speaking,
* protocol sends remote request in the `Invoker` implementation. <br>
* 3. When there's check=false set in URL, the implementation must not throw exception but try to recover when
* connection fails.
*
* @param <T> Service type
* @param type Service class
* @param url URL address for the remote service
* @return invoker service's local proxy
* @throws RpcException when there's any error while connecting to the service provider
*/
@Adaptive
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
/**
* Destroy protocol: <br>
* 1. Cancel all services this protocol exports and refers <br>
* 2. Release all occupied resources, for example: connection, port, etc. <br>
* 3. Protocol can continue to export and refer new service even after it's destroyed.
*/
void destroy();
}
使用實例
ExtensionLoader<Protocol> protocolLoader = ExtensionLoader.getExtensionLoader(Protocol.class);
Protocol dubboProtocol = protocolLoader.getExtension(DubboProtocol.NAME);
可以參考dubbo源碼單元測試 com.alibaba.dubbo.common.extensionloader.ExtensionLoaderTest
@Test
public void test_getDefaultExtension() throws Exception {
SimpleExt ext = ExtensionLoader.getExtensionLoader(SimpleExt.class).getDefaultExtension();
assertThat(ext, instanceOf(SimpleExtImpl1.class));
String name = ExtensionLoader.getExtensionLoader(SimpleExt.class).getDefaultExtensionName();
assertEquals("impl1", name);
SimpleExt simpleExt = ExtensionLoader.getExtensionLoader(SimpleExt.class).getExtension("impl2");
assertThat(simpleExt, instanceOf(SimpleExtImpl2.class));
}
如上單元測試可以看到峦朗,可以通過ExtensionLoader.getExtensionLoader(SimpleExt.class).getExtension("impl2"); name來加載不同的擴展建丧,相比于JDK SPI機制,方便很多波势。
通過
- ExtensionLoader.getExtensionLoader(Protocol.class) 獲取對應的ExtensionLoader 實例
- protocolLoader.getExtension(DubboProtocol.NAME):根據Key獲取相應的擴展實現(xiàn)類實例
配置文件掃描
Dubbo默認依次掃描META-INF/dubbo/internal/翎朱、META-INF/dubbo/、META-INF/services/三個classpath目錄下的配置文件尺铣。配置文件以具體擴展接口全名命名拴曲,如:com.alibaba.dubbo.rpc.Filter
如下配置
# Comment 1
impl1=com.alibaba.dubbo.common.extensionloader.ext1.impl.SimpleExtImpl1#Hello World
impl2=com.alibaba.dubbo.common.extensionloader.ext1.impl.SimpleExtImpl2 # Comment 2
impl3=com.alibaba.dubbo.common.extensionloader.ext1.impl.SimpleExtImpl3 # with head space
ExtensionLoader#getExtensionClasses
private Map<String, Class<?>> getExtensionClasses() {
Map<String, Class<?>> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
private Map<String, Class<?>> loadExtensionClasses() {
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation != null) {
String value = defaultAnnotation.value();
if ((value = value.trim()).length() > 0) {
String[] names = NAME_SEPARATOR.split(value);
if (names.length > 1) {
throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
+ ": " + Arrays.toString(names));
}
if (names.length == 1) cachedDefaultName = names[0];
}
}
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
loadDirectory(extensionClasses, DUBBO_DIRECTORY);
loadDirectory(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
}
擴展適配器
在dubbo擴展中,適配器模式被廣泛應用凛忿,其作用在于為同一個接口下面不同的多個實現(xiàn)的調用路由功能澈灼,如制定優(yōu)先級,dubbo提供靜態(tài)適配和動態(tài)適配器店溢。
靜態(tài)適配器
/**
* AdaptiveCompiler. (SPI, Singleton, ThreadSafe)
*/
@Adaptive
public class AdaptiveCompiler implements Compiler {
private static volatile String DEFAULT_COMPILER;
public static void setDefaultCompiler(String compiler) {
DEFAULT_COMPILER = compiler;
}
@Override
public Class<?> compile(String code, ClassLoader classLoader) {
Compiler compiler;
ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
String name = DEFAULT_COMPILER; // copy reference
if (name != null && name.length() > 0) {
compiler = loader.getExtension(name);
} else {
compiler = loader.getDefaultExtension();
}
return compiler.compile(code, classLoader);
}
}
動態(tài)適配器
動態(tài)適配器擴展即通過動態(tài)代理生成的動態(tài)代理類叁熔,在dubbo中通過javassist技術生成。
從ExtensionLoader構造器中會調用getAdaptiveExtension()方法觸發(fā)為當前擴展類型生成適配器
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
ExtensionFactory
@SPI
public interface ExtensionFactory {
/**
* Get extension.
*
* @param type object type.
* @param name object name.
* @return object instance.
*/
<T> T getExtension(Class<T> type, String name);
}
初始化 ExtensionFactory 實例床牧,如果非ExtensionFactory 荣回,則直接獲取getAdaptiveExtension()
@SuppressWarnings("unchecked")
public T getAdaptiveExtension() {
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if (createAdaptiveInstanceError == null) {
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
}
}
}
} else {
throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
}
}
return (T) instance;
}
private T createAdaptiveExtension() {
try {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
第一步獲取getAdaptiveExtensionClass
private Class<?> getAdaptiveExtensionClass() {
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
private Class<?> createAdaptiveExtensionClass() {
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
ClassLoader classLoader = findClassLoader();
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader);
}
getExtensionClasses中,再繼續(xù)調用loadExtensionClasses架子啊默認的擴展機制戈咳。
ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension() 中dubbo底層會動態(tài)編譯驹马,生成。
Protocol產生的AdaptiveExtension如下:
package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
public void destroy() {
throw new UnsupportedOperationException(
"method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
public int getDefaultPort() {
throw new UnsupportedOperationException(
"method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
public com.alibaba.dubbo.rpc.Exporter export( com.alibaba.dubbo.rpc.Invoker arg0)
throws com.alibaba.dubbo.rpc.RpcException {
if (arg0 == null) {
throw new IllegalArgumentException(
"com.alibaba.dubbo.rpc.Invoker argument == null");
}
if (arg0.getUrl() == null) {
throw new IllegalArgumentException(
"com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
}
com.alibaba.dubbo.common.URL url = arg0.getUrl();
String extName = ((url.getProtocol() == null) ? "dubbo"
: url.getProtocol());
if (extName == null) {
throw new IllegalStateException(
"Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" +
url.toString() + ") use keys([protocol])");
}
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class)
.getExtension(extName);
return extension.export(arg0);
}
public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0,
com.alibaba.dubbo.common.URL arg1)
throws com.alibaba.dubbo.rpc.RpcException {
if (arg1 == null) {
throw new IllegalArgumentException("url == null");
}
com.alibaba.dubbo.common.URL url = arg1;
String extName = ((url.getProtocol() == null) ? "dubbo"
: url.getProtocol());
if (extName == null) {
throw new IllegalStateException(
"Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" +
url.toString() + ") use keys([protocol])");
}
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class)
.getExtension(extName);
return extension.refer(arg0, arg1);
}
}
ExtensionFactory 類似Spring的IOC作用除秀,ExtensionLoader#injectExtension 主要完成IOC功能糯累。
private T createExtension(String name) {
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
type + ") could not be instantiated: " + t.getMessage(), t);
}
}
private T injectExtension(T instance) {
try {
if (objectFactory != null) {
for (Method method : instance.getClass().getMethods()) {
if (method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers())) {
/**
* Check {@link DisableInject} to see if we need auto injection for this property
*/
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
Class<?> pt = method.getParameterTypes()[0];
try {
String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("fail to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
由以下代碼完成真正的注入:
Object object = objectFactory.getExtension(pt, property);
AdaptiveExtensionFactory實例中的factories的size返回應為2,里面只會保存這兩個類實例:
spring=com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory
spi=com.alibaba.dubbo.common.extension.factory.SpiExtensionFactory
因為adaptive=com.alibaba.dubbo.common.extension.factory.AdaptiveExtensionFactory是保存在cachedAdaptiveClass上的
public AdaptiveExtensionFactory() {
ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
for (String name : loader.getSupportedExtensions()) {
list.add(loader.getExtension(name));
}
factories = Collections.unmodifiableList(list);
}
其中册踩,SpringExtensionFactory 中泳姐,會更具Spring的ApplicationContext 來獲取對應的bean實例。
SpringExtensionFactory
public <T> T getExtension(Class<T> type, String name) {
for (ApplicationContext context : contexts) {
if (context.containsBean(name)) {
Object bean = context.getBean(name);
if (type.isInstance(bean)) {
return (T) bean;
}
}
}
logger.warn("No spring extension (bean) named:" + name + ", try to find an extension (bean) of type " + type.getName());
if (Object.class == type) {
return null;
}
for (ApplicationContext context : contexts) {
try {
return context.getBean(type);
} catch (NoUniqueBeanDefinitionException multiBeanExe) {
logger.warn("Find more than 1 spring extensions (beans) of type " + type.getName() + ", will stop auto injection. Please make sure you have specified the concrete parameter type and there's only one extension of that type.");
} catch (NoSuchBeanDefinitionException noBeanExe) {
if (logger.isDebugEnabled()) {
logger.debug("Error when get spring extension(bean) for type:" + type.getName(), noBeanExe);
}
}
}
logger.warn("No spring extension (bean) named:" + name + ", type:" + type.getName() + " found, stop get bean.");
return null;
}
直接從Spring容器中獲取對應的bean
SpiExtensionFactory
@Override
public <T> T getExtension(Class<T> type, String name) {
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
if (!loader.getSupportedExtensions().isEmpty()) {
return loader.getAdaptiveExtension();
}
}
return null;
}
最終還有由 loader.getAdaptiveExtension() 完成暂吉。
AdaptiveExtensionFactory 作用
(1)就是只有當相應的SPI接口的所有方法上是否有帶Adaptive注解的方法胖秒,如果有就會生成動態(tài)類的代碼然后進行動態(tài)編譯(比如使用javassist框架),如果沒有帶Adaptive注解的方法 ,那就說明該SPI接口是沒有Adaptive性質的實現(xiàn)類的缎患,就會拋出異常
(2)動態(tài)類的本質也是在實現(xiàn)相應的SPI接口,它最終也是在調一個現(xiàn)成的SPI實現(xiàn)類來工作,這樣就會有這樣的疑問阎肝,那為何不直接指定呢挤渔,還非得用動態(tài)的呢,這就是為什么凡是在方法上出現(xiàn)Adaptive注解的SPI的Adaptive形式都要動態(tài)的原因了风题,因為dubbo這樣一來就可以做到用不同的Adaptive方法判导,調不同的SPI實現(xiàn)類去處理。
SPI的應用
具體文件內容
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
配置了一個鍵值對沛硅,key為dubbo,值為org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol眼刃,在其它幾個子包下,也有名稱叫做org.apache.dubbo.rpc.Protocol的配置文件摇肌,說明Protocol插口有幾個對應的插件
可以猜測一下擂红,當<dubbo:protocol/>僅僅配置了name="dubbo",port="20880"時围小,會加載哪一個協(xié)議插件呢昵骤,根據名稱,可以猜測肯适,加載的DubboProtocol插件涉茧。那dubbo是怎樣做到的呢,我們來一探究竟疹娶。
三個注解
- SPI:這個注解使用在接口上,標識接口是否是extension(擴展或插口)伦连,可以接收一個默認的extension名稱
- Adaptive: 這個注解可以使用在類或方法上雨饺,決定加載哪一個extension,值為字符串數(shù)組,數(shù)組中的字符串是key值惑淳,比如new String[]{"key1","key2"};先在URL中尋找key1的值额港,如果找到,則使用此值加載extension歧焦,如果key1沒有移斩,則尋找key2的值,如果key2也沒有绢馍,則使用接口SPI注解的值向瓷,如果接口SPI注解,沒有配置默認值舰涌,則將接口名按照首字母大寫分成多個部分猖任,然后以'.'分隔,例如org.apache.dubbo.xxx.YyyInvokerWrapper接口名會變成yyy.invoker.wrapper瓷耙,然后以此名稱做為key到URL尋找朱躺,如果仍沒有找到刁赖,則拋出IllegalStateException異常;Adaptive注解用在類上长搀,表示此類是它實現(xiàn)接口(插口)的自適應插件
- Activate:這個注解可以使用在類或方法上宇弛,用以根據URL的key值判斷當前extension是否生效,當一個extension有多個實現(xiàn)時源请,可以加載特定的extension實現(xiàn)類枪芒,例如extension實現(xiàn)類上有注解@Activate("cache, validation"),則當URL上出現(xiàn)"cache”或“validation" key時巢钓,當前extension才會生效
ExtensionLoader#getAdaptiveExtensionClass
private Class<?> getAdaptiveExtensionClass() {
getExtensionClasses(); //獲取所有插件
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass; //存在插件則返回
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();//創(chuàng)建
}
dubbo啟動時病苗,加載所有的SPI注解的插件,最終根據我們指定的name="dubbo"來加載對應的dubbo協(xié)議症汹。
參考 http://dubbo.apache.org/zh-cn/docs/source_code_guide/dubbo-spi.html