Java中的動態代理與Spring AOP編程

第一章:引言

大家好,我是小黑,在Java里,動態代理和Spring AOP(面向切面編程)是兩個能讓代碼更加靈活、更加干凈的強大工具。作為一名Java程序員,小黑覺得掌握它們對于寫出高質量的代碼來說非常重要。動態代理讓我們能在運行時創建一個實現了一組給定接口的新類,這個過程完全由Java的反射機制控制。而Spring AOP則讓我們能在不修改源代碼的情況下,增強方法的功能,比如日志記錄、性能統計、安全控制等等。

咱們經常聽說,要想做好一件事,最重要的是用對方法。在編程世界里,這句話同樣適用。通過動態代理和Spring AOP,咱們可以更加聚焦于業務邏輯的實現,而將那些重復的代碼邏輯,比如日志記錄、權限檢查這些,通過AOP的方式統一處理,大大提高了代碼的復用性和可維護性。

第二章:動態代理基礎

動態代理,這個聽起來有點高深的概念,實際上和咱們日常生活中的代理沒什么兩樣。就像咱們有時候會委托旅行社幫咱們訂機票、訂酒店一樣,程序中的動態代理也是幫咱們完成一些任務,但是更智能一些,因為它是在程序運行時動態創建的,完全由Java的反射機制控制。

Java中實現動態代理的方式主要有兩種:一種是基于接口的JDK動態代理,另一種是CGLIB動態代理。JDK動態代理是通過實現被代理類的接口,然后在調用實際方法前后加入自己的邏輯來實現的。而CGLIB動態代理,則是通過繼承被代理類,覆蓋其方法來實現增強功能。

讓咱們通過一個簡單的例子來看看JDK動態代理是怎么回事。假設有一個接口和一個實現類,接口定義了一個方法,實現類實現了這個方法。小黑現在用動態代理在這個方法調用前后打印一些信息:

interface Greeting {void sayHello(String name);
}class GreetingImpl implements Greeting {public void sayHello(String name) {System.out.println("你好, " + name);}
}class DynamicProxyHandler implements InvocationHandler {private Object target;public DynamicProxyHandler(Object target) {this.target = target;}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("方法調用前");Object result = method.invoke(target, args);System.out.println("方法調用后");return result;}public static void main(String[] args) {Greeting greeting = (Greeting) Proxy.newProxyInstance(Greeting.class.getClassLoader(),new Class[]{Greeting.class},new DynamicProxyHandler(new GreetingImpl()));greeting.sayHello("世界");}
}

小黑偷偷告訴你一個買會員便宜的網站: 小黑整的視頻會園優惠站

第三章:深入Spring AOP

咱們談過動態代理后,接下來進入Spring AOP的世界。AOP(面向切面編程)是一種編程范式,它允許咱們將橫切關注點(比如日志、事務管理等)與業務邏輯分離,從而使得業務邏輯更加干凈、模塊化。Spring AOP就是Spring框架提供的一套AOP實現,它利用了動態代理來實現。

首次接觸Spring AOP時,咱們可能會對“切面(Aspect)”、“連接點(JoinPoint)”、“通知(Advice)”等術語感到困惑。別擔心,小黑來一一解釋。

  • 切面(Aspect):一個關注點的模塊化,這個關注點可能會橫切多個對象。簡單來說,就是把咱們想要實現的功能比如日志記錄、性能統計封裝起來,稱之為一個切面。
  • 連接點(JoinPoint):程序執行過程中的某個特定點,比如方法的調用或異常的拋出。在Spring AOP中,一個連接點總是代表一個方法的執行。
  • 通知(Advice):切面在特定連接點執行的動作。有不同類型的通知,比如“前置通知”在方法執行之前執行,“后置通知”在方法執行之后執行等等。

讓咱們來看一個簡單的例子,演示如何在Spring中定義一個切面,并在方法執行前后添加日志:

