精通Dubbo——dubbo2.0源码中的设计模式与SPI介绍

Dubbo源码包介绍
当我们从github把Dubbo源码下载下来之后有如下源码包
 


下面来说明每个包的作用,以便我们有目的的阅读代码
dubbo-admin
dubbo管理平台源码包,用来管理dubbo服务的启动、禁用、降权、接口测试等,操作界面如下
 

 

dubbo-cluster
集群模块,将多个服务提供方伪装为一个提供方,包括:负载均衡, 容错,路由等,集群的地址列表可以是静态配置的,也可以是由注册中心下发。
dubbo-common
公共逻辑模块,包括Util类和通用模型
dubbo-config
配置模块,是Dubbo对外的API,用户通过Config使用Dubbo,隐藏Dubbo所有细节
例如:

<bean id=“xxxService” class=“com.xxx.XxxServiceImpl” /> <!-- 和本地服务一样实现远程服务 -->
<dubbo:service interface=“com.xxx.XxxService” ref=“xxxService” /> <!-- 增加暴露远程服务配置 -->

dubbo-container
容器模块,是一个Standlone的容器,以简单的Main加载Spring启动,因为服务通常不需要Tomcat/JBoss等Web容器的特性,没必要用Web容器去加载服务
dubbo-demo
dubbo服务provider和consumer例子
dubbo-filter
dubbo扩展过滤器功能,针对dubbo接口,类似Spring的AOP,经常用的并发控制、超时设置、异常设置、tps限流设置等等都是通过filter来实现的。
dubbo-monitor
监控模块,统计服务调用次数,调用时间的,调用链跟踪的服务
dubbo-registry
注册中心模块,基于注册中心下发地址的集群方式,以及对各种注册中心的抽象。
dubbo-remoting
远程通讯模块,相当于Dubbo协议的实现,如果RPC用RMI协议则不需要使用此包
dubbo-rpc
远程调用模块,抽象各种协议,以及动态代理,只包含一对一的调用,不关心集群的管理

dubbo用到的设计模式
工厂模式
Java SPI
在我们分析工厂模式之前我们先了解下Java SPI(service provice interface)。SPI是JDK内置的一种服务发现机制。目前有不少框架用它来做服务的扩展发现, 简单来说,它就是一种动态替换发现的机制, 举个例子来说, 有个接口,想运行时动态的给它添加实现,你只需要添加一个实现。


而后把新加的实现描述给JDK即可,Dubbo框架就是基于SPI机制提供扩展功能。
我们看下文件目录
 

各个类的源码如下:

 1 package com.swk.spi;
 2 public interface HelloInterface {
 3     public void sayHello();
 4 }
 5 package com.swk.spi.impl;
 6 import com.swk.spi.HelloInterface;
 7 public class ImageHello implements HelloInterface{
 8     @Override
 9     public void sayHello() {
10         System.out.println("image hello");
11     }
12 }
13 package com.swk.spi.impl;
14 import com.swk.spi.HelloInterface;
15 public class TextHello implements HelloInterface{
16     @Override
17     public void sayHello() {
18         System.out.println("text hello");
19     }
20 }
21 package com.swk.spi;
22 import java.util.ServiceLoader;
23 public class SPIMain {
24 
25     public static void main(String[] args) {
26         ServiceLoader<HelloInterface> loaders = ServiceLoader.load(HelloInterface.class);
27         for(HelloInterface in:loaders){
28             in.sayHello();
29         }
30     }
31 }
32 
33 com.swk.spi.HelloInterface文件内容如下,添加一个实现类的全名就可以调用这个类里面的方法
34 
35 com.swk.spi.impl.TextHello
36 com.swk.spi.impl.ImageHello

 


运行结果如下

text hello
image hello


下面我们看看dubbo的实现,dubbo的扩展机制和java的SPI机制非常相似,但是又增加了如下功能:
1、可以方便的获取某一个想要的扩展实现,java的SPI机制就没有提供这样的功能
2、对于扩展实现IOC依赖注入功能:
现在实现者A1含有setB()方法,会自动注入一个接口B的实现者,此时注入B1还是B2呢?都不是,而是注入一个动态生成的接口B的实现者B$Adpative,该实现者能够根据参数的不同,自动引用B1或者B2来完成相应的功能
3、对扩展采用装饰器模式进行功能增强,类似AOP实现的功能

