淺析Spring AOP

来源:http://www.cnblogs.com/maypattis/archive/2016/06/19/5597265.html
-Advertisement-
Play Games

在正常的業務流程中,往往存在著一些業務邏輯,例如安全審計、日誌管理,它們存在於每一個業務中,然而卻和實際的業務邏輯沒有太強的關聯關係。 圖1 這些邏輯我們稱為橫切邏輯。如果把橫切的邏輯代碼寫在業務代碼中,散落在各個地方,則會變得非常難以維護,代碼也會顯得過於臃腫。 Spring AOP為處理這些問題 ...


    在正常的業務流程中,往往存在著一些業務邏輯,例如安全審計、日誌管理,它們存在於每一個業務中,然而卻和實際的業務邏輯沒有太強的關聯關係。

        

                   圖1

  這些邏輯我們稱為橫切邏輯。如果把橫切的邏輯代碼寫在業務代碼中,散落在各個地方,則會變得非常難以維護,代碼也會顯得過於臃腫。

  Spring AOP為處理這些問題提供了一種很好的方法。

1 AOP術語

  1 通知(advice)。通知是指真正執行的目標邏輯。例如為系統記日誌,那麼通知做的事情,就是記錄日誌。

  2 連接點(joinpoit)。連接點指何時執行advice的代碼。系統中如果想要為所有的start()方法執行之前,記錄一條"our system is starting"的日誌,那麼,start()方法被調用的時機,稱為一個連接點。

  3 切點(pointcut)。切點指何處執行advice的代碼。例如指定特定的一個路徑下的start()方法被調用時,執行advice的代碼,那麼,這個特定的路徑可以理解為切點。通常我們使用正則表達式定義匹配的類和方法來確定切點。

  4 切麵(aspect)。 切麵是通知、切點和連接點的全部內容。它定義了何時在何處,完成什麼功能。

  5 織入(weaving)。織入指實際代碼執行到切點時,完成對目標對象代理的過程。織入有如下三種方式:

    a.編譯器織入。需要特殊編譯器支持,例如AspectJ的織入編譯器,在編譯期間將代碼織入。

    b.類載入時。目標類在載入JVM時將切麵引入。需要特殊的classLoader支持,改變目標類的位元組碼,增加新的動作。

    c.運行期。在運行期運用動態代理技術,代理目標類。Spring AOP就是使用這種方式。

2 Spring對AOP的支持

  Spring對AOP提供了多種支持,大體上分為2種思路。

  1 使用動態代理,在運行期對目標類生成代理對象。代理類封裝了目標類,並攔截被通知的方法的調用,再將調用轉發給真正的目標類方法調用。在代理類中,可以執行切麵邏輯。基於這種模式,僅限於對方法的攔截。如果想要實現更細粒度的攔截,例如特定的欄位的修改,則需要使用第2種思路了。

  2 使用AspectJ特有的AOP語言。這種方式的成本在於,需要學習AspectJ的語法。

  大多數情況下,第一種思路已經可以滿足我們的需求。

3 Demo

  我們可以使用execution指示器來指示切點。如圖2

  

                   圖2

  execution指示器具體指定了哪個包下麵哪個類執行什麼方法時,會被織入橫切邏輯。Spring AOP的demo網上有很多,但是原理其實都一樣。這裡貼出其中一種。

  我們需要2個類,一個類(Biz)用作正常的業務邏輯,一個類用作橫切(AdviceTest)。同時寫一個類Main測試

  1.Biz

1 public class Biz {
2     public void doBiz(){
3         System.out.println("doing biz");
4     }
5 }
View Code

  2.AdviceTest

 1 public class AdviceTest implements Advice, MethodInterceptor {
 2 
 3     @Override
 4     public Object invoke(MethodInvocation invocation) throws Throwable {
 5         advice();
 6         invocation.proceed();
 7         return null;
 8     }
 9 
10     public void advice(){
11         System.out.println("this is a advice");
12     }
13 
14 }
View Code

  3.Main