// 定義一個切面
@Aspect
@Component
public class LogAspect {// 定義前置通知@Before("execution(* com.example.service.*.*(..))")public void beforeAdvice(JoinPoint joinPoint) {System.out.println("方法執行前:調用" + joinPoint.getSignature().getName() + "方法");}// 定義后置通知@After("execution(* com.example.service.*.*(..))")public void afterAdvice(JoinPoint joinPoint) {System.out.println("方法執行后:調用" + joinPoint.getSignature().getName() + "方法");}
}

在這個例子中,@Aspect標注的類LogAspect定義了一個切面。@Before@After注解定義了前置和后置通知,execution(* com.example.service.*.*(..))是一個切點表達式,表示com.example.service包下所有類的所有方法都是連接點,即在這些方法執行前后,執行相應的通知。

通過這種方式,咱們可以很容易地為業務邏輯添加額外的行為,而不需要修改業務邏輯本身。這不僅使得代碼更加模塊化,而且提高了代碼的復用性和可維護性。

Spring AOP背后的工作原理是動態代理。對于實現了接口的Bean,Spring默認使用JDK動態代理。對于沒有實現接口的Bean,則使用CGLIB來創建代理。這一切對開發者來說都是透明的,Spring框架自動處理了這些底層細節。

通過深入了解Spring AOP,咱們可以更好地利用這一強大的編程范式,編寫出更加簡潔、高效的代碼。

第四章:Spring AOP實現機制

繼續深入Spring AOP的世界,這一章節咱們聚焦于Spring AOP的實現機制,包括如何在Spring框架中配置和使用AOP,以及它是如何工作的。理解了這些,咱們就能更加靈活地在項目中利用AOP來解決問題了。

在Spring中配置AOP

Spring AOP的配置非常靈活,可以通過XML配置文件,也可以通過注解的方式來實現。由于Spring框架推薦使用注解方式,因為它更簡潔、直觀,所以小黑這里也主要介紹基于注解的配置方法。

為了啟用Spring AOP,咱們需要在配置類上添加@EnableAspectJAutoProxy注解。這個注解會告訴Spring框架,自動代理那些標注了@Aspect注解的類。

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}

定義了切面后,咱們就可以在切面類中使用@Aspect注解來標注這個類是一個切面,然后通過@Before@After@Around等注解來定義不同類型的通知。

Spring AOP使用的動態代理技術

正如之前提到的,Spring AOP在底層使用了動態代理技術。具體來說,如果目標對象實現了接口,Spring AOP會默認使用JDK動態代理。如果目標對象沒有實現接口,則會使用CGLIB庫來創建代理。

JDK動態代理只能代理接口,不支持類。而CGLIB可以在運行時動態生成一個被代理類的子類,通過方法重寫的方式來實現代理,因此它不需要接口也能實現代理功能。

使用AspectJ注解實現AOP

AspectJ是一個面向切面的框架,它擴展了Java語言。Spring AOP支持使用AspectJ的注解來定義切面和通知,這使得AOP的實現更加直觀和強大。

以下是使用AspectJ注解定義切面和通知的一個簡單例子:

@Aspect
@Component
public class LoggingAspect {// 定義一個前置通知@Before("execution(* com.example.service.*.*(..))")public void logBefore(JoinPoint joinPoint) {System.out.println("即將執行方法: " + joinPoint.getSignature().getName());}// 定義一個后置通知@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")public void logAfterReturning(JoinPoint joinPoint, Object result) {System.out.println("方法執行完成: " + joinPoint.getSignature().getName() + ", 返回值: " + result);}
}

在這個例子中,@Before注解定義了一個前置通知,它會在匹配的方法執行之前執行。@AfterReturning注解定義了一個后置通知,它會在匹配的方法成功執行之后執行,并且可以訪問到方法的返回值。

通過這樣的方式,咱們可以非常方便地在方法執行的不同階段織入自己的邏輯,而不需要改動原有的業務代碼。這對于實現日志記錄、性能監控、事務管理等橫切關注點非常有用。

