1. 程式人生 > >向切面程式設計(AOP)的理解

向切面程式設計(AOP)的理解

  在傳統的編寫業務邏輯處理程式碼時,我們通常會習慣性地做幾件事情:日誌記錄、事務控制及許可權控制等,然後才是編寫核心的業務邏輯處理程式碼。當代碼編寫完成回頭再看時,不禁發現,揚揚灑灑上百行程式碼中,真正用於核心業務邏輯處理才那麼幾行,如圖6-4所示。方法複方法,類復類,就這樣子帶著無可奈何遺憾地度過了多少個春秋。這倒也罷,倘若到了專案的尾聲,突然決定在許可權控制上需要進行大的變動時,成千上萬個方法又得一一"登門拜訪",痛苦"雪上加霜"。

 

如果能把圖6-4中眾多方法中的所有共有程式碼全部抽取出來,放置到某個地方集中管理,然後在具體執行時,再由容器動態織入這些共有程式碼的話,最起碼可以解決兩個問題:

Java EE程式設計師在編寫具體的業務邏輯處理方法時,只需關心核心的業務邏輯處理,既提高了工作效率,又使程式碼變更簡潔優雅。

在日後的維護中由於業務邏輯程式碼與共有程式碼分開存放,而且共有程式碼是集中存放的,因此使維護工作變得簡單輕鬆。

面向切面程式設計AOP技術就是為解決這個問題而誕生的,切面就是橫切面,如圖6-5所示,代表的是一個普遍存在的共有功能,例如,日誌切面、許可權切面及事務切面等。

 

下面我們以使用者管理業務邏輯元件UserService的AOP實現過程(見圖6-6)為例,深度剖析一下AOP技術的實現原理。AOP技術是建立在Java語言的反射機制與動態代理機制之上的。業務邏輯元件在執行過程中,AOP容器會動態建立一個代理物件供使用者呼叫,該代理物件已經按Java EE程式設計師的意圖將切面成功切入到目標方法的連線點上,從而使切面的功能與業務邏輯的功能同時得以執行。從原理上講,呼叫者直接呼叫的其實是AOP容器動態生成的代理物件,再由代理物件呼叫目標物件完成原始的業務邏輯處理,而代理物件則已經將切面與業務邏輯方法進行了合成。

 

現將圖6-6中涉及到的一些概念解釋如下。

切面(Aspect):其實就是共有功能的實現。如日誌切面、許可權切面、事務切面等。在實際應用中通常是一個存放共有功能實現的普通Java類,之所以能被AOP容器識別成切面,是在配置中指定的。

通知(Advice):是切面的具體實現。以目標方法為參照點,根據放置的地方不同,可分為前置通知(Before)、後置通知(AfterReturning)、異常通知(AfterThrowing)、最終通知(After)與環繞通知(Around)5種。在實際應用中通常是切面類中的一個方法,具體屬於哪類通知,同樣是在配置中指定的。

連線點(Joinpoint):就是程式在執行過程中能夠插入切面的地點。例如,方法呼叫、異常丟擲或欄位修改等,但Spring只支援方法級的連線點。

切入點(Pointcut):用於定義通知應該切入到哪些連線點上。不同的通知通常需要切入到不同的連線點上,這種精準的匹配是由切入點的正則表示式來定義的。

目標物件(Target):就是那些即將切入切面的物件,也就是那些被通知的物件。這些物件中已經只剩下乾乾淨淨的核心業務邏輯程式碼了,所有的共有功能程式碼等待AOP容器的切入。

代理物件(Proxy):將通知應用到目標物件之後被動態建立的物件。可以簡單地理解為,代理物件的功能等於目標物件的核心業務邏輯功能加上共有功能。代理物件對於使用者而言是透明的,是程式執行過程中的產物。

織入(Weaving):將切面應用到目標物件從而建立一個新的代理物件的過程。這個過程可以發生在編譯期、類裝載期及執行期,當然不同的發生點有著不同的前提條件。譬如發生在編譯期的話,就要求有一個支援這種AOP實現的特殊編譯器;發生在類裝載期,就要求有一個支援AOP實現的特殊類裝載器;只有發生在執行期,則可直接通過Java語言的反射機制與動態代理機制來動態實現。

=============================================================

Spring如此流行,當我第一次接觸Spring的時候,到網上看了一些文章,都講得神乎其乎,最後我篇也沒看懂,我當時就是認為這個東西一定很高深,於是我就遇到做WEB開發的人就會打聽一下。得到最多的一個回答就是“Spring是一個框架”,然後我就會問框架是什麼,但都沒有一種說法不讓我感覺玄乎乎的,同時也沒有聽懂,於是,我更感覺Spring很神了,這可能叫做朦朧美。還有一種說法就是“Spring兩個首要的特性就是AOP和IoC”,這種說法讓我感覺Spring簡直神的飛上天了,我都不敢接著問了,再問可能想上天去找Spring了,後來我就懷疑這玩意兒真有那麼高深嗎,強列的好奇心讓我實在hold,決定一定要試一下這個神器,於是就看了一些Step By Step的文章,自己寫了一個HelloWorld,發現這AOP確實是一個比較新穎的思想,也算是打破了常規,是從不同方面思考問題。不過沒有那些童鞋說得那麼神。

