在 Dubbo 中,不少拓展都是经过 SPI 机制进行加载的,好比 Protocol、Cluster、LoadBalance 等,这些都是Dubbo的基础组件。这些基础组件的拓展不是在系统框架启动阶段被加载,而是拓展方法被调用时,根据运行时参数(URL)进行动态加载的。自适应拓展机制是创建在SPI基础之上的,自适应拓展类的核心实现:在拓展接口的方法被调用时,经过SPI加载具体的拓展实现类,并调用拓展对象的同名方法。能够看到自适应拓展是创建在SPI基础上的实现的功能,自适应拓展加上SPI实现Dubbo可拓展和解耦,这是Dubbo很是重要机制。java
自适应拓展类的过程是:选根据接口方法上的"@Adaptive" 注解生成自适应代码,而后经过Javassist编译建立自适应拓展类代码(使用系统内置特殊的自适应拓展类),再经过反射建立自适应拓展类的实例,而且保存至缓存。须要注意系统内置特殊的自适应拓展类是,分别是 AdaptiveCompiler 和 AdaptiveExtensionFactory,由于这两个类的类被 Adaptive 注解了(是类被注解,而不是方法)。Dubbo这样设计的缘由是:在产生自适应拓展类代码时,为了不死循环产生自适应拓展类代码,这一点很重要,特别是新手很容易搞混淆。apache
这里以"org.apache.dubbo.rpc.Protocol"做为示例讲解下建立自适应拓展类的逻辑,由于这部分功能逻辑单纯从代码上看很容易搞错顺序或关系,因此单独拿出来解释下。设计模式
代码入口是"ExtensionLoader.getExtensionLoader"和SPI章节入口是同样的,但咱们要了解的内容不同。下面是获取ExtensionLoader实例的代码在SPI章节讲解过,这里就不啰嗦了。缓存
@SuppressWarnings("unchecked") public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { logger.info("type:"+type); //空值判断 if (type == null) { throw new IllegalArgumentException("Extension type == null"); } //判断是否接口类 if (!type.isInterface()) { throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!"); } // 即:type.isAnnotationPresent(SPI.class),是否包含SPI注解 if (!withExtensionAnnotation(type)) { throw new IllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!"); } //new ExtensionLoader()时, //触发ExtensionFactory的ExtensionLoader // 实例建立:ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); 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; }
只是有一点,ExtensionLoader的私用构造方法还有其它代码逻辑,也就是在初始化其它扩展类的ExtensionLoader实例以前,必须先初始化ExtensionFactory.class的ExtensionLoader实例。app
private ExtensionLoader(Class<?> type) { this.type = type; //先执行:ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension(),即获得AdaptiveExtensionFactory //ExtensionFactory的objectFactory属性为空 //非ExtensionFactory的objectFactory属性为AdaptiveExtensionFactory objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); }
ExtensionFactory.class在获取自适应拓展类实例时,由于ExtensionFactory.class的字类AdaptiveExtensionFactory有“@Adaptive”,因此AdaptiveExtensionFactory直接返回而且成为自适应拓展类,这是特殊的例子。框架
在获取自适应拓展类实例,先是从缓存获取,若是缓存没有命中,再建立。由于建立一个自适应拓展类实例太耗资源和时间了,因此这些对象都是以单例方式运行。dom
@SuppressWarnings("unchecked") public T getAdaptiveExtension() { logger.info("getAdaptiveExtension.type:"+type); // 从缓存中获取自适应拓展 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; }
这里和SPI同样,均使用了Dubbo的IOC,在注入依赖以前先获取自适应类,并建立实例。ide
@SuppressWarnings("unchecked") private T createAdaptiveExtension() { try { //获取自适应拓展类,而后建立实例,并经过反射注入该实例的依赖(Dubbo的IOC实现) return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e); } }
这个方法包含三个逻辑性能
private Class<?> getAdaptiveExtensionClass() { // 获取全部的拓展类 getExtensionClasses(); // 加载完全部的实现以后,若是发现有cachedAdaptiveClass不为空,则直接返回缓存, // 同时也意味着自拓展适应类:cachedAdaptiveClass是AdaptiveExtensionFactory或AdaptiveCompiler if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } // 建立自适应拓展类 return cachedAdaptiveClass = createAdaptiveExtensionClass(); }
先获取指定类(如:"org.apache.dubbo.rpc.Protocol")全部的拓展类,在获取拓展类时,判断拓展类是否标注了"@Adaptive",若是标注了"@Adaptive"表示当前类为自适应拓展类,不须要经过动态生成代码建立自适应拓展类。如:AdaptiveExtensionFactory和AdaptiveCompiler。ui
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
//检查clazz(扩展实现类)是否type(接口)的实现类
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error occurred when loading extension class (interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + " is not subtype of interface.");
}
// 检测目标类上是否有 Adaptive 注解,若是有则设置 cachedAdaptiveClass缓存
if (clazz.isAnnotationPresent(Adaptive.class)) {
cacheAdaptiveClass(clazz);
// 检测 clazz 是不是 Wrapper 类型,若是有则存储 clazz 到 cachedWrapperClasses 缓存中
} else if (isWrapperClass(clazz)) {
//判断是不是wrapper类型(包装类型),实现类的构造方法中的参数是扩展点类型的,就是一个Wrapper类
cacheWrapperClass(clazz);
} else {// 程序进入此分支,代表 clazz 是一个普通的拓展类
// 获取默认的构造方法
clazz.getConstructor();
if (StringUtils.isEmpty(name)) {//name即Key值,如:spi=org.apache.dubbo.common.extension.factory.SpiExtensionFactory
// 若是 name 为空,则尝试从 Extension 注解中获取 name,或使用小写的类名做为 name
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
String[] names = NAME_SEPARATOR.split(name);// 切分 name
if (ArrayUtils.isNotEmpty(names)) {
cacheActivateClass(clazz, names[0]);
for (String n : names) {
cacheName(clazz, n);// 存储 Class 到名称的映射关系
saveInExtensionClass(extensionClasses, clazz, n);//缓存Name与Clazz映谢关系
}
}
}
}
private void cacheAdaptiveClass(Class<?> clazz) { if (cachedAdaptiveClass == null) { // 设置 cachedAdaptiveClass缓存,并且这个类只有一个,也就是在指定的扩展类范围只能有一个类能够标注“@Adaptive” cachedAdaptiveClass = clazz; } else if (!cachedAdaptiveClass.equals(clazz)) { throw new IllegalStateException("More than 1 adaptive class found: " + cachedAdaptiveClass.getClass().getName() + ", " + clazz.getClass().getName()); } }
若是全部拓展实现类都没有合适标注"@Adaptive",那建立自适应拓展类的代码。这个方法详细解释了为何须要 AdaptiveCompiler 和 AdaptiveExtensionFactory的缘由。
private Class<?> createAdaptiveExtensionClass() { // 构建自适应拓展代码 String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate(); // 获取类加载器 ClassLoader classLoader = findClassLoader(); //dubbo默认使用javassist获取编译器 //若是没有AdaptiveExtensionFactory和AdaptiveCompiler这两个类,那cachedAdaptiveClass一直为空,这个方法就会一直是死循环 //这也是Dubbo有会把@Adaptive标注类上面的缘由。 //解析Compiler的实现类的时候,会在getAdaptiveExtensionClass中直接返回AdaptiveCompiler,不须要动态生成代码 org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader. getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); // 编译代码,生成 Class return compiler.compile(code, classLoader); }
产生自适应拓展类代码的逻辑比较复杂,涉及到的细节也比较多,这里以"org.apache.dubbo.rpc.Protocol"产生的适应拓展类代码做为例子,方便在阅读源码时做为对比,方便理解。
package org.apache.dubbo.rpc; import org.apache.dubbo.common.extension.ExtensionLoader; public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol { public void destroy() { throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!"); } public int getDefaultPort() { throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!"); } public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException { if (arg1 == null) throw new IllegalArgumentException("url == null"); org.apache.dubbo.common.URL url = arg1; String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); if (extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])"); org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName); return extension.refer(arg0, arg1); } public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException { if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null"); if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null"); org.apache.dubbo.common.URL url = arg0.getUrl(); String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); if (extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])"); org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName); return extension.export(arg0); } }
要产生自适应拓展类的接口至少有一个方法被标注了"@Adaptive",不然不产生代任何码。由于在这个方法前面已经排除类的标注了,这里只须要确认方法有注解就好了。
public String generate() { //Dubbo 要求该接口至少有一个方法被 Adaptive 注解修饰 if (!hasAdaptiveMethod()) { throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!"); } StringBuilder code = new StringBuilder(); code.append(generatePackageInfo());//生成包代码,如:package org.apache.dubbo.rpc; code.append(generateImports());//生成import代码,如import org.apache.dubbo.common.extension.ExtensionLoader; code.append(generateClassDeclaration());//生成声明类代码,如:public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol { // 经过反射获取全部的方法 Method[] methods = type.getMethods(); // 遍历方法列表 for (Method method : methods) { code.append(generateMethod(method)); } code.append("}"); if (logger.isDebugEnabled()) { logger.debug(code.toString()); } logger.info("\n\n code.toString:"+code.toString()); return code.toString(); }
产生代码的重点在于方法,产生方法的时须要根据参数判断和逻辑处理,相对复杂。
/** * generate method declaration * 生成"方法"代码逻辑 */ private String generateMethod(Method method) { //返回参数类型,如:org.apache.dubbo.rpc.Exporter String methodReturnType = method.getReturnType().getCanonicalName(); //方法名代码,如:export String methodName = method.getName(); //方法内容代码 String methodContent = generateMethodContent(method); //方法参数代码,如:org.apache.dubbo.rpc.Invoker arg0 String methodArgs = generateMethodArguments(method); //抛异常代码,如:throws org.apache.dubbo.rpc.RpcException String methodThrows = generateMethodThrows(method); return String.format(CODE_METHOD_DECLARATION, methodReturnType, methodName, methodArgs, methodThrows, methodContent); }
产生方法代码时,首先产生是没有标注 "@Adaptive"方法的逻辑,而后须要直接处理参数URL或间接处理URL参数,如:判断是否为空,判断参数的属性getURL方法等。若是全部参数都不直接包含URL或间接包含URL参数,就直接抛异常。URL是Dubbo在每一个层流转过程当中,用于上传下达传递参数的,是一个很重要的数据类型。
private String generateMethodContent(Method method) { //获取方法上Adaptive注解 Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class); StringBuilder code = new StringBuilder(512); //没有标注 Adaptive 注解的方法生成代理逻辑:仅会生成一句抛出异常的代码。 // throw new UnsupportedOperationException("The method if (adaptiveAnnotation == null) { return generateUnsupported(method); } else { // 遍历参数列表,肯定 org.apache.dubbo.common.URL在方法参数位置 int urlTypeIndex = getUrlTypeIndex(method); // urlTypeIndex != -1,表示参数列表中存在 URL 参数 // found parameter in URL type if (urlTypeIndex != -1) { // 为 URL 类型参数生成判空代码,格式以下: // if (arg1 == null) throw new IllegalArgumentException("url == null"); code.append(generateUrlNullCheck(urlTypeIndex)); } else { // did not find parameter in URL type //若是参数不包括URL,那从参数的属性应该包含URL,若是参数的属性不包含URL属性,那抛异常,如: //if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null"); // if (arg0.getUrl() == null) // throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null"); code.append(generateUrlAssignmentIndirectly(method)); } //获取 Adaptive 注解值,若是不存在,采用类名,如:LoadBalance 通过处理后,获得 load.balance String[] value = getMethodAdaptiveValue(adaptiveAnnotation); //检测方法列表中是否存在 org.apache.dubbo.rpc.Invocation 类型的参数 boolean hasInvocation = hasInvocationArgument(method); // append代码:为Invocation 类型参数生成判空代码 code.append(generateInvocationArgumentNullCheck(method)); //append代码:生成拓展名获取逻辑代码 // String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() ); code.append(generateExtNameAssignment(value, hasInvocation)); // 生成 extName 判空代码 //if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])"); code.append(generateExtNameNullCheck(value)); //生成拓展获取代码 //如:org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName); code.append(generateExtensionAssignment()); //生成返回值代码 //return extension.export(arg0); code.append(generateReturnAndInvocation(method)); } return code.toString(); }
经过反射方法获取方法名称、访问权限、方法参数数量,而后判断参数是否包括getUrl方法。
private String generateUrlAssignmentIndirectly(Method method) { Class<?>[] pts = method.getParameterTypes(); // 遍历方法的参数类型列表 // find URL getter method for (int i = 0; i < pts.length; ++i) { // 获取某一类型参数的所有方法 for (Method m : pts[i].getMethods()) { // 遍历方法列表,寻找可返回 URL 的 getter 方法 String name = m.getName(); // 1. 方法名以 get 开头,或方法名大于3个字符 // 2. 方法的访问权限为 public // 3. 非静态方法 // 4. 方法参数数量为0 // 5. 方法返回值类型为 URL if ((name.startsWith("get") || name.length() > 3) && Modifier.isPublic(m.getModifiers()) && !Modifier.isStatic(m.getModifiers()) && m.getParameterTypes().length == 0 && m.getReturnType() == URL.class) { return generateGetUrlNullCheck(i, pts[i], name); } } } // 若是全部参数中均不包含可返回 URL 的 getter 方法,则抛出异常 // getter method not found, throw throw new IllegalStateException("Failed to create adaptive class for interface " + type.getName() + ": not found url parameter or url attribute in parameters of method " + method.getName()); }
生成拓展名获取逻辑代码,这部分代码比较复杂,判断条件分支也多,须要屡次断点而且对照已产生的代码一块儿阅读比较容易理解。
private String generateExtNameAssignment(String[] value, boolean hasInvocation) { String getNameCode = null; // 遍历 value,这里的 value 是 Adaptive 的注解值。 // 此处循环目的是生成从 URL 中获取拓展名的代码,生成的代码会赋值给 getNameCode 变量。注意这 // 个循环的遍历顺序是由后向前遍历的。 for (int i = value.length - 1; i >= 0; --i) { if (i == value.length - 1) { // 默认拓展名非空 if (null != defaultExtName) { // protocol 是 url 的一部分,可经过 getProtocol 方法获取,其余的则是从 // URL 参数中获取。由于获取方式不一样,因此这里要判断 value[i] 是否为 protocol if (!"protocol".equals(value[i])) { // hasInvocation 用于标识方法参数列表中是否有 Invocation 类型参数 if (hasInvocation) { // 生成的代码功能等价于下面的代码: // url.getMethodParameter(methodName, value[i], defaultExtName) // 以 LoadBalance 接口的 select 方法为例,最终生成的代码以下: // url.getMethodParameter(methodName, "loadbalance", "random") getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName); } else { // 生成的代码功能等价于下面的代码: // url.getParameter(value[i], defaultExtName) getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName); } } else { // 生成的代码功能等价于下面的代码: // ( url.getProtocol() == null ? defaultExtName : url.getProtocol() ) 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 { // 生成的代码功能等价于下面的代码: // url.getParameter(value[i]) getNameCode = String.format("url.getParameter(\"%s\")", value[i]); } } else { // 生成从 url 中获取协议的代码,好比 "dubbo" getNameCode = "url.getProtocol()"; } } } else { if (!"protocol".equals(value[i])) { if (hasInvocation) { // 生成代码格式同上 getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName); } else { // 生成的代码功能等价于下面的代码: // url.getParameter(value[i], getNameCode) // 以 Transporter 接口的 connect 方法为例,最终生成的代码以下: // url.getParameter("client", url.getParameter("transporter", "netty")) getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode); } } else { // 生成的代码功能等价于下面的代码: // url.getProtocol() == null ? getNameCode : url.getProtocol() // 以 Protocol 接口的 connect 方法为例,最终生成的代码以下: // url.getProtocol() == null ? "dubbo" : url.getProtocol() getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode); } } } // 生成 extName 赋值代码 return String.format(CODE_EXT_NAME_ASSIGNMENT, getNameCode); }
获得一个完整的自适应拓展类后,自适应拓展类的核心逻辑:在拓展接口的方法被调用时,基于SPI机制而且根据URL或间接的URL参数加载具体的拓展实现类,并调用拓展对象的同名方法。
最后获得的是String类型自适应拓展类代码,根据String代码建立Class对象和实现,这时候就是轮到Javassit出场了。
@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 ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class); //默认的Compiler名字 String name = DEFAULT_COMPILER; // copy reference if (name != null && name.length() > 0) { compiler = loader.getExtension(name); } else { //若是没有设置编译器的扩展名,就使用默认编译器 //默认值:org.apache.dubbo.common.compiler.support.JavassistCompiler compiler = loader.getDefaultExtension(); } //扩展实现类来编译代码 return compiler.compile(code, classLoader); } }
本章节涉及的知识点有字节码技术,Java主要流行的字节码操纵框架有Javassist 和 ASM。字节码技术主要是Dubbo默认是使用Javassist字节码,Dubbo使用Javassist是基于性能和易用性两方面考虑的。具体能够参考早期 dubbo 的做者梁飞的博客 http://javatar.iteye.com/blog/814426,从这里面也能够知道技术选型的过程和考量点。
本章节涉及的知识点还有设计模式-代理模式,代理模式在Dubbo中是普遍应用的,Dubbo并无直接使用JDK的代理技术,而是经过Javassist实现代理。代理模式在Dubbo、Spring都是普遍应用, 特别是Dubbo的服务导入与导出部分大量使用。
本章节涉及到自适应拓展类机制,是Dubbo的重要的基础机制。基于Dubbo的SPI的自适应拓展机制能够动态调用拓展实现类,不少基础组件都依赖些机制Protocol、Cluster、LoadBalance 等。