1 public class Main {
2     public static void main(String[] args) {
3         ApplicationContext applicationContext = new ClassPathXmlApplicationContext(new String[]{"config/spring/spring-aop.xml"});
4         Biz biz = (Biz)applicationContext.getBean("biz");
5         biz.doBiz();
6     }
7 }
View Code

  4.Spring配置

 1 <?xml version="1.0" encoding="UTF-8" ?>
 2 <beans
 3         xmlns="http://www.springframework.org/schema/beans"
 4         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 5         xmlns:aop="http://www.springframework.org/schema/aop"
 6         xsi:schemaLocation="
 7             http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
 8             http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
 9             ">
10 
11     <bean id="adviceTest" class="aop.AdviceTest"/>
12     
13     <bean id="biz" class="aop.Biz"/>
14     
15     <aop:config>
16         <aop:pointcut id="bizPointCut" expression="execution(* aop.Biz.*(..))"/>
17         <aop:advisor pointcut-ref="bizPointCut" advice-ref="adviceTest"/>
18     </aop:config>
19 
20 </beans>
View Code

  執行Main函數代碼,輸出如下 

this is a advice
doing biz

  可見,實際的biz邏輯已經經過增強。

4 源碼分析

  Spring提供了兩種方式來生成代理對象: JDKProxy和Cglib,具體使用哪種方式生成由AopProxyFactory根據AdvisedSupport對象的配置來決定。預設的策略是如果目標類是介面,則使用JDK動態代理技術,否則使用Cglib來生成代理。在我們的例子中,顯然使用的是Cglib。如圖3

  

                           圖3

  為什麼我們配置了Biz的bean,得到的卻不是Biz的實例呢?這和Spring對xml文件的標簽解析策略有關。對於AOP相關的bean的解析,在AopNamespaceHandler類裡面有相關代碼,感興趣的同學可以去研究下。

  我們繼續往下麵單步調試,發現進入了CglibAopProxy類裡面。

 1 public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
 2             Object oldProxy = null;
 3             boolean setProxyContext = false;
 4             Class<?> targetClass = null;
 5             Object target = null;
 6             try {
 7                 if (this.advised.exposeProxy) {
 8                     // Make invocation available if necessary.
 9                     oldProxy = AopContext.setCurrentProxy(proxy);
10                     setProxyContext = true;
11                 }
12                 // May be null. Get as late as possible to minimize the time we
13                 // "own" the target, in case it comes from a pool...
14                 target = getTarget();
15                 if (target != null) {
16                     targetClass = target.getClass();
17                 }
18                 List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
19                 Object retVal;
20                 // Check whether we only have one InvokerInterceptor: that is,
21                 // no real advice, but just reflective invocation of the target.
22                 if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
23                     // We can skip creating a MethodInvocation: just invoke the target directly.
24                     // Note that the final invoker must be an InvokerInterceptor, so we know
25                     // it does nothing but a reflective operation on the target, and no hot
26                     // swapping or fancy proxying.
27                     Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
28                     retVal = methodProxy.invoke(target, argsToUse);
29                 }
30                 else {
31                     // We need to create a method invocation...
32                     retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
33                 }
34                 retVal = processReturnType(proxy, target, method, retVal);
35                 return retVal;
36             }
37             finally {
38                 if (target != null) {
39                     releaseTarget(target);
40                 }
41                 if (setProxyContext) {
42                     // Restore old proxy.
43                     AopContext.setCurrentProxy(oldProxy);
44                 }
45             }
46         }
View Code

  無恥的把代碼直接貼出來。我們重點關註下下麵這一行

   retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed(); 

  在本文例子的場景下,會在此創建一個CglibMethodInvocation執行上下文,並執行proceed()方法。

                         圖4

