Cglib動態代理從入門到掌握

Cglib 動態代理

本文的寫作目的是為了探究 Spring 框架中在使用@Transactional標注的方法中使用 this 進行自調用時事務失效的原因,各種視頻教程中只是簡單指出 this 指向的不是代理類對象,而是目標類對象,但是并沒有解釋為什么 this 不是代理類對象?

在學習完 JDK 動態代理之后,我認為是動態代理的原因。雖然知道 Cglib Proxy 和 JDK Proxy 的實現原理不同,但當時認為方法調用只能通過 invoke 進行反射調用(錯誤依據),而傳遞給 invoke 方法的對象就是目標類對象,因此 this 指向的就是傳遞過來的目標類對象。具體可以查看另一篇博客。

最近學習完 Cglib 動態代理之后,發現動態代理類進行方法調用并不是只能依靠反射調用的,因此那一篇博客的分析也就不成立了。先說結論,在 Cglib 動態代理中,由于繞開了反射調用方法,所以 this 既可以指向代理類對象,也可以和 JDK 動態代理一樣指向目標類對象,而 Spring 框架中選擇了后者,從而有了 this 造成事務失效的情況。但是就 Cglib 本身實現動態代理而言,這個問題是可以避免的。

依賴環境

下面兩個 pom 依賴二選一即可

<!--spring-core中包含cglib-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>5.3.30</version>
</dependency><dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.1</version>
</dependency>

案例演示

