Spring之AOP由淺入深

来源:http://www.cnblogs.com/zhaozihan/archive/2016/10/14/5953063.html
-Advertisement-
Play Games

1.AOP的作用 在OOP中,正是這種分散在各處且與對象核心功能無關的代碼(橫切代碼)的存在,使得模塊復用難度增加。AOP則將封裝好的對象剖開,找出其中對多個對象產生影響的公共行為,並將其封裝為一個可重用的模塊,這個模塊被命名為“切麵”(Aspect),切麵將那些與業務無關,卻被業務模塊共同調用的邏 ...


1.AOP的作用

  在OOP中,正是這種分散在各處且與對象核心功能無關的代碼(橫切代碼)的存在,使得模塊復用難度增加。AOP則將封裝好的對象剖開,找出其中對多個對象產生影響的公共行為,並將其封裝為一個可重用的模塊,這個模塊被命名為“切麵”(Aspect),切麵將那些與業務無關,卻被業務模塊共同調用的邏輯提取並封裝起來,減少了系統中的重覆代碼,降低了模塊間的耦合度,同時提高了系統的可維護性。

2.DI 和 IOC 概念

  依賴註入或控制反轉的定義中,調用者不負責被調用者的實例創建工作,該工作由Spring框架中的容器來負責,它通過開發者的配置來判斷實例類型,創建後再註入調用者。由於Spring容器負責被調用者實例,實例創建後又負責將該實例註入調用者,因此稱為依賴註入。而被調用者的實例創建工作不再由調用者來創建而是由Spring來創建,控制權由應用代碼轉移到了外部容器,控制權發生了反轉,因此稱為控制反轉。

3.BeanFactory與ApplicationContext

  ApplicationContext是BeanFactory的子介面,也被稱為應用上下文。BeanFactory提供了Spring的配置框架和基本功能,ApplicationContext則添加了更多企業級功能(如國際化的支持),他另一重要優勢在於當ApplicationContext容器初始化完成後,容器中所有的 singleton Bean 也都被實例化了,也就是說當你需要使用singleton Bean 是,在應用中無需等待就可以用,而其他BeanFactory介面的實現類,則會延遲到調用 getBean()方法時構造,ApplicationContext的初始化時間會稍長些,調用getBean()是由於Bean已經構造完畢,速度會更快。因此大部分系統都使用ApplicationContext,而只在資源較少的情況下,才考慮使用BeanFactory。

4.AOP的實現策略

(1)Java SE動態代理:
    使用動態代理可以為一個或多個介面在運行期動態生成實現對象,生成的對象中實現介面的方法時可以添加增強代碼,從而實現AOP。缺點是只能針對介面進行代理,另外由於動態代理是通過反射實現的,有時可能要考慮反射調用的開銷。
(2)位元組碼生成(CGLib 動態代理)
    動態位元組碼生成技術是指在運行時動態生成指定類的一個子類對象,並覆蓋其中特定方法,覆蓋方法時可以添加增強代碼,從而實現AOP。其常用工具是cglib。
(3)定製的類載入器
    當需要對類的所有對象都添加增強,動態代理和位元組碼生成本質上都需要動態構造代理對象,即最終被增強的對象是由AOP框架生成,不是開發者new出來的。解決的辦法就是實現自定義的類載入器,在一個類被載入時對其進行增強。JBoss就是採用這種方式實現AOP功能。
(4)代碼生成
    利用工具在已有代碼基礎上生成新的代碼,其中可以添加任何橫切代碼來實現AOP。
(5)語言擴展
    可以對構造方法和屬性的賦值操作進行增強,AspectJ是採用這種方式實現AOP的一個常見Java語言擴展。

 

註意:AOP中的切麵封裝了增強(Advice)和切點(Pointcut),下麵先開始只使用增強,切點暫且不加入。

 

5.編程式增強

  這裡我先用“編程式”的方法,也就是暫且不用Spring的配置文件去定義Bean對象,不把代碼中的new操作取代。

(1)創建一個介面和實現類

 

(2)編寫前置增強和後置增強(這裡我將兩個增強合併,即實現兩個介面)

 

 

(3)JUnit來測試

 

 

(3)環繞增強(當把兩個介面合併時,其實完全可以用一個介面就行)

 

  環繞增強類需要實現 org.aopalliance.intercept.MethodInterceptor 介面。註意,這個介面不是 Spring 提供的,它是 AOP 聯盟寫的,Spring 只是借用了它。

 

之後再JUnit中添加

 

6. 聲明式增強

  現在通過Spring配置文件配置bean。同時使用Bean掃描,可以不用在配置文件中配置<bean id="..." class="..."/>.

(1)Spring配置文件(增強類為環繞增強)

 

 

(2)在相應的實現類和增強類上添加Component註解

 

 

(3)JUnit測試

  從 Context 中根據 id 獲取 Bean 對象(其實就是一個代理),調用代理的方法。

 