private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();


我们以Protocal为例来分析下

 1 @SPI("dubbo")
 2 public interface Protocol {
 3 
 4     /**
 5      * 获取缺省端口,当用户没有配置端口时使用。
 6      * 
 7      * @return 缺省端口
 8      */
 9     int getDefaultPort();
10     /**
11      * 暴露远程服务:<br>
12      * 1. 协议在接收请求时,应记录请求来源方地址信息:RpcContext.getContext().setRemoteAddress();<br>
13      * 2. export()必须是幂等的,也就是暴露同一个URL的Invoker两次,和暴露一次没有区别。<br>
14      * 3. export()传入的Invoker由框架实现并传入,协议不需要关心。<br>
15      * 
16      * @param <T> 服务的类型
17      * @param invoker 服务的执行体
18      * @return exporter 暴露服务的引用,用于取消暴露
19      * @throws RpcException 当暴露服务出错时抛出,比如端口已占用
20      */
21     @Adaptive
22     <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
23     /**
24      * 引用远程服务:<br>
25      * 1. 当用户调用refer()所返回的Invoker对象的invoke()方法时,协议需相应执行同URL远端export()传入的Invoker对象的invoke()方法。<br>
26      * 2. refer()返回的Invoker由协议实现,协议通常需要在此Invoker中发送远程请求。<br>
27      * 3. 当url中有设置check=false时,连接失败不能抛出异常,并内部自动恢复。<br>
28      * 
29      * @param <T> 服务的类型
30      * @param type 服务的类型
31      * @param url 远程服务的URL地址
32      * @return invoker 服务的本地代理
33      * @throws RpcException 当连接服务提供方失败时抛出
34      */
35     @Adaptive
36     <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
37     /**
38      * 释放协议:<br>
39      * 1. 取消该协议所有已经暴露和引用的服务。<br>
40      * 2. 释放协议所占用的所有资源,比如连接和端口。<br>
41      * 3. 协议在释放后,依然能暴露和引用新的服务。<br>
42      */
43     void destroy();
44 }

Protocol的实现类有
 

 


ExtensionLoader中含有一个静态属性,用于缓存所有的扩展加载实例,这里加载Protocol.class,就以Protocol.class为key,创建的ExtensionLoader为value存储到上述EXTENSION_LOADERS中

 1 public class ExtensionLoader<T> {
 2     private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class);
 3 
 4     private static final String SERVICES_DIRECTORY = "META-INF/services/";
 5     private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
 6 
 7     private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
 8     private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");
 9 
10     private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
11     private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
12 }

我们来看下,ExtensionLoader实例是如何来加载Protocol的实现类的:
1、先解析Protocol上的Extension注解的name,存至String cachedDefaultName属性中,作为默认的实现
2、到类路径下的加载 META-INF/services/com.alibaba.dubbo.rpc.Protocol文件
 

 


文件内容如下

1 filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
2 listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
3 mock=com.alibaba.dubbo.rpc.support.MockProtocol


这里的

1  private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
2  private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

我们可以理解为一种工厂模式,这种实现方式的优点是可扩展性强,想要扩展实现,只需要在 classpath 下增加个文件就可以了,代码零侵入。另外,像上面的 Adaptive 实现,可以做到调用时动态决定调用哪个实现,但是由于这种实现采用了动态代理,会造成代码调试比较麻烦,需要分析出实际调用的实现类。

