前言
最近三周基本处于 9-10-6 与 9-10-7 之间, 忙碌的节奏机会丢失了自己. 除了之前干施工的那段经历, 只看参加软件开发以来, 前段时间是最繁忙的了. 忙的原因, 不是要完成的工作量大, 而是各种环境问题, 各种沟通协调问题. 从这个项目, 我是体会到了人一多, 花在沟通协调上的成本真的会不成比例的放大, 制度好, 再加上协调好, 会极大的提高整体工作效率. 怪不得当年华为跟 IBM 学完工作组织管理制度之后能爆发出如此强劲的战斗力. 从另一个角度, 也能发觉出为什么大公司招人都比较注重员工的个人实力与团队协作能力, 因为如果是多人协作的工作, 一旦有人跟不上会极大的拖延整体进度, 而且相对而言技术能力强的人更容易沟通交流达成共识, 工作协作成本会比能力弱的人低很多. 大道千条, 我选其一, 多提升个人能力才是王道.
闲话少叙, 下面继续 Dubbo 源码的学习. 上一节说的是 Dubbo 用 SPI 机制来进行 Bean 的管理与引用, 类似于 Spring 中 BeanFactory 对 bean 的管理. SPI 实现了类似于 "在容器中管理 Bean" 的功能, 那么问题来了, 如果我想在程序运行时调用 SPI 中管理的类的方法, 再通过运行时的参数来确定调用哪个实现类, 这么矛盾的场景应该怎么实现? 这时就要靠 Dubbo 的自适应扩展机制了.
正文
实现的思路其实不难, 我们先一起来分析一下. 首先程序运行时直接调用的 SPI 管理类中的方法不是通过 SPI 加载的类, 因为这时候还未加载, 所以此时只能先通过代理类代理, 在代理类的方法中再进行判断, 看需要调用哪个实现类, 再去加载这个实现类并调用目标方法. 即最先调用的那个方法只是最终要调用的实现类方法的一个代理而已. 添加了一个代理层, 就实现了一个看似矛盾的场景, 从这也可以看出软件开发的一个重要的思想武器 - 分层.
但要真正实现这个思路, 将它落地, 还是比较复杂的. 首先要确定, 哪些方法需要生成代理类进行代理? Dubbo 中是通过 @Adaptive 注解来标识类与方法实现的. 其次, 代理类如何生成? Dubbo 中先拼接出一段 java 代码的字符串, 然后默认使用 javassit 编译这段代码加载进 JVM 得到 class 对象, 再利用反射生成代理类. 最后, 代理类生成后, 通过什么来确认最终要加载调用的实现类? Dubbo 中对此进行了规范, 统一从 URL 对象中获取参数找到最终调用的实现类. 注意此处的 URL 是 Dubbo 中自己定义的一个类, 类路径为 org.apache.dubbo.common.URL.
一,@Adaptive 注解
此注解是自适应扩展的触发点, 可以加在类上跟方法上. 加在类上, 表示该类是一个扩展类, 不需要生成代理直接用即可; 加在方法上则表示该方法需生成代理. Dubbo 中此注解加载类上的情况, 只有两个类: AdaptiveCompiler 和 AdaptiveExtensionFactory. 以 AdaptiveExtensionFactory 为例, 源码如下所示:
- @Adaptive
- public class AdaptiveExtensionFactory implements ExtensionFactory {
- private final List<ExtensionFactory> factories;
- 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);
- }
- @Override
- public <T> T getExtension(Class<T> type, String name) {
- for (ExtensionFactory factory : factories) {
- T extension = factory.getExtension(type, name);
- if (extension != null) {
- return extension;
- }
- }
- return null;
- }
- }
可见其 getExtension 方法自己进行了实现, 属性 factories 中放的就是两个类: SPIExtensionFactory 跟 SpringExtensionFactory, 分别是 Dubbo 自身的 SPI 扩展工厂以及 Spring 的相关扩展工厂.
注解加在方法上的情况, 以 Protocol 接口为例:
- @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();
- }
可见其中的 export 方法跟 refer 方法都加上了 @Adaptive 注解
二, 代理如何生成
下面以 Protocol 接口中的 export 服务导出方法为例, 看看 Dubbo 源码中是如何实现的代理生成.
在 ServiceConfig 类中的 doExportUrlsFor1Protocol 方法中, 有一段这样的代码:
- Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
- DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
- Exporter<?> exporter = protocol.export(wrapperInvoker);
此处就是往远程导出服务的触发点. 先生成了 invoker, 然后生成 invoker 的包装类可以看到在第三行调用了 protocol 接口的 export 方法. protocol 属性为:
1 private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
追踪 getAdaptiveExtension() 方法, 最终找到生成代理类代码的地方: org.apache.dubbo.common.extension.AdaptiveClassCodeGenerator#generate
- public String generate() {
- // no need to generate adaptive class since there's no adaptive method found.
- 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());
- code.append(generateImports());
- code.append(generateClassDeclaration());
- Method[] methods = type.getMethods();
- for (Method method : methods) {
- code.append(generateMethod(method));
- }
- code.append("}");
- if (logger.isDebugEnabled()) {
- logger.debug(code.toString());
- }
- return code.toString();
- }
整个代码拼接的过程比较复杂, 按照 java 语法拼装各个部分, 最终得到一个代理类的代码. 具体的代码实现如果感兴趣可以自行查看, 太多太麻烦, 此处就不一一例举了.
然后在 ExtensionLoader 中编译, 加载, 得到 Class 类, 方法如下所示:
- 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);
- }
最后用反射实例化, 得到代理类对象.
三, 根据 URL 加载指定的 SPI 实现类, 调用方法
此步是在代理类的代码拼接中实现的. 追踪上述 generate() 方法中的 generateMethod(method) 方法:
- private String generateMethod(Method method) {
- String methodReturnType = method.getReturnType().getCanonicalName();
- String methodName = method.getName();
- String methodContent = generateMethodContent(method);
- String methodArgs = generateMethodArguments(method);
- String methodThrows = generateMethodThrows(method);
- return String.format(CODE_METHOD_DECLARATION, methodReturnType, methodName, methodArgs, methodThrows, methodContent);
- }
可见此处将一个方法分成了五部分: 方法返回值, 方法名, 方法内容, 方法参数, 方法异常. 分别获得这 5 部分后再拼接, 组成一个完整的方法.
其余都比较简单, 主要关注方法内容的获取 generateMethodContent() 方法.
- private String generateMethodContent(Method method) {
- Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
- StringBuilder code = new StringBuilder(512);
- if (adaptiveAnnotation == null) {
- return generateUnsupported(method);
- } else {
- int urlTypeIndex = getUrlTypeIndex(method);
- // found parameter in URL type
- if (urlTypeIndex != -1) {
- // Null Point check
- code.append(generateUrlNullCheck(urlTypeIndex));
- } else {
- // did not find parameter in URL type
- code.append(generateUrlAssignmentIndirectly(method));
- }
- String[] value = getMethodAdaptiveValue(adaptiveAnnotation);
- boolean hasInvocation = hasInvocationArgument(method);
- code.append(generateInvocationArgumentNullCheck(method));
- code.append(generateExtNameAssignment(value, hasInvocation));
- // check extName == null?
- code.append(generateExtNameNullCheck(value));
- code.append(generateExtensionAssignment());
- // return statement
- code.append(generateReturnAndInvocation(method));
- }
- return code.toString();
- }
由于 Dubbo 统一规定通过 URL 来获取动态加载的类的 key, 所以我们要带着这样的设计前提来看这个方法.
首先 getUrlTypeIndex 这个方法是用来判断当前方法的参数中有没有 URL, 如果有的话返回值就是 URL 参数在整个参数列表中的下标位置, 没有的话返回 - 1.
由于 Protocol 的 export 方法参数中没有 URL, 所以此处应该进入 else 中的方法 generateUrlAssignmentIndirectly() 中. 此方法是去找到参数中的 getUrl 方法, 然后获取到. 执行完此方法后得到的内容为:
- 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();
执行 generateExtNameAssignment 方法后得到的结果为:
1 String extName = url.getProtocol();
执行 generateExtensionAssignment 方法得到的结果为:
1 org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
执行 generateReturnAndInvocation 方法得到的结果为:
1 return extension.export(arg0);
这样, 代理类的代码便拼凑出来了, 后面通过编译类编译, 加载进 JVM, 得到实例对象就可一气呵成的完成了.
尾声
至此, 便完成了 Dubbo 的自适应扩展机制. 可以发现, 整个过程没有什么难的地方, 大都是平常用过或者见过的用法, 但是经优秀的阿里中间件工程师们之手一组合, 就可以实现如此的功能, 很让人佩服. 下一期是 Dubbo 的服务导出功能解读, 敬请关注.
来源: https://www.cnblogs.com/zzq6032010/p/11219611.html