1. 程式人生 > >《Spring設計思想》AOP設計思想與原理(圖文並茂) 侵立刪

《Spring設計思想》AOP設計思想與原理(圖文並茂) 侵立刪

轉自:https://mp.weixin.qq.com/s/1p2XSfhM1V5CcWro6PZEwQ

 

前言

 

Spring 提供了AOP(Aspect Oriented Programming) 的支援, 那麼,什麼是AOP呢?本文將通過一個另外一個角度來詮釋AOP的概念,幫助你更好地理解和使用Spring AOP。

 

讀完本文,你將瞭解到:

1.    Java程式執行在JVM中的特徵

2.    Java程式的執行流【瞭解AOP、連線點(Join Point)、切入點(point cut)   的概念 】

3.    引入了代理模式的Java程式執行流(AOP實現的機制)

4.    Spring AOP的工作原理

 

1. Java程式執行在JVM中的特徵

 

當我們在某個類Foo中寫好了一個main()方法,然後執行java Foo,你的Java程式之旅就開啟了,如下:

 

 

那麼在這個執行的過程中,JVM都為你幹了什麼呢?

 

當你執行java Foo 的時候,JVM會建立一個主執行緒main,這個主執行緒以上述的main()方法作為入口,開始執行你的程式碼。每一個執行緒在記憶體中都會維護一個屬於自己的棧(Stack),記錄著整個程式執行的過程。棧裡的每一個元素稱為棧幀(Stack Frame),棧幀表示著某個方法呼叫,會記錄方法呼叫的資訊;實際上我們在程式碼中呼叫一個方法的時候,在記憶體中就對應著一個棧幀的入棧和出棧。

 

在某個特定的時間點,一個Main執行緒內的棧會呈現如下圖所示的情況:

 

 

從執行緒棧的角度來看,我們可以看到,JVM處理Java程式的基本單位是方法呼叫。實際上,JVM執行的最基本單位的指令(即原子操作)是組合語言性質的機器位元組碼。這裡之所以講方法呼叫時Java程式的基本執行單位,是從更巨集觀的角度看待的。

 

如何獲取到虛擬機器執行緒棧中的內容(即方法呼叫過程)?

 

試想一下,如何能夠獲取到JVM執行緒棧中的方法呼叫的內容? 我相信所有的Java programmer都知道這個答案。Java Programmer幾乎每天都能看到它------當我們的程式碼丟擲異常而未捕獲或者執行時出現了Error錯誤時,我們會受到一個非常討厭的Log資訊,如下:

 

 

當然,除了程式碼丟擲異常外,我們還是可以其他方式察覺JVM執行緒棧內的內容。可以通過Thread.dumpStack()方法建立一個假的Exception例項,然後將這個Exception例項記錄的當前執行緒棧的內容輸出到標準錯誤流中。例如我在某處程式碼裡執行了Thread.dumpStack()方法,輸出瞭如下的結果:

 

 

2.  Java程式執行流 【瞭解AOP、連線點(Join Point)、切入點(point cut)   的概念 】

 

如果從虛擬機器執行緒棧的角度考慮Java程式執行的話,那麼,你會發現,真個程式執行的過程就是方法呼叫的過程。我們按照方法執行的順序,將方法呼叫排成一串,這樣就構成了Java程式流。

 

我們將上述的執行緒棧裡的方法呼叫按照執行流排列,會有如下類似的圖:

 

 

基於時間序列,我們可以將方法呼叫排成一條線。而每個方法呼叫則可以看成Java執行流中的一個節點。這個節點在AOP的術語中,被稱為Join Point,即連線點。 一個Java程式的執行的過程,就是若干個連線點連線起來依次執行的過程。

 

在我們正常的面向物件的思維中, 我們考慮的是如何按照時間序列通過方法呼叫來實現我們的業務邏輯。那麼,什麼是AOP(即面向切面的程式設計)呢?

 

通常面向物件的程式,程式碼都是按照時間序列縱向展開的,而他們都有一個共性:即都是已方法呼叫作為基本執行單位展開的。 將方法呼叫當做一個連線點,那麼由連線點串起來的程式執行流就是整個程式的執行過程。

 

AOP(Aspect Oriented Programming)則是從另外一個角度來考慮整個程式的,AOP將每一個方法呼叫,即連線點作為程式設計的入口,針對方法呼叫進行程式設計。從執行的邏輯上來看,相當於在之前縱向的按照時間軸執行的程式橫向切入。相當於將之前的程式橫向切割成若干的面,即Aspect.每個面被稱為切面。

 

所以,根據我的理解,AOP本質上是針對方法呼叫的程式設計思路。

 

 

既然AOP是針對切面進行的程式設計的,那麼,你需要選擇哪些切面(即 連線點Joint Point)作為你的程式設計物件呢?

 

因為切面本質上是每一個方法呼叫,選擇切面的過程實際上就是選擇方法的過程。那麼,被選擇的切面(Aspect)在AOP術語裡被稱為切入點(Point Cut).  切入點實際上也是從所有的連線點(Join point)挑選自己感興趣的連線點的過程。

 

 

Spring AOP框架中通過 方法匹配表示式來表示切入點(Point Cut),至於詳細的表示式語法是什麼 不是本文的重點,請讀者自行參考Spring相應的說明文件。

 

