歡迎光臨
每天分享高質量文章

分散式事務 TCC-Transaction 原始碼分析 —— Dubbo 支援

本文主要基於 TCC-Transaction 1.2.3.3 正式版

  • 1. 概述

  • 2. Dubbo 代理

  • 2.1 JavassistProxyFactory

    • 2.1.1 Javassist

    • 2.1.2 TccJavassistProxyFactory

    • 2.1.3 TccProxy & TccClassGenerator

    • 2.1.4 配置 Dubbo Proxy

  • 2.2 JdkProxyFactory

    • 2.2.1 JDK Proxy

    • 2.2.2 TccJdkProxyFactory

    • 2.2.3 TccInvokerInvocationHandler

    • 2.2.4 配置 Dubbo Proxy

  • 3. Dubbo 事務背景關係編輯器

  • 666. 彩蛋

友情提示:歡迎關註公眾號【芋道原始碼】。?關註後,拉你進【原始碼圈】微信群和【芋艿】搞基嗨皮。

友情提示:歡迎關註公眾號【芋道原始碼】。?關註後,拉你進【原始碼圈】微信群和【芋艿】】搞基嗨皮。

友情提示:歡迎關註公眾號【芋道原始碼】。?關註後,拉你進【原始碼圈】微信群和【芋艿】】搞基嗨皮。 


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 ,自動生成註解的 confirmMethodcancelMethodtransactionContextEditor 屬性,例子程式碼如下:

@Compensable(propagation=Propagation.SUPPORTS, confirmMethod="record", cancelMethod="record", transactionContextEditor=DubboTransactionContextEditor.class)
public String record(RedPacketTradeOrderDto paramRedPacketTradeOrderDto) {
   // ... 省略程式碼
}
  • 該程式碼透過 Javassist 生成的 Proxy 程式碼的示例。

  • propagation=Propagation.SUPPORTS :支援當前事務,如果當前沒有事務,就以非事務方式執行。為什麼不使用 REQUIRED ?如果使用 REQUIRED 事務傳播級別,事務恢復重試時,會發起新的事務。

  • confirmMethodcancelMethod 使用和 try 方法相同方法名本地發起遠端服務 TCC confirm / cancel 階段,呼叫相同方法進行事務的提交或回滾。遠端服務的 CompensableTransactionInterceptor 會根據事務的狀態是 CONFIRMING / CANCELLING 來呼叫對應方法。

  • transactionContextEditor=DubboTransactionContextEditor.class,使用 Dubbo 事務背景關係編輯器,在「3. Dubbo 事務背景關係編輯器」詳細分享。

Dubbo Service Proxy 提供了兩種生成方式:

  • JavassistProxyFactory,基於 Javassist 方式

  • JdkProxyFactory,基於 JDK 動態代理機制

這塊內容我們不拓展開,感興趣的同學點選如下文章:

  • 《Dubbo學習-理解動態代理》

  • 《Dubbo 作者部落格 —— 動態代理方案效能對比》

  • 《Dubbo原理解析-代理之Javassist生成的偽程式碼》

  • 《Dubbo的服務暴露細節》

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 getProxy(Invoker 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 cache;
31:     synchronized (ProxyCacheMap) {
32:         cache = ProxyCacheMap.get(cl);
33:         if (cache == null) {
34:             cache = new HashMap();
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 worked = new HashSet(); // 已處理方法簽名集合。key:方法簽名
74:         List methods = new ArrayList(); // 已處理方法集合。
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(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> ProxyCacheMap = new WeakHashMap>();
    • 使用 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 的程式碼

    • 基於 Javassist 生成類。這裡不做拓展解釋,配合《Java學習之javassist》一起理解。

    • 第 18 行,新增 org.mengyun.tcctransaction.dubbo.proxy.javassist.TccClassGenerator.DC 動態生成類標記,標記該類由 TccClassGenerator 生成的。

    • 第 34 至 50 行,設定 @Compensable 預設屬性。

    • x

    • x

    • x

    • x

    • 第 79 至 88 行,生成類的包名。

    • 第 89 至 90 行,呼叫 TccClassGenerator#addInterface(cl) 方法,新增生成類的介面( Dubbo Service 介面 )。實現程式碼如下:

      /**
      * 生成類的介面集合
      */

      private Set mInterfaces;

      public TccClassGenerator addInterface(Class> cl) {
        return addInterface(cl.getName());
      }

      public TccClassGenerator addInterface(String cn) {
        if (mInterfaces == null) {
            mInterfaces = new HashSet();
        }
        mInterfaces.add(cn);
        return this;
      }
    • 第 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 mMethods;

      /**
      * 帶 @Compensable 方法程式碼集合
      */

      private Set compensableMethods = new HashSet();

      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();
        }
        mMethods.add(code);
        return this;
      }
    • x

    • ClassPool 是一個 CtClass 物件的 hash 表,類名做為 key 。ClassPool 的 #get(key) 搜尋 hash 表找到與指定 key 關聯的 CtClass 物件。如果沒有找到 CtClass 物件,#get(key) 讀一個類檔案構建新的 CtClass 物件,它是被記錄在 hash 表中然後傳回這個物件。

    • 第 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;
      }

      }

    • 第 76 至 120 行,處理介面。

    • 第 122 至 130 行,生成類名( 例如,org.mengyun.tcctransaction.dubbo.proxy.javassist.proxy3),並呼叫 TccClassGenerator#setClassName(...) 方法,設定類名。實現程式碼如下:

      /**
      * 生成類的類名
      */

      private String mClassName;

      public TccClassGenerator setClassName(String name) {
        mClassName = name;
        return this;
      }
    • 第 131 至 134 行,呼叫 TccClassGenerator#addField(...) 方法,新增靜態屬性 methods ( Dubbo Service 方法集合 )和屬性 handler ( Dubbo InvocationHandler )。實現程式碼如下:

      /**
      * 生成類的屬性集合
      */

      private List mFields;

      public TccClassGenerator addField(String code) {
        if (mFields == null) {
            mFields = new ArrayList();
        }
        mFields.add(code);
        return this;
      }
    • 第 135 至 136 行,呼叫 TccClassGenerator#addConstructor(...) 方法,新增引數為 handler 的構造方法。實現程式碼如下:

      /**
      * 生成類的非空構造方法程式碼集合
      */

      private List 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();
        }
        mConstructors.add(code);
        return this;
      }

      public TccClassGenerator addConstructor(String code) {
        if (mConstructors == null) {
            mConstructors = new LinkedList();
        }
        mConstructors.add(code);
        return this;
      }
    • 第 137 至 138 行,呼叫 TccClassGenerator#addDefaultConstructor() 方法,新增預設空構造方法。實現程式碼如下:

      /**
      * 預設空構造方法
      */

      private boolean mDefaultConstructor = false;

      public TccClassGenerator addDefaultConstructor() {
        mDefaultConstructor = true;
        return this;
      }
    • 第 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: }
    • 第 141 至 142 行,設定 Dubbo Service 方法集合設定到靜態屬性 methods 上。

  • 第 144 至 157 行,生成 Dubbo Service 呼叫 Proxy 工廠的程式碼

    • x

    • 第 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;
      }
    • 第 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 getProxy(Invoker 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 TccInvokerInvocationHandler(T target, Invoker 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 相關的文章:

  • 《Dubbo的服務暴露細節》。

  • 《Dubbo Provider啟動主流程》

胖友,分享一波朋友圈可好。

贊(0)

分享創造快樂