淺談JDK動態代理(上)

作者簡介:大家好,我是smart哥,前中興通訊、美團架構師,現某互聯網公司CTO

聯系qq:184480602,加我進群,大家一起學習,一起進步,一起對抗互聯網寒冬

到目前為止,在Java基礎進階這個章節,我們已經幫大家梳理了很多晦澀但極其重要的知識點,包括反射、注解和泛型。這些都是我們邁向中高級程序員的小碎步,我們已經離“成熟的碼農”越來越近了,但還不夠。今天,我們仍需一起再往前走一小步:JDK動態代理。個人認為Java基礎有“四大神獸”,除了剛才說的反射、注解和泛型,JDK動態代理就是最后一道坎。

“動態代理”四個字一出來,估計很多初學者已經開始冒冷汗。它之所以給人感覺很難,有三點原因:

  • 代碼形式很詭異,讓人搞不清調用邏輯
  • 用到了反射,而很多初學者不了解反射(現在你應該感覺好些了)
  • 包含代理模式的思想,本身比較抽象

盡管動態代理看起來似乎有一定難度,但卻必須拿下。因為Spring的事務控制依賴于AOP,AOP底層實現便是動態代理 + 責任鏈,環環相扣。所以說,搞編程的,拼到到最后還是看基本功,要么是語言基礎、要么是計算機基礎。

一個小需求:給原有方法添加日志打印

假設你剛進入一個項目組,項目中存在一個Calculator類,代表一個計算器,它可以進行加減乘除操作:

public class Calculator {// 加public int add(int a, int b) {int result = a + b;return result;}// 減public int subtract(int a, int b) {int result = a - b;return result;}// 乘法、除法...
}

現在老大給你提了一個需求:在每個方法執行前后打印日志。

你有什么好的方案?

方案一:直接修改

很多人最直觀的想法是直接修改Calculator類:

public class Calculator {// 加public int add(int a, int b) {System.out.println("add方法開始...");int result = a + b;System.out.println("add方法結束...");return result;}// 減public int subtract(int a, int b) {System.out.println("subtract方法開始...");int result = a - b;System.out.println("subtract方法結束...");return result;}// 乘法、除法...
}

上面的方案是有問題的:

  • 直接修改源程序,不符合開閉原則,即好的程序設計應該對擴展開放,對修改關閉
  • 如果Calculator類內部有幾十個、上百個方法,修改量太大
  • 存在重復代碼(都是在核心代碼前后打印日志)
  • 日志打印硬編碼在代理類中,不利于后期維護:比如你花了一上午終于寫完了,組長告訴你這個功能不做了,于是你又要打開Calculator花十分鐘刪除日志打印的代碼(或回滾分支)!

所以,此種方案PASS!

方案二:靜態代理實現日志打印

“靜態代理”四個字包含了兩個概念:靜態、代理。我們先來了解什么叫“代理”,至于何為“靜態”,需要和“動態”對比著講。

代理是一種模式,提供了對目標對象的間接訪問方式,即通過代理訪問目標對象。如此便于在目標實現的基礎上增加額外的功能操作,前攔截,后攔截等,以滿足自身的業務需求。

常用的代理方式可以粗分為:靜態代理動態代理

靜態代理的實現比較簡單:編寫一個代理類,實現與目標對象相同的接口,并在內部維護一個目標對象的引用。通過構造器塞入目標對象,在代理對象中調用目標對象的同名方法,并添加前攔截,后攔截等所需的業務功能。

是不是有點暈?是的,我最討厭這種干巴巴的描述。簡而言之,就是這樣:

按上面的描述,代理類和目標類需要實現同一個接口,所以我打算這樣做:

  • 將Calculator抽取為接口
  • 創建目標類CalculatorImpl實現Calculator
  • 創建代理類CalculatorProxy實現Calculator

抽取接口

/*** Calculator接口*/
public interface Calculator {int add(int a, int b);int subtract(int a, int b);
}

原目標類實現接口

/*** 目標類,實現Calculator接口(如果一開始就面向接口編程,其實是不存在這一步的,CalculatorImpl原本就實現Calculator接口)*/
public class CalculatorImpl implements Calculator {// 加public int add(int a, int b) {int result = a + b;return result;}// 減public int subtract(int a, int b) {int result = a - b;return result;}// 乘法、除法...
}

新增代理類并實現接口

/*** 靜態代理類,實現Calculator接口*/
public class CalculatorProxy implements Calculator {// 代理對象內部維護一個目標對象引用private Calculator target;// 通過構造方法,傳入目標對象public CalculatorProxy(Calculator target) {this.target = target;}// 調用目標對象的add,并在前后打印日志@Overridepublic int add(int a, int b) {System.out.println("add方法開始...");int result = target.add(a, b);System.out.println("add方法結束...");return result;}// 調用目標對象的subtract,并在前后打印日志@Overridepublic int subtract(int a, int b) {System.out.println("subtract方法開始...");int result = target.subtract(a, b);System.out.println("subtract方法結束...");return result;}// 乘法、除法...
}

