精通Dubbo——dubbo2.0源码中的设计模式与SPI介绍
2017-06-04 10:53
489 查看
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机制提供扩展功能。
我们看下文件目录
各个类的源码如下:
package com.swk.spi; public interface HelloInterface { public void sayHello(); } package com.swk.spi.impl; import com.swk.spi.HelloInterface; public class ImageHello implements HelloInterface{ @Override public void sayHello() { System.out.println("image hello"); } } package com.swk.spi.impl; import com.swk.spi.HelloInterface; public class TextHello implements HelloInterface{ @Override public void sayHello() { System.out.println("text hello"); } } package com.swk.spi; import java.util.ServiceLoader; public class SPIMain { public static void main(String[] args) { ServiceLoader<HelloInterface> loaders = ServiceLoader.load(HelloInterface.class); for(HelloInterface in:loaders){ in.sayHello(); } } }
com.swk.spi.HelloInterface文件内容如下,添加一个实现类的全名就可以调用这个类里面的方法
com.swk.spi.impl.TextHello 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为例来分析下
@SPI("dubbo") public interface Protocol { /** * 获取缺省端口,当用户没有配置端口时使用。 * * @return 缺省端口 */ int getDefaultPort(); /** * 暴露远程服务:<br> * 1. 协议在接收请求时,应记录请求来源方地址信息:RpcContext.getContext().setRemoteAddress();<br> * 2. export()必须是幂等的,也就是暴露同一个URL的Invoker两次,和暴露一次没有区别。<br> * 3. export()传入的Invoker由框架实现并传入,协议不需要关心。<br> * * @param <T> 服务的类型 * @param invoker 服务的执行体 * @return exporter 暴露服务的引用,用于取消暴露 * @throws RpcException 当暴露服务出错时抛出,比如端口已占用 */ @Adaptive <T> Exporter<T> export(Invoker<T> invoker) throws RpcException; /** * 引用远程服务:<br> * 1. 当用户调用refer()所返回的Invoker对象的invoke()方法时,协议需相应执行同URL远端export()传入的Invoker对象的invoke()方法。<br> * 2. refer()返回的Invoker由协议实现,协议通常需要在此Invoker中发送远程请求。<br> * 3. 当url中有设置check=false时,连接失败不能抛出异常,并内部自动恢复。<br> * * @param <T> 服务的类型 * @param type 服务的类型 * @param url 远程服务的URL地址 * @return invoker 服务的本地代理 * @throws RpcException 当连接服务提供方失败时抛出 */ @Adaptive <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException; /** * 释放协议:<br> * 1. 取消该协议所有已经暴露和引用的服务。<br> * 2. 释放协议所占用的所有资源,比如连接和端口。<br> * 3. 协议在释放后,依然能暴露和引用新的服务。<br> */ void destroy(); }
Protocol的实现类有
ExtensionLoader中含有一个静态属性,用于缓存所有的扩展加载实例,这里加载Protocol.class,就以Protocol.class为key,创建的ExtensionLoader为value存储到上述EXTENSION_LOADERS中
public class ExtensionLoader<T> { private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class); private static final String SERVICES_DIRECTORY = "META-INF/services/"; private static final String DUBBO_DIRECTORY = "META-INF/dubbo/"; private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/"; private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*"); private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>(); private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>(); }
我们来看下,ExtensionLoader实例是如何来加载Protocol的实现类的:
1、先解析Protocol上的Extension注解的name,存至String cachedDefaultName属性中,作为默认的实现
2、到类路径下的加载 META-INF/services/com.alibaba.dubbo.rpc.Protocol文件
文件内容如下
filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper mock=com.alibaba.dubbo.rpc.support.MockProtocol
这里的
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); 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 的作用是判断是否是回声测试请求,是的话直接返回内容,这是一种责任链的体现,源码如下:
/** * EchoInvokerFilter * * @author william.liangf */ @Activate(group = Constants.PROVIDER, order = -110000) public class EchoFilter implements Filter { public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException { // 是否是回声测试请求 if(inv.getMethodName().equals(Constants.$ECHO) && inv.getArguments() != null && inv.getArguments().length == 1 ) return new RpcResult(inv.getArguments()[0]); return invoker.invoke(inv); } }
而像 ClassLoaderFilter则只是在主功能上添加了功能,更改当前线程的 ClassLoader,这是典型的装饰器模式,源码如下
/** * ClassLoaderInvokerFilter * * @author william.liangf */ @Activate(group = Constants.PROVIDER, order = -30000) public class ClassLoaderFilter implements Filter { public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { ClassLoader ocl = Thread.currentThread().getContextClassLoader(); // 在主功能上添加了功能,更改当前线程的 ClassLoader Thread.currentThread().setContextClassLoader(invoker.getInterface().getClassLoader()); try { return invoker.invoke(invocation); } finally { Thread.currentThread().setContextClassLoader(ocl); } } }
观察者模式
当一个对象被修改时,则会自动通知它的依赖对象在dubbo provider服务启动时候要向注册中心注册自己的服务,在dubbo consumer向注册中心订阅服务时则是一种观察者模式,他开启了一个listener,注册中心会每 5 秒定时检查是否有服务更新,如果有更新,向该服务的提供者发送一个 notify 消息, provider 接受到 notify 消息后,即运行 NotifyListener 的 notify 方法,执行监听器方法。
public interface NotifyListener { /** * 当收到服务变更通知时触发。 * * 通知需处理契约:<br> * 1. 总是以服务接口和数据类型为维度全量通知,即不会通知一个服务的同类型的部分数据,用户不需要对比上一次通知结果。<br> * 2. 订阅时的第一次通知,必须是一个服务的所有类型数据的全量通知。<br> * 3. 中途变更时,允许不同类型的数据分开通知,比如:providers, consumers, routers, overrides,允许只通知其中一种类型,但该类型的数据必须是全量的,不是增量的。<br> * 4. 如果一种类型的数据为空,需通知一个empty协议并带category参数的标识性URL数据。<br> * 5. 通知者(即注册中心实现)需保证通知的顺序,比如:单线程推送,队列串行化,带版本对比。<br> * * @param urls 已注册信息列表,总不为空,含义同{@link com.alibaba.dubbo.registry.RegistryService#lookup(URL)}的返回值。 */ void notify(List<URL> urls); }
动态代理模式
在代理模式(Proxy Pattern)中,一个类代表另一个类的功能Dubbo 扩展 jdk spi 的类 ExtensionLoader 的 Adaptive 实现是典型的动态代理实现。 Dubbo需要灵活地控制实现类,即在调用阶段动态地根据参数决定调用哪个实现类,所以采用先生成代理类的方法,能够做到灵活的调用。生成代理类的代码是 ExtensionLoader 的createAdaptiveExtensionClassCode 方法。代理类的主要逻辑是,获取 URL 参数中指定参数的值作为获取实现类的 key。
/** * 根据URL传参获取指定实现类的key * 2017年6月4日 上午10:20:25 * @return */ private String createAdaptiveExtensionClassCode() { StringBuilder codeBuidler = new StringBuilder(); // 根据反射获取方法集合 Method[] methods = type.getMethods(); boolean hasAdaptiveAnnotation = false; for(Method m : methods) { // 判断每个方法是否使用了Adaptive注解 if(m.isAnnotationPresent(Adaptive.class)) { hasAdaptiveAnnotation = true; break; } } // 完全没有Adaptive方法,则不需要生成Adaptive类 if(! hasAdaptiveAnnotation) throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!"); codeBuidler.append("package " + type.getPackage().getName() + ";"); codeBuidler.append("\nimport " + ExtensionLoader.class.getName() + ";"); codeBuidler.append("\npublic class " + type.getSimpleName() + "$Adpative" + " implements " + type.getCanonicalName() + " {"); for (Method method : methods) { // 获取方法返回值类型 Class<?> rt = method.getReturnType(); // 获取方法参数类型 Class<?>[] pts = method.getParameterTypes(); // 获取方法异常类型 Class<?>[] ets = method.getExceptionTypes(); // 获取Adaptive的注解信息 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) { // 如果是URL类型 if (pts[i].equals(URL.class)) { urlTypeIndex = i; break; } } // 有类型为URL的参数 if (urlTypeIndex != -1) { // Null Point check 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); } // 参数没有URL类型 else { String attribMethod = null; // 找到参数的URL属性 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 adative class for interface " + type.getName() + ": not found url parameter or url attribute in parameters of method " + method.getName()); } // Null point check 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(); // 没有设置Key,则使用“扩展点接口名的点分隔 作为Key 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")) { // Null Point check 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(";"); // check extName == null? 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); // return statement 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(");"); } codeBuidler.append("\npublic " + rt.getCanonicalName() + " " + method.getName() + "("); for (int i = 0; i < pts.length; i ++) { if (i > 0) { codeBuidler.append(", "); } codeBuidler.append(pts[i].getCanonicalName()); codeBuidler.append(" "); codeBuidler.append("arg" + i); } codeBuidler.append(")"); if (ets.length > 0) { codeBuidler.append(" throws "); for (int i = 0; i < ets.length; i ++) { if (i > 0) { codeBuidler.append(", "); } codeBuidler.append(ets[i].getCanonicalName()); } } codeBuidler.append(" {"); codeBuidler.append(code.toString()); codeBuidler.append("\n}"); } codeBuidler.append("\n}"); if (logger.isDebugEnabled()) { logger.debug(codeBuidler.toString()); } return codeBuidler.toString(); }
相关文章推荐
- Dubbo源码分析(二):Dubbo中采用的设计模式
- dubbo源码阅读-remoting 设计模式
- Dubbo系列(八)Dubbo源码分析之Dubbo中采用的设计模式
- Struts2 内核之我见(转) -(主要是拦截器链和过滤链介绍和源码及其设计模式)
- 精通Dubbo——dubbo2.0源码中Spring Bean的加载
- Dubbo源码分析-微内核插件式开发(SPI介绍)
- 精通Dubbo——dubbo2.0源码中Spring Bean的加载
- dubbo源码分析1——SPI机制的概要介绍
- WEB互动的革命 - JSF框架中使用的设计模式介绍
- 设计模式介绍(in C#)
- [介绍]PHP设计模式:DAO(数据访问对象模式)
- 架构性宣言: MIDP 2.0 中的工厂设计模式
- WEB互动的革命 - JSF框架中使用的设计模式介绍
- 软件的架构与设计模式之模式的种类介绍
- 推荐一个学习设计模式的地方及其合成模式的介绍
- 《设计模式解析》第5章 设计模式介绍
- 收藏一些国内较早系统介绍设计模式的资源
- JSF框架中使用的设计模式介绍
- JSF框架中使用的设计模式介绍
- bbs源码分析 之 jive中的设计模式