public class CglibProxyTest {private static final String CLASSPATH = ClassLoader.getSystemResource("").getPath().substring(1);public static void main(String[] args) {// 設置生成字節碼文件System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, CLASSPATH);MethodInterceptor methodInterceptor = new MyMethodInterceptor();Enhancer enhancer = new Enhancer();// 設置父類字節碼enhancer.setSuperclass(ServiceImpl.class);// 設置增強方法(即方法攔截器)// TODO: 從代理類的源碼中可以看出來,setCallbacks只會使用到第一個攔截器,那么setCallbacks方法有什么意義呢?enhancer.setCallback(methodInterceptor);ServiceImpl serviceImpl = (ServiceImpl) enhancer.create();serviceImpl.show("Hello World");// 測試cglib代理方式下的this自調用和Spring事務的this自調用的區別//serviceImpl.getMsg(1, 2);}
}class MyMethodInterceptor implements MethodInterceptor {/*** 攔截方法** @param proxy        代理類對象* @param targetMethod 目標類中的方法對象* @param args         方法參數* @param methodProxy  Cglib底層使用到的MethodProxy對象,并不是代理方法* @return* @throws Throwable*/@Overridepublic Object intercept(Object proxy, Method targetMethod, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("proxy.getClass() = " + proxy.getClass());System.out.println("targetMethod.getName() = " + targetMethod.getName());System.out.println("targetMethod.getDeclaringClass().getName() = " + targetMethod.getDeclaringClass().getName());System.out.println("===========targetMethod before==========");// 可能出現的情況//  情況一:methodProxy.invoke(proxy, args) 陷入invoke -> intercept -> invoke ->...的無限遞歸中////  情況二:targetMethod.invoke(proxy, args) 陷入 intercept->show->intercept的死循環中。//  這是因為targetMethod是一個Method方法對象,只有invoke方法,此時沒有涉及到MethodProxy的invoke和invokeSuper方法//  而proxy是targetClass的一個子類對象,因此targetMethod.invoke(proxy, args)相當于在調用proxy對象中的同名targetMethod方法,即增強方法。所以陷入死循環// Spring使用this無法增強的原因,使用下面的情況三方案,其中target對象是外部傳入的,和MethodInvocationHandler一樣// 情況三:methodProxy.invoke(target, args)//Object result = methodProxy.invokeSuper(proxy, args);System.out.println("===========targetMethod after===========");return result;}
}/*** 目標類(被代理類)不存在接口*/
class ServiceImpl {public void show(String msg) {System.out.println(msg);}public String getMsg(int x, int y) {this.show("Hello World");return String.valueOf(x + y);}
}

代理類和目標類的關系結構圖

在這里插入圖片描述

圖:Cglib動態代理中代理類與目標類之間的關系

代理類字節碼

對反編譯后的源代碼文件進行變量名的調整,刪減一些非核心的細節內容。

從重寫的增強方法中可以看出來,傳遞給 intercept() 方法的參數的含義分別是:

  1. Object:(ServiceImplCglibProxy)代理類對象(this)
  2. Method:代理類中對于目標類方法的對象引用,即上圖中的 Method01Method02Method03
  3. Object[]:方法參數數組(args)
  4. MethodProxy:根據代理類中的 cglibMethod0x()method0x() 生成的 MethodProxy 對象
public class ServiceImplCglibProxyextends ServiceImplimplements Factory {// 目標類中的的方法對象(show)private static Method showMethod;// 代理類中show方法和cglibShow方法共同構建的MethodProxy對象private static MethodProxy showMethodProxy;// 目標類中的的方法對象(show)private static Method getMsgMethod;// 代理類中getMsg方法和cglibGetMsg方法共同構建的MethodProxy對象private static MethodProxy getMsgMethodProxy;// 用來包裝Object[0]private static final Object[] emptyArgs = new Object[0];// Object類中的方法private static Method finalizeMethod;private static MethodProxy finalizeMethodProxy;// 判斷是否綁定過ThreadLocal中的Callback,如果綁定過,那么之后就不需要綁定了private boolean bound;// 自定義增強邏輯,MethodInterceptor是Callback的一個子接口,在實例化的時候會通過調用靜態方法進行設置,省略private MethodInterceptor methodInterceptor;static {init();}@SneakyThrowsstatic void init() {// 獲取當前代理類的字節碼對象Class proxyClass = Class.forName("org.example.ServiceImplCglibProxy");Method[] methods;// 處理Object類中的所有方法Class targetClass = Class.forName("java.lang.Object");methods = ReflectUtils.findMethods(new String[]{"finalize", "()V","equals", "(Ljava/lang/Object;)Z","toString", "()Ljava/lang/String;","hashCode", "()I","clone", "()Ljava/lang/Object;"},targetClass.getDeclaredMethods());finalizeMethod = methods[0];finalizeMethodProxy = MethodProxy.create(targetClass, proxyClass, "()V", "finalize", "cglibFinalize");// 省略 Object 類中的其他方法的處理...// 處理ServiceImpl(父類)中的所有方法targetClass = Class.forName("org.example.ServiceImpl");methods = ReflectUtils.findMethods(new String[]{"show", "(Ljava/lang/String;)V","getMsg", "(II)Ljava/lang/String;"},targetClass.getDeclaredMethods());// 處理ServiceImpl中的show方法showMethod = methods[0];showMethodProxy = MethodProxy.create(targetClass, proxyClass, "(Ljava/lang/String;)V", "show", "cglibShow");// 處理ServiceImpl中的getMsg方法getMsgMethod = methods[1];getMsgMethodProxy = MethodProxy.create(targetClass, proxyClass, "(II)Ljava/lang/String;", "getMsg", "cglibGetMsg");}public static MethodProxy findMethodProxy(Signature signature) {String methodSignature = signature.toString();// 先比較hashCode值,再比較字符串switch (methodSignature.hashCode()) {case -1574182249:if (methodSignature.equals("finalize()V")) {return finalizeMethodProxy;}break;case 550733602:if (methodSignature.equals("show(Ljava/lang/String;)V")) {return showMethodProxy;}break;case 351083702:if (methodSignature.equals("getMsg(II)Ljava/lang/String;")) {return getMsgMethodProxy;}break;}return null;}// 為便于區分,稱為cglib方法void cglibShow(String msg) {super.show(msg);}// 為便于區分,稱為重寫方法@SneakyThrows@Overridepublic void show(String msg) {if(methodInterceptor == null){// 當使用methodProxy.invoke(target, args),由于target沒有methodInterceptor,所有會進入到這個邏輯分支中super.show(msg);return;}// 將參數封裝成Object數組,調用intercept方法// 在正常傳遞MethodInterceptor的情況下,會調用該方法methodInterceptor.intercept(this, showMethod, new Object[]{msg}, showMethodProxy);}// 為便于區分,稱為cglib方法String cglibGetMsg(int x, int y) {return super.getMsg(x, y);}// 為便于區分,稱為重寫方法@SneakyThrows@Overridepublic String getMsg(int x, int y) {if(methodInterceptor == null){return super.getMsg(x, y);}return (String) methodInterceptor.intercept(this, getMsgMethod, new Object[]{x, y}, getMsgMethodProxy);}}

增強方法的調用流程圖

在進一步討論增強方法的調用流程之前,先重新查看 MethodInterceptor 的 intercept() 方法。我們可以得到四個參數,那么怎么選擇來達成我們調用原始方法的目的。

結論:

  • 在不考慮引入額外的目標類對象 target,僅使用 intercept 方法中提供的四個參數的情況下,只有 invokeSuper() 方法能夠正確執行,同時 this 指針指向的是 ServiceImplCglibProxy(代理類)對象,因為不會出現像 Spring 中使用 @Transactional 標注的方法中使用 this 會造成事務失效的問題。
  • 在使用額外引入的目標類對象 target 的情況下(和 JDK 動態代理的 InvocationHandler 類似,但 JDK Proxy 強制要求一個目標類對象,而 Cglib Proxy 并不要求目標類對象),除了 invokeSuper() 方法外,另外兩個方法都可以正確執行。
  • **使用 MethodProxy 對象和 Method 對象的區別在于,MethodProxy 借助額外生成的字節碼 FastClass 來實現對方法的直接調用,而 Method 則是通過反射來調用方法。**因此 MethodProxy 方式調用可以解決 Spring 的 this 造成的事務失效問題,但 Spring 是故意設計成和 JDK Proxy 的效果一樣,為此還舍棄 invokeSuper 的調用方式,而是引入 target 來使用 invoke 調用。這樣設計的官方解釋沒有仔細了解,個人分析,為了統一結果,因為 JDK Proxy 是通過反射來實現的,因此 this 只能代表目標類對象;如果僅僅因為使用的動態代理不同,就造成有的時候 this 失效,有的時候 this 有效,那么就憑空多添加了混亂。因此還不如都設計成 this 失效。
class MyMethodInterceptor implements MethodInterceptor {// 不是必要的,Spring注入target造成this無法調用自身對象,直接使用proxy對象就不會出現這種情況private Object target;@Overridepublic Object intercept(Object proxy, Method targetMethod, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("===========targetMethod before==========");// 由于FastClass的機制,這里不能調用methodProxy.invoke(proxy, args),否則// 正常可能出現的情況//  情況一:methodProxy.invoke(proxy, args) 陷入invoke -> intercept -> invoke ->...的無限遞歸中////  情況二:targetMethod.invoke(proxy, args) 陷入 intercept->show->intercept的死循環中。//  這是因為targetMethod是一個Method方法對象,只有invoke方法,此時沒有涉及到MethodProxy的invoke和invokeSuper方法//  而proxy是targetClass的一個子類對象,因此targetMethod.invoke(proxy, args)相當于在調用proxy對象中的同名targetMethod方法,即增強方法。所以陷入死循環// Spring使用this無法增強的原因,使用下面的情況三方案,其中target對象是外部傳入的,和MethodInvocationHandler一樣// 情況三:methodProxy.invoke(target, args)//Object result = methodProxy.invokeSuper(proxy, args);System.out.println("===========targetMethod after===========");return result;}
}

在這里插入圖片描述

圖:增強方法調用流程

MethodProxy

主要關注 invoke() 方法和 invokeSuper() 方法,因為這兩個方法會在 MethodInterceptor 對象的 intercept() 方法中被我們調用來達到調用目標類中的原始方法的目的。

public class MethodProxy {private Signature overrideMethodSignature;private Signature cglibMethodSignature;// create方法中生成,init方法中清空private CreateInfo createInfo;private final Object initLock = new Object();private volatile FastClassInfo fastClassInfo;public static MethodProxy create(Class targetClass, Class proxyClass, String desc, String overrideMethodName, String cglibMethodName) {MethodProxy proxy = new MethodProxy();proxy.overrideMethodSignature = new Signature(overrideMethodName, desc);proxy.cglibMethodSignature = new Signature(cglibMethodName, desc);proxy.createInfo = new CreateInfo(targetClass, proxyClass);return proxy;}public Object invoke(Object object, Object[] args) throws Throwable {this.init();FastClassInfo fci = this.fastClassInfo;// 默認情況下 object 是 proxyClass 類型,那么 overrideMethod -> intercept -> invoke -> overrideMethod 形成死循環。// 如果按照Spring的方式手動傳遞 targetClass 類型的對象,object是targetClass類型(目標類)return fci.targetFastClass.invoke(fci.overrideMethodIndex, object, args);}public Object invokeSuper(Object proxy, Object[] args) throws Throwable {this.init();FastClassInfo fci = this.fastClassInfo;// 重寫父類方法時,留了一份拷貝,稱之為cglibMethod,因此可以在當前類直接調用父類中的同名方法return fci.proxyFastClass.invoke(fci.cglibMethodIndex, proxy, args);}private void init() {if (this.fastClassInfo == null) {synchronized (this.initLock) {if (this.fastClassInfo == null) {CreateInfo ci = this.createInfo;FastClassInfo fci = new FastClassInfo();fci.targetFastClass = helper(ci, ci.targetClass);fci.proxyFastClass = helper(ci, ci.proxyClass);// overrideMethodSignature在targetClass和proxyClass中的簽名都相同fci.overrideMethodIndex = fci.targetFastClass.getIndex(this.overrideMethodSignature);fci.cglibMethodIndex = fci.proxyFastClass.getIndex(this.cglibMethodSignature);this.fastClassInfo = fci;this.createInfo = null;}}}}private static FastClass helper(CreateInfo ci, Class classType) {FastClass.Generator generator = new FastClass.Generator();generator.setType(classType);generator.setClassLoader(ci.proxyClass.getClassLoader());generator.setNamingPolicy(ci.namingPolicy);generator.setStrategy(ci.strategy);generator.setAttemptLoad(ci.attemptLoad);return generator.create();}private MethodProxy() {}public Signature getSignature() {return this.overrideMethodSignature;}public String getSuperName() {return this.cglibMethodSignature.getName();}public int getSuperIndex() {this.init();return this.fastClassInfo.cglibMethodIndex;}FastClass getFastClass() {this.init();return this.fastClassInfo.targetFastClass;}FastClass getSuperFastClass() {this.init();return this.fastClassInfo.proxyFastClass;}@SneakyThrowspublic static MethodProxy find(Class classType, Signature sig) {Method method = classType.getDeclaredMethod("findMethodProxy", MethodInterceptorGenerator.FIND_PROXY_TYPES);return (MethodProxy) method.invoke(null, sig);}private static class CreateInfo {Class targetClass;Class proxyClass;NamingPolicy namingPolicy;GeneratorStrategy strategy;boolean attemptLoad;public CreateInfo(Class targetClass, Class proxyClass) {this.targetClass = targetClass;this.proxyClass = proxyClass;AbstractClassGenerator fromEnhancer = AbstractClassGenerator.getCurrent();if (fromEnhancer != null) {this.namingPolicy = fromEnhancer.getNamingPolicy();this.strategy = fromEnhancer.getStrategy();this.attemptLoad = fromEnhancer.getAttemptLoad();}}}private static class FastClassInfo {FastClass targetFastClass;FastClass proxyFastClass;int overrideMethodIndex;int cglibMethodIndex;private FastClassInfo() {}}
}

FastClass

為了避免使用 Method 的 invoke 方法來進行反射調用而設計的。

TargetFastClass

public Object invoke(int methodIndex, Object obj, Object[] args) throws InvocationTargetException {// 這里無論傳入的是ServiceImpl還是ServiceImplCglibProxy,都可以進行強轉// 如果是ServiceImplCglibProxy,就調用同名方法ServiceImpl serviceImpl = (ServiceImpl)obj;// 沒有匹配就拋出異常try {switch (methodIndex) {case 0:return serviceImpl.getMsg(((Number)args[0]).intValue(), ((Number)args[1]).intValue());case 1:// 如果傳入的實際對象是ServiceImplCglibProxy類型的,那么就會調用增強方法,而增強方法又會進而到intercept中,從而造成死循環serviceImpl.show((String)args[0]);return null;}} catch (Throwable throwable) {throw new InvocationTargetException(throwable);}throw new IllegalArgumentException("Cannot find matching method/constructor");
}

ProxyFastClass

public Object invoke(int methodIndex, Object obj, Object[] args) throws InvocationTargetException {// 強轉成代理對象(ServiceImplCglibProxy)ServiceImplCglibProxy serviceImplProxy = (ServiceImplCglibProxy)obj;// 沒有匹配就拋出異常try {// ProxyFastClass的索引順序和TargetFastClass的索引順序沒有任何關系switch (methodIndex) {case 0:return serviceImpl.getMsg(((Number)args[0]).intValue(), ((Number)args[1]).intValue());case 1:return serviceImpl.cglibGetMsg(((Number)args[0]).intValue(), ((Number)args[1]).intValue());case 2:serviceImpl.cglibShow((String)args[0]);return null;case 3:serviceImpl.show((String)args[0]);return null;}} catch (Throwable throwable) {throw new InvocationTargetException(throwable);}throw new IllegalArgumentException("Cannot find matching method/constructor");
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/211584.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/211584.shtml
英文地址,請注明出處:http://en.pswp.cn/news/211584.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

麒麟系統使用桌面共享遠程桌面

客戶端安裝vinager 服務端 安裝 vnc4server xrdp tightvncserver vino 安裝完成后 需要重啟 在用戶的家目錄下新建 .xsession 寫入xfce4-session防止閃退 雪花屏 開啟xrdp服務 遠程鏈接 Vnc只能鏈接系統登錄的用戶 Rdp可以鏈接所有普通用戶

vscode插件webview和插件通信

如果你要在 VS Code 插件的 WebView 中調用插件中的方法&#xff0c;可以使用 vscode.postMessage API。具體步驟如下&#xff1a; 在插件中&#xff0c;在創建 WebView 時&#xff0c;指定一個 onDidReceiveMessage 回調方法&#xff0c;該方法會在 WebView 中調用 vscode.po…

【C語言】結構體內存對齊

目錄 引入結構體 結構的聲明 創建和初始化 內部元素的使用&#xff1b; 特殊聲明&#xff1a; 結構體在內存中的對齊 練習&#xff1a; 引入結構體 C語言有各種數據類型&#xff0c;我們已經對一些數據類型很熟悉&#xff1a; 整型&#xff08;int&#xff09;- 存儲整…

力扣-151. 反轉字符串中的單詞

文章目錄 看下去&#xff0c;你一定可以理解此題&#xff0c;寫的簡單易懂力扣題目解題思路函數構成1.反轉函數2.消除掉多余空格函數 整體函數 看下去&#xff0c;你一定可以理解此題&#xff0c;寫的簡單易懂 力扣題目 給你一個字符串 s &#xff0c;請你反轉字符串中 單詞 …

京東商品詳情數據在數據分析行業中的重要性

京東商品詳情數據在數據分析行業中具有重要作用。這些數據提供了豐富的信息&#xff0c;可以幫助企業了解市場趨勢、消費者需求、產品表現以及運營策略等多個方面。 首先&#xff0c;京東商品詳情數據可以為企業提供市場趨勢分析的依據。通過觀察商品的銷售量、銷售額、價格等…

c語言:理解和避免野指針

野指針的定義&#xff1a; 野指針是指一個指針變量存儲了一個無效的地址&#xff0c;通常是一個未初始化的指針或者指向已經被釋放的內存地址。當程序嘗試使用野指針時&#xff0c;可能會導致程序崩潰、內存泄漏或者其他不可預測的行為。因此&#xff0c;在編程中需要特別注意…

Pandas中DataFrame對象的創建與常用屬性方法(第2講)

Pandas中DataFrame對象的創建與常用屬性方法(第2講) ??????? ??博主 侯小啾 感謝您的支持與信賴。?? ???????????????????????????????????????????????????????????????????????????…

智能優化算法應用:基于孔雀算法無線傳感器網絡(WSN)覆蓋優化 - 附代碼

智能優化算法應用&#xff1a;基于孔雀算法無線傳感器網絡(WSN)覆蓋優化 - 附代碼 文章目錄 智能優化算法應用&#xff1a;基于孔雀算法無線傳感器網絡(WSN)覆蓋優化 - 附代碼1.無線傳感網絡節點模型2.覆蓋數學模型及分析3.孔雀算法4.實驗參數設定5.算法結果6.參考文獻7.MATLAB…

[足式機器人]Part2 Dr. CAN學習筆記-數學基礎Ch0-2 特征值與特征向量

本文僅供學習使用 本文參考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN學習筆記-數學基礎Ch0-2 特征值與特征向量 1. 定義1.1 線性變換1.2 求解特征值&#xff0c;特征向量1.3 應用&#xff1a;對角化矩陣——解耦Decouple 2. Summary 1. 定義 A v ? λ v ? A\vec{v}\lambd…

【網絡奇緣】- 計算機網絡|深入學習物理層|網絡安全

? &#x1f308;個人主頁: Aileen_0v0&#x1f525;系列專欄: 一見傾心,再見傾城 --- 計算機網絡~&#x1f4ab;個人格言:"沒有羅馬,那就自己創造羅馬~" 回顧鏈接&#xff1a;http://t.csdnimg.cn/ZvPOS 這篇文章是關于深入學習原理參考模型-物理層的相關知識點&…

Linux權限命令詳解

Linux權限命令詳解 文章目錄 Linux權限命令詳解一、什么是權限&#xff1f;二、權限的本質三、Linux中的用戶四、linux中文件的權限4.1 文件訪問者的分類&#xff08;人&#xff09;4.2 文件類型和訪問權限&#xff08;事物屬性&#xff09; 五、快速掌握修改權限的做法【第一種…

Spark-Streaming+Kafka+mysql實戰示例

文章目錄 前言一、簡介1. Spark-Streaming簡介2. Kafka簡介二、實戰演練1. MySQL數據庫部分2. 導入依賴3. 編寫實體類代碼4. 編寫kafka主題管理代碼5. 編寫kafka生產者代碼6. 編寫Spark-Streaming代碼總結前言 本文將介紹一個使用Spark Streaming和Kafka進行實時數據處理的示例…

實戰1-python爬取安全客新聞

一般步驟&#xff1a;確定網站--搭建關系--發送請求--接受響應--篩選數據--保存本地 1.拿到網站首先要查看我們要爬取的目錄是否被允許 一般網站都會議/robots.txt目錄&#xff0c;告訴你哪些地址可爬&#xff0c;哪些不可爬&#xff0c;以安全客為例子 2. 首先測試在不登錄的…

Docker Network(網絡)——8

目錄&#xff1a; Docker 為什么需要網絡管理Docker 網絡架構簡介 CNMLibnetwork驅動常見網絡類型 bridge 網絡host 網絡container 網絡none 網絡overlay 網絡docker 網絡管理命令 docker network createdocker network inspectdocker network connectdocker network disconne…

class072 最長遞增子序列問題與擴展【算法】

class072 最長遞增子序列問題與擴展【算法】 code1 300. 最長遞增子序列 // 最長遞增子序列和最長不下降子序列 // 給定一個整數數組nums // 找到其中最長嚴格遞增子序列長度、最長不下降子序列長度 // 測試鏈接 : https://leetcode.cn/problems/longest-increasing-subsequen…

830. 單調棧

?????? ??????830. 單調棧 - AcWing題庫 給定一個長度為 N 的整數數列&#xff0c;輸出每個數左邊第一個比它小的數&#xff0c;如果不存在則輸出 ?1?1。 輸入格式 第一行包含整數 N&#xff0c;表示數列長度。 第二行包含 N個整數&#xff0c;表示整數數列…

你知道MySQL中 group by 怎么優化嗎

更好的閱讀體驗&#xff0c;請點擊 YinKai s Blog。 ? 在 MySQL 中 group by 用于按照一個或多個列對結果集進行分組。在討論 group by 怎么優化之前&#xff0c;我們先來看看 group by 的執行流程&#xff0c;這樣我們才能對癥下藥。 group by 執行流程 ? 我們先用下面的 …

Ubuntu 18.04使用Qemu和GDB搭建運行內核的環境

安裝busybox 參考博客&#xff1a; 使用GDBQEMU調試Linux內核環境搭建 一文教你如何使用GDBQemu調試Linux內核 ubuntu22.04搭建qemu環境測試內核 交叉編譯busybox 編譯busybox出現Library m is needed, can’t exclude it (yet)的解釋 S3C2440 制作最新busybox文件系統 https:…

block-recurrent-transformer-pytorch 學習筆記

目錄 有依賴項1&#xff1a; 沒有依賴項&#xff0c;沒有使用例子 沒有依賴項2&#xff1a; 有依賴項1&#xff1a; GitHub - dashstander/block-recurrent-transformer: Pytorch implementation of "Block Recurrent Transformers" (Hutchins & Schlag et a…