Dubbo源码学习之-Adaptive自适应扩展

前言

最近三周基本处于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为例,源码如下所示:

 1 @Adaptive
 2 public class AdaptiveExtensionFactory implements ExtensionFactory {
 3
 4     private final List<ExtensionFactory> factories;
 5
 6     public AdaptiveExtensionFactory() {
 7         ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
 8         List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
 9         for (String name : loader.getSupportedExtensions()) {
10             list.add(loader.getExtension(name));
11         }
12         factories = Collections.unmodifiableList(list);
13     }
14
15     @Override
16     public <T> T getExtension(Class<T> type, String name) {
17         for (ExtensionFactory factory : factories) {
18             T extension = factory.getExtension(type, name);
19             if (extension != null) {
20                 return extension;
21             }
22         }
23         return null;
24     }
25
26 }

可见其getExtension方法自己进行了实现,属性factories中放的就是两个类: SPIExtensionFactory跟SpringExtensionFactory,分别是Dubbo自身的SPI扩展工厂以及Spring的相关扩展工厂。

注解加在方法上的情况,以Protocol接口为例:

 1 @SPI("dubbo")
 2 public interface Protocol {
 3
 4     int getDefaultPort();
 5
 6     @Adaptive
 7     <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
 8
 9     @Adaptive
10     <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
11
12     void destroy();
13
14 }

可见其中的export方法跟refer方法都加上了@Adaptive注解

二、代理如何生成

下面以Protocol接口中的export服务导出方法为例,看看Dubbo源码中是如何实现的代理生成。

在ServiceConfig类中的doExportUrlsFor1Protocol方法中,有一段这样的代码:

1 Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
2 DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
3 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

 1 public String generate() {
 2         // no need to generate adaptive class since there‘s no adaptive method found.
 3         if (!hasAdaptiveMethod()) {
 4             throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
 5         }
 6
 7         StringBuilder code = new StringBuilder();
 8         code.append(generatePackageInfo());
 9         code.append(generateImports());
10         code.append(generateClassDeclaration());
11
12         Method[] methods = type.getMethods();
13         for (Method method : methods) {
14             code.append(generateMethod(method));
15         }
16         code.append("}");
17
18         if (logger.isDebugEnabled()) {
19             logger.debug(code.toString());
20         }
21         return code.toString();
22     }

整个代码拼接的过程比较复杂,按照java语法拼装各个部分,最终得到一个代理类的代码。具体的代码实现如果感兴趣可以自行查看,太多太麻烦,此处就不一一例举了。

然后在ExtensionLoader中编译,加载,得到Class类,方法如下所示:

1 private Class<?> createAdaptiveExtensionClass() {
2         String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
3         ClassLoader classLoader = findClassLoader();
4         org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
5         return compiler.compile(code, classLoader);
6     }

最后用反射实例化,得到代理类对象。

三、根据URL加载指定的SPI实现类,调用方法

此步是在代理类的代码拼接中实现的。追踪上述generate()方法中的generateMethod(method)方法:

1 private String generateMethod(Method method) {
2         String methodReturnType = method.getReturnType().getCanonicalName();
3         String methodName = method.getName();
4         String methodContent = generateMethodContent(method);
5         String methodArgs = generateMethodArguments(method);
6         String methodThrows = generateMethodThrows(method);
7         return String.format(CODE_METHOD_DECLARATION, methodReturnType, methodName, methodArgs, methodThrows, methodContent);
8     }

可见此处将一个方法分成了五部分:方法返回值、方法名、方法内容、方法参数、方法异常。分别获得这5部分后再拼接,组成一个完整的方法。

其余都比较简单,主要关注方法内容的获取 generateMethodContent()方法。

 1 private String generateMethodContent(Method method) {
 2         Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
 3         StringBuilder code = new StringBuilder(512);
 4         if (adaptiveAnnotation == null) {
 5             return generateUnsupported(method);
 6         } else {
 7             int urlTypeIndex = getUrlTypeIndex(method);
 8
 9             // found parameter in URL type
10             if (urlTypeIndex != -1) {
11                 // Null Point check
12                 code.append(generateUrlNullCheck(urlTypeIndex));
13             } else {
14                 // did not find parameter in URL type
15                 code.append(generateUrlAssignmentIndirectly(method));
16             }
17
18             String[] value = getMethodAdaptiveValue(adaptiveAnnotation);
19
20             boolean hasInvocation = hasInvocationArgument(method);
21
22             code.append(generateInvocationArgumentNullCheck(method));
23
24             code.append(generateExtNameAssignment(value, hasInvocation));
25             // check extName == null?
26             code.append(generateExtNameNullCheck(value));
27
28             code.append(generateExtensionAssignment());
29
30             // return statement
31             code.append(generateReturnAndInvocation(method));
32         }
33
34         return code.toString();
35     }

由于Dubbo统一规定通过URL来获取动态加载的类的key,所以我们要带着这样的设计前提来看这个方法。

首先getUrlTypeIndex这个方法是用来判断当前方法的参数中有没有URL,如果有的话返回值就是URL参数在整个参数列表中的下标位置,没有的话返回-1。

由于Protocol的export方法参数中没有URL,所以此处应该进入else中的方法 generateUrlAssignmentIndirectly() 中。此方法是去找到参数中的getUrl方法,然后获取到。执行完此方法后得到的内容为:

1 if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
2 if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
3 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

