dubbo2.0原始碼中的設計模式與SPI介紹
Dubbo原始碼包介紹
當我們從github把Dubbo原始碼下載下來之後有如下原始碼包
下面來說明每個包的作用,以便我們有目的的閱讀程式碼
dubbo-admin
dubbo管理平臺原始碼包,用來管理dubbo服務的啟動、禁用、降權、介面測試等,操作介面如下
dubbo-cluster
叢集模組,將多個服務提供方偽裝為一個提供方,包括:負載均衡, 容錯,路由等,叢集的地址列表可以是靜態配置的,也可以是由註冊中心下發。
dubbo-common
公共邏輯模組,包括Util類和通用模型
dubbo-config
配置模組,是Dubbo對外的API,使用者通過Config使用Dubbo,隱藏Dubbo所有細節
例如:
<beanid=“xxxService”class=“com.xxx.XxxServiceImpl” /> <!-- 和本地服務一樣實現遠端服務 -->
<dubbo:serviceinterface=“com.xxx.XxxService”ref=“xxxService” /> <!-- 增加暴露遠端服務配置 -->
- 1
- 2
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 interfaceHelloInterface {
public void sayHello();
}
package com.swk.spi.impl;
import com.swk.spi.HelloInterface;
public classImageHelloimplementsHelloInterface{
@Override
public void sayHello() {
System.out.println("image hello");
}
}
package com.swk.spi.impl;
import com.swk.spi.HelloInterface;
public classTextHelloimplementsHelloInterface{
@Override
public void sayHello() {
System.out.println("text hello");
}
}
package com.swk.spi;
import java.util.ServiceLoader;
public classSPIMain {
public static void main(String[] args) {
ServiceLoader<HelloInterface> loaders = ServiceLoader.load(HelloInterface.class);
for(HelloInterface in:loaders){
in.sayHello();
}
}
}
- 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
com.swk.spi.HelloInterface檔案內容如下,新增一個實現類的全名就可以呼叫這個類裡面的方法
com.swk.spi.impl.TextHello
com.swk.spi.impl.ImageHello
- 1
- 2
執行結果如下
text hello
image hello
- 1
- 2
下面我們看看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();
- 1
我們以Protocal為例來分析下
@SPI("dubbo")
public interfaceProtocol {
/**
* 獲取預設埠,當用戶沒有配置埠時使用。
*
* @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();
}
- 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
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>();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
我們來看下,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
- 1
- 2
- 3
這裡的
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
- 1
- 2
我們可以理解為一種工廠模式,這種實現方式的優點是可擴充套件性強,想要擴充套件實現,只需要在 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 classEchoFilterimplementsFilter {
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);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
而像 ClassLoaderFilter則只是在主功能上添加了功能,更改當前執行緒的 ClassLoader,這是典型的裝飾器模式,原始碼如下
/**
* ClassLoaderInvokerFilter
*
* @author william.liangf
*/
@Activate(group = Constants.PROVIDER, order = -30000)
public classClassLoaderFilterimplementsFilter {
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);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
觀察者模式
當一個物件被修改時,則會自動通知它的依賴物件
在dubbo provider服務啟動時候要向註冊中心註冊自己的服務,在dubbo consumer向註冊中心訂閱服務時則是一種觀察者模式,他開啟了一個listener,註冊中心會每 5 秒定時檢查是否有服務更新,如果有更新,向該服務的提供者傳送一個 notify 訊息, provider 接受到 notify 訊息後,即執行 NotifyListener 的 notify 方法,執行監聽器方法。
public interfaceNotifyListener {
/**
* 當收到服務變更通知時觸發。
*
* 通知需處理契約:<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);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
動態代理模式
在代理模式(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();
}
- 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