溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何理解MyBatis動(dòng)態(tài)代理

發(fā)布時(shí)間:2021-10-20 11:58:40 來源:億速云 閱讀:138 作者:iii 欄目:web開發(fā)

這篇文章主要講解了“如何理解MyBatis動(dòng)態(tài)代理”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“如何理解MyBatis動(dòng)態(tài)代理”吧!

動(dòng)態(tài)代理實(shí)戰(zhàn)

眾所周知哈,Mybatis 底層封裝使用的 JDK 動(dòng)態(tài)代理。說 Mybatis 動(dòng)態(tài)代理之前,先來看一下平常我們寫的動(dòng)態(tài)代理 Demo,拋磚引玉

一般來說定義 JDK 動(dòng)態(tài)代理分為三個(gè)步驟,如下所示

  1. 定義代理接口

  2. 定義代理接口實(shí)現(xiàn)類

  3. 定義動(dòng)態(tài)代理調(diào)用處理器

三步代碼如下所示,玩過動(dòng)態(tài)代理的小伙伴看過就能明白

public interface Subject { // 定義代理接口     String sayHello(); }  public class SubjectImpl implements Subject {  // 定義代理接口實(shí)現(xiàn)類     @Override     public String sayHello() {         System.out.println(" Hello World");         return "success";     } }  public class ProxyInvocationHandler implements InvocationHandler {  // 定義動(dòng)態(tài)代理調(diào)用處理器     private Object target;      public ProxyInvocationHandler(Object target) {         this.target = target;     }      @Override     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {         System.out.println(" ? ? ? 進(jìn)入代理調(diào)用處理器 ");         return method.invoke(target, args);     } }

1.寫個(gè)測試程序,運(yùn)行一下看看效果,同樣是分三步

2.創(chuàng)建被代理接口的實(shí)現(xiàn)類

  • 創(chuàng)建動(dòng)態(tài)代理類,說一下三個(gè)參數(shù)

  • 類加載器