既然AOP是針對方法呼叫(連線點)的程式設計, 現在又選取了你感興趣的自己感興趣的連結點---切入點(Point Cut)了,那麼,AOP能對它做什麼型別的程式設計呢?AOP能做什麼呢? 

 

瞭解這個之前,我們先要知道一個非常重要的問題: 既然AOP是對方法呼叫進行的程式設計,那麼,AOP如何捕獲方法呼叫的呢? 弄清楚這個問題,你不得不瞭解設計模式中的代理模式了。下面我們先來了解一下引入了代理模式的Java程式執行流是什麼樣子的。

 

3.    引入了代理模式的Java程式執行流(AOP實現的機制)

 

我們假設在我們的Java程式碼裡,都為例項物件通過代理模式建立了代理物件,訪問這些例項物件必須要通過代理,那麼,加入了proxy物件的Java程式執行流會變得稍微複雜起來。

 

我們來看下加入了proxy物件後,Java程式執行流的示意圖:

 

 

由上圖可以看出,只要想呼叫某一個例項物件的方法時,都會經過這個例項物件相對應的代理物件, 即執行的控制權先交給代理物件。

 

關於代理模式

 

代理模式屬於Java程式碼中經常用到的、也是比較重要的設計模式。代理模式可以為某些物件除了實現本身的功能外,提供一些額外的功能,大致作用如下圖所示:

 

 

加入了代理模式的Java程式執行流,使得所有的方法呼叫都經過了代理物件。對於Spring AOP框架而言,它負責控制著真個容器內部的代理物件。當我們呼叫了某一個例項物件的任何一個非final的public方法時,整個Spring框架都會知曉。

 

此時的SpringAOP框架在某種程度上扮演著一個上帝的角色:它知道你在這個框架內所做的任何操作,你對每一個例項物件的非final的public方法呼叫都可以被框架察覺到!

 

 

既然Spring代理層可以察覺到你所做的每一次對例項物件的方法呼叫,那麼,Spring就有機會在這個代理的過程中插入Spring的自己的業務程式碼。

 

4.     Spring AOP的工作原理

 

前面已經介紹了AOP程式設計首先要選擇它感興趣的連線點----即切入點(Point cut),那麼,AOP能對切入點做什麼樣的程式設計呢? 我們先將代理模式下的某個連線點細化,你會看到如下這個示意圖所表示的過程:

 

 

為了降低我們對Spring的AOP的理解難度,我在這裡將代理角色的職能進行了簡化,方便大家理解。(注意:真實的Spring AOP的proxy角色扮演的只能比這複雜的多,這裡只是簡化,方便大家理解,請不要先入為主)代理模式的代理角色最起碼要考慮三個階段:

 

1.呼叫真正物件的方法之前,應該需要做什麼?

 

2. 在調用真正物件的方法過程中,如果丟擲了異常,需要做什麼?

 

3.在呼叫真正物件的方法後,返回了結果了,需要做什麼?

 

AOP對這個方法呼叫的程式設計,就是針對這三個階段插入自己的業務程式碼。

 

現在我們假設當前RealSubject這個角色的類是org.luanlouis.springlearning.aop.FooService,當前這個連線點對應的方法簽名是:public void foo()。那麼上述的代理物件的三個階段將會有以下的處理邏輯:            

 

1. 在呼叫真正物件的方法之前,

 

proxy會告訴Spring AOP:  "我將要呼叫類org.luanlouis.springlearning.aop.FooService  的public void foo(),在呼叫之前,你有什麼處理建議嗎?";

 

Spring AOP這時根據proxy提供的類名和方法簽名,然後拿這些資訊嘗試匹配是否在其感興趣的切入點內,如果在感興趣的切入點內,Spring AOP會返回 MethodBeforeAdvice處理建議,告訴proxy應該執行的操作;

 

2. 在呼叫真正物件的方法過程中,如果丟擲了異常,需要做什麼?

 

proxy告訴Spring AOP: “我呼叫類org.luanlouis.springlearning.aop.FooService  的public void foo()過程中丟擲了異常,你有什麼處理建議?”

 

Spring AOP根據proxy提供的型別和方法簽名,確定了在其感興趣的切入點內,則返回相應的處理建議ThrowsAdvice,告訴proxy這個時期應該採取的操作。

 

3.在呼叫真正物件的方法後,返回了結果了,需要做什麼?

 

proxy告訴Spring AOP:"我呼叫類org.luanlouis.springlearning.aop.FooService  的public void foo()結束了,並返回了結果你現在有什麼處理建議?";

 

Spring AOP 根據proxy提供的型別名和方法簽名,確定了在其感興趣的切入點內,則返回AfterReturingAdivce處理建議,proxy得到這個處理建議,然後執行建議;

 

 

上述的示意圖中已經明確表明了Spring AOP應該做什麼樣的工作:根據proxy提供的特定類的特定方法執行的特定時期階段給出相應的處理建議。要完成該工作,Spring AOP應該實現:

 

1.確定自己對什麼類的什麼方法感興趣? -----即確定 AOP的切入點(Point Cut),這個可以通過切入點(Point Cut)表示式來完成;

 

2. 對應的的類的方法的執行特定時期給出什麼處理建議?------這個需要Spring AOP提供相應的建議 ,即我們常說的Advice。

 

 

到此為止,AOP的基本工作機制已經介紹完畢了。歡迎各位童鞋關注交流

 

作者: 亦山

點選閱讀原文檢視原文