理解Spring AOP的實現機制,對于高效利用這一技術解決實際編程問題非常關鍵。希望通過本章的介紹,咱們能對Spring AOP有了更深入的理解。

第五章:動態代理與Spring AOP的高級話題

咱們已經掌握了基礎的概念和實現方式。現在,讓咱們進一步探索一些高級話題,包括性能考量、最佳實踐以及如何解決一些常見的問題。

動態代理和Spring AOP的性能考量

在使用動態代理和Spring AOP時,性能是一個不可忽視的話題。雖然動態代理和AOP為咱們提供了極大的便利和靈活性,但是它們也引入了一定的性能開銷。比如,動態代理的方法調用比直接調用慢,因為它需要通過反射機制來實現;Spring AOP的通知執行也會增加執行時間。

為了最小化性能開銷,咱們可以采取一些措施:

  • 盡量減少通知的復雜度:在通知中盡量避免執行復雜的邏輯。
  • 合理選擇通知類型:例如,如果不需要方法返回后處理,就不要使用@AfterReturning通知。
  • 使用編譯時織入:相比于運行時織入,編譯時織入(如AspectJ的編譯時織入)可以減少運行時的性能開銷。
動態代理和Spring AOP的最佳實踐

要充分發揮動態代理和Spring AOP的威力,遵循一些最佳實踐是非常有幫助的:

  • 切面應該盡量輕量:切面執行的邏輯應該簡單快速,避免在切面中執行耗時操作。
  • 合理定義切點表達式:避免使用過于寬泛的切點表達式,這樣可以減少不必要的切面邏輯執行,提高系統性能。
  • 明智地選擇切面的應用場景:并不是所有的功能都適合通過切面來實現。對于核心業務邏輯,直接實現可能更加清晰和直接。
解決在AOP中遇到的常見問題

在實際應用中,咱們可能會遇到一些問題,比如切面不生效、通知執行順序不符合預期等。這些問題通常都有解決方案:

  • 切面不生效:檢查是否在Spring配置中啟用了AOP(通過@EnableAspectJAutoProxy注解),以及切面類是否被正確掃描并注冊為Bean。
  • 通知執行順序問題:可以通過實現org.springframework.core.Ordered接口或使用@Order注解來指定切面的執行順序。
  • 循環依賴:如果切面和目標Bean之間存在循環依賴,可能會導致問題。這時候,檢查并重構代碼結構,解決循環依賴問題是關鍵。

通過上述內容,咱們對動態代理和Spring AOP的高級話題有了進一步的理解。這些知識不僅能幫助咱們解決實際開發中的問題,還能讓咱們更加高效地利用這兩項技術來設計和實現軟件。

第六章:實戰案例:構建一個簡單的Spring AOP應用

項目需求分析

在很多應用中,監控方法的執行時間是一個常見需求,它幫助開發者了解應用的性能狀況。使用Spring AOP,咱們可以輕松實現這一功能,而無需修改現有業務邏輯代碼。目標是創建一個切面,它能夠在任意方法執行前后記錄時間,計算出方法的執行耗時。

逐步構建Spring AOP項目

首先,確保咱們的項目已經包含了Spring Boot的起步依賴,以及AOP的依賴:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>

接下來,定義咱們的日志切面MethodExecutionTimeAspect

@Aspect
@Component
public class MethodExecutionTimeAspect {private static final Logger logger = LoggerFactory.getLogger(MethodExecutionTimeAspect.class);// 定義切點為所有Service層的方法@Pointcut("within(@org.springframework.stereotype.Service *)")public void monitor() {}// 在方法執行前后記錄時間@Around("monitor()")public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {long startTime = System.currentTimeMillis();Object proceed = joinPoint.proceed();long executionTime = System.currentTimeMillis() - startTime;logger.info(joinPoint.getSignature() + " executed in " + executionTime + "ms");return proceed;}
}

