Dubbo强大的扩展能力,主要依赖于它本身实现的一套SPI机制,开发人员能够根据dubbo的规范进行扩展示有的功能或者替换现有的实现,dubbo内部的功能的实现也都是经过的SPI来实现的,这是dubbo的功能高度可插拔的缘由。可是dubbo并无使用Java的SPI,之因此没有使用Java自带的SPI在官方文档上有以下阐述。java
若是想要使用Java自带的SPI,能够参考java SPI (Service Provider Interface)缓存
在扩展类的jar包内,放置扩展点配置文件:META-INF/dubbo/接口全限定名,内容为:配置名=扩展实现类全限定名,多个实现类用换行符分隔。
注意:这里的配置文件是放在你本身的jar包内,不是dubbo自己的jar包内,Dubbo会全ClassPath扫描全部jar包内同名的这个文件,而后进行合并
dubbo SPI的主要是经过ExtensionLoader这个类来实现的,全部关键代码也都在这个类中,使用方式有两种,一种能够手工指定,另一种是自动生成。ruby
有一个DuSPI的接口,接口中有一个sayHi的方法,而后经过dubbo的SPI机制进行调用其相关实现。ide
接口类url
必需要有@SPI注解 ,SPI注解能够有一个value参数,表明默认的实现。spa
//接口类,定义了一个SayHello的方法,须要被实现 @SPI public interface DuSPI { public String SayHello(String hi); } //实现类,没什么好说的。 public class LocalDuSPI implements DuSPI { @Override public String SayHello(String hi) { return "FROM LocalDuSPI : "+hi; } }
扩展点配置.net
调用方式设计
public class DuSPIMain { private static final DuSPI duSPI = ExtensionLoader.getExtensionLoader(DuSPI.class).getExtension("local"); public static void main(String[] args) { String hi = duSPI.SayHello("扩展SPI"); System.out.println(hi); } }
输出结果为代理
上述就是很是简单的手工指定实现类用例。getExtension("local");接收一个key值,这个key就是spi文件中的key,获得的实现就是key对应的实现类。code
ExtensionLoader还有一种自动寻找扩展的方法,getAdaptiveExtension,不须要手工指定实现,而是在方法具体调用的时候自动发现其具体的实现类。例如Protocol扩展,它的export方法就是经过invoke的URL中的Protocol,自动的发现实现
private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension(); Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString())); Exporter<?> exporter = protocol.export(invoker);
经过对类注释,能够看到这个类主要实现如下几个功能
自动注入关联扩展点
自动Wrap上扩展点的Wrap类
缺省得到的扩展点的一个内部类Adaptive
这个类的实例需简要有一个getExtensionLoader的静态工厂方法,接收一个Class类型的对象,来获取其实例。
@SuppressWarnings("unchecked") 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)) { throw new IllegalArgumentException("Extension type(" + type + ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!"); } 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; }
经过以上代码能够看出,传入的对象必须是一个接口,而且必须是带有@SPI注解,获取完了之后,针对相应的接口类型会缓存到map中去。
根据key返回相应的实现类方法
getExtensionClass(String name)
首先会去在缓存里面查找,看是否已经加载过这个类,若是加载过的话就直接返回,若是没有,则配置文件中查找,而后进行加载。这样就达到了它说的jdk的spi要所有加载全部扩展,而不是按需加载,若是有相应的扩展找不到就会报错的或者一个类的加载很耗的问题。
建立扩展是使用的createExtension方法,类实例化之后,会查看有没有对其余扩展点的应用,若是有的话就调用injectExtension方法,去注入其余的扩展类,这样就达到了它所说的AOP的功能。
加载扩展类的方法是loadExtensionClasses
private Map<String, Class<?>> loadExtensionClasses() { final SPI defaultAnnotation = type.getAnnotation(SPI.class); if(defaultAnnotation != null) { String value = defaultAnnotation.value(); if(value != null && (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<?>>(); loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY); loadFile(extensionClasses, DUBBO_DIRECTORY); loadFile(extensionClasses, SERVICES_DIRECTORY); return extensionClasses; }
经过以上能够得出如下结论
private void loadFile(Map<String, Class<?>> extensionClasses, String dir) { String fileName = dir + type.getName(); try { ... if (urls != null) { while (urls.hasMoreElements()) { java.net.URL url = urls.nextElement(); try { BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8")); try { String line = null; while ((line = reader.readLine()) != null) { final int ci = line.indexOf('#'); if (ci >= 0) line = line.substring(0, ci); line = line.trim(); ... int i = line.indexOf('='); if (i > 0) { name = line.substring(0, i).trim(); line = line.substring(i + 1).trim(); } ... Class<?> clazz = Class.forName(line, true, classLoader); if (! type.isAssignableFrom(clazz)) { throw new IllegalStateException("Error when load extension class(interface: " + type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + "is not subtype of interface."); } if (clazz.isAnnotationPresent(Adaptive.class)) { if(cachedAdaptiveClass == null) { cachedAdaptiveClass = clazz; } else if (! cachedAdaptiveClass.equals(clazz)) { throw new IllegalStateException("More than 1 adaptive class found: " + cachedAdaptiveClass.getClass().getName() + ", " + clazz.getClass().getName()); } } else { try { clazz.getConstructor(type); ... String[] names = NAME_SEPARATOR.split(name); if (names != null && names.length > 0) { Activate activate = clazz.getAnnotation(Activate.class); if (activate != null) { cachedActivates.put(names[0], activate); } ... } } } } } }
在生成对象引用的时候,并不直接生成实现对象,而是先生成一个代理对象,直到调用的时候,在根据传递的参数决定调用的是哪一个具体的实现类。Dubbo的SPI实现了自动发现机制。经过调用getAdaptiveExtension方法生成代理对象。在Protocol接口的使用中就用到了这个方法。
ServiceConfig类用须要有引用对象Protocol,这是标明dubbo的服务注册所使用的协议。Protocol的实现类不少
可是在声明的时候并不指名要调用哪一种协议的实现。
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
代码上调用了getAdaptiveExtension()方法,生成代理对象,因为是代码生成的代理对象,在dubbo中并无定义,因此只能把类文件拼装的代码的代码打印出来,生成类文件的代码是ExtensionLoader的createAdaptiveExtensionClassCode方法。生成的代理对象的类的定义以下:
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.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws java.lang.Class { 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); } public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.Invoker { 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); } }
这是一个经过javassit自动生成的一个类,实现了Protocol接口,对于服务端内容导出发布,实现了export方法,export 方法中,首先获取Invoker的url,URL中获取Protocol的protocol属性的值,看是否进行了设置,默认为dubbo,而后再调用dubboSPI(ExtensionLoader)机制中的getExtension方法,把Key传入进去,以达到自动获取Protocol的目的,而后找到真正的实现类,再调用其export方法。若是采用zk做为注册中心,经过配置<dubbo:registry address="zookeeper://localhost:2181" />
这样取得的key是就是registry,真正的实现类则是
com.alibaba.dubbo.registry.integration.RegistryProtocol
固然,上述只是讲述了dubbo spi设计的一个大概的思路,还有不少的其余的细节问题在本文中并未提到,不过这对于了解dubbo自定义实现SPI的设计已经足够。