得到結果

 7.Introduction Advice(引入增強)

  上面的增強僅僅是對方法增強,也就是織入,對類的增強才能叫做引入增強,比如說我不想讓GreetingImpl去直接實現Greeting介面,因為這樣的話,我就必須去實現他的方法。這時我就能靠Spring引入增強來幫我動態實現。

(1)定義一個新介面Love

 

 

(2)定義授權引入增強類

  定義一個授權引入增強類,實現Love介面,用以豐富GreetingImpl類的功能,這樣GreetingImpl就能很巧妙的使用Love介面里的方法而不用去implement。

 

 

配置如下:

 

 

proxyTargetClass屬性表示是否代理目標類,預設是false,也就是代理介面,上面一個例子的配置就是沒有這一項屬性所以用JDK動態代理,現在是true即使用CGLib動態代理。所以在測試方法中是GreetingImpl greetingImpl = (GreetingImpl)context.getBean("beans.xml"),而不會是Greeting greeting = (Greeting)context.getBean("beans.xml"),因為現在是代理目標類而不是代理介面。

 

(3)JUnit測試

 

 

 

註意:這裡的Love love = (Love)greetingImpl 是將目標類強制向上轉型為Love介面,就是引入增強(DelegatingIntroductionInterceptor)的特性--介面動態實現”功能。所以display()方法可以由GreetingImpl的對象來調用,只需要強制轉換介面就行。

8. 面向切麵編程

(1)通知(增強)Advice

  通知定義了切麵是什麼以及何時使用,應該應用在某個方法被調用之前?之後?還是拋出異常時?等等。

(2)連接點 Join point

  連接點是在應用執行過程中能夠插入切麵的一個點。這個點可以是調用方法時,拋出異常時,甚至修改一個欄位時。切麵代碼可以利用這些點插入到應用的正常流程中,並添加新的行為。

(3)切點 Pointcut

  切點有助於縮小切麵所通知的連接點的範圍。如果說通知定義了切麵的“什麼”和“何時”的話,那麼切點就定義了“何處”,切點會匹配通知所要織入的一個或多個連接點,一般常用正則表達式定義所匹配的類和方法名稱來指定這些切點。

(4)切麵 Aspect

  切麵是通知和切點的結合。通知和切點定義了切麵的全部內容——它是什麼,在何時何處完成其功能。

(5)引入 Introduction

  引入允許我們向現有的類添加新方法或屬性,從而無需修改這些現有類的情況下,讓他們具有新的行為和狀態。

(6)織入 Weaving

  在過去我常常把織入與引入的概念混淆,我是這樣來辨別的,“引入”我把它看做是一個定義,也就是一個名詞,而“織入”我把它看做是一個動作,一個動詞,也就是切麵在指定的連接點被織入到目標對象中。

9.總結一下

  通知包含了需要用於多個應用對象的橫切行為;連接點是程式執行過程中能夠應用通知的所有點;切點定義了通知被應用的具體位置(在哪些連接點)。其中關鍵的概念是切點定義了哪些連接點會得到通知(增強)。創建切點來定義切麵所織入的連接點是AOP框架的基本功能。

  另外,Spring是基於動態代理的,所以Spring只支持方法連接點,而像AspectJ和JBoss除了方法切點,它們還提供欄位和構造器接入點。如果需要方法攔截之外的連接點攔截功能,則可以利用AspectJ來補充SpringAOP的功能。

10.使用基於正則表達式的SpringAOP切麵類

  這裡使用springAOP的切麵類RegexpMethodPointcutAdvisor來配置切麵,併在GreetingImpl類中增加兩個都以“good”開頭的方法,下麵要做的就是攔截兩個新增方法,而對sayHello()不攔截。

 

 

 

在上面的InterceptorNames屬性不再是原來的增強,而是一個定義好的切麵greetingAdvisor,切麵裡面還用正則表達式定義了一個切點,即攔截GreetingImpl類中以good開頭的方法。

JUnit測試:

 11.AOP自動代理

 (1)Spring框架自動生成代理。

 

 

 

   屬性optimize意思是對代理生成策略是否優化,true表示如果目標類有介面則代理介面(JDK動態代理),如果沒有則代理類(CGLib動態代理),這樣便可以取代前面強制代理類的proxyTargetClass屬性。

 

 

 

此時因為是自動代理,getBean()中的值不再是原來代理id(greetingProxy),而是目標類GreetingImpl的Bean的id(greetingImpl),他同樣也是一個代理對象

 

 

(2)spring根據Bean名稱來生成自動代理

beanNames屬性代表只為bean的id尾碼是“Impl”生成代理。

12. AspectJ execution 表達式攔截

  定義一個切麵類,實現環繞增強。@Aspect註解就不需要類再實現介面,@Around註解為AspectJ切點表達式,參數ProceedingJoinPoint的對象即為連接點,此連接點可以取得方法名,參數等等。

這樣兩行配置,節約了配置大量代理和切麵的時間,proxy-target-class為true表示代理目標類。

 

之前的切點表達式定義了攔截類中所有方法,所以每個方法都被增強。同時在ApplicationContext中獲取的greetingImpl代理對象,可轉型為自己靜態實現的介面Greeting也可以是實現類GreetingImpl。屬性proxy-target-class預設為false,代表只代理介面,也就是說只能將代理轉型為Greeting,而不能是GreetingImpl