在這個切面中,咱們定義了一個切點monitor,它匹配所有標記有@Service注解的類中的方法。使用@Around注解定義了一個環繞通知,它在目標方法執行前后執行,計算并記錄方法的執行時間。

為了展示這個切面的效果,咱們可以創建一個簡單的服務類:

@Service
public class SampleService {public void execute() {// 模擬業務邏輯執行時間try {Thread.sleep(1000);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}
}

最后,在Spring Boot的主類或任意配置類中,確保啟用了AOP:

@SpringBootApplication
@EnableAspectJAutoProxy
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}
測試和調試AOP功能

構建完畢后,咱們可以通過編寫單元測試或直接運行應用來測試AOP功能。每當SampleServiceexecute方法被調用時,咱們的切面應該能夠記錄并打印出方法的執行時間。

通過這個簡單的實戰案例,咱們不僅加深了對Spring AOP的理解,也掌握了如何在實際項目中應用AOP來解決具體問題。希望這個案例能夠激發出咱們更多關于使用AOP優化項目的想法。

第七章:總結

經過前面幾章的學習和探索,咱們一起深入了解了Java中的動態代理和Spring AOP編程。從基本概念到高級應用,再到實戰案例,小黑希望這些內容能夠幫助咱們更好地掌握這兩項強大的技術。現在,讓咱們在本章做一個總結回顧,鞏固咱們所學的知識。

動態代理與Spring AOP核心要點回顧
  • 動態代理:動態代理是一種強大的Java機制,它允許在運行時動態創建代理對象,用于在實際對象前后插入自定義的操作。Java支持兩種動態代理機制:基于接口的JDK動態代理和基于類的CGLIB代理。
  • Spring AOP:面向切面編程(AOP)是一種編程范式,它允許咱們將橫切關注點(如日志、事務管理等)與業務邏輯分離。Spring AOP提供了一套易于使用的AOP實現,使得在應用中實現橫切關注點變得簡單而高效。
  • 實戰案例:通過構建一個簡單的Spring AOP應用,記錄方法的執行時間,咱們實踐了如何在項目中利用AOP解決具體問題,增強了對Spring AOP應用場景和實現方式的理解。
學習路徑建議

掌握動態代理和Spring AOP是一個持續深入的過程,小黑建議咱們在未來的學習和實踐中:

  • 繼續深化理解:通過高級教程、專業書籍,加深對動態代理和Spring AOP更深層次原理的理解。
  • 實戰演練:理論知識的學習需要通過實踐來鞏固。嘗試在自己的項目中應用動態代理和Spring AOP,解決實際問題。
  • 參與社區交流:加入Java和Spring相關的社區,參與討論,分享經驗,可以讓咱們更快地解決遇到的問題,也能了解到更多的最佳實踐和新技術趨勢。
結語

通過動態代理和Spring AOP,咱們可以編寫出更加模塊化、可維護和可重用的代碼,提高開發效率和代碼質量。希望通過本系列文章的學習,咱們能夠更加自信地在Java開發中使用這些強大的工具,寫出更加優秀的代碼。

小黑在這里祝愿每一位跟隨這一系列文章學習的朋友,都能在程序員這條路上越走越遠,遇到的問題越來越少,收獲的快樂越來越多。記住,學習之路上永遠不會孤單,因為咱們都在這條路上,一起前進。


更多推薦

詳解SpringCloud之遠程方法調用神器Fegin

掌握Java Future模式及其靈活應用

小黑整的視頻會園優惠站

使用Apache Commons Chain實現命令模式

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

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

相關文章

Property ‘glob‘ does not exist on type ‘ImportMeta‘

參考文章&#xff1a; vite導入文件&#xff0c;Property ‘globEager‘ does not exist on type ‘ImportMeta‘

通過GitHub探索Python爬蟲技術

