面試官:cglib為什麼不能代理private方法?
本文首發於公眾號【看點程式碼再上班】,歡迎圍觀,第一時間獲取最新文章。
一定要讀的原文: https://mp.weixin.qq.com/s/PDeE329ngo4bui-688PoPg
大家好,我是tin,這是我的第14篇原創文章
你用過Spring麼?聽說過Spring AOP麼?肯定都有吧!
今天就說一說Spring AOP裡面的一種代理方式cglib的原理,以及順便回答一下cglib為什麼不能代理private方法,先上一個目錄:
-
二、cglib使用示例
-
三、cglib原始碼解析
-
四、結語
一、cglib是什麼
cglib是一個開源的動態程式碼生成工具,其被廣泛應用於AOP、測試、資料訪問框架等生成動態代理物件和攔截方法欄位訪問,github地址是:https://github.com/cglib/cglib。其最新release版本是3.0.0
cglib作用是為沒有實現介面的類提供代理的實現,這點區別於JDK動態代理,也算是JDK動態代理的一種補充。
這裡說一下,在我們Spring AOP中,是同時採用了JDK動態代理和cglib兩種代理實現,預設下是優先使用JDK動態代理,其次才會使用cglib,Spring doc官方文件介紹也表示了這點:
https://docs.spring.io/spring-framework/docs/6.0.x/reference/html/core.html#aop
cglib是通過動態生成代理類的子類實現代理功能 ,所生成的子類重寫了代理類的所有方法(不包括final方法,至於private方法後文會講到),cglib生成的子類中再通過方法攔截的方式實現對代理類方法的程式碼織入。
cglib底層採用位元組碼處理框架asm操作生成新的子類,下圖可以比較直觀地瞭解cglib相關的層次關係:
二、cglib使用示例
首先引入jar包:
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency>
定義我們自己的業務類Developer:
再寫一個方法攔截器DeveloperInterceptor:
方法攔截器實現了cglib的MethodInterceptor介面。
最後,通過cglib的位元組碼增強器即可生成新的代理子類:
package com.tin.example.cglib.cglib; import com.tin.example.cglib.design.pattern.SexEnum; import com.tin.example.cglib.design.pattern.employee.Developer; import net.sf.cglib.core.DebuggingClassWriter; import net.sf.cglib.proxy.Enhancer; /** * title: CglibTest * <p> * description: * * @author tin @公眾號【看點程式碼再上班】 on 2022/1/15 下午5:00 */ public class CglibTest { public static void main(String args[]) { //輸出cglib動態代理產生的類 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/ericli/**all-workshop/workspace/tin-example/cglib-class"); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(Developer.class); //設定攔截器DeveloperInterceptor enhancer.setCallback(new DeveloperInterceptor()); //enhancer.create()生成代理類並強轉為Developer,cglig生成的代理類命名格式:業務類名$$EnhancerByCGLIB$$... Object obj = enhancer.create(); System.out.println("proxy class:" + obj.getClass()); Developer developer = (Developer) obj; developer.setLines(30000); developer.setName("程式設計師@【看點程式碼再上班】"); developer.setNumber(1L); developer.setSex(SexEnum.MALE); developer.setSalary("¥15000"); System.out.println(developer.print()); } }
最後執行列印結果如下:
三、cglib原始碼解析
前面講到cglib是通過生成代理類的子類實現代理功能,那麼它又是怎麼生成代理類的呢?我們通過原始碼一探究竟。
cglib的基本類關係圖如下:
ClassGenerator是一個介面,也是cglib生成物件的最核心介面,它只有一個方法,方法的入參就是asm的ClassVisitor類,如下:
package net.sf.cglib.core; import org.objectweb.a**.ClassVisitor; public interface ClassGenerator { void generateClass(ClassVisitor v) throws Exception; }
我們上面示例程式碼CglibTest中是通過Enhancer.create()方法生成目標類的代理類的,我們從這裡dubug進去看一下。
Enhancer.create方法呼叫了createHelper()方法,這個方法會再呼叫到AbstractClassGenerator的create()方法:
AbstractClassGenerator的create()方法初始化了ClassLoaderData物件:
ClassLoaderData是一個內部類,其包含了類載入器、生成的代理類等資訊。進入到ClassLoaderData的構造器方法內:
構造器最終還是呼叫了AbstractClassGenerator的generate方法:
generateClassName方法定義了代理類名生成規則,就是我們很熟悉的命名格式:
……$$EnhancrByCGLIB$$……
DefaultGeneratorStrategy#generate()方法返回了一個位元組陣列,其內部就使用到了asm位元組碼處理框架,繼續跟進去:
在Enhancer內,重寫了generateClass()方法,該方法通過asm操作class位元組碼並生成新的類(也就是我們的cglib代理類)。截部分原始碼圖如下:
到此,原始碼基本走完了,還記得我的測試類main方法中加的下面這行程式碼麼:
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/ericli/**all-workshop/workspace/tin-example/cglib-class");
它是用來把cglib生成的代理類的class檔案輸出到本地磁碟的,如果沒有配置,預設只會儲存在JVM內。已經生成了,我們看下生成的cglib代理類長什麼樣(代理類實在是太長了,只能放到git上,可訪問以下連結檢視):
https://github.com/iam-tin/tin-example/blob/mast……
關於冗長的代理類,我只說兩點重點吧:
-
1、代理類繼承了業務類
-
2、代理類作為子類,重寫了業務類所有可以重寫的方法,這些重寫的方法也是代理類真正呼叫的方法
比如重寫業務類中的accept方法,會先判斷是否有配置攔截器,有的話會執行攔截方法:
說到這,重點來了,cglib實質上是通過繼承父類並重寫父類的方法達到生成代理類的,那麼自然的,final類和final方法一定無法通過cglib代理,在生成的class檔案中也不會找到對應的final方法。
既然如此,private方法呢?我們知道子類是無法訪問父類的private方法的,但是我們可以在子類寫一個和父類一模一樣的方法呀!這樣不就可以了麼?為什麼cglib不這麼幹呢?
這個問題,已經脫離了cglib本身,實際是一個Java基礎知識點。 Java中父類中的方法是private的,子類不可訪問,即使子類實現一個和父類一模一樣的方法,實際上只是屬於子類的新方法,並不會覆蓋父類對應的方法。
比如下面BClass繼承了AClass,BClass“重寫了”AClass的私有方法print():
/** * title: AClass * <p> * description: * * @author tin @公眾號【看點程式碼再上班】 on 2022/1/16 下午6:13 */ public class AClass { private void print() { System.out.println("A"); } public static void main(String[] args) { AClass a = new BClass(); a.print(); BClass b = new BClass(); b.print(); new BClass().print(); } } class BClass extends AClass { public void print() { System.out.println("B"); } }
看看結果輸出了什麼:
如果把AClass的print()方法改為public,結果就不一樣了:
這就是最後的結論:cglib代理類無法訪問業務類的私有方法,也就無法代理業務類的私有方法了。
四、結語
我是tin,一個在努力讓自己變得更優秀的普通工程師。自己閱歷有限、學識淺薄,如有發現文章不妥之處,非常歡迎加我提出,我一定細心推敲並加以修改。
堅持創作不容易,你的正反饋是我堅持輸出的最強大動力,謝謝!
- 面試官:cglib為什麼不能代理private方法?
- 想看Dubbo原始碼?一定要先看一看這一篇!
- 死磕synchronized二:系統剖析延遲偏向篇一
- 架構與思維:高併發下解決主從延時的一些思路
- 道與術
- OopMap看不懂,怎麼調優哇
- Kafka 精妙的高效能設計(下篇)
- 一次tcp視窗被填滿問題的排查實踐
- 搶了個票,還以為發現了12306的系統BUG
- 微服務5:服務註冊與發現(實踐篇)
- 看一遍就理解:零拷貝詳解
- 分散式:分散式系統下的唯一序列
- 面試官:為什麼jdk動態代理只能代理介面實現類?
- 揭開記憶體屏障的神祕面紗
- 微服務4:服務註冊與發現
- 我就奇了怪了,STW到底是怎麼做到的
- 這樣使用 IDEA ,效率提升10倍!| IDEA 高效使用指南
- 從hotspot原始碼層面剖析Java的多型實現原理
- 垃圾回收全集之十二:GC 調優的實戰篇—Weak, Soft 及 Phantom 引用
- JVM的多型是如何實現的