本文旨在幫助還沒有理解AOP的童鞋看透弄懂AOP,也歡迎高手批評指正。

先說一個Spring是什麼吧,大家都是它是一個框架,但框架這個詞對新手有點抽象,以致於越解釋越模糊,不過它確實是個框架的,但那是從功能的角度來定義的,從本質意義上來講,Spring是一個庫,一個Java庫,所以我個人覺得應該這樣回答Spring是什麼:Spring是一個庫,它的功能是提供了一個軟體框架,這個框架目的是使軟體之間的邏輯更加清晰,配置更靈活,實現這個目的的手段使用AOP和IoC,而AOP和IoC是一種思想,是一種什麼樣的思想呢,等下細說,先說AOP在Java裡是利用反射機制實現(你也可以認為是動態代理,不過動態代理也是反射機制實現的,所以還是先不要管動態代理,我們這裡化繁為簡,不讓它干擾咱們對AOP的理解),如何使用AOP呢,很簡單滴,等下介紹。

下面先說AOP是什麼樣的思想,我們一步一步慢慢來,先看一下傳統程式的流程,比如銀行系統會有一個取款流程

我們可以把方框裡的流程合為一個,另外系統還會有一個查詢餘額流程,我們先把這兩個流程放到一起:

有沒有發現,這個兩者有一個相同的驗證流程,我們先把它們圈起來再說下一步:

有沒有想過可以把這個驗證使用者的程式碼是提取出來,不放到主流程裡去呢,這就是AOP的作用了,有了AOP,你寫程式碼時不要把這個驗證使用者步驟寫進去,即完全不考慮驗證使用者,你寫完之後,在另我一個地方,寫好驗證使用者的程式碼,然後告訴Spring你要把這段程式碼加到哪幾個地方,Spring就會幫你加過去,而不要你自己Copy過去,這裡還是兩個地方,如果你有多個控制流呢,這個寫程式碼的方法可以大大減少你的時間,不過AOP的目的不是這樣,這只是一個“副作用”,真正目的是,你寫程式碼的時候,事先只需考慮主流程,而不用考慮那些不重要的流程,懂C的都知道,良好的風格要求在函式起始處驗證引數,如果在C上可以用AOP,就可以先不管校驗引數的問題,事後使用AOP就可以隔山打牛的給所有函式一次性加入校驗程式碼,而你只需要寫一次校驗程式碼。不知道C的沒關係,舉一個通用的例子,經常在debug的時候要打log吧,你也可以寫好主要程式碼之後,把打log的程式碼寫到另一個單獨的地方,然後命令AOP把你的程式碼加過去,注意AOP不會把程式碼加到原始檔裡,但是它會正確的影響最終的機器程式碼。

現在大概明白了AOP了嗎,我們來理一下頭緒,上面那個方框像不像個平面,你可以把它當塊板子,這塊板子插入一些控制流程,這塊板子就可以當成是AOP中的一個切面。所以AOP的本質是在一系列縱向的控制流程中,把那些相同的子流程提取成一個橫向的面,這句話應該好理解吧,我們把縱向流程畫成一條直線,然把相同的部分以綠色突出,如下圖左,而AOP相當於把相同的地方連一條橫線,如下圖右,這個圖沒畫好,大家明白意思就行。

    

這個驗證使用者這個子流程就成了一個條線,也可以理解成一個切面,aspect的意思我認為是方面,你用什麼實物去類比,只要你能理解都可以。這裡的切面只插了兩三個流程,如果其它流程也需要這個子流程,也可以插到其它地方去。

講了這麼多,那到AOP該如何使用呢?我們要寫一個HelloWorld嗎,我看還是算了,關於這種型別的文章,網上已經氾濫成災,我再寫也不一定比人家寫得好,所以,我會在下面貼幾個我認為寫得不錯的文章連結,但我在這裡先介紹一下Spring如何實現AOP的吧。其實也不難理解,Spring的實現是基於函式(或叫方法)的,就是說,你寫好了一個函式後,你還可以在不更改原來的程式碼情況,通過Spring在函數前或函式後動態的加入新的程式碼。比如你原來的代是這樣的:

void foo() {     System.out.println("in foo()");}

然後你想在函式執行前(當成也可以加到執行後,或前後都加,原理是一樣)加一句:

System.out.println("before execute foo()");

你也可以多加幾句,通過Spring,你可以把這些程式碼動態的加到函式前面,而不用改變原來的程式碼。從而會得到與以下等效的執行碼:

void foo() {     System.out.println("before execute foo()");     System.out.pringln("in foo()"); }

 

**歡迎關注我的個人公眾號:we-aibook,裡面有相關技術文章分享,專案架構,知識星球,技術交流群,不定期進行抽獎送書活動喲!**