  • 被代理類所實(shí)現(xiàn)的接口數(shù)組

3.用處理器(調(diào)用被代理類方法,每次都經(jīng)過它)

被代理實(shí)現(xiàn)類調(diào)用方法

public class ProxyTest {     public static void main(String[] args) {         Subject subject = new SubjectImpl();         Subject proxy = (Subject) Proxy                 .newProxyInstance(                         subject.getClass().getClassLoader(),                         subject.getClass().getInterfaces(),                         new ProxyInvocationHandler(subject));          proxy.sayHello();         /**          * 打印輸出如下          * 調(diào)用處理器:? ? ? 進(jìn)入代理調(diào)用處理器          * 被代理實(shí)現(xiàn)類:Hello World          */     } }

Demo 功能實(shí)現(xiàn)了,大致運(yùn)行流程也清楚了,下面要針對原理實(shí)現(xiàn)展開分析

動(dòng)態(tài)代理原理分析

從原理的角度上解析一下,上面動(dòng)態(tài)代理測試程序是如何執(zhí)行的

第一步簡單明了,創(chuàng)建了 Subject 接口的實(shí)現(xiàn)類,也是我們常規(guī)的實(shí)現(xiàn)

第二步是創(chuàng)建被代理對象的動(dòng)態(tài)代理對象。這里有朋友就問了,怎么證明這是個(gè)動(dòng)態(tài)代理對象?如圖所示

如何理解MyBatis動(dòng)態(tài)代理

JDK 動(dòng)態(tài)代理對象名稱是有規(guī)則的,凡是經(jīng)過 Proxy 類生成的動(dòng)態(tài)代理對象,前綴必然是 $Proxy,后面的數(shù)字也是名稱組成部分

如果有小伙伴想要一探究竟,關(guān)注 Proxy 內(nèi)部類 ProxyClassFactory,這里會有想要的答案

如何理解MyBatis動(dòng)態(tài)代理

回歸正題,繼續(xù)看一下 ProxyInvocationHandler,內(nèi)部保持了被代理接口實(shí)現(xiàn)類的引用,invoke  方法內(nèi)部使用反射調(diào)用被代理接口實(shí)現(xiàn)類方法

如何理解MyBatis動(dòng)態(tài)代理

可以看出生成的動(dòng)態(tài)代理類,繼承了 Proxy 類,然后對 Subject 接口進(jìn)行了實(shí)現(xiàn),而實(shí)現(xiàn)方法 sayHello 中實(shí)際調(diào)用的是  ProxyInvocationHandler 的 invoke 方法

一不小心發(fā)現(xiàn)了 JDK 動(dòng)態(tài)代理不能對類進(jìn)行代理的原因 ^ ^

也就是說,當(dāng)我們調(diào)用 Subject#sayHello 時(shí),方法調(diào)用鏈?zhǔn)沁@樣的

如何理解MyBatis動(dòng)態(tài)代理

但是,Demo 里有被代理接口的實(shí)現(xiàn)類,Mybatis Mapper 沒有,這要怎么玩

不知道不要緊,知道了估計(jì)也看不到這了,一起看下 mybatis 源碼是怎么玩的

mybatis version:3.4.x

Mybatis 源碼實(shí)現(xiàn)

不知道大家考沒考慮過這么一個(gè)問題,Mybatis Mapper 為什么不需要實(shí)現(xiàn)類?

假如說,我們項(xiàng)目使用的三層設(shè)計(jì),Controller 控制請求接收,Service 負(fù)責(zé)業(yè)務(wù)處理,Mapper 負(fù)責(zé)數(shù)據(jù)庫交互

如何理解MyBatis動(dòng)態(tài)代理

Mapper 層也就是我們常說的數(shù)據(jù)庫映射層,負(fù)責(zé)對數(shù)據(jù)庫的操作,比如對數(shù)據(jù)的查詢或者新增、刪除等

大膽設(shè)想下,項(xiàng)目沒有使用 Mybatis,需要在 Mapper 實(shí)現(xiàn)層寫數(shù)據(jù)庫交互,會寫一些什么內(nèi)容?

會寫一些常規(guī)的 JDBC 操作,比如:

// 裝載Mysql驅(qū)動(dòng) Class.forName(driveName); // 獲取連接 con = DriverManager.getConnection(url, user, pass); // 創(chuàng)建Statement Statement state = con.createStatement(); // 構(gòu)建SQL語句 String stuQuerySqlStr = "SELECT * FROM STUDENT"; // 執(zhí)行SQL返回結(jié)果 ResultSet result = state.executeQuery(stuQuerySqlStr); ...

如果項(xiàng)目中所有 Mapper 實(shí)現(xiàn)層都要這么玩,那豈不是很想打人...

所以 Mybatis 結(jié)合項(xiàng)目痛點(diǎn),應(yīng)運(yùn)而生,怎么做的呢

將所有和 JDBC 交互的操作,底層采用 JDK 動(dòng)態(tài)代理封裝,使用者只需要自定義 Mapper 和 .xml 文件

SQL 語句定義在 .xml 文件或者 Mapper 中,項(xiàng)目啟動(dòng)時(shí)通過解析器解析 SQL 語句組裝為 Java 中的對象

解析器分為多種,因?yàn)?Mybatis 中不僅有靜態(tài)語句,同時(shí)也包含動(dòng)態(tài) SQL 語句

這也就是為什么 Mapper 接口不需要實(shí)現(xiàn)類,因?yàn)槎家呀?jīng)被 Mybatis 通過動(dòng)態(tài)代理封裝了,如果每個(gè) Mapper  都來一個(gè)實(shí)現(xiàn)類,臃腫且無用。經(jīng)過這一頓操作,展示給我們的就是項(xiàng)目里用到的 Mybatis 框架