时间: 2024-10-22 07:36:08

Dubbo源码学习之-Adaptive自适应扩展的相关文章

Dubbo源码学习(二)

@Adaptive注解 在上一篇ExtensionLoader的博客中记录了,有两种扩展点,一种是普通的扩展实现,另一种就是自适应的扩展点,即@Adaptive注解的实现类. @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) public @interface Adaptive { String[] value() default {}; } @Adapt

Dubbo源码学习--优雅停机原理及在SpringBoot中遇到的问题

相关文章: Dubbo源码学习文章目录 前言 主要是前一阵子换了工作,第一个任务就是解决目前团队在 Dubbo 停机时产生的问题,同时最近又看了一下 Dubbo 的源码,想重新写一下 Dubbo 相关的文章. 优雅停机原理 对于一个 java 应用,如果想在关闭应用时,执行一些释放资源的操作一般是通过注册一个 ShutDownHook ,当关闭应用时,不是调用 kill -9 命令来直接终止应用,而是通过调用 kill -15 命令来触发这个 ShutDownHook 进行停机前的释放资源操作.

dubbo源码学习(四)初始化过程细节:解析服务

初学dubbo的源码,只做尝试性的去学习,做为自己学习的一个记录,各位看官如果觉得写的有错误或理解的不对,请在留言区告诉我,互相学习.本人能力有限,有大神进入 时请指点. 前面大概介绍了一下关于学习dubbo源码的一些基本知识,今天将真正去看dubbo内部的实现过程,看dubbo的源码前我先把dubbo的用户指南和开发指指南大概的看了一遍,然后从上面找到相应的切入点去看源码,今天将介绍的是dubbo的初始化解析bean的过程.从之前使用过dubbo一些经验,加上http://dubbo.io/的

dubbo源码学习(五)dubbo暴露服务的过程

初学dubbo的源码,只做尝试性的去学习,做为自己学习的一个记录,各位看官如果觉得写的有错误或理解的不对,请在留言区告诉我,互相学习.本人能力有限,有大神进入 时请指点. dubbo采用的nio异步的通信,通信协议默认为 netty,当然也可以选择 mina,grizzy.在服务端(provider)在启动时主要是开启netty监听,在zookeeper上注册服务节点,处理消费者请求,返回处理后的消息给消费者,消费者使用服务时主要是订阅服务的节点,监听zookeeper节点目录,服务端的变化时z

dubbo源码学习(一)基础知识及使用的相关技术

初学dubbo的源码,只做尝试性的去学习,做为自己学习的一个记录,各位看官如果觉得写的有错误或理解的不对,请在留言区告诉我,互相学习.本人能力有限,有大神进入 时请指点. Dubbo是Alibaba开源的分布式服务框架,它最大的特点是按照分层的方式来架构,使用这种方式可以使各个层之间解耦合(或者最大限度地松耦合),我们可以非常容易地通过Dubbo来构建分布式服务,并根据自己实际业务应用场景来选择合适的集群容错模式,这个对于很多应用都是迫切希望的,只需要通过简单的配置就能够实现分布式服务调用,也就

Dubbo源码学习--服务发布(ProxyFactory、Invoker)

上文分析了Dubbo服务发布的整体流程,但服务代理生成的具体细节介绍得还不是很详细.下面将会接着上文继续分析.上文介绍了服务代理生成的切入点,如下: Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url); 这里的proxyFactory是在ServiceConfig中定义的,是final类型静态变量,赋值后无法进行修改.如下: private static final ProxyFactor

Dubbo源码学习之-服务导出

前言 忙的时候,会埋怨学习的时间太少,缺少个人的空间,于是会争分夺秒的工作.学习.而一旦繁忙的时候过去,有时间了之后,整个人又会不自觉的陷入一种懒散的状态中,时间也显得不那么重要了,随便就可以浪费掉几个小时.可见普通人的学习之路要主动地去克服掉很多阻碍,最主要的阻碍还是来自于自身,周期性的不想学习.不自觉的懒散.浅尝辄止的态度.好高骛远贪多的盲目...哎,学习之路,还是要时刻提醒自己,需勤勉致知. 闲话少叙,今天的学习目标是要尽量的了解清楚Dubbo框架中的服务导出功能,先附上Dubbo官网上的

Dubbo源码学习--环境搭建及基础准备(ServiceLoader、ExtensionLoader)

环境搭建 1. Github上下载 "Dubbo最新发布版本" ,楼主下载版本为2.5.7. 2. cd到源码解压目录,maven编译,命令为: 3. 生成Intellij idea相关配置文件,命令为: 4. 双击运行生成的 文件 Java SPI SPI是Service Provider Int http://p.baidu.com/itopic/main/qlog?qid=ae116162633263383961612700&type=questionlog http:/

dubbo源码学习一:基础知识及使用的相关技术

Dubbo是Alibaba开源的分布式服务框架,它最大的特点是按照分层的方式来架构,使用这种方式可以使各个层之间解耦合(或者最大限度地松耦合),我们可以非常容易地通过Dubbo来构建分布式服务,并根据自己实际业务应用场景来选择合适的集群容错模式,这个对于很多应用都是迫切希望的,只需要通过简单的配置就能够实现分布式服务调用,也就是说服务提供方(Provider)发布的服务可以天然就是集群服务. Dubbo的产生背景.最初的需求.架构设计 等可以详细看官方的文档:http://dubbo.apach