1.檢索爬取內容案例。 2.找到最近更新的。(最新一般都可以直接運行) 3.選擇適合自己的項目&#xff0c;目前測試下面畫紅圈的是可行的。 4.方便大家查看就把代碼粘貼出來了。 #圖中畫圈一代碼 import requests import os import rewhile True:music_id input("請輸入歌曲…

IDEA創建SpringMVC項目沒有java和resources

跟著一些教程創建SpringMVC項目&#xff0c;完了之后沒有java和resources兩個文件夾&#xff0c;他們教程讓我們自己新建&#xff08;感覺不是很科學啊&#xff0c;為什么必須自己建&#xff0c;生成的就沒有呢&#xff09; 分享一下新建的方法 在src-main目錄下右鍵new—>D…

鴻蒙Harmony應用開發—ArkTS聲明式開發(通用屬性:位置設置)

設置組件的對齊方式、布局方向和顯示位置。 說明&#xff1a; 從API Version 7開始支持。后續版本如有新增內容&#xff0c;則采用上角標單獨標記該內容的起始版本。 align align(value: Alignment) 設置容器元素繪制區域內的子元素的對齊方式。 卡片能力&#xff1a; 從API…

收盤價時空模式挖掘與多股票走勢聚類分析:探索市場行為共性

收盤價時空模式挖掘與多股走勢聚類分析:探索市場行為共性 一.版本信息二.操作步驟1.下載各股歷史交易數據A.代碼(download_stocks.py)B.執行2.遍歷各股的csv文件,提取收盤價數據,歸一化,繪制曲線,保存圖片A.代碼B.執行3.用上面的圖片集訓練VAE模型A.代碼B.執行4.用上面訓出的V…

【遠程開發調試】Pycharm或Webstorm使用遠程服務器調試開發

Pycharm如何使用遠程服務器環境進行開發_pycharm使用服務器環境-CSDN博客 Pycharm配置遠程調試_pycharm 遠程調試-CSDN博客

langchain學習筆記(八)

RunnableLambda: Run Custom Functions | &#x1f99c;?&#x1f517; Langchain 可以在pipeline中使用任意函數&#xff0c;但要注意所有的輸入都只能是“1”個參數&#xff0c;當函數需要多個參數時需要采用字典來包裝 itemgetter用法見langchain學習筆記&#xff08;六&…

【系統分析師】-系統配置與性能評價

1、性能指標 主頻&#xff1a;又稱時鐘頻率&#xff0c;1GHZ表示1秒有1G個時鐘周期 1s10^9ns 主頻外頻 * 倍頻 時鐘周期 主頻的倒數指令周期&#xff1a;取出并執行一條指令的時間 總線周期&#xff1a;一個訪存儲器或IO操作所用時間平均執行周期數&#xff1a;CPI表示…

【學習心得】網絡中常見數據格式(爬蟲入門知識)

在爬蟲爬取數據的之前&#xff0c;必須先系統的了解一下我們待爬取的數據有哪些格式&#xff0c;這樣做的好處在與能針對不同的數據類型采取不同分方法手段。 一、XML XML&#xff08;Extensible Markup Language&#xff09;是一種可擴展的標記語言&#xff0c;它定義了一套標…

如何解決幻獸帕魯/Palworld服務器聯機游戲時的丟包問題?

如何解決幻獸帕魯/Palworld服務器聯機游戲時的丟包問題&#xff1f; 等待服務器維護&#xff1a;首先&#xff0c;確保網絡連接穩定&#xff0c;然后查看游戲官方或社區論壇&#xff0c;了解是否有服務器維護的消息。這是解決丟包問題的一種直接且有效的方法。 更新顯卡驅動&a…

Siemens-NXUG二次開發-獲取prt中體與類型、實體面與類型、實體邊與類型、邊上點的Tag標識[Python UF][20240302]