上面鋪墊這么久,終于要到主角了,為什么 Mybatis Mapper 接口沒有實(shí)現(xiàn)類也可以實(shí)現(xiàn)動(dòng)態(tài)代理

想要嚴(yán)格按照先后順序介紹 Mybatis 動(dòng)態(tài)代理流程,而不超前引用未介紹過的術(shù)語,這幾乎是不可能的,筆者盡量說的通俗易懂

無實(shí)現(xiàn)類完成動(dòng)態(tài)代理

核心點(diǎn)來了,拿起小本本坐板正了

我們先來看下普通動(dòng)態(tài)代理有沒有可能不用實(shí)現(xiàn)類,僅靠接口完成

public interface Subject {     String sayHello(); }  public class ProxyInvocationHandler implements InvocationHandler {      @Override     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {         System.out.println(" ? ? ? 進(jìn)入代理調(diào)用處理器 ");         return "success";     } }

根據(jù)代碼可以看到,我們并沒有實(shí)現(xiàn)接口 Subject,繼續(xù)看一下怎么實(shí)現(xiàn)動(dòng)態(tài)代理

public class ProxyTest {     public static void main(String[] args) {         Subject proxy = (Subject) Proxy                 .newProxyInstance(                         subject.getClass().getClassLoader(),                         new Class[]{Subject.class},                         new ProxyInvocationHandler());          proxy.sayHello();         /**          * 打印輸出如下          * 調(diào)用處理器:? ? ? 進(jìn)入代理調(diào)用處理器          */     } }

可以看到,對比文初的 Demo,這里對 Proxy.newProxyInstance 方法的參數(shù)作出了變化

之前是通過實(shí)現(xiàn)類獲取所實(shí)現(xiàn)接口的 Class 數(shù)組,而這里是把接口本身放到 Class 數(shù)組中,殊歸同途

有實(shí)現(xiàn)類接口和無實(shí)現(xiàn)類接口產(chǎn)生的動(dòng)態(tài)代理類有什么區(qū)別

  1. 有實(shí)現(xiàn)類接口是對 InvocationHandler#invoke 方法調(diào)用,invoke  方法通過反射調(diào)用被代理對象(SubjectImpl)方法(sayHello)

  2. 無實(shí)現(xiàn)類接口則是僅對 InvocationHandler#invoke 產(chǎn)生調(diào)用。所以有實(shí)現(xiàn)類接口返回的是被代理對象接口返回值,而無實(shí)現(xiàn)類接口返回的僅是  invoke 方法返回值

InvocationHandler#invoke 方法返回值是 success 字符串,定義個(gè)字符串變量,是否能成功返回

如何理解MyBatis動(dòng)態(tài)代理

現(xiàn)在第一個(gè)問題答案已經(jīng)浮現(xiàn),Mapper 沒有實(shí)現(xiàn)類,所有調(diào)用 JDBC 等操作都是在 Mybatis InvocationHandler 實(shí)現(xiàn)的

問題既然已經(jīng)得到了解決,給人一種感覺,好像沒那么難,但是你不好奇,Mybatis 底層怎么做的么?

先拋出一個(gè)問題,然后帶著問題去看源碼,可能讓你記憶 Double 倍深刻

咱們 Demo 里的接口是固定的,Mybatis Mapper 可是不固定的,怎么搞?

Mybatis 是這么說的

看看 Mybatis 底層它怎么實(shí)現(xiàn)的動(dòng)態(tài)接口代理,小伙伴只需要關(guān)注標(biāo)記處的代碼即可

如何理解MyBatis動(dòng)態(tài)代理

和我們的 Demo 代碼很像,核心點(diǎn)在于 mapperInterface 它是怎么賦值的