装饰器模式与责任链模式
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构
责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型对请求的发送者和接收者进行解耦
在我们介绍工厂模式的时候我们也提到了装饰器模式,Dubbo 在启动和调用阶段都大量使用了装饰器模式。
以 Provider 提供的调用链为例,具体的调用链代码是在 ProtocolFilterWrapper 的 buildInvokerChain 完成的,具体是将注解中含
有 group=provider 的 Filter 实现,按照 order 排序,最后的调用顺序是查看文本打印
EchoFilter-》 ClassLoaderFilter-》 GenericFilter-》 ContextFilter-》 ExceptionFilter-》TimeoutFilter-》 MonitorFilter-》 TraceFilter。更确切地说,这里是装饰器和责任链模式的混合使用。例如, EchoFilter 的作用是判断是否是回声测试请求,是的话直接返回内容,这是一种责任链的体现,源码如下:

 1 /**
 2  * EchoInvokerFilter
 3  * 
 4  * @author william.liangf
 5  */
 6 @Activate(group = Constants.PROVIDER, order = -110000)
 7 public class EchoFilter implements Filter {
 8     public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException {
 9         // 是否是回声测试请求
10         if(inv.getMethodName().equals(Constants.$ECHO) && inv.getArguments() != null && inv.getArguments().length == 1 )
11             return new RpcResult(inv.getArguments()[0]);
12         return invoker.invoke(inv);
13     }
14 }
15 
而像 ClassLoaderFilter则只是在主功能上添加了功能,更改当前线程的 ClassLoader,这是典型的装饰器模式,源码如下
17 
18 /**
19  * ClassLoaderInvokerFilter
20  * 
21  * @author william.liangf
22  */
23 @Activate(group = Constants.PROVIDER, order = -30000)
24 public class ClassLoaderFilter implements Filter {
25     public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
26         ClassLoader ocl = Thread.currentThread().getContextClassLoader();
27         // 在主功能上添加了功能,更改当前线程的 ClassLoader
28         Thread.currentThread().setContextClassLoader(invoker.getInterface().getClassLoader());
29         try {
30             return invoker.invoke(invocation);
31         } finally {
32             Thread.currentThread().setContextClassLoader(ocl);
33         }
34     }
35 }

观察者模式
当一个对象被修改时,则会自动通知它的依赖对象
在dubbo provider服务启动时候要向注册中心注册自己的服务,在dubbo consumer向注册中心订阅服务时则是一种观察者模式,他开启了一个listener,注册中心会每 5 秒定时检查是否有服务更新,如果有更新,向该服务的提供者发送一个 notify 消息, provider 接受到 notify 消息后,即运行 NotifyListener 的 notify 方法,执行监听器方法。

 1 public interface NotifyListener {
 2     /**
 3      * 当收到服务变更通知时触发。
 4      * 
 5      * 通知需处理契约:<br>
 6      * 1. 总是以服务接口和数据类型为维度全量通知,即不会通知一个服务的同类型的部分数据,用户不需要对比上一次通知结果。<br>
 7      * 2. 订阅时的第一次通知,必须是一个服务的所有类型数据的全量通知。<br>
 8      * 3. 中途变更时,允许不同类型的数据分开通知,比如:providers, consumers, routers, overrides,允许只通知其中一种类型,但该类型的数据必须是全量的,不是增量的。<br>
 9      * 4. 如果一种类型的数据为空,需通知一个empty协议并带category参数的标识性URL数据。<br>
10      * 5. 通知者(即注册中心实现)需保证通知的顺序,比如:单线程推送,队列串行化,带版本对比。<br>
11      * 
12      * @param urls 已注册信息列表,总不为空,含义同{@link com.alibaba.dubbo.registry.RegistryService#lookup(URL)}的返回值。
13      */
14     void notify(List<URL> urls);
15 }

