面試官:cglib為什麼不能代理private方法?

語言: CN / TW / HK

本文首發於公眾號【看點代碼再上班】,歡迎圍觀,第一時間獲取最新文章。
歡迎閲讀原文:http://mp.weixin.qq.com/s/PDeE329ngo4bui-688PoPg

大家好,我是tin,這是我的第14篇原創文章

你用過Spring麼?聽説過Spring AOP麼?肯定都有吧!

今天就説一説Spring AOP裏面的一種代理方式cglib的原理,以及順便回答一下cglib為什麼不能代理private方法,先上一個目錄:

一、cglib是什麼

cglib是一個開源的動態代碼生成工具,其被廣泛應用於AOP、測試、數據訪問框架等生成動態代理對象和攔截方法字段訪問,github地址是:http://github.com/cglib/cglib。其最新release版本是3.0.0 cglib作用是為沒有實現接口的類提供代理的實現,這點區別於JDK動態代理,也算是JDK動態代理的一種補充。

這裏説一下,在我們Spring AOP中,是同時採用了JDK動態代理和cglib兩種代理實現,默認下是優先使用JDK動態代理,其次才會使用cglib,Spring doc官方文檔介紹也表示了這點: ​ http://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 *

* 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/small-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.asm.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/small-workshop/workspace/tin-example/cglib-class"); 它是用來把cglib生成的代理類的class文件輸出到本地磁盤的,如果沒有配置,默認只會保存在JVM內。已經生成了,我們看下生成的cglib代理類長什麼樣(代理類實在是太長了,只能放到git上,可訪問以下鏈接查看):
http://github.com/iam-tin/tin-example/blob/master/cglib-test/src/main/java/com/tin/example/cglib/cglib/cglib-class-example

關於宂長的代理類,我只説兩點重點吧:

  • 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,一個在努力讓自己變得更優秀的普通工程師。自己閲歷有限、學識淺薄,如有發現文章不妥之處,非常歡迎加我提出,我一定細心推敲並加以修改。

堅持創作不容易,你的正反饋是我堅持輸出的最強大動力,謝謝!

最後別忘了關注我哦!附上原文鏈接⏬⏬⏬
http://mp.weixin.qq.com/s/PDeE329ngo4bui-688PoPg