最終執行AdviceTest中invoke()方法的調用。在AdviceTest的invoke方法中,我們可以自定義自己想要執行的增強邏輯invocation.proceed()來執行目標類的方法。

  

                       圖5

5 總結

  Spring AOP為許多與業務無關的邏輯的執行,提供了一種很好的解決思路。本文也只是拋磚引玉,在實際的Spring源碼中,還是比較複雜的,還需要細細研究才行。

 

參考文獻:

 《Spring In Action》

 《Spring源碼深度解析》

  

作者:mayday芋頭 出處:http://www.cnblogs.com/maypattis/ 本博客中未標明轉載的文章歸作者mayday芋頭和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 習題集解析部分 第8章 動態存儲管理 ——《數據結構題集》-嚴蔚敏.吳偉民版 源碼使用說明 鏈接☛☛☛ 《數據結構-C語言版》(嚴蔚敏,吳偉民版)課本源碼+習題集解析使用說明 課本源碼合輯 鏈接☛☛☛ 《數據結構》課本源碼合輯 習題集全解析 鏈接☛☛☛ 《數據結構題集》習題解析合輯 相關測試數據下載 ...
  • 有些語言支持函數指針、代理、lambda表達式,或者支持類似的機制,允許程式把“調用特殊函數的能力”儲存起來並傳遞這種能力。這種機制通常用於允許函數的調用者通過傳入第二個函數,來指定自己的行為。比較器函數有兩個參數,都是指向元素的指針。如果第一個參數所指的元素小於第二個參數所指的元素,則返回一個負整 ...
  • 課本源碼部分 第8章 動態存儲管理 - 存儲緊縮 ——《數據結構》-嚴蔚敏.吳偉民版 源碼使用說明 鏈接☛☛☛ 《數據結構-C語言版》(嚴蔚敏,吳偉民版)課本源碼+習題集解析使用說明 課本源碼合輯 鏈接☛☛☛ 《數據結構》課本源碼合輯 習題集全解析 鏈接☛☛☛ 《數據結構題集》習題解析合輯 本源碼引 ...
  • 上一節中的單例對象MarkerFactory 就是一個獨立對象的例子。儘管它管理著Marker類,但是它並沒有關聯到任何類上。 scala也可以創建關聯到類上的對象。這樣的對象同類共用同一個名字,這樣的對象稱為伴生對象,對應的類就稱為伴生類。在scala里,類和伴生對象沒有界限,它們互相可以訪問彼此... ...
  • 課本源碼部分 第8章 伙伴系統 - 邊界標識法 ——《數據結構》-嚴蔚敏.吳偉民版 源碼使用說明 鏈接☛☛☛ 《數據結構-C語言版》(嚴蔚敏,吳偉民版)課本源碼+習題集解析使用說明 課本源碼合輯 鏈接☛☛☛ 《數據結構》課本源碼合輯 習題集全解析 鏈接☛☛☛ 《數據結構題集》習題解析合輯 本源碼引入 ...
  • - -|||... 這周上了七天班啊有木有 1、團隊組建完畢,雖然不是一個很龐大的團隊,但是有人能做事,每天充分利用好8個小時,彼此互相幫助就可以作為一個強大的團隊來支撐公司 團隊並不需要那些每天上下班打個卡,有任務就做,沒任務就上網的那種,這樣的員工見多了,曾經我待過一家所謂的大公司,在旁人眼裡, ...
  • 有了前面的基礎,後面就是將頁面展示出來。 預覽圖如下:1號和31號分別有活動,會一併顯示出來 這裡需要搞定幾個問題,一個就是資料庫的連接,我們用\sys\class\class.db_connect.inc.php db = $db; } else { // Constants are define... ...
  • echo命令:在shell中主要用於輸出 1. -n 不換行的顯示結果(預設是換行的) 2. -e " " 支持雙引號中使用一些特殊字元 常用的特殊字元有 腳本實例 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...