动态代理模式
在代理模式(Proxy Pattern)中,一个类代表另一个类的功能
Dubbo 扩展 jdk spi 的类 ExtensionLoader 的 Adaptive 实现是典型的动态代理实现。 Dubbo需要灵活地控制实现类,即在调用阶段动态地根据参数决定调用哪个实现类,所以采用先生成代理类的方法,能够做到灵活的调用。生成代理类的代码是 ExtensionLoader 的createAdaptiveExtensionClassCode 方法。代理类的主要逻辑是,获取 URL 参数中指定参数的值作为获取实现类的 key。

 

  1 /**
  2      * 根据URL传参获取指定实现类的key
  3      * 2017年6月4日 上午10:20:25
  4      *
  5      * @return
  6      */
  7     private String createAdaptiveExtensionClassCode() {
  8         StringBuilder codeBuidler = new StringBuilder();
  9         // 根据反射获取方法集合
 10         Method[] methods = type.getMethods();
 11         boolean hasAdaptiveAnnotation = false;
 12         for (Method m : methods) {
 13             // 判断每个方法是否使用了Adaptive注解
 14             if (m.isAnnotationPresent(Adaptive.class)) {
 15                 hasAdaptiveAnnotation = true;
 16                 break;
 17             }
 18         }
 19         // 完全没有Adaptive方法,则不需要生成Adaptive类
 20         if (!hasAdaptiveAnnotation)
 21             throw new IllegalStateException("No adaptive method on extension " 
          + type.getName() + ", refuse to create the adaptive class!"); 22 23 codeBuidler.append("package " + type.getPackage().getName() + ";"); 24 codeBuidler.append("\nimport " + ExtensionLoader.class.getName() + ";"); 25 codeBuidler.append("\npublic class " + type.getSimpleName() +
        "$Adpative" + " implements " + type.getCanonicalName() + " {"); 26 27 for (Method method : methods) { 28 // 获取方法返回值类型 29 Class<?> rt = method.getReturnType(); 30 // 获取方法参数类型 31 Class<?>[] pts = method.getParameterTypes(); 32 // 获取方法异常类型 33 Class<?>[] ets = method.getExceptionTypes(); 34 // 获取Adaptive的注解信息 35 Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class); 36 StringBuilder code = new StringBuilder(512); 37 if (adaptiveAnnotation == null) { 38 code.append("throw new UnsupportedOperationException(\"method ") 39 .append(method.toString()).append(" of interface ") 40 .append(type.getName()).append(" is not adaptive method!\");"); 41 } else { 42 int urlTypeIndex = -1; 43 for (int i = 0; i < pts.length; ++i) { 44 // 如果是URL类型 45 if (pts[i].equals(URL.class)) { 46 urlTypeIndex = i; 47 break; 48 } 49 } 50 // 有类型为URL的参数 51 if (urlTypeIndex != -1) { 52 // Null Point check 53 String s = String.format("\nif (arg%d == null) 、
                throw new IllegalArgumentException(\"url == null\");",urlTypeIndex); 55 code.append(s); 56 57 s = String.format("\n%s url = arg%d;", URL.class.getName(), urlTypeIndex); 58 code.append(s); 59 } 60 // 参数没有URL类型 61 else { 62 String attribMethod = null; 63 64 // 找到参数的URL属性 65 LBL_PTS: 66 for (int i = 0; i < pts.length; ++i) { 67 Method[] ms = pts[i].getMethods(); 68 for (Method m : ms) { 69 String name = m.getName(); 70 if ((name.startsWith("get") || name.length() > 3) 71 && Modifier.isPublic(m.getModifiers()) 72 && !Modifier.isStatic(m.getModifiers()) 73 && m.getParameterTypes().length == 0 74 && m.getReturnType() == URL.class) { 75 urlTypeIndex = i; 76 attribMethod = name; 77 break LBL_PTS; 78 } 79 } 80 } 81 if (attribMethod == null) { 82 throw new IllegalStateException("fail to create adative class for interface " + type.getName() 83 + ": not found url parameter or url attribute in parameters of method " + method.getName()); 84 } 85 86 // Null point check 87 String s = String.format("\nif (arg%d == null)
                    throw new IllegalArgumentException(\"%s argument == null\");", 88 urlTypeIndex, pts[urlTypeIndex].getName()); 89 code.append(s); 90 s = String.format("\nif (arg%d.%s() == null)
                    throw new IllegalArgumentException(\"%s argument %s() == null\");", 91 urlTypeIndex, attribMethod, pts[urlTypeIndex].getName(), attribMethod); 92 code.append(s); 93 s = String.format("%s url = arg%d.%s();", URL.class.getName(), urlTypeIndex, attribMethod); 94 code.append(s); 95 } 96 97 String[] value = adaptiveAnnotation.value(); 98 // 没有设置Key,则使用“扩展点接口名的点分隔 作为Key 99 if (value.length == 0) { 100 char[] charArray = type.getSimpleName().toCharArray(); 101 StringBuilder sb = new StringBuilder(128); 102 for (int i = 0; i < charArray.length; i++) { 103 if (Character.isUpperCase(charArray[i])) { 104 if (i != 0) { 105 sb.append("."); 106 } 107 sb.append(Character.toLowerCase(charArray[i])); 108 } else { 109 sb.append(charArray[i]); 110 } 111 } 112 value = new String[]{sb.toString()}; 113 } 114 115 boolean hasInvocation = false; 116 for (int i = 0; i < pts.length; ++i) { 117 if (pts[i].getName().equals("com.alibaba.dubbo.rpc.Invocation")) { 118 // Null Point check 119 String s = String.format("\nif (arg%d == null)
                  throw new IllegalArgumentException(\"invocation == null\");", i); 120 code.append(s); 121 s = String.format("\nString methodName = arg%d.getMethodName();", i); 122 code.append(s); 123 hasInvocation = true; 124 break; 125 } 126 } 127 128 String defaultExtName = cachedDefaultName; 129 String getNameCode = null; 130 for (int i = value.length - 1; i >= 0; --i) { 131 if (i == value.length - 1) { 132 if (null != defaultExtName) { 133 if (!"protocol".equals(value[i])) 134 if (hasInvocation) 135 getNameCode = String.format("url.getMethodParameter(methodName,
                         \"%s\", \"%s\")", value[i], defaultExtName); 136 else 137 getNameCode = String.format("url.getParameter(\"%s\", \"%s\")",
                         value[i], defaultExtName);
138 else 139 getNameCode = String.format("( url.getProto                     \"%s\" : url.getProtocol() )", defau140 } else {
141                             if (!"protocol".equals(value[i]))
142                                 if (hasInvocation)
143                                     getNameCode = String.format("url.getMethodParameter(methodName, 
                        \"%s\", \"%s\")", value[i], defaultExtName); 144 else 145 getNameCode = String.format("url.getParameter(\"%s\")", value[i]); 146 else 147 getNameCode = "url.getProtocol()"; 148 } 149 } else { 150 if (!"protocol".equals(value[i])) 151 if (hasInvocation) 152 getNameCode = String.format("url.getMethodParameter(methodName,
                       \"%s\", \"%s\")", value[i], defaultExtName); 153 else 154 getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode); 155 else 156 getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode); 157 } 158 } 159 code.append("\nString extName = ").append(getNameCode).append(";"); 160 // check extName == null? 161 String s = String.format("\nif(extName == null) " + 162 "throw new IllegalStateException(\"Fail to get extension(%s) name
                     from url(\" + url.toString() + \") use keys(%s)\");", 163 type.getName(), Arrays.toString(value)); 164 code.append(s); 165 166 s = String.format("\n%s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);", 167 type.getName(), ExtensionLoader.class.getSimpleName(), type.getName()); 168 code.append(s); 169 170 // return statement 171 if (!rt.equals(void.class)) { 172 code.append("\nreturn "); 173 } 174 s = String.format("extension.%s(", method.getName()); 175 code.append(s); 176 for (int i = 0; i < pts.length; i++) { 177 if (i != 0) 178 code.append(", "); 179 code.append("arg").append(i); 180 } 181 code.append(");"); 182 } 183 184 codeBuidler.append("\npublic " + rt.getCanonicalName() + " " + method.getName() + "("); 185 for (int i = 0; i < pts.length; i++) { 186 if (i > 0) { 187 codeBuidler.append(", "); 188 } 189 codeBuidler.append(pts[i].getCanonicalName()); 190 codeBuidler.append(" "); 191 codeBuidler.append("arg" + i); 192 } 193 codeBuidler.append(")"); 194 if (ets.length > 0) { 195 codeBuidler.append(" throws "); 196 for (int i = 0; i < ets.length; i++) { 197 if (i > 0) { 198 codeBuidler.append(", "); 199 } 200 codeBuidler.append(ets[i].getCanonicalName()); 201 } 202 } 203 codeBuidler.append(" {"); 204 codeBuidler.append(code.toString()); 205 codeBuidler.append("\n}"); 206 } 207 codeBuidler.append("\n}"); 208 if (logger.isDebugEnabled()) { 209 logger.debug(codeBuidler.toString()); 210 } 211 return codeBuidler.toString(); 212 }

 


---------------------
作者:孙_悟_空
来源:CSDN
原文:https://blog.csdn.net/fuyuwei2015/article/details/72857722
版权声明:本文为博主原创文章,转载请附上博文链接!

posted @ 2018-12-03 15:30  一念永恒乐  阅读(554)  评论(0编辑  收藏  举报