前言 上一篇文章中我们分析了dubbo spi机制,但是遗留了自适应扩展并没有展开说明,这篇文章就是来填坑的。上篇文章中也介绍了固定的自适应扩展类以及加载的流程,这篇文章主要专注于自动生成的自适应扩展类以及自适应扩展对象的创建,就不再过多介绍固定的自适应扩展。自适应扩展整体上需要讨论三部分内容:自适应扩展原理
、自适应扩展类串的生成
和 动态编译
。 该篇文章将讨论前两个部分,动态编译会单独写一篇文章详细说明。
自适应扩展原理 扩展点的扩展类一般会在框架启动时被加载,但我们这次的主角并不会在框架启动时被加载,只可能在获取自适应实现的时候被创建、编译和实例化。这里之所以说可能,是当一个扩展接口既有固定的自适应扩展类,又想实现自动生成自适应扩展类的情况下,只会以固定的自适应扩展类为准,不会去创建动态的自适应扩展类,在框架启动时就会加载固定扩展类并放入缓存。当缓存中不存在自适应扩展类时,dubbo没有直接使用代理模式实现自适应扩展,而是为扩展接口生成具有代理功能的代码,然后通过动态编译得到自适应类,整个过程最终的目的是为扩展点生成代理对象,而代理对象主要任务就是从URL中获取扩展名对应的扩展实现。接下来我们通过对官网的例子稍加改动来说明自动生成的自适应扩展的原理。
车轮制造接口 WheelMaker
1 2 3 public interface WheelMaker { void makeWheel (URL url) ; }
WheelMaker 接口的普通实现类
1 2 3 4 5 6 public class CommonWheelMaker implements WheelMaker { public void makeWheel (URL url) { System.out.println("打印url,制造全宇宙最好的车轮..." + url); } }
WheelMaker 接口的自适应实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class AdaptiveWheelMaker implements WheelMaker { public void makeWheel (URL url) { if (url == null ) { throw new IllegalArgumentException("url == null" ); } String wheelMakerName = url.getParameter("wheel.maker" ); if (wheelMakerName == null ) { throw new IllegalArgumentException("wheelMakerName == null" ); } WheelMaker wheelMaker = ExtensionLoader.getExtensionLoader(WheelMaker.class ).getExtension (wheelMakerName ) ; wheelMaker.makeWheel(url); } }
AdaptiveWheelMaker 是一个代理类[在dubbo框架中该类型的类是自动生成的],与传统的代理逻辑不同,AdaptiveWheelMaker 所代理的对象是在 makeWheel 方法中通过 SPI 加载得到的。makeWheel 方法主要做了三件事情:
从 URL 中获取 WheelMaker 扩展名
通过 SPI 加载具体的 WheelMaker 实现类
调用目标方法
程序运行时,假设我们获取到了AdaptiveWheelMaker对象,然后调用它的makeWheel方法,然后有这样一个 url 参数传入:
1 dubbo://192.168.0.101:20880/XxxService?wheel.maker=commonWheelMaker
AdaptiveWheelMaker 的 makeWheel 方法从 url 中提取 wheel.maker 参数,得到扩展名 commonWheelMaker,之后再通过 SPI 加载扩展名为 commonWheelMaker 的实现类,最终得到具体的 WheelMaker 实例。
原理小结 这个例子展示了自动生成的自适应扩展类的核心实现,即在扩展接口的方法被调用(dubbo中是使用自适应扩展对象调用的)时,通过SPI加载具体的扩展对象,并调用该扩展对象的同名方法。
自适应扩展类串的生成 通过上面的例子,我们直观的认识了自适应扩展类的工作原理。通过上一篇文章我们知道@Adaptive 可注解在类或方法上,注解在类上时,Dubbo 不会为该类生成代理类。注解在扩展接口的方法上时,Dubbo 会为为该接口生成代理逻辑。接下来我们从上一篇文章提到的getAdaptiveExtension方法入口继续分析。
getAdaptiveExtension 方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 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 方法尝试创建自适应对象。我们继续跟进 createAdaptiveExtension 方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 private T createAdaptiveExtension () { try { return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e); } }
上面的方法先获取自适应扩展类,然后利用反射创建自适应对象,接着会向创建的自适应对象注入依赖。现在,我们已经知道了自适应扩展类分为两类,固定的自适应扩展类中可能存在一些依赖,这时需要使用扩展工厂进行setter注入,自动生成的扩展实现一般不会依赖其它属性。接下来我们分析下自适应扩展类怎么获取的。
1 2 3 4 5 6 7 8 9 10 11 12 private Class<?> getAdaptiveExtensionClass() { getExtensionClasses(); if (cachedAdaptiveClass != null ) { return cachedAdaptiveClass; } return cachedAdaptiveClass = createAdaptiveExtensionClass(); }
上面的代码先是刷新扩展点实现类集合,注意如果扩展接口的实现类中有标注@Adaptive注解的类,那么cachedAdaptiveClass缓存属性中保存的就是该类,即固定的自适应扩展类。如果没有的话,说明当前扩展接口的实现类中不存在固定的自适应扩展类,那么只能尝试创建该接口的自适应扩展类,代码逻辑如下:
1 2 3 4 5 6 7 8 9 10 11 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); }
createAdaptiveExtensionClass 方法包含三个步骤:
生成自适应扩展实现类的代码字符串
获取Compiler自适应扩展对象
动态编译 自适应拓展实现类的代码字符串 ,生成Class
后面两个步骤属于 动态编译
部分,不在本文范畴,我们主要关注 自适应扩展实现类的代码字符串 的生成逻辑。
自适应扩展类代码生成 createAdaptiveExtensionClassCode方法代码非常多,不过总的逻辑大致可以分为八个逻辑分支,已经进行详细的注释,下面就直接贴上代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 private String createAdaptiveExtensionClassCode () { StringBuilder codeBuilder = new StringBuilder(); Method[] methods = type.getMethods(); boolean hasAdaptiveAnnotation = false ; for (Method m : methods) { if (m.isAnnotationPresent(Adaptive.class )) { hasAdaptiveAnnotation = true ; break ; } } if (!hasAdaptiveAnnotation) { throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!" ); } codeBuilder.append("package " ).append(type.getPackage().getName()).append(";" ); codeBuilder.append("\nimport ").append(ExtensionLoader.class.getName()).append(";"); codeBuilder.append("\npublic class " ).append(type.getSimpleName()).append("$Adaptive" ).append(" implements " ).append(type.getCanonicalName()).append(" {" ); 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 ; } } if (urlTypeIndex != -1 ) { 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); } else { String attribMethod = null ; 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 adaptive class for interface " + type.getName() + ": not found url parameter or url attribute in parameters of method " + method.getName()); } 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(); 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" )) { 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(";" ); 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); 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(");" ); } codeBuilder.append("\npublic " ).append(rt.getCanonicalName()).append(" " ).append(method.getName()).append("(" ); for (int i = 0 ; i < pts.length; i++) { if (i > 0 ) { codeBuilder.append(", " ); } codeBuilder.append(pts[i].getCanonicalName()); codeBuilder.append(" " ); codeBuilder.append("arg" ).append(i); } codeBuilder.append(")" ); if (ets.length > 0 ) { codeBuilder.append(" throws " ); for (int i = 0 ; i < ets.length; i++) { if (i > 0 ) { codeBuilder.append(", " ); } codeBuilder.append(ets[i].getCanonicalName()); } } codeBuilder.append(" {" ); codeBuilder.append(code.toString()); codeBuilder.append("\n}" ); } codeBuilder.append("\n}" ); if (logger.isDebugEnabled()) { logger.debug(codeBuilder.toString()); } return codeBuilder.toString(); }
上面的代码逻辑就一个任务,使用字符串拼接一个自适应扩展类串,梳理出来后并没有那么复杂,其实就是按照编写一个类的步骤进行拼接的。如果非要说复杂的话,那么就体现在拼接扩展名的逻辑代码中,因为情况非常多,胖友们可以多调试多归类。想要观察生成的自适应扩展类有两种办法,日志级别设置成debug是一种简单粗暴的方式,还可以使用反编译工具进行查看。
注意: 获取扩展实现的代码中,如果扩展名是 protocol
,由于 protocol
是 URL 主干部分,需要通过 getProtocol
方法直接获取。如果是其他扩展名则需要是从URL参数部分获取。两者获取方法不一样,这个要区别对待。如 ConfiguratorFactory
、RouterFactory
,它们的自适应方法 @Adaptive("protocol")
指定的扩展名就是 protocol
。
总结 本篇文章主要分析了自动生成的自适应扩展类的原理,以及详细分析了生成一个自适应扩展类的过程,总体来说还是很复杂的。至于为什么不直接使用代理的方式生成自适应扩展类,主要的原因是代理方式效率太低,更详细的可以参考梁飞大佬的博客 动态代理方案性能对比 。自适应扩展还没结束,我们虽然有了一个自适应扩展类的字符串,但是还需要对这个字符串进行编译处理成Class,这样才能创建一个对象自适应扩展对象,下一篇文章中我们就来分析dubbo的动态编译原理。