SPI 全稱爲 Service Provider Interface,是一種服務發現機制。SPI 的本質是將接口實現類的全限定名配置在文件中,並由服務加載器讀取配置文件,加載實現類。這樣能夠在運行時,動態爲接口加載實現類。其實這有有點像IOC的思想,將裝配的控制權移到程序以外java
首先定義一個接口:設計模式
public interface Robot { void sayHello(); }
再定義兩個實現類:緩存
public class OptimusPrime implements Robot { @Override public void sayHello() { System.out.println("Hello, I am Optimus Prime."); } } public class Bumblebee implements Robot { @Override public void sayHello() { System.out.println("Hello, I am Bumblebee."); } }
而後META-INF/services 文件夾下建立一個文件,名稱爲 Robot 的全限定名 com.laowangzi.spi.Robot。文件內容爲實現類的全限定的類名,以下:ruby
com.laowangzi.spi.OptimusPrime
com.laowangzi.spi.Bumblebee
測試代碼:app
public class JavaSPITest { @Test public void sayHello() throws Exception { ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class); System.out.println("Java SPI"); serviceLoader.forEach(Robot::sayHello); } }
測試結果:ide
從測試結果能夠看出,兩個實現類被成功的加載,並輸出了相應的內容。函數
Dubbo 並未使用 Java SPI,而是從新實現了一套功能更強的 SPI 機制。Dubbo SPI 的相關邏輯被封裝在了 ExtensionLoader 類中,經過 ExtensionLoader,能夠加載指定的實現類。Dubbo SPI 的實現類配置放置在 META-INF/dubbo 路徑下源碼分析
optimusPrime = com.laowangzi.spi.OptimusPrime
bumblebee = com.laowangzi.spi.Bumblebee
與 Java SPI 實現類配置不一樣,Dubbo SPI 是經過鍵值對的方式進行配置,這樣就能夠按需加載指定的實現類了。另外,在測試 Dubbo SPI 時,須要在 Robot 接口上標註 @SPI 註解。下面來演示一下 Dubbo SPI 的使用方式:性能
public class DubboSPITest { @Test public void sayHello() throws Exception { ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class); Robot optimusPrime = extensionLoader.getExtension("optimusPrime"); optimusPrime.sayHello(); Robot bumblebee = extensionLoader.getExtension("bumblebee"); bumblebee.sayHello(); } }
測試結果以下:學習
Dubbo SPI 對 Java SPI 作的改進,如下內容引用至 Dubbo 官方文檔。
其實就是提升了性能和增長了功能
一想到性能就應該想到緩存,因此若是與jdk的spi對比,那麼能夠有如下幾個點
1.增長緩存
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { if (type == null)//拓展點類型非空判斷 throw new IllegalArgumentException("Extension type == null"); if(!type.isInterface()) {//拓展點類型只能是接口 throw new IllegalArgumentException("Extension type(" + type + ") is not interface!"); } if(!withExtensionAnnotation(type)) {//須要添加spi註解,不然拋異常 throw new IllegalArgumentException("Extension type(" + type + ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!"); } //從緩存EXTENSION_LOADERS中獲取,若是不存在則新建後加入緩存 //對於每個拓展,都會有且只有一個ExtensionLoader與其對應 ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); if (loader == null) { EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); } return loader; } private ExtensionLoader(Class<?> type) { this.type = type; //這裏會存在遞歸調用,ExtensionFactory的objectFactory爲null,其餘的均爲AdaptiveExtensionFactory //AdaptiveExtensionFactory的factories中有SpiExtensionFactory,SpringExtensionFactory //getAdaptiveExtension()這個是獲取一個拓展裝飾類對象.細節在下篇講解 objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); }
如下是緩存拓展點對象的
public T getExtension(String name) { if (name == null || name.length() == 0) throw new IllegalArgumentException("Extension name == null"); if ("true".equals(name)) { return getDefaultExtension(); } Holder<Object> holder = cachedInstances.get(name); if (holder == null) { cachedInstances.putIfAbsent(name, new Holder<Object>()); holder = cachedInstances.get(name); } Object instance = holder.get(); if (instance == null) { synchronized (holder) { instance = holder.get(); if (instance == null) { instance = createExtension(name); holder.set(instance); } } } return (T) instance; }
2.@SPI註解
@SPI("javassist") public interface Compiler { //省略... }
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); }
//com.alibaba.dubbo.common.compiler.Compiler 文件配置以下 adaptive=com.alibaba.dubbo.common.compiler.support.AdaptiveCompiler jdk=com.alibaba.dubbo.common.compiler.support.JdkCompiler javassist=com.alibaba.dubbo.common.compiler.support.JavassistCompiler
spi
要用for循環,而後if判斷才能獲取到指定的spi對象,dubbo用指定的key就能夠獲取//返回指定名字的擴展 public T getExtension(String name){}
spi
不支持默認值,dubbo增長了默認值的設計//@SPI("javassist")表明默認的spi對象,好比Compiler默認使用的是javassist,可經過 ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class); compiler = loader.getDefaultExtension(); //方式獲取實現類,根據配置,即爲 //com.alibaba.dubbo.common.compiler.support.JavassistCompiler
spi
增長了IoC
、AOP
dubbo spi 中的IOC
objectFactory
就是dubbo的IoC提供對象。
public <T> T getExtension(Class<T> type, String name) { //factories=[SpiExtensionFactory,SpringExtensionFactory] //遍歷獲取spi,先從SpiExtensionFactory獲取,若是沒有,再從SpringExtensionFactory獲取 for (ExtensionFactory factory : factories) { T extension = factory.getExtension(type, name); if (extension != null) { return extension; } } return null; }
SpiExtensionFactory
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().size() > 0) { return loader.getAdaptiveExtension(); } } return null; }
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; } } } return null; }
dubbo spi 中的 aop
//dubbo spi中的aop instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
文檔介紹:
ExtensionLoader 在加載擴展點時,若是加載到的擴展點有拷貝構造函數,則斷定爲擴展點 Wrapper 類。
Wrapper類內容:
package com.alibaba.xxx; import com.alibaba.dubbo.rpc.Protocol; public class XxxProtocolWrapper implemenets Protocol { Protocol impl; public XxxProtocol(Protocol protocol) { impl = protocol; } // 接口方法作一個操做後,再調用extension的方法 public void refer() { //... 一些操做 impl.refer(); // ... 一些操做 } // ... }
經過 Wrapper 類能夠把全部擴展點公共邏輯移至 Wrapper 中。新加的 Wrapper 在全部的擴展點上添加了邏輯,有些相似 AOP,即 Wrapper 代理了擴展點。
dubbo裏面的spi
增長的aop
,其實就是裝飾者設計模式
dubbo裏面有個概念叫擴展點自適應
,也就是給接口注入拓展點是一個Adaptive
實例,直到方法執行時,才決定調用的是哪個拓展點的實現.
實現類的對應關係是配置在文件中的,從ExtensionLoader會解析文件將數據添加到一個Map中,這樣能夠直接經過「dubbo」來獲取到實現類DubboProtocol。這種實現仍是比較簡單,但有一個問題就是須要在代碼中寫死使用哪一個實現類,這個就和SPI的初衷有所差異了,所以Dubbo提供了一個另一個註解@Adaptive
獲取適配器類
Dubbo經過註解@Adaptive做爲標記實現了一個適配器類,而且這個類是動態生成的,所以在Dubbo的源碼中是看不到代碼的,可是咱們仍是能夠看到其實現方式的。Dubbo提供一個動態的適配器類的緣由就是能夠經過配置文件來動態的使用想要的接口實現類,而且不用改變任何接口的代碼,簡單來講其也是經過代理來實現的。
@SPI("dubbo") public interface Protocol { int getDefaultPort(); @Adaptive <T> Exporter<T> export(Invoker<T> invoker) throws RpcException; @Adaptive <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException; void destroy(); }
在接口Protocol的兩個方法export和refer方法中都添加了註解@Adaptive,咱們能夠認爲這兩個接口方法會被代理實現。
ExtensionLoader提供了接口獲取代理類的方法:
Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
一、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("fail to create adaptive instance: " + t.toString(), t); } } } } else { throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError); } } return (T) instance; }
二、createAdaptiveExtension獲取一個對象在初始化
@SuppressWarnings("unchecked") private T createAdaptiveExtension() { try { return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e); } }
三、getAdaptiveExtensionClass獲取類對象
private Class<?> getAdaptiveExtensionClass() { getExtensionClasses(); if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } return cachedAdaptiveClass = createAdaptiveExtensionClass(); }
四、createAdaptiveExtensionClass中首先會獲取代理類的源碼,而後在編譯源碼獲取一個Class
private Class<?> createAdaptiveExtensionClass() { //獲取一個字符串源碼 String code = createAdaptiveExtensionClassCode(); ClassLoader classLoader = findClassLoader(); com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); //編譯生成代理類 return compiler.compile(code, classLoader); }
五、Dubbo實現一個適配器類的關鍵在createAdaptiveExtensionClassCode,會生成一個類的代理適配器類的源碼
//生成一個類的代理類源碼 private String createAdaptiveExtensionClassCode() { StringBuilder codeBuidler = new StringBuilder(); Method[] methods = type.getMethods(); boolean hasAdaptiveAnnotation = false; for(Method m : methods) { if(m.isAnnotationPresent(Adaptive.class)) { hasAdaptiveAnnotation = true; break; } } // 徹底沒有Adaptive方法,則不須要生成Adaptive類 if(! hasAdaptiveAnnotation) throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!"); codeBuidler.append("package " + type.getPackage().getName() + ";"); codeBuidler.append("\nimport " + ExtensionLoader.class.getName() + ";"); codeBuidler.append("\npublic class " + type.getSimpleName() + "$Adpative" + " implements " + type.getCanonicalName() + " {"); for (Method method : methods) { Class<?> rt = method.getReturnType(); Class<?>[] pts = method.getParameterTypes(); Class<?>[] ets = method.getExceptionTypes(); Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class); StringBuilder code = new StringBuilder(512); if (adaptiveAnnotation == null) { code.append("throw new UnsupportedOperationException(\"method ") .append(method.toString()).append(" of interface ") .append(type.getName()).append(" is not adaptive method!\");"); } else { int urlTypeIndex = -1; for (int i = 0; i < pts.length; ++i) { if (pts[i].equals(URL.class)) { urlTypeIndex = i; break; } } // 有類型爲URL的參數 if (urlTypeIndex != -1) { // Null Point check String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"url == null\");", urlTypeIndex); code.append(s); s = String.format("\n%s url = arg%d;", URL.class.getName(), urlTypeIndex); code.append(s); } // 參數沒有URL類型 else { String attribMethod = null; // 找到參數的URL屬性 LBL_PTS: for (int i = 0; i < pts.length; ++i) { Method[] ms = pts[i].getMethods(); for (Method m : ms) { String name = m.getName(); if ((name.startsWith("get") || name.length() > 3) && Modifier.isPublic(m.getModifiers()) && !Modifier.isStatic(m.getModifiers()) && m.getParameterTypes().length == 0 && m.getReturnType() == URL.class) { urlTypeIndex = i; attribMethod = name; break LBL_PTS; } } } if(attribMethod == null) { throw new IllegalStateException("fail to create adative class for interface " + type.getName() + ": not found url parameter or url attribute in parameters of method " + method.getName()); } // Null point check String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");", urlTypeIndex, pts[urlTypeIndex].getName()); code.append(s); s = String.format("\nif (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");", urlTypeIndex, attribMethod, pts[urlTypeIndex].getName(), attribMethod); code.append(s); s = String.format("%s url = arg%d.%s();",URL.class.getName(), urlTypeIndex, attribMethod); code.append(s); } String[] value = adaptiveAnnotation.value(); // 沒有設置Key,則使用「擴展點接口名的點分隔 做爲Key if(value.length == 0) { char[] charArray = type.getSimpleName().toCharArray(); StringBuilder sb = new StringBuilder(128); for (int i = 0; i < charArray.length; i++) { if(Character.isUpperCase(charArray[i])) { if(i != 0) { sb.append("."); } sb.append(Character.toLowerCase(charArray[i])); } else { sb.append(charArray[i]); } } value = new String[] {sb.toString()}; } boolean hasInvocation = false; for (int i = 0; i < pts.length; ++i) { if (pts[i].getName().equals("com.alibaba.dubbo.rpc.Invocation")) { // Null Point check String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");", i); code.append(s); s = String.format("\nString methodName = arg%d.getMethodName();", i); code.append(s); hasInvocation = true; break; } } String defaultExtName = cachedDefaultName; String getNameCode = null; for (int i = value.length - 1; i >= 0; --i) { if(i == value.length - 1) { if(null != defaultExtName) { if(!"protocol".equals(value[i])) if (hasInvocation) getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName); else getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName); else getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName); } else { if(!"protocol".equals(value[i])) if (hasInvocation) getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName); else getNameCode = String.format("url.getParameter(\"%s\")", value[i]); else getNameCode = "url.getProtocol()"; } } else { if(!"protocol".equals(value[i])) if (hasInvocation) getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName); else getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode); else getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode); } } code.append("\nString extName = ").append(getNameCode).append(";"); // check extName == null? String s = String.format("\nif(extName == null) " + "throw new IllegalStateException(\"Fail to get extension(%s) name from url(\" + url.toString() + \") use keys(%s)\");", type.getName(), Arrays.toString(value)); code.append(s); s = String.format("\n%s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);", type.getName(), ExtensionLoader.class.getSimpleName(), type.getName()); code.append(s); // return statement if (!rt.equals(void.class)) { code.append("\nreturn "); } s = String.format("extension.%s(", method.getName()); code.append(s); for (int i = 0; i < pts.length; i++) { if (i != 0) code.append(", "); code.append("arg").append(i); } code.append(");"); } codeBuidler.append("\npublic " + rt.getCanonicalName() + " " + method.getName() + "("); for (int i = 0; i < pts.length; i ++) { if (i > 0) { codeBuidler.append(", "); } codeBuidler.append(pts[i].getCanonicalName()); codeBuidler.append(" "); codeBuidler.append("arg" + i); } codeBuidler.append(")"); if (ets.length > 0) { codeBuidler.append(" throws "); for (int i = 0; i < ets.length; i ++) { if (i > 0) { codeBuidler.append(", "); } codeBuidler.append(ets[i].getCanonicalName()); } } codeBuidler.append(" {"); codeBuidler.append(code.toString()); codeBuidler.append("\n}"); } codeBuidler.append("\n}"); if (logger.isDebugEnabled()) { logger.debug(codeBuidler.toString()); } return codeBuidler.toString(); }
Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); 生成的代理適配器類的源碼以下:
package com.alibaba.dubbo.rpc; import com.alibaba.dubbo.common.extension.ExtensionLoader; public class Protocol$Adpative 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 (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); } }
生成的代理器類若是被調用refer方法時會發生的處理操做,簡單來講是經過代理方式來生成一個適配器類。
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"); //被Adattive註解的方法中有個參數必須是URL對象或者參數能夠經過getUrl()獲取到一個url否則會拋出異常 com.alibaba.dubbo.common.URL url = arg1; //首先會判斷url中是否指定了協議,否則使用默認 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); //最終調用實現類的refer方法 return extension.refer(arg0, arg1); }
Dubbo經過兩個註解SPI和Adaptive,經過參數中URL.getProtocol能夠動態的使用各類實現類。
參考:1.田小波dubbo源碼分析-SPI機制