測試案例

使用代理對象完成加減乘除,并且打印日志:

public class Test {public static void main(String[] args) {// 把目標對象通過構造器塞入代理對象Calculator calculator = new CalculatorProxy(new CalculatorImpl());// 代理對象調用目標對象方法完成計算,并在前后打印日志calculator.add(1, 2);calculator.subtract(2, 1);}
}  

靜態代理的優點:可以在不修改目標對象的前提下,對目標對象進行功能的擴展和攔截。但是它也僅僅解決了上一種方案4大缺點中的第1、4兩點:

  • 直接修改源程序,不符合開閉原則,即好的程序設計應該對擴展開放,對修改關閉(?,如果一開始就面向接口編程,這一步其實是不需要的)
  • 如果Calculator類內部有幾十個、上百個方法,修改量太大(?,目標類有多少個方法,代理類就要重寫多少個方法)
  • 存在重復代碼(都是在核心代碼前后打印日志)(?,代理類中的日志代碼是重復的)
  • 日志打印硬編碼在代理類中,不利于后期維護:比如你花了一上午終于寫完了,組長告訴你這個功能不做了(?,別用代理類就好了)

靜態代理的問題

上面的代碼中,為了給目標類做日志增強,我們編寫了代理類,而且準備了一個構造器接收目標對象。代理代理對象構造器的參數類型是Calculator,這意味著它只能接受Calculator的實現類對象,亦即我們寫的代理類CalculatorProxy只能給Calculator做代理,它們綁定死了!

如果現在我們系統需要全面改造,要給其他類也添加日志打印功能,就得為其他幾百個接口都各自寫一份代理類...

自己手動寫一個類并實現接口實在太麻煩了。仔細一想,我們其實想要的并不是代理類,而是代理對象!

你細品上面加粗的這句話,是不是好像一句廢話?沒有類哪來的對象?!

其實我的意思是,能否讓JVM根據接口自動生成代理對象呢?

比如,有沒有一個方法,我傳入接口+增強的代碼(比如打印日志),它就給我自動返回代理對象呢?這樣就能省去編寫代理類這個無用的“中介”了,沒有中間商賺差價,豈不爽哉?

JDK,能做到嗎?

預知后事如何,請聽下回分解~

作者簡介:大家好,我是smart哥,前中興通訊、美團架構師,現某互聯網公司CTO

進群,大家一起學習,一起進步,一起對抗互聯網寒冬

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

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

相關文章

Splunk 編寫高效 查詢語句

1: 背景: splunk 的查詢語句的是否優化,對是否節省資源有很大的影響。下面說一下大概的方法: There are a set of basic principles that you can follow to optimize your searches. Retrieve only the required data Move as little data as possible Parallelize as mu…

力扣OJ題講解——循環隊列

今天我們一起來做一道關于隊列的OJ題目,這是力扣題目622題,點擊題目鏈接可以直接跳轉,https://leetcode.cn/problems/design-circular-queue/ 首先,我們看到要求,需要我們實現哪些功能? 我們需要設置隊列長…

2023亞太杯數學建模A題B題C題選題建議,思路分析,模型代碼

目錄 ABC題思路模型代碼:獲取見文末名片,第一時間更新 視頻連接講解如上 A題思路:采果機器人的圖像識別技術思路模型代碼 B題思路:玻璃溫室中的微氣候法規 C題思路:我國新能源電動汽車的發展趨勢 ABC題思路模型代…

經典雙指針算法試題(二)

📘北塵_:個人主頁 🌎個人專欄:《Linux操作系統》《經典算法試題 》《C》 《數據結構與算法》 ??走在路上,不忘來時的初心 文章目錄 一、有效三角形的個數1、題目講解2、講解算法原理3、代碼實現 二、查找總價格為目標值的兩個商…

Excel使用技巧匯總

1 單元格內換行 altenter

Hutool

一、簡介 Hutool是一個小而全的Java工具類庫,通過靜態方法封裝,降低相關API的學習成本,提高工作效率,使Java擁有函數式語言般的優雅 官方文檔: https://www.hutool.cn/docs/#/ 二、包含組件 一個Java基礎工具類,對文…

allegro畫封裝時使用坐標指令無效

使用坐標指令時顯示:“Pick is outside the extent of the drawing…pick again” 這是因為你放的引腳已經超出你這個繪制界面的定義尺寸,需要到Setup->Design pararmeters…里面去將圖幅改大一點,如下圖所示: 然后點擊Design…

消息中間件——RabbitMQ(三)理解RabbitMQ核心概念和AMQP協議!

前言 本章學習,我們可以了解到以下知識點: 互聯網大廠為什么選擇RabbitMQ?RabbiMQ的高性能之道是如何做到的?什么是AMQP高級協議?AMQP核心概念是什么?RabbitMQ整體架構模型是什么樣子的?Rabbi…

P8599 [藍橋杯 2013 省 B] 帶分數(dfs+全排列+斷點判斷)

思路&#xff1a;1.深度枚舉所有排列情況 2.設置為每個排列設置兩個斷點&#xff0c;分為三部分&#xff1a;a,b,c 3.轉換為乘法判斷條件&#xff0c;滿足加一 代碼如下&#xff1a;&#xff08;可用next_permutation全排列函數代替dfs&#xff09; #include<iostream>…

機器學習調參指南:提升模型性能的關鍵步驟

諸神緘默不語-個人CSDN博文目錄 文章目錄 1. 理解模型的參數和超參數2. 使用網格搜索進行超參數調優3. 隨機搜索4. 貝葉斯優化5. 使用交叉驗證避免過擬合6. 考慮正則化7. 調整學習率和其他優化器參數8. 實驗和記錄9. 模型的早停法10. 總結 在機器學習和深度學習的領域中&#x…

全面的日志監控管理工具

企業網絡由眾多日志源組成。集中監控這些日志源有助于防止數據威脅和網絡攻擊&#xff0c;綜合日志監控解決方案可以自動執行日志管理流程&#xff0c;通過關聯日志來識別惡意活動&#xff0c;并幫助滿足IT合規性要求。 不同類型的日志監控 EventLog Analyzer 綜合日志監控解…

智慧法院檔案數字化解決方案

智慧法院檔案數字化解決方案可以采用以下步驟&#xff1a; 1. 確定數字化目標&#xff1a;明確數字化的目標和范圍&#xff0c;比如將所有的案件相關文件、紙質檔案和材料進行數字化。 2. 確定數字化流程&#xff1a;制定數字化的流程和標準&#xff0c;比如采用哪些設備和軟件…

【Linux 文件傳輸系列 1.1 -- rsync 詳細介紹】

文章目錄 rsync 詳細介紹rsync 基本特性rsync 常用選項rsync 各種是使用示例 rsync 詳細介紹 rsync 是一個在 Linux 和 Unix 系統上廣泛使用的文件同步和傳輸工具。它被設計用于快速高效地同步文件和目錄之間的變化&#xff0c;不論是本地還是通過網絡。rsync 命令有許多選項&…

【C語言】qsort函數

目錄 簡介 頭文件 ?編輯 函數原型&#xff1a; 參數函數如何寫&#xff1a; 參數函數要求&#xff1a; qsort對整性數據的排序&#xff1a; qsort對字符型數據的排序&#xff1a; 對結構體類型的內部元素排序&#xff1a; 函數的底層是以快速排序實現的 但是本文不深入…

rxjs中combineLatest的用法

RxJS中的combineLatest操作符可以用于將多個Observable對象合并成一個新的Observable對象&#xff0c;新的Observable對象的值是由原始Observable對象的最新值組成的一個數組。當任何一個原始Observable對象發出新值時&#xff0c;新的Observable對象的值也會更新。 combineLa…

小黑子—Maven高級

Maven高級篇 二 小黑子的Maven高級篇學習1. 分模塊開發1.1 分模塊開發設計1.2 分模塊開發實現1.2.1 抽取domain層1.2.2 抽取dao層 2. 依賴管理2.1 依賴傳遞2.2 可選依賴2.3 排除依賴 3. 繼承與聚合3.1 聚合3.2 繼承3.3 總結 4. 屬性4.1 配置文件加載屬性4.2 版本管理 5. 多環境…

【開源】基于Vue.js的民宿預定管理系統

項目編號&#xff1a; S 058 &#xff0c;文末獲取源碼。 \color{red}{項目編號&#xff1a;S058&#xff0c;文末獲取源碼。} 項目編號&#xff1a;S058&#xff0c;文末獲取源碼。 目錄 一、摘要1.1 項目介紹1.2 項目錄屏 二、功能模塊2.1 用例設計2.2 功能設計2.2.1 租客角色…

夢開始的地方——Adobe Premiere Pro

今天&#xff0c;我們來說說一款老生常談的相信也是很多人都經常迫切需要的軟件。Adobe Premiere Pro&#xff0c;簡稱Pr&#xff0c;是由Adobe公司開發的一款視頻編輯軟件。 Premiere Pro是視頻編輯愛好者和專業人士必不可少的視頻編輯工具。它可以提升您的創作能力和創作自由…

httpd(Web服務器)

名詞解釋 1、URL&#xff1a;Uniform Resource Locator&#xff0c;統?資源定位符 2、?址格式&#xff1a;<協議>://<主機或主機名>[:port]/<?錄資源,路徑> 3、主機地址/主機名&#xff1a;主機地址是服務器在因特?所在的IP地址。主機名就需要域名解析…

裝飾器設計模式是什么?什么是 Decorator 裝飾器設計模式?Python 裝飾器設計模式示例代碼

什么是 Decorator 裝飾器設計模式&#xff1f; 裝飾器模式是一種結構型設計模式&#xff0c;它允許向現有對象動態地添加新功能&#xff0c;同時不改變其結構。這種模式實現了對對象的包裝&#xff0c;稱為裝飾器&#xff0c;并且可以在運行時動態地添加、修改或刪除對象的行為…