摘要: 原創出處 http://www.iocoder.cn/TCC-Transaction/dubbo-support/ 「芋道原始碼」歡迎轉載,保留摘要,謝謝!
本文主要基於 TCC-Transaction 1.2.3.3 正式版
???關注**微信公眾號:【芋道原始碼】**有福利:
- RocketMQ / MyCAT / Sharding-JDBC 所有原始碼分析文章列表
- RocketMQ / MyCAT / Sharding-JDBC 中文註釋原始碼 GitHub 地址
- 您對於原始碼的疑問每條留言都將得到認真回覆。甚至不知道如何讀原始碼也可以請教噢。
- 新的原始碼解析文章實時收到通知。每週更新一篇左右。
- 認真的原始碼交流微信群。
1. 概述
本文分享 Dubbo 支援。
TCC-Transaction 通過 Dubbo 隱式傳參的功能,避免自己對業務程式碼的入侵。可能有同學不太理解為什麼說 TCC-Transaction 對業務程式碼有一定的入侵性,一起來看個程式碼例子:
public interface CapitalTradeOrderService {
String record(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto);
}
複製程式碼
- 程式碼來自
tcc-transaction-http-sample
。宣告遠端呼叫時,增加了引數 TransactionContext。當然你也可以通過自己使用的遠端呼叫框架做一定封裝,避免入侵。
如下是對 Dubbo 封裝了後,Dubbo Service 方法的例子:
public interface CapitalTradeOrderService {
@Compensable
String record(CapitalTradeOrderDto tradeOrderDto);
}
複製程式碼
- 程式碼來自
http-transaction-dubbo-sample
。是不是不需要傳入引數 TransactionContext。當然,註解是肯定需要的,否則 TCC-Transaction 怎麼知道哪些方法是 TCC 方法。
TCC-Transaction 通過 Dubbo Proxy 的機制,實現 @Compensable
屬性自動生成,增加開發體驗,也避免出錯。
Dubbo 支援( Maven 專案 tcc-transaction-dubbo
) 整體程式碼結構如下:
proxy
context
我們分成兩個小節分享這兩個包實現的功能。
筆者暫時對 Dubbo 瞭解的不夠深入,如果有錯誤的地方,還煩請指出,謝謝。
你行好事會因為得到讚賞而愉悅
同理,開源專案貢獻者會因為 Star 而更加有動力
為 TCC-Transaction 點贊!傳送門
ps:筆者假設你已經閱讀過《tcc-transaction 官方文件 —— 使用指南1.2.x》。
2. Dubbo 代理
將 Dubbo Service 方法上的註解 @Compensable
,自動生成註解的 confirmMethod
、cancelMethod
、transactionContextEditor
屬性,例子程式碼如下:
@Compensable(propagation=Propagation.SUPPORTS, confirmMethod="record", cancelMethod="record", transactionContextEditor=DubboTransactionContextEditor.class)
public String record(RedPacketTradeOrderDto paramRedPacketTradeOrderDto) {
// ... 省略程式碼
}
複製程式碼
- 該程式碼通過 Javassist 生成的 Proxy 程式碼的示例。
propagation=Propagation.SUPPORTS
:支援當前事務,如果當前沒有事務,就以非事務方式執行。為什麼不使用 REQUIRED ?如果使用 REQUIRED 事務傳播級別,事務恢復重試時,會發起新的事務。confirmMethod
、cancelMethod
使用和 try 方法相同方法名:本地發起遠端服務 TCC confirm / cancel 階段,呼叫相同方法進行事務的提交或回滾。遠端服務的 CompensableTransactionInterceptor 會根據事務的狀態是 CONFIRMING / CANCELLING 來呼叫對應方法。transactionContextEditor=DubboTransactionContextEditor.class
,使用 Dubbo 事務上下文編輯器,在「3. Dubbo 事務上下文編輯器」詳細分享。
Dubbo Service Proxy 提供了兩種生成方式:
- JavassistProxyFactory,基於 Javassist 方式
- JdkProxyFactory,基於 JDK 動態代理機制
這塊內容我們不擴充開,感興趣的同學點選如下文章:
Dubbo 的 Invoker 模型是非常關鍵的概念,看下圖:
2.1 JavassistProxyFactory
2.1.1 Javassist
Javassist 是一個開源的分析、編輯和建立 Java 位元組碼的類庫。通過使用Javassist 對位元組碼操作可以實現動態 ”AOP” 框架。
關於 Java 位元組碼的處理,目前有很多工具,如 bcel,asm( cglib只是對asm又封裝了一層 )。不過這些都需要直接跟虛擬機器指令打交道。
Javassist 的主要的優點,在於簡單,而且快速,直接使用 Java 編碼的形式,而不需要了解虛擬機器指令,就能動態改變類的結構,或者動態生成類。
- 粗略一看,可能不夠形象,下面我們通過看 TCC-Transaction 如何使用來理解理解。
- 《Java學習之javassist 》
- 《Javassist 位元組碼操作》
2.1.2 TccJavassistProxyFactory
org.mengyun.tcctransaction.dubbo.proxy.javassist.TccJavassistProxyFactory
,TCC Javassist 代理工廠。實現程式碼如下:
public class TccJavassistProxyFactory extends JavassistProxyFactory {
@SuppressWarnings("unchecked")
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
return (T) TccProxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}
}
複製程式碼
- 專案啟動時,呼叫
TccJavassistProxyFactory#getProxy(...)
方法,生成 Dubbo Service 呼叫 Proxy。 com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler
,Dubbo 呼叫處理器,點選連線檢視程式碼。
2.1.3 TccProxy & TccClassGenerator
org.mengyun.tcctransaction.dubbo.proxy.javassist.TccProxy
,TCC Proxy 工廠,生成 Dubbo Service 呼叫 Proxy 。筆者認為,TccProxy 改成 TccProxyFactory 更合適,原因在下文。
org.mengyun.tcctransaction.dubbo.proxy.javassist.TccClassGenerator
,TCC 類程式碼生成器,基於 Javassist 實現。
?案例
一個 Dubbo Service,TccProxy 會動態生成兩個類:
- Dubbo Service 呼叫 Proxy
- Dubbo Service 呼叫 ProxyFactory,生成對應的 Dubbo Service Proxy
例如 Dubbo Service 介面如下:
public interface RedPacketTradeOrderService {
@Compensable
String record(RedPacketTradeOrderDto tradeOrderDto);
}
複製程式碼
生成 Dubbo Service 呼叫 ProxyFactory 如下 :
public class TccProxy3 extends TccProxy implements TccClassGenerator.DC {
public Object newInstance(InvocationHandler paramInvocationHandler) {
return new proxy3(paramInvocationHandler);
}
}
複製程式碼
- TccProxy 提供
#newInstance(handler)
方法,建立 Proxy,所以筆者認為,TccProxy 改成 TccProxyFactory 更合適。 org.mengyun.tcctransaction.dubbo.proxy.javassist.TccClassGenerator.DC
動態生成類標記,標記該類由 TccClassGenerator 生成的。
生成 Dubbo Service 呼叫 Proxy 如下 :
public class proxy3 implements TccClassGenerator.DC, RedPacketTradeOrderService, EchoService {
public static Method[] methods;
private InvocationHandler handler;
public proxy3() {}
public proxy3(InvocationHandler paramInvocationHandler) {
this.handler = paramInvocationHandler;
}
@Compensable(propagation = Propagation.SUPPORTS, confirmMethod = "record", cancelMethod = "record", transactionContextEditor = DubboTransactionContextEditor.class)
public String record(RedPacketTradeOrderDto paramRedPacketTradeOrderDto) {
Object[] arrayOfObject = new Object[1];
arrayOfObject[0] = paramRedPacketTradeOrderDto;
Object localObject = this.handler.invoke(this, methods[0], arrayOfObject);
return (String) localObject;
}
public Object $echo(Object paramObject) {
Object[] arrayOfObject = new Object[1];
arrayOfObject[0] = paramObject;
Object localObject = this.handler.invoke(this, methods[1], arrayOfObject);
return (Object) localObject;
}
}
複製程式碼
com.alibaba.dubbo.rpc.service.EchoService
,Dubbo Service 回聲服務介面,用於服務健康檢查,Dubbo Service 預設自動實現該介面,點選連線檢視程式碼。org.mengyun.tcctransaction.dubbo.proxy.javassist.TccClassGenerator.DC
動態生成類標記,標記該類由 TccClassGenerator 生成的。
?實現
呼叫 TccProxy#getProxy(...)
方法,獲得 TCC Proxy 工廠,實現程式碼如下:
1: // 【TccProxy.java】
2: public static TccProxy getProxy(ClassLoader cl, Class<?>... ics) {
3: // 校驗介面超過上限
4: if (ics.length > 65535) {
5: throw new IllegalArgumentException("interface limit exceeded");
6: }
7:
8: // use interface class name list as key.
9: StringBuilder sb = new StringBuilder();
10: for (Class<?> ic : ics) {
11: String itf = ic.getName();
12: // 校驗是否為介面
13: if (!ic.isInterface()) {
14: throw new RuntimeException(itf + " is not a interface.");
15: }
16: // 載入介面類
17: Class<?> tmp = null;
18: try {
19: tmp = Class.forName(itf, false, cl);
20: } catch (ClassNotFoundException ignored) {
21: }
22: if (tmp != ic) { // 載入介面類失敗
23: throw new IllegalArgumentException(ic + " is not visible from class loader");
24: }
25: sb.append(itf).append(';');
26: }
27: String key = sb.toString();
28:
29: // get cache by class loader.
30: Map<String, Object> cache;
31: synchronized (ProxyCacheMap) {
32: cache = ProxyCacheMap.get(cl);
33: if (cache == null) {
34: cache = new HashMap<String, Object>();
35: ProxyCacheMap.put(cl, cache);
36: }
37: }
38:
39: // 獲得 TccProxy 工廠
40: TccProxy proxy = null;
41: synchronized (cache) {
42: do {
43: // 從快取中獲取 TccProxy 工廠
44: Object value = cache.get(key);
45: if (value instanceof Reference<?>) {
46: proxy = (TccProxy) ((Reference<?>) value).get();
47: if (proxy != null) {
48: return proxy;
49: }
50: }
51: // 快取中不存在,設定生成 TccProxy 程式碼標記。建立中時,其他建立請求等待,避免併發。
52: if (value == PendingGenerationMarker) {
53: try {
54: cache.wait();
55: } catch (InterruptedException ignored) {
56: }
57: } else {
58: cache.put(key, PendingGenerationMarker);
59: break;
60: }
61: }
62: while (true);
63: }
64:
65: long id = PROXY_CLASS_COUNTER.getAndIncrement();
66: String pkg = null;
67: TccClassGenerator ccp = null; // proxy class generator
68: TccClassGenerator ccm = null; // proxy factory class generator
69: try {
70: // 建立 Tcc class 程式碼生成器
71: ccp = TccClassGenerator.newInstance(cl);
72:
73: Set<String> worked = new HashSet<String>(); // 已處理方法簽名集合。key:方法簽名
74: List<Method> methods = new ArrayList<Method>(); // 已處理方法集合。
75:
76: // 處理介面
77: for (Class<?> ic : ics) {
78: // 非 public 介面,使用介面包名
79: if (!Modifier.isPublic(ic.getModifiers())) {
80: String npkg = ic.getPackage().getName();
81: if (pkg == null) {
82: pkg = npkg;
83: } else {
84: if (!pkg.equals(npkg)) { // 實現了兩個非 public 的介面,
85: throw new IllegalArgumentException("non-public interfaces from different packages");
86: }
87: }
88: }
89: // 新增介面
90: ccp.addInterface(ic);
91: // 處理介面方法
92: for (Method method : ic.getMethods()) {
93: // 新增方法簽名到已處理方法簽名集合
94: String desc = ReflectUtils.getDesc(method);
95: if (worked.contains(desc)) {
96: continue;
97: }
98: worked.add(desc);
99: // 生成介面方法實現程式碼
100: int ix = methods.size();
101: Class<?> rt = method.getReturnType();
102: Class<?>[] pts = method.getParameterTypes();
103: StringBuilder code = new StringBuilder("Object[] args = new Object[").append(pts.length).append("];");
104: for (int j = 0; j < pts.length; j++) {
105: code.append(" args[").append(j).append("] = ($w)$").append(j + 1).append(";");
106: }
107: code.append(" Object ret = handler.invoke(this, methods[").append(ix).append("], args);");
108: if (!Void.TYPE.equals(rt)) {
109: code.append(" return ").append(asArgument(rt, "ret")).append(";");
110: }
111: methods.add(method);
112: // 新增方法
113: Compensable compensable = method.getAnnotation(Compensable.class);
114: if (compensable != null) {
115: ccp.addMethod(true, method.getName(), method.getModifiers(), rt, pts, method.getExceptionTypes(), code.toString());
116: } else {
117: ccp.addMethod(false, method.getName(), method.getModifiers(), rt, pts, method.getExceptionTypes(), code.toString());
118: }
119: }
120: }
121:
122: // 設定包路徑
123: if (pkg == null) {
124: pkg = PACKAGE_NAME;
125: }
126:
127: // create ProxyInstance class.
128: // 設定類名
129: String pcn = pkg + ".proxy" + id;
130: ccp.setClassName(pcn);
131: // 新增靜態屬性 methods
132: ccp.addField("public static java.lang.reflect.Method[] methods;");
133: // 新增屬性 handler
134: ccp.addField("private " + InvocationHandler.class.getName() + " handler;");
135: // 新增構造方法,引數 handler
136: ccp.addConstructor(Modifier.PUBLIC, new Class<?>[]{InvocationHandler.class}, new Class<?>[0], "handler=$1;");
137: // 新增構造方法,引數 空
138: ccp.addDefaultConstructor();
139: // 生成類
140: Class<?> clazz = ccp.toClass();
141: // 設定靜態屬性 methods
142: clazz.getField("methods").set(null, methods.toArray(new Method[0]));
143:
144: // create TccProxy class.
145: // 建立 Tcc class 程式碼生成器
146: ccm = TccClassGenerator.newInstance(cl);
147: // 設定類名
148: String fcn = TccProxy.class.getName() + id;
149: ccm.setClassName(fcn);
150: // 新增構造方法,引數 空
151: ccm.addDefaultConstructor();
152: // 設定父類為 TccProxy.class
153: ccm.setSuperClass(TccProxy.class);
154: // 新增方法 #newInstance(handler)
155: ccm.addMethod("public Object newInstance(" + InvocationHandler.class.getName() + " h){ return new " + pcn + "($1); }");
156: // 生成類
157: Class<?> pc = ccm.toClass();
158: // 建立 TccProxy 物件
159: proxy = (TccProxy) pc.newInstance();
160: } catch (RuntimeException e) {
161: throw e;
162: } catch (Exception e) {
163: throw new RuntimeException(e.getMessage(), e);
164: } finally {
165: // release TccClassGenerator
166: if (ccp != null) {
167: ccp.release();
168: }
169: if (ccm != null) {
170: ccm.release();
171: }
172: // 喚醒快取 wait
173: synchronized (cache) {
174: if (proxy == null) {
175: cache.remove(key);
176: } else {
177: cache.put(key, new WeakReference<TccProxy>(proxy));
178: }
179: cache.notifyAll();
180: }
181: }
182: return proxy;
183: }
複製程式碼
-
第 3 至 7 行 :校驗介面超過上限。
-
第 8 至 27 行 :使用介面集合類名以
;
分隔拼接,作為 Proxy 的唯一標識。例如 :key=org.mengyun.tcctransaction.sample.dubbo.redpacket.api.RedPacketAccountService;com.alibaba.dubbo.rpc.service.EchoService;
。 -
第 29 至 37 行 :獲得 Proxy 對應的 ClassLoader。這裡我們看下靜態屬性
ProxyCacheMap
的定義:/** * Proxy 物件快取 * key :ClassLoader * value.key :Tcc Proxy 標識。使用 Tcc Proxy 實現介面名拼接 * value.value :Tcc Proxy 工廠物件 */ private static final Map<ClassLoader, Map<String, Object>> ProxyCacheMap = new WeakHashMap<ClassLoader, Map<String, Object>>(); 複製程式碼
- 使用 WeakHashMap,當 ClassLoader 被回收時,其對應的值一起被移除。
- 《WeakHashMap和HashMap的區別》
- 《Java 集合系列13之 WeakHashMap詳細介紹(原始碼解析)和使用示例》
-
第 39 至 63 行 :一直獲得 TCC Proxy 工廠直到成功。
- 第 43 至 50 行 :從快取中獲取 TCC Proxy 工廠。
- 第 51 至 60 行 :若快取中不存在,設定正在生成 TccProxy 程式碼標記。建立中時,其他建立請求等待,避免併發。
-
第 65 行 :
PROXY_CLASS_COUNTER
,Proxy Class 計數,用於生成 Proxy 類名自增。程式碼如下:private static final AtomicLong PROXY_CLASS_COUNTER = new AtomicLong(0); 複製程式碼
-
第 66 至 67 行
ccm
,生成 Dubbo Service 呼叫 ProxyFactory 的程式碼生成器ccp
,生成 Dubbo Service 呼叫 Proxy 的程式碼生成器
-
第 70 至 142 行 :生成 Dubbo Service 呼叫 Proxy 的程式碼。
-
第 70 至 71 行 :呼叫
TccClassGenerator#newInstance(loader)
方法, 建立生成 Dubbo Service 呼叫 Proxy 的程式碼生成器。實現程式碼如下:// TccClassGenerator.java public final class TccClassGenerator { /** * CtClass hash 集合 * key:類名 */ private ClassPool mPool; public static TccClassGenerator newInstance(ClassLoader loader) { return new TccClassGenerator(getClassPool(loader)); } private TccClassGenerator(ClassPool pool) { mPool = pool; } } 複製程式碼
- ClassPool 是一個 CtClass 物件的 hash 表,類名做為 key 。ClassPool 的
#get(key)
搜尋 hash 表找到與指定 key 關聯的 CtClass 物件。如果沒有找到 CtClass 物件,#get(key)
讀一個類檔案構建新的 CtClass 物件,它是被記錄在 hash 表中然後返回這個物件。
- ClassPool 是一個 CtClass 物件的 hash 表,類名做為 key 。ClassPool 的
-
第 76 至 120 行,處理介面。
-
第 79 至 88 行,生成類的包名。
-
第 89 至 90 行,呼叫
TccClassGenerator#addInterface(cl)
方法,新增生成類的介面( Dubbo Service 介面 )。實現程式碼如下:/** * 生成類的介面集合 */ private Set<String> mInterfaces; public TccClassGenerator addInterface(Class<?> cl) { return addInterface(cl.getName()); } public TccClassGenerator addInterface(String cn) { if (mInterfaces == null) { mInterfaces = new HashSet<String>(); } mInterfaces.add(cn); return this; } 複製程式碼
- x
-
第 93 至 98 行,新增方法簽名到已處理方法簽名集合。多個介面可能存在相同的介面方法,跳過相同的方法,避免衝突。
-
第 99 至 110 行,生成 Dubbo Service 呼叫實現程式碼。案例程式碼如下:
public String record(RedPacketTradeOrderDto paramRedPacketTradeOrderDto) { Object[] arrayOfObject = new Object[1]; arrayOfObject[0] = paramRedPacketTradeOrderDto; Object localObject = this.handler.invoke(this, methods[0], arrayOfObject); return (String)localObject; } 複製程式碼
-
第 112 至 118 行 :呼叫
TccClassGenerator#addMethod(...)
方法,新增生成的方法。實現程式碼如下:/** * 生成類的方法程式碼集合 */ private List<String> mMethods; /** * 帶 @Compensable 方法程式碼集合 */ private Set<String> compensableMethods = new HashSet<String>(); public TccClassGenerator addMethod(boolean isCompensableMethod, String name, int mod, Class<?> rt, Class<?>[] pts, Class<?>[] ets, String body) { // 拼接方法 StringBuilder sb = new StringBuilder(); sb.append(modifier(mod)).append(' ').append(ReflectUtils.getName(rt)).append(' ').append(name); sb.append('('); for (int i = 0; i < pts.length; i++) { if (i > 0) sb.append(','); sb.append(ReflectUtils.getName(pts[i])); sb.append(" arg").append(i); } sb.append(')'); if (ets != null && ets.length > 0) { sb.append(" throws "); for (int i = 0; i < ets.length; i++) { if (i > 0) sb.append(','); sb.append(ReflectUtils.getName(ets[i])); } } sb.append('{').append(body).append('}'); // 是否有 @Compensable 註解 if (isCompensableMethod) { compensableMethods.add(sb.toString()); } return addMethod(sb.toString()); } public TccClassGenerator addMethod(String code) { if (mMethods == null) { mMethods = new ArrayList<String>(); } mMethods.add(code); return this; } 複製程式碼
-
-
第 122 至 130 行,生成類名( 例如,
org.mengyun.tcctransaction.dubbo.proxy.javassist.proxy3
),並呼叫TccClassGenerator#setClassName(...)
方法,設定類名。實現程式碼如下:/** * 生成類的類名 */ private String mClassName; public TccClassGenerator setClassName(String name) { mClassName = name; return this; } 複製程式碼
- x
-
第 131 至 134 行,呼叫
TccClassGenerator#addField(...)
方法,新增靜態屬性methods
( Dubbo Service 方法集合 )和屬性handler
( Dubbo InvocationHandler )。實現程式碼如下:/** * 生成類的屬性集合 */ private List<String> mFields; public TccClassGenerator addField(String code) { if (mFields == null) { mFields = new ArrayList<String>(); } mFields.add(code); return this; } 複製程式碼
- x
-
第 135 至 136 行,呼叫
TccClassGenerator#addConstructor(...)
方法,新增引數為handler
的構造方法。實現程式碼如下:/** * 生成類的非空構造方法程式碼集合 */ private List<String> mConstructors; public TccClassGenerator addConstructor(int mod, Class<?>[] pts, Class<?>[] ets, String body) { // 構造方法程式碼 StringBuilder sb = new StringBuilder(); sb.append(modifier(mod)).append(' ').append(SIMPLE_NAME_TAG); sb.append('('); for (int i = 0; i < pts.length; i++) { if (i > 0) sb.append(','); sb.append(ReflectUtils.getName(pts[i])); sb.append(" arg").append(i); } sb.append(')'); if (ets != null && ets.length > 0) { sb.append(" throws "); for (int i = 0; i < ets.length; i++) { if (i > 0) sb.append(','); sb.append(ReflectUtils.getName(ets[i])); } } sb.append('{').append(body).append('}'); // return addConstructor(sb.toString()); } public TccClassGenerator addConstructor(String code) { if (mConstructors == null) { mConstructors = new LinkedList<String>(); } mConstructors.add(code); return this; } public TccClassGenerator addConstructor(String code) { if (mConstructors == null) { mConstructors = new LinkedList<String>(); } mConstructors.add(code); return this; } 複製程式碼
- x
-
第 137 至 138 行,呼叫
TccClassGenerator#addDefaultConstructor()
方法,新增預設空構造方法。實現程式碼如下:/** * 預設空構造方法 */ private boolean mDefaultConstructor = false; public TccClassGenerator addDefaultConstructor() { mDefaultConstructor = true; return this; } 複製程式碼
- x
-
第 139 行,呼叫
TccClassGenerator#toClass()
方法,生成類。實現程式碼如下:1: public Class<?> toClass() { 2: // mCtc 非空時,進行釋放;下面會進行建立 mCtc 3: if (mCtc != null) { 4: mCtc.detach(); 5: } 6: long id = CLASS_NAME_COUNTER.getAndIncrement(); 7: try { 8: CtClass ctcs = mSuperClass == null ? null : mPool.get(mSuperClass); 9: if (mClassName == null) { // 類名 10: mClassName = (mSuperClass == null || javassist.Modifier.isPublic(ctcs.getModifiers()) 11: ? TccClassGenerator.class.getName() : mSuperClass + "$sc") + id; 12: } 13: // 建立 mCtc 14: mCtc = mPool.makeClass(mClassName); 15: if (mSuperClass != null) { // 繼承類 16: mCtc.setSuperclass(ctcs); 17: } 18: mCtc.addInterface(mPool.get(DC.class.getName())); // add dynamic class tag. 19: if (mInterfaces != null) { // 實現介面集合 20: for (String cl : mInterfaces) { 21: mCtc.addInterface(mPool.get(cl)); 22: } 23: } 24: if (mFields != null) { // 屬性集合 25: for (String code : mFields) { 26: mCtc.addField(CtField.make(code, mCtc)); 27: } 28: } 29: if (mMethods != null) { // 方法集合 30: for (String code : mMethods) { 31: if (code.charAt(0) == ':') { 32: mCtc.addMethod(CtNewMethod.copy(getCtMethod(mCopyMethods.get(code.substring(1))), code.substring(1, code.indexOf('(')), mCtc, null)); 33: } else { 34: CtMethod ctMethod = CtNewMethod.make(code, mCtc); 35: if (compensableMethods.contains(code)) { 36: // 設定 @Compensable 屬性 37: ConstPool constpool = mCtc.getClassFile().getConstPool(); 38: AnnotationsAttribute attr = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag); 39: Annotation annot = new Annotation("org.mengyun.tcctransaction.api.Compensable", constpool); 40: EnumMemberValue enumMemberValue = new EnumMemberValue(constpool); 41: enumMemberValue.setType("org.mengyun.tcctransaction.api.Propagation"); 42: enumMemberValue.setValue("SUPPORTS"); 43: annot.addMemberValue("propagation", enumMemberValue); 44: annot.addMemberValue("confirmMethod", new StringMemberValue(ctMethod.getName(), constpool)); 45: annot.addMemberValue("cancelMethod", new StringMemberValue(ctMethod.getName(), constpool)); 46: ClassMemberValue classMemberValue = new ClassMemberValue("org.mengyun.tcctransaction.dubbo.context.DubboTransactionContextEditor", constpool); 47: annot.addMemberValue("transactionContextEditor", classMemberValue); 48: attr.addAnnotation(annot); 49: ctMethod.getMethodInfo().addAttribute(attr); 50: } 51: mCtc.addMethod(ctMethod); 52: } 53: } 54: } 55: if (mDefaultConstructor) { // 空引數構造方法 56: mCtc.addConstructor(CtNewConstructor.defaultConstructor(mCtc)); 57: } 58: if (mConstructors != null) { // 帶引數構造方法 59: for (String code : mConstructors) { 60: if (code.charAt(0) == ':') { 61: mCtc.addConstructor(CtNewConstructor.copy(getCtConstructor(mCopyConstructors.get(code.substring(1))), mCtc, null)); 62: } else { 63: String[] sn = mCtc.getSimpleName().split("\\$+"); // inner class name include $. 64: mCtc.addConstructor(CtNewConstructor.make(code.replaceFirst(SIMPLE_NAME_TAG, sn[sn.length - 1]), mCtc)); 65: } 66: } 67: } 68: // mCtc.debugWriteFile("/Users/yunai/test/" + mCtc.getSimpleName().replaceAll(".", "/") + ".class"); 69: // 生成 70: return mCtc.toClass(); 71: } catch (RuntimeException e) { 72: throw e; 73: } catch (NotFoundException e) { 74: throw new RuntimeException(e.getMessage(), e); 75: } catch (CannotCompileException e) { 76: throw new RuntimeException(e.getMessage(), e); 77: } 78: } 複製程式碼
- 基於 Javassist 生成類。這裡不做擴充解釋,配合《Java學習之javassist》一起理解。
- 第 18 行,新增
org.mengyun.tcctransaction.dubbo.proxy.javassist.TccClassGenerator.DC
動態生成類標記,標記該類由 TccClassGenerator 生成的。 - 第 34 至 50 行,設定 @Compensable 預設屬性。
-
第 141 至 142 行,設定 Dubbo Service 方法集合設定到靜態屬性
methods
上。
-
-
第 144 至 157 行,生成 Dubbo Service 呼叫 Proxy 工廠的程式碼。
-
第 146 行,呼叫
TccClassGenerator#newInstance(loader)
方法, 建立生成 Dubbo Service 呼叫 Proxy 工廠 的程式碼生成器。 -
第 147 至 149 行,生成類名( 例如,
org.mengyun.tcctransaction.dubbo.proxy.javassist.TccProxy3
),並呼叫TccClassGenerator#setClassName(...)
方法,設定類名。 -
第 150 至 151 行,呼叫
TccClassGenerator#addDefaultConstructor()
方法,新增預設空構造方法。 -
第 152 至 153 行,呼叫
TccClassGenerator#mSuperClass()
方法,設定繼承父類TccProxy
。實現程式碼如下:/** * 生成類的父類名字 */ private String mSuperClass; public TccClassGenerator setSuperClass(Class<?> cl) { mSuperClass = cl.getName(); return this; } 複製程式碼
- x
-
第 154 至 155 行,呼叫
TccClassGenerator#addInterface(cl)
方法,新增生成 Proxy 實現程式碼的方法。程式碼案例如下:public Object newInstance(InvocationHandler paramInvocationHandler) { return new proxy3(paramInvocationHandler); } 複製程式碼
- x
-
第 156 至 157 行,呼叫
TccClassGenerator#toClass()
方法,生成類。
-
-
第 159 行,呼叫
TccProxy#newInstance()
方法,建立 Proxy 。實現程式碼如下:/** * get instance with default handler. * * @return instance. */ public Object newInstance() { return newInstance(THROW_UNSUPPORTED_INVOKER); } /** * get instance with special handler. * * @return instance. */ abstract public Object newInstance(InvocationHandler handler); 複製程式碼
#newInstance(handler)
,抽象方法,上面第 154 至 155 行生成。TccJavassistProxyFactory 呼叫該方法,獲得 Proxy 。
-
第 165 至 171 行,釋放 TccClassGenerator 。實現程式碼如下:
public void release() { if (mCtc != null) { mCtc.detach(); } if (mInterfaces != null) { mInterfaces.clear(); } if (mFields != null) { mFields.clear(); } if (mMethods != null) { mMethods.clear(); } if (mConstructors != null) { mConstructors.clear(); } if (mCopyMethods != null) { mCopyMethods.clear(); } if (mCopyConstructors != null) { mCopyConstructors.clear(); } } 複製程式碼
-
第 172 至 180 行,設定 Proxy 工廠快取,並喚醒等待執行緒。
**ps:**程式碼比較多,收穫會比較多,算是 Javassist 實戰案例了。TCC-Transaction 作者在實現上述類,可能參考了 Dubbo 自帶的實現:
com.alibaba.dubbo.common.bytecode.Proxy
com.alibaba.dubbo.common.bytecode.ClassGenerator
com.alibaba.dubbo.common.bytecode.Wrapper
2.1.4 配置 Dubbo Proxy
// META-INF.dubbo/com.alibaba.dubbo.rpc.ProxyFactory
tccJavassist=org.mengyun.tcctransaction.dubbo.proxy.javassist.TccJavassistProxyFactory
// tcc-transaction-dubbo.xml
<dubbo:provider proxy="tccJavassist"/>
複製程式碼
目前 Maven 專案 tcc-transaction-dubbo
已經預設配置,引入即可。
2.2 JdkProxyFactory
2.2.1 JDK Proxy
《 Java JDK 動態代理(AOP)使用及實現原理分析》
2.2.2 TccJdkProxyFactory
org.mengyun.tcctransaction.dubbo.proxy.jd.TccJdkProxyFactory
,TCC JDK 代理工廠。實現程式碼如下:
public class TccJdkProxyFactory extends JdkProxyFactory {
@SuppressWarnings("unchecked")
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
T proxy = (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), interfaces, new InvokerInvocationHandler(invoker));
return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), interfaces, new TccInvokerInvocationHandler(proxy, invoker));
}
}
複製程式碼
- 專案啟動時,呼叫
TccJavassistProxyFactory#getProxy(...)
方法,生成 Dubbo Service 呼叫 Proxy。 - 第一次呼叫
Proxy#newProxyInstance(...)
方法,建立呼叫 Dubbo Service 服務的 Proxy。com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler
,Dubbo 呼叫處理器,點選連線檢視程式碼。 - 第二次呼叫
Proxy#newProxyInstance(...)
方法,建立對呼叫 Dubbo Service 的 Proxy 的 Proxy。為什麼會有兩層 Proxy?答案在下節 TccInvokerInvocationHandler 。
2.2.3 TccInvokerInvocationHandler
org.mengyun.tcctransaction.dubbo.proxy.jdk.TccInvokerInvocationHandler
,TCC 呼叫處理器,在呼叫 Dubbo Service 服務時,使用 ResourceCoordinatorInterceptor 攔截處理。實現程式碼如下:
1: public class TccInvokerInvocationHandler extends InvokerInvocationHandler {
2:
3: /**
4: * proxy
5: */
6: private Object target;
7:
8: public TccInvokerInvocationHandler(Invoker<?> handler) {
9: super(handler);
10: }
11:
12: public <T> TccInvokerInvocationHandler(T target, Invoker<T> invoker) {
13: super(invoker);
14: this.target = target;
15: }
16:
17: public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
18: Compensable compensable = method.getAnnotation(Compensable.class);
19: if (compensable != null) {
20: // 設定 @Compensable 屬性
21: if (StringUtils.isEmpty(compensable.confirmMethod())) {
22: ReflectionUtils.changeAnnotationValue(compensable, "confirmMethod", method.getName());
23: ReflectionUtils.changeAnnotationValue(compensable, "cancelMethod", method.getName());
24: ReflectionUtils.changeAnnotationValue(compensable, "transactionContextEditor", DubboTransactionContextEditor.class);
25: ReflectionUtils.changeAnnotationValue(compensable, "propagation", Propagation.SUPPORTS);
26: }
27: // 生成切面
28: ProceedingJoinPoint pjp = new MethodProceedingJoinPoint(proxy, target, method, args);
29: // 執行
30: return FactoryBuilder.factoryOf(ResourceCoordinatorAspect.class).getInstance().interceptTransactionContextMethod(pjp);
31: } else {
32: return super.invoke(target, method, args);
33: }
34: }
35:
36: }
複製程式碼
-
第 18 至 26 行,設定帶有 @Compensable 屬性的預設屬性。
-
第 28 行,生成方法切面
org.mengyun.tcctransaction.dubbo.proxy.jdk.MethodProceedingJoinPoint
。實現程式碼如下:public class MethodProceedingJoinPoint implements ProceedingJoinPoint, JoinPoint.StaticPart { /** * 代理物件 */ private Object proxy; /** * 目標物件 */ private Object target; /** * 方法 */ private Method method; /** * 引數 */ private Object[] args; @Override public Object proceed() throws Throwable { // Use reflection to invoke the method. try { ReflectionUtils.makeAccessible(method); return method.invoke(target, args); } catch (InvocationTargetException ex) { // Invoked method threw a checked exception. // We must rethrow it. The client won't see the interceptor. throw ex.getTargetException(); } catch (IllegalArgumentException ex) { throw new SystemException("Tried calling method [" + method + "] on target [" + target + "] failed", ex); } catch (IllegalAccessException ex) { throw new SystemException("Could not access method [" + method + "]", ex); } } @Override public Object proceed(Object[] objects) throws Throwable { // throw new UnsupportedOperationException(); // TODO 芋艿:疑問 return proceed(); } // ... 省略不重要的方法和物件 } 複製程式碼
- 該類參考
org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint
實現。 - TODO【1】 proxy 和 target 是否保留一個即可?
- 在切面處理完成後,呼叫
#proceed(...)
方法,進行遠端 Dubbo Service 服務呼叫。 - TODO【2】
#proceed(objects)
丟擲 throw new UnsupportedOperationException();。需要跟作者確認下。
- 該類參考
-
呼叫
ResourceCoordinatorAspect#interceptTransactionContextMethod(...)
方法,對方法切面攔截處理。為什麼無需呼叫 CompensableTransactionAspect 切面?因為傳播級別為 Propagation.SUPPORTS,不會發起事務。
2.2.4 配置 Dubbo Proxy
// META-INF.dubbo/com.alibaba.dubbo.rpc.ProxyFactory
tccJdk=org.mengyun.tcctransaction.dubbo.proxy.jdk.TccJdkProxyFactory
// appcontext-service-dubbo.xml
<dubbo:provider proxy="tccJdk"/>
<dubbo:reference proxy="tccJdk" id="captialTradeOrderService"
interface="org.mengyun.tcctransaction.sample.dubbo.capital.api.CapitalTradeOrderService" timeout="5000"/>
複製程式碼
- ProxyFactory 的
tccJdk
在 Maven 項tcc-transaction-dubbo
已經宣告。 - 宣告
dubbo:provider
的proxy="tccJdk"
。 - 宣告
dubbo:reference
的proxy="tccJdk"
,否則不生效。
3. Dubbo 事務上下文編輯器
org.mengyun.tcctransaction.dubbo.context.DubboTransactionContextEditor
,Dubbo 事務上下文編輯器實現,實現程式碼如下:
public class DubboTransactionContextEditor implements TransactionContextEditor {
@Override
public TransactionContext get(Object target, Method method, Object[] args) {
String context = RpcContext.getContext().getAttachment(TransactionContextConstants.TRANSACTION_CONTEXT);
if (StringUtils.isNotEmpty(context)) {
return JSON.parseObject(context, TransactionContext.class);
}
return null;
}
@Override
public void set(TransactionContext transactionContext, Object target, Method method, Object[] args) {
RpcContext.getContext().setAttachment(TransactionContextConstants.TRANSACTION_CONTEXT, JSON.toJSONString(transactionContext));
}
}
複製程式碼
- 通過 Dubbo 的隱式傳參的方式,避免在 Dubbo Service 介面上宣告 TransactionContext 引數,對介面產生一定的入侵。
666. 彩蛋
HOHO,對動態代理又學習了一遍,蠻 High 的。
這裡推薦動態代理無關,和 Dubbo 相關的文章:
胖友,分享一波朋友圈可好。