Siemens-NXUG二次開發-獲取prt中體與類型、實體面與類型、實體邊與類型、邊上點的Tag標識[Python UF][20240302] 1.python uf函數1.1 NXOpen.UF.Obj.CycleObjsInPart1.2 NXOpen.UF.Obj.AskTypeAndSubtype1.3 NXOpen.UF.Modeling.AskBodyFaces1.4 NXOpen.UF.Modeling.AskFaceEdg…

RISC-V特權架構 - 機器模式下的異常處理

RISC-V特權架構 - 機器模式下的異常處理 1 進入異常1.1 從mtvec 定義的PC 地址開始執行1.2 更新CSR 寄存器mcause1.3 更新CSR 寄存器mepc1.4 更新CSR 寄存器mtval1.5 更新CSR 寄存器mstatus 2 退出異常2.1 從mepc 定義的PC 地址開始執行2.2 更新CSR 寄存器mstatus 3 異常服務程…

Android Tombstone 分析

1.什么是tombstone Tombstone是指在分布式系統中用于標記數據已被刪除的記錄&#xff0c;通常包含刪除操作的時間戳和相關信息。 當一個動態庫&#xff08;native程序&#xff09;開始執行時&#xff0c;系統會注冊一些連接到 debuggerd 的signal handlers。當系統發生崩潰時…

wpa_supplicant與用戶態程序的交互分析

1 wpa_supplicant與用戶態程序wpa_cli的交互過程 1.1 交互接口類型 wpa_supplicant與用戶態程序交互的主要接口包括以下幾種&#xff1a; 1&#xff09;命令行界面&#xff1a;通過命令行工具 wpa_cli 可以與 wpa_supplicant 進行交互。wpa_cli 允許用戶執行各種 wpa_suppli…

Spark Shuffle Tracking 原理分析

Shuffle Tracking Shuffle Tracking 是 Spark 在沒有 ESS(External Shuffle Service)情況&#xff0c;并且開啟 Dynamic Allocation 的重要功能。如在 K8S 上運行 spark 沒有 ESS。本文檔所有的前提都是基于以上條件的。 如果開啟了 ESS&#xff0c;那么 Executor 計算完后&a…

MySQL 表的基本操作,結合項目的表自動初始化來講

有了數據庫以后&#xff0c;我們就可以在數據庫中對表進行增刪改查了&#xff0c;這也就意味著&#xff0c;一名真正的 CRUD Boy 即將到來&#xff08;&#x1f601;&#xff09;。 查表 查看當前數據庫中所有的表&#xff0c;使用 show tables; 命令 由于當前數據庫中還沒有…

基于Python3的數據結構與算法 - 09 希爾排序

一、引入 希爾排序是一種分組插入排序的算法。 二、排序思路 首先取一個整數d1 n/2&#xff0c;將元素分為d1個組&#xff0c;每組相鄰量取元素距離為d1&#xff0c;在各組內直接進行插入排序&#xff1b;取第二個整數d2 d1/2&#xff0c; 重復上述分組排序過程&#xff0…

Angular 2 中的樣式綁定和 NgStyle

在 Angular 2 模板中綁定內聯樣式很容易。以下是一個綁定單個樣式值的示例&#xff1a; 你還可以指定單位&#xff0c;例如在這里我們將單位設置為 em&#xff0c;但也可以使用 px、% 或 rem&#xff1a; <p [style.font-size.em]"3">A paragraph at 3em! &l…

CSS 自測題 -- 用 flex 布局繪制骰子(一、二、三【含斜三點】、四、五、六點)

一點 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>css flex布局-畫骰子</title><sty…

vue3 滾動條觸底監聽

問題&#xff1a;指定區域內&#xff0c;顯示返回的數據&#xff0c;要求先顯示20條&#xff0c;區域超出部分滾動顯示&#xff0c;對滾動條進行監聽&#xff0c;滾動條觸底后&#xff0c;繼續顯示下20條... 解決過程&#xff1a; 1.在區域的div上&#xff0c;添加scroll事件…