實現類GreetingImpl:

 

 

P.s 如果將切麵類里的切點從原來的實現類GreetingImpl改為介面Greeting又會發生什麼呢?

 

改為:

 

 

結果發現,實現類中的實現介面的方法被增強了,而自己創建的good方法沒有被增強,這就是因為切點設置為Greeting介面裡面所有方法被加強,所以實現了這個介面中的方法被增強了

 

 

 

13. AspectJ @DeclareParents 註解(引入增強)

  定義一個切麵類AroundAspect:value屬性指定了哪種類型的bean要引入該介面。defaultImpl屬性指定了為引入功能提供實現的類,@DeclareParents註解所標註的屬性指明要引入的介面。

 

LoveImpl實現類:將這個實現類引入目標類GreetingImpl中,就能使用display方法。

JUnit測試:

 

註意:在ApplicationContext中獲取的greetingImpl對象是個代理對象,可轉型為自己靜態實現的介面Greeting,也可以轉型為自己動態實現的介面Love,可隨意切換。現在的AspectJ的引入增強跟上面的SpringAOP的引入增強只能面向實現類相比,還可面向介面編程。所以有兩種方式實現:

 

控制台輸出:

 

而對於SpringAOP引入的增強,則只能面向實現類:

 

 

14.Spring的AspectJ自動代理

  Spring的AspectJ自動代理僅僅使用@AspectJ作為創建切麵的指導,切麵依然是基於代理的。在本質上,它依然是Spring基於代理的切麵。這意味著儘管使用的是@AspectJ註解,但我們仍然限於代理方法的調用。當Spring發現一個bean使用了@Aspect註解時,Spring就會創建一個代理,然後將調用委托給被代理的bean或被引入的實現,這取決於調用的方法屬於被代理的bean還是屬於被引入的介面。

15.在XML中聲明切麵

  在Spring中,註解和自動代理提供了一種很方便的方式來創建切麵,但是面向註解的切麵有一個明顯的劣勢:你必須能夠為通知類添加註解,為了這一點,必須要有源碼。如果你沒有源碼的話,或者不想將AspectJ註解放到你的代碼之中,Spring提供了另外一種方法,Spring XML 配置文件中聲明切麵。

  將前面實現類GreetingImpl和切麵類AroundAspect的相關註解@Component,@Aspect,@Around全都移除。編輯XML:

 

我們發現原來的兩條配置都可以刪除,但是要註意,沒有顯式配置<aop:aspectj-autoproxy/>不代表不使用自動代理,這條配置預設屬性為“false”,表示只代理介面(JDK動態代理),所以如果只想代理介面,可以不用顯式寫出。

如果想要使用CGLib動態代理,則增加

這時又可以代理目標類了:

 

 

16. END

  這篇文章是我對《Spring實戰》和《AOP那點事兒》的一些知識的整理和例子的實現,希望你也能一起實現一下,如果你覺得還不錯的話,請點個贊或關註我,以後會有更多的知識分享!

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • *************************hibernate.cfg.xml <?xml version='1.0' encoding='utf-8'?><!DOCTYPE hibernate-configuration PUBLIC"-//Hibernate/Hibernate Confi ...
  • 之前是一直做asp.net開發的,Visual Studio 2012 用起來感覺是相當的舒服,調試功能也是相當好、相當智能,大大的提高了開發效率減少了程 序BUG的出現,而且直接安裝好就能用了,無需多餘的操作,非常方便。 由於這次要做PHP開發,為了搭建開發環境也是搜索了不少資料,選擇IDE也是參 ...
  • 雖然目前Java算不上前端開發的主力,但是作為Java入門基礎的一部分,學習Java的GUI編程還是有必要的,而且可以做出一些小且有趣的圖形程式來提高學習熱情。本篇學習總結均為一個Beginner的筆記與心得,如有描述不到或錯誤之處,敬請指正。 一個合格的Java Developer,不僅要掌握技術... ...
  • 一、Hibernate 二級緩存 1.Hibernate 二級緩存是 SessionFactory 級別的緩存。 2.二級緩存分為兩類: (1)Hibernate內置二級緩存 (2)外置緩存,可配置的,可插撥的,外置緩存中的數據是資料庫數據的複製。 3.二級緩存的併發訪問策略 (1)兩個併發的事務同 ...
  • sizeof關鍵字和strlen()標準函數都可以用來測試字元串的長度,但是兩者有很大的不同 sizeof只能在 本函數內 , 使用 和 不指定長度的字元數組 中才能測出字元串的真實長度,當然,包括 strlen()任何情況下都能正確的得出字元串的字面值大小 在 本函數內 ,sizeof測試 的結果 ...
  • ...
  • ``` include int main(int argc, const char argv[]) { int i=0,j=0; for(i=1;i ...
  • C語言 變數存儲 數據結構 嵌入式 ARM 靜態變數 局部變數 全局變數 單片機 記憶體分配 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...