先來說一下 Mybatis 代理工廠中具體生成動(dòng)態(tài)代理類具體邏輯

  1. 根據(jù) .xml 上關(guān)聯(lián)的 namespace, 通過 Class#forName 反射的方式返回 Class 對象(不止 .xml namespace  一種方式)

  2. 將得到的 Class 對象(實(shí)際就是接口對象)傳遞給 Mybatis 代理工廠生成代理對象,也就是剛才 mapperInterface 屬性

謎底揭曉,Mybatis 使用接口全限定名通過 Class#forName 生成 Class 對象,這個(gè) Class 對象類型就是接口

為了方便大家理解,通過 Mybatis 源碼提供的測試類舉例。假設(shè)已有接口 AutoConstructorMapper 以及對應(yīng)的 .xml 如下

如何理解MyBatis動(dòng)態(tài)代理

如何理解MyBatis動(dòng)態(tài)代理

執(zhí)行第一步,根據(jù) .xml namespace 得到 Class 對象

如何理解MyBatis動(dòng)態(tài)代理

首先第一步獲取 .xml 上 mapper 標(biāo)簽 namespace 屬性,得到 mapper 接口全限定信息

根據(jù) mapper 全限定信息獲取 Class 對象

添加到對應(yīng)的映射器容器中,等待生成動(dòng)態(tài)代理對象

如果此時(shí)調(diào)用生成動(dòng)態(tài)代理對象,代理工廠 newInstance 方法如下:

如何理解MyBatis動(dòng)態(tài)代理

至此,文初提的 Proxy、Mybatis 動(dòng)態(tài)代理相關(guān)問題已全部答疑

抽象類能否 JDK 動(dòng)態(tài)代理

說代碼前結(jié)論先行,不能!

public abstract class AbstractProxy {     abstract void sayHello(); }  AbstractProxy proxyInterface = (AbstractProxy) Proxy         .newProxyInstance(                 ProxyTest.class.getClassLoader(),                 new Class[]{AbstractProxy.class},                 new ProxyInvocationHandler()); proxyInterface.sayHello();

毫無疑問,報(bào)錯(cuò)是必然的,JDK 是不能對類進(jìn)行代理的

如何理解MyBatis動(dòng)態(tài)代理

帶著小疑惑我們看一下 Proxy 源碼報(bào)錯(cuò)位置,JDK 動(dòng)態(tài)代理在生成代理類的過程代碼中,會有是否接口驗(yàn)證

如何理解MyBatis動(dòng)態(tài)代理

抽象類終歸是類,加個(gè) abstract 也成不了接口(就像我,雖然胖了 60 斤,但依然是帥哥)

下次面試官如果有問這問題的,斬釘截鐵一點(diǎn),就是不能

結(jié)言

結(jié)合 Mybatis 使用 JDK 動(dòng)態(tài)代理相關(guān)的問題,展開了文章的講述,這里總結(jié)如下

Q:JDK 動(dòng)態(tài)代理能否對類代理?

因?yàn)?JDK 動(dòng)態(tài)代理生成的代理類,會繼承 Proxy 類,由于 Java 無法多繼承,所以無法對類進(jìn)行代理

Q:抽象類是否可以 JDK 動(dòng)態(tài)代理?

不可以,抽象類本質(zhì)上也是類,Proxy 生成代理類過程中,會校驗(yàn)傳入 Class 是否接口

Q:Mybatis Mapper 接口沒有實(shí)現(xiàn)類,怎么實(shí)現(xiàn)的動(dòng)態(tài)代理?

Mybatis 會通過 Class#forname 得到 Mapper 接口 Class 對象,生成對應(yīng)的動(dòng)態(tài)代理對象,核心業(yè)務(wù)處理都會在  InvocationHandler#invoke 進(jìn)行處理

感謝各位的閱讀,以上就是“如何理解MyBatis動(dòng)態(tài)代理”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對如何理解MyBatis動(dòng)態(tài)代理這一問題有了更深刻的體會,具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識點(diǎn)的文章,歡迎關(guān)注!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI