官术网_书友最值得收藏!

3.1 Spring AOP概述

3.1.1 AOP概念回顧

AOP是Aspect-Oriented Programming(面向方面編程或面向切面)的簡稱,維基百科對它的解釋如下。

維基百科對“AOP”相關(guān)概念的敘述

Aspect是一種新的模塊化機(jī)制,用來描述分散在對象、類或函數(shù)中的橫切關(guān)注點(diǎn)(crosscutting concern)。從關(guān)注點(diǎn)中分離出橫切關(guān)注點(diǎn)是面向切面的程序設(shè)計(jì)的核心概念。分離關(guān)注點(diǎn)使解決特定領(lǐng)域問題的代碼從業(yè)務(wù)邏輯中獨(dú)立出來,業(yè)務(wù)邏輯的代碼中不再含有針對特定領(lǐng)域問題代碼的調(diào)用,業(yè)務(wù)邏輯同特定領(lǐng)域問題的關(guān)系通過切面來封裝、維護(hù),這樣原本分散在整個(gè)應(yīng)用程序中的變動(dòng)就可以很好地管理起來。

這里提到的概念是從模塊化出發(fā)的,開發(fā)者一定不會對模塊化這個(gè)概念感到陌生。記得我初學(xué)編程(C語言)時(shí),總喜歡把所有代碼寫進(jìn)一個(gè)main函數(shù)里。這種編碼方式造成了一個(gè)很不好的后果—程序的維護(hù)性很差。如果程序規(guī)模較小,而且是由一個(gè)人開發(fā)完成的,維護(hù)時(shí)還能控制;如果程序規(guī)模較大,而且需要多個(gè)人合作才能完成,維護(hù)時(shí)就會遇到很大的麻煩。再加上當(dāng)時(shí)根本沒有版本管理的概念,隨著項(xiàng)目進(jìn)展,功能越加越多,整個(gè)程序就逐漸變成了一團(tuán)亂麻。

經(jīng)過一段痛苦的經(jīng)歷后,我終于在開發(fā)實(shí)踐中對軟件工程的相關(guān)概念有了一些認(rèn)識,開始明白了自己原來只是在寫程序,并不是在開發(fā)軟件,更談不上是在開發(fā)軟件產(chǎn)品了。痛定思痛,我不斷地在編碼中學(xué)習(xí)和思考,開始使用子函數(shù)來對程序進(jìn)行模塊劃分,并對一些基本的功能進(jìn)行封裝。當(dāng)時(shí)只是希望一個(gè)函數(shù)不要太長,能把不同的功能模塊分給不同的開發(fā)人員完成。想法雖簡單,但每個(gè)開發(fā)人員都渴望這樣做,就像普通開發(fā)人員對優(yōu)秀的架構(gòu)師的渴望一樣。有了架構(gòu)師,每個(gè)人就可以各自負(fù)責(zé)自己的“一畝三分地”,日子也許就好過了!

但是很不幸,萬能的架構(gòu)師始終沒有出現(xiàn),最后只能自己想辦法:在工作中總結(jié),在教訓(xùn)中學(xué)習(xí),在摸索中前進(jìn)。在成長的過程中,很自然地發(fā)現(xiàn),將一些代碼用子函數(shù)封裝以后,只要把接口定義設(shè)計(jì)好,子函數(shù)中的代碼變動(dòng)是不會對主程序中的代碼產(chǎn)生太大影響的,從而大大降低了維護(hù)的成本。

后來,為了讓代碼的維護(hù)更方便,又把不同的子函數(shù)的實(shí)現(xiàn)放到了不同的文件中。這樣更方便了,不僅不用在一長串的代碼文件里查找和維護(hù),還可以讓不同的開發(fā)人員并行開發(fā)和維護(hù),大大提高了開發(fā)效率。除了技術(shù)方面的提高,還有精神上的收獲。這種分而治之的策略讓我慢慢具備了設(shè)計(jì)大型程序的信心,不會再為那些長長的代碼感到頭疼。用這種方法來編寫一般的C語言程序基本沒問題,直到后來涉及面向?qū)ο蟮某绦蛟O(shè)計(jì),新的問題又出現(xiàn)了。

有了一定的面向?qū)ο缶幊探?jīng)驗(yàn)后發(fā)現(xiàn),面向?qū)ο笤O(shè)計(jì)其實(shí)也是一種模塊化的方法,它把相關(guān)的數(shù)據(jù)及其處理方法放在了一起。與單純使用子函數(shù)進(jìn)行封裝相比,面向?qū)ο蟮哪K化特性更完備,它體現(xiàn)了計(jì)算的一個(gè)基本原則—讓計(jì)算盡可能靠近數(shù)據(jù)。這樣一來,代碼組織起來就更加整齊和清晰,一個(gè)類就是一個(gè)基本的模塊。很多程序的功能還可以通過設(shè)計(jì)類的繼承關(guān)系而得到重用,進(jìn)一步提高了開發(fā)效率。再后來,又出現(xiàn)了各種各樣的設(shè)計(jì)模式,使設(shè)計(jì)程序功能變得更加得心應(yīng)手。

后來又在開發(fā)中發(fā)現(xiàn)了一些問題。雖然利用面向?qū)ο蟮姆椒梢院芎玫亟M織代碼,也可以通過繼承關(guān)系實(shí)現(xiàn)代碼重用,但是程序中總是會出現(xiàn)一些重復(fù)的代碼,而且不太方便使用繼承的方法把它們重用和管理起來。它們功能重復(fù)并且需要用在不同的地方,雖然可以對這些代碼做一些簡單的封裝,使之成為公共函數(shù),但是在這種顯式的調(diào)用中,使用它們并不是很方便。例如,這個(gè)公共函數(shù)在什么情況下可以使用,能不能更靈活地使用等。

另外,在使用這些公共函數(shù)的時(shí)候,往往也需要進(jìn)行一些邏輯設(shè)計(jì),也就是需要代碼實(shí)現(xiàn)來支持,而這些邏輯代碼也是需要維護(hù)的。這時(shí)就是AOP大顯身手的時(shí)候,使用AOP后,不僅可以將這些重復(fù)的代碼抽取出來單獨(dú)維護(hù),在需要使用時(shí)統(tǒng)一調(diào)用,還可以為如何使用這些公共代碼提供豐富靈活的手段。這雖然與設(shè)計(jì)公共子模塊有幾分相似,但在傳統(tǒng)的公共子模塊調(diào)用中,除了直接硬調(diào)用之外并沒有其他的手段,而AOP為處理這一類問題提供了一套完整的理論和靈活多樣的實(shí)現(xiàn)方法。也就是說,通過AOP提出橫切的概念以后,在把模塊功能正交化的同時(shí),也在此基礎(chǔ)上提供了一系列橫切的靈活實(shí)現(xiàn)。比如通過使用Proxy代理對象、攔截器字節(jié)碼翻譯技術(shù)等一系列已有的AOP或者AOP實(shí)現(xiàn)技術(shù),來實(shí)現(xiàn)切面應(yīng)用的各種編織實(shí)現(xiàn)和環(huán)繞增強(qiáng);為了更好地應(yīng)用AOP技術(shù),技術(shù)專家們還成立了AOP聯(lián)盟來探討AOP的標(biāo)準(zhǔn)化,有了這些支持,AOP的發(fā)展就更快了。關(guān)于AOP技術(shù),可以到AOP聯(lián)盟的文檔里找到一些相關(guān)的介紹,從而加強(qiáng)對AOP的理解。比如,在AOP聯(lián)盟的網(wǎng)站上有以下AOP技術(shù):

? AspectJ:源代碼和字節(jié)碼級別的編織器,用戶需要使用不同于Java的新語言。

? AspectWerkz:AOP框架,使用字節(jié)碼動(dòng)態(tài)編織器和XML配置。

? JBoss-AOP:基于攔截器和元數(shù)據(jù)的AOP框架,運(yùn)行在JBoss應(yīng)用服務(wù)器上。以及在AOP中用到的一些相關(guān)的技術(shù)實(shí)現(xiàn):

? BCEL(Byte-Code Engineering Library):Java字節(jié)碼操作類庫,具體的信息可以參見項(xiàng)目網(wǎng)站http://jakarta.apache.org/bcel/。

? Javassist:Java字節(jié)碼操作類庫,JBoss的一個(gè)子項(xiàng)目,項(xiàng)目信息可以參見項(xiàng)目網(wǎng)站http://jboss.org/javassist/。

對應(yīng)于現(xiàn)有的AOP實(shí)現(xiàn)方案,AOP聯(lián)盟對它們進(jìn)行了一定程度的抽象,從而定義出AOP體系結(jié)構(gòu)。結(jié)合這個(gè)AOP體系結(jié)構(gòu)去了解AOP技術(shù),對我們理解AOP的概念是非常有幫助的,AOP聯(lián)盟定義的AOP體系結(jié)構(gòu)如圖3-1所示。

圖3-1 AOP聯(lián)盟定義的AOP體系結(jié)構(gòu)

AOP聯(lián)盟定義的AOP體系結(jié)構(gòu)把與AOP相關(guān)的概念大致分為由高到低、從使用到實(shí)現(xiàn)的三個(gè)層次。從上往下,最高層是語言和開發(fā)環(huán)境,在這個(gè)環(huán)境中可以看到幾個(gè)重要的概念:“基礎(chǔ)”(base)可以視為待增強(qiáng)對象或者說目標(biāo)對象;“切面”(aspect)通常包含對于基礎(chǔ)的增強(qiáng)應(yīng)用;“配置”(configuration)可以看成是一種編織,通過在AOP體系中提供這個(gè)配置環(huán)境,可以把基礎(chǔ)和切面結(jié)合起來,從而完成切面對目標(biāo)對象的編織實(shí)現(xiàn)。

在Spring AOP實(shí)現(xiàn)中,使用Java語言來實(shí)現(xiàn)增強(qiáng)對象與切面增強(qiáng)應(yīng)用,并為這兩者的結(jié)合提供了配置環(huán)境。對于編織配置,毫無疑問,可以使用IoC容器來完成;對于POJO對象的配置,本來就是Spring的核心IoC容器的強(qiáng)項(xiàng)。因此,對于使用Spring的AOP開發(fā)而言,使用POJO就能完成AOP任務(wù)。但是,對于其他的AOP實(shí)現(xiàn)方案,可能需要使用特定的實(shí)現(xiàn)語言、配置環(huán)境甚至是特定的編譯環(huán)境。例如在AspectJ中,盡管切面增強(qiáng)的對象是Java對象,但卻需要使用特定的Aspect語言和AspectJ編譯器。AOP體系結(jié)構(gòu)的第二個(gè)層次是為語言和開發(fā)環(huán)境提供支持的,在這個(gè)層次中可以看到AOP框架的高層實(shí)現(xiàn),主要包括配置和編織實(shí)現(xiàn)兩部分內(nèi)容。例如配置邏輯和編織邏輯實(shí)現(xiàn)本身,以及對這些實(shí)現(xiàn)進(jìn)行抽象的一些高層API封裝。這些實(shí)現(xiàn)和API封裝,為前面提到的語言和開發(fā)環(huán)境的實(shí)現(xiàn)提供了有力的支持。

最底層是編織的具體實(shí)現(xiàn)模塊,圖3-1中的各種技術(shù)都可以作為編織邏輯的具體實(shí)現(xiàn)方法,比如反射、程序預(yù)處理、攔截器框架、類裝載器框架、元數(shù)據(jù)處理等。閱讀完本章對Spring AOP實(shí)現(xiàn)原理的分析,我們可以了解到,在Spring AOP中,使用的是Java本身的語言特性,如Java Proxy代理類、攔截器等技術(shù),來完成AOP編織的實(shí)現(xiàn)。

對Spring平臺或者說生態(tài)系統(tǒng)來說,AOP是Spring框架的核心功能模塊之一。AOP與IoC容器的結(jié)合使用, 為應(yīng)用開發(fā)或Spring自身功能的擴(kuò)展都提供了許多便利。Spring AOP的實(shí)現(xiàn)和其他特性的實(shí)現(xiàn)一樣,除了可以使用Spring本身提供的AOP實(shí)現(xiàn)之外,還封裝了業(yè)界優(yōu)秀的AOP解決方案AspectJ來供應(yīng)用使用。本章主要對Spring自身的AOP實(shí)現(xiàn)原理進(jìn)行分析。在這個(gè)AOP實(shí)現(xiàn)中,Spring充分利用了IoC容器Proxy代理對象以及AOP攔截器的功能特性,通過這些對AOP基本功能的封裝機(jī)制,為用戶提供了AOP的實(shí)現(xiàn)框架。因此,要了解這些AOP的基本實(shí)現(xiàn),需要對Java的Proxy機(jī)制有一些基本了解。在Spring中,有一些相關(guān)的概念與AOP設(shè)計(jì)相對應(yīng)。本章將按照筆者個(gè)人的理解,結(jié)合Spring的AOP實(shí)現(xiàn),先簡單地回顧一些相關(guān)的AOP概念,然后逐步展開對AOP實(shí)現(xiàn)原理的分析,通過對實(shí)現(xiàn)原理的分析來了解Spring AOP模塊,在這些實(shí)現(xiàn)原理的分析中,包括代理對象的生成、AOP攔截器的實(shí)現(xiàn)等。在分析中,以ProxyFactoryBean和ProxyFactory為例進(jìn)行說明。

3.1.2 Advice通知

Advice(通知)定義在連接點(diǎn)做什么,為切面增強(qiáng)提供織入接口。在Spring AOP中,它主要描述Spring AOP圍繞方法調(diào)用而注入的切面行為。Advice是AOP聯(lián)盟定義的一個(gè)接口,具體的接口定義在org.aopalliance.aop.Advice中。在Spring AOP的實(shí)現(xiàn)中,使用了這個(gè)統(tǒng)一接口,并通過這個(gè)接口,為AOP切面增強(qiáng)的織入功能做了更多的細(xì)化和擴(kuò)展,比如提供了更具體的通知類型,如BeforeAdvice、AfterAdvice、ThrowsAdvice等。作為Spring AOP定義的接口類,具體的切面增強(qiáng)可以通過這些接口集成到AOP框架中去發(fā)揮作用。對于這些接口類,下面會逐個(gè)進(jìn)行詳細(xì)討論,我們從接口BeforeAdvice開始,首先了解它的類層次關(guān)系,如圖3-2所示。

圖3-2 BeforeAdvice的類層次關(guān)系

在BeforeAdvice的繼承關(guān)系中,定義了為待增強(qiáng)的目標(biāo)方法設(shè)置的前置增強(qiáng)接口MethodBeforeAdvice,使用這個(gè)前置接口需要實(shí)現(xiàn)一個(gè)回調(diào)函數(shù):

        void before(Method method, Object[] args, Object target) throws Throwable;

作為回調(diào)函數(shù),before方法的實(shí)現(xiàn)在Advice中被配置到目標(biāo)方法后,會在調(diào)用目標(biāo)方法時(shí)被回調(diào)。具體的調(diào)用參數(shù)有:Method對象,這個(gè)參數(shù)是目標(biāo)方法的反射對象;Object[]對象數(shù)組,這個(gè)對象數(shù)組中包含目標(biāo)方法的輸入?yún)?shù)。以CountingBeforeAdvice為例來說明BeforeAdvice的具體使用,CountingBeforeAdvice是接口MethodBeforeAdvice的具體實(shí)現(xiàn),如代碼清單3-1所示。可以看到,它的實(shí)現(xiàn)比較簡單,完成的工作是統(tǒng)計(jì)被調(diào)用的方法次數(shù)。作為切面增強(qiáng)實(shí)現(xiàn),它會根據(jù)調(diào)用方法的方法名進(jìn)行統(tǒng)計(jì),把統(tǒng)計(jì)結(jié)果根據(jù)方法名和調(diào)用次數(shù)作為鍵值對放入一個(gè)map中。

代碼清單3-1 CountingBeforeAdvice的實(shí)現(xiàn)

        public class CountingBeforeAdvice extends MethodCounter implements MethodBeforeAdvice {
        //實(shí)現(xiàn)before回調(diào)接口,這是接口MethodBeforeAdvice的要求
        public void before(Method m, Object[] args, Object target) throws Throwable {
            count(m);
            }
        }

這里調(diào)用了count方法,使用了目標(biāo)方法的反射對象作為參數(shù),完成對調(diào)用方法名的統(tǒng)計(jì)工作。count方法在CountingBeforeAdvice的基類MethodCounter中實(shí)現(xiàn),如代碼清單3-2所示。這個(gè)切面增強(qiáng)完成的統(tǒng)計(jì)實(shí)現(xiàn)并不復(fù)雜,它在對象中維護(hù)一個(gè)哈希表,用來存儲統(tǒng)計(jì)數(shù)據(jù)。在統(tǒng)計(jì)過程中,首先通過目標(biāo)方法的反射對象得到方法名,然后進(jìn)行累加,把統(tǒng)計(jì)結(jié)果放到維護(hù)的哈希表中。如果需要統(tǒng)計(jì)數(shù)據(jù),就到這個(gè)哈希表中根據(jù)key來獲取。

代碼清單3-2 MethodCounter實(shí)現(xiàn)統(tǒng)計(jì)目標(biāo)方法調(diào)用次數(shù)

        public class MethodCounter implements Serializable {
        /* 這個(gè)HashMap用來存儲方法名和調(diào)用次數(shù)的鍵值對 */
        private HashMap<String, Integer> map = new HashMap<String, Integer>();
        //所有的調(diào)用次數(shù),不管是什么方法名
        private int allCount;
        //CountingBeforeAdvice的調(diào)用入口
        protected void count(Method m) {
            count(m.getName());
        }
        //根據(jù)目標(biāo)方法的方法名統(tǒng)計(jì)調(diào)用次數(shù)
        protected void count(String methodName) {
            Integer i = map.get(methodName);
            i = (i != null) ? new Integer(i.intValue() + 1) : new Integer(1);
            map.put(methodName, i);
            ++allCount;
        }
        //根據(jù)方法名取得調(diào)用的次數(shù)
        public int getCalls(String methodName) {
            Integer i = map.get(methodName);
            return (i != null ? i.intValue() : 0);
        }
            //取得所有的方法調(diào)用次數(shù)
        public int getCalls() {
            return allCount;
        }
        public boolean equals(Object other) {
            return (other != null && other.getClass() == this.getClass());
        }
        public int hashCode() {
            return getClass().hashCode();
        }
        }

在Advice的實(shí)現(xiàn)體系中,Spring還提供了AfterAdvice這種通知類型,它的類接口關(guān)系如圖3-3所示。

圖3-3 AfterAdvice的接口關(guān)系

在圖3-3所示的AfterAdvice類接口關(guān)系中,可以看到一系列對AfterAdvice的實(shí)現(xiàn)和接口擴(kuò)展,比如AfterReturningAdvice就是其中比較常用的一個(gè)。在這里,以AfterReturning-Advice通知的實(shí)現(xiàn)為例,分析一下AfterAdvice通知類型的實(shí)現(xiàn)原理。在AfterReturning-Advice接口中定義了接口方法,如下所示:

        void afterReturning(Object returnValue, Method method, Object[] args, Object
        target) throws Throwable;

afterReturning方法也是一個(gè)回調(diào)函數(shù),AOP應(yīng)用需要在這個(gè)接口實(shí)現(xiàn)中提供切面增強(qiáng)的具體設(shè)計(jì),在這個(gè)Advice通知被正確配置以后,在目標(biāo)方法調(diào)用結(jié)束并成功返回的時(shí)候,接口會被Spring AOP回調(diào)。對于回調(diào)參數(shù),有目標(biāo)方法的返回結(jié)果、反射對象以及調(diào)用參數(shù)(AOP把這些參數(shù)都封裝在一個(gè)對象數(shù)組中傳遞進(jìn)來)等。與前面分析BeforeAdvice一樣,在Spring AOP的包中,同樣可以看到一個(gè)CountingAfterReturningAdvice,作為熟悉AfterReturningAdvice使用的例子,它的實(shí)現(xiàn)基本上與CountingBeforeAdvice是一樣的,如代碼清單3-3所示。

代碼清單3-3 CountingAfterReturningAdvice的實(shí)現(xiàn)

        public class CountingAfterReturningAdvice extends MethodCounter implements AfterReturningAdvice {
        public void afterReturning(Object o, Method m, Object[] args, Object target) throws Throwable {
            count(m);
            }
        }

在實(shí)現(xiàn)AfterReturningAdvice的接口方法afterReturning中,可以調(diào)用MethodCounter的count方法,從而完成根據(jù)方法名來對目標(biāo)方法調(diào)用的次數(shù)進(jìn)行統(tǒng)計(jì)。count方法調(diào)用的實(shí)現(xiàn)與前面看到的CountingBeforeAdvice基本一樣,所不同的是調(diào)用發(fā)生的時(shí)間。盡管增強(qiáng)邏輯相同,但是,如果它實(shí)現(xiàn)不同的AOP通知接口,就會被AOP編織到不同的調(diào)用場合中。盡管它們完成的增強(qiáng)行為是一樣的,都是根據(jù)目標(biāo)方法名對調(diào)用次數(shù)進(jìn)行統(tǒng)計(jì),但是它們的最終實(shí)現(xiàn)卻有很大的不同,一個(gè)是在目標(biāo)方法調(diào)用前實(shí)現(xiàn)切面增強(qiáng),一個(gè)是在目標(biāo)方法成功調(diào)用返回結(jié)果后實(shí)現(xiàn)切面增強(qiáng)。由此可見,AOP技術(shù)給應(yīng)用帶來的靈活性,使得相同的代碼完全可以根據(jù)應(yīng)用的需要靈活地出現(xiàn)在不同的應(yīng)用場合。

了解了BeforeAdvice和AfterAdvice,在Spring AOP中,還可以看到另外一種Advice通知類型,那就是ThrowsAdvice,它的類層次關(guān)系如圖3-4所示。

圖3-4 ThrowsAdvice的類層次關(guān)系

對于ThrowsAdvice,并沒有指定需要實(shí)現(xiàn)的接口方法,它在拋出異常時(shí)被回調(diào),這個(gè)回調(diào)是AOP使用反射機(jī)制來完成的。可以通過已經(jīng)很熟悉的CountingThrowsAdvice來了解ThrowsAdvice的使用方法,如代碼清單3-4所示。

代碼清單3-4 CountingThrowsAdvice的實(shí)現(xiàn)

        public static class CountingThrowsAdvice extends MethodCounter implements ThrowsAdvice {
            public void afterThrowing(IOException ex) throws Throwable {
                count(IOException.class.getName());
            }
            public void afterThrowing(UncheckedException ex) throws Throwable {
                count(UncheckedException.class.getName());
            }
        }

在afterThrowing方法中,從輸入的異常對象中得到異常的名字并進(jìn)行統(tǒng)計(jì)。這個(gè)count方法同樣是在MethodCounter中實(shí)現(xiàn)的,與前面看到的兩個(gè)Advice的實(shí)現(xiàn)相同,只是前面的CountingBeforeAdvice和CountingAfterReturningAdvice統(tǒng)計(jì)的是目標(biāo)方法的調(diào)用次數(shù),在這里,count方法完成的是根據(jù)異常名稱統(tǒng)計(jì)拋出異常的次數(shù)。

3.1.3 Pointcut切點(diǎn)

Pointcut(切點(diǎn))決定Advice通知應(yīng)該作用于哪個(gè)連接點(diǎn),也就是說通過Pointcut來定義需要增強(qiáng)的方法的集合,這些集合的選取可以按照一定的規(guī)則來完成。在這種情況下,Pointcut通常意味著標(biāo)識方法,例如,這些需要增強(qiáng)的地方可以由某個(gè)正則表達(dá)式進(jìn)行標(biāo)識,或根據(jù)某個(gè)方法名進(jìn)行匹配等。

為了方便用戶使用,Spring AOP提供了具體的切點(diǎn)供用戶使用,切點(diǎn)在Spring AOP中的類繼承體系如圖3-5所示。

圖3-5 切點(diǎn)在Spring AOP中的類繼承體系

從源代碼實(shí)現(xiàn)上同樣可以得到相應(yīng)的Spring AOP的Pointcut設(shè)計(jì),如圖3-6所示。

圖3-6 Spring AOP的Pointcut類繼承關(guān)系

在Pointcut的基本接口定義中可以看到,需要返回一個(gè)MethodMatcher。對于Point的匹配判斷功能,具體是由這個(gè)返回的MethodMatcher來完成的,也就是說,由這個(gè)MethodMatcher來判斷是否需要對當(dāng)前方法調(diào)用進(jìn)行增強(qiáng),或者是否需要對當(dāng)前調(diào)用方法應(yīng)用配置好的Advice通知。在Pointcut的類繼承關(guān)系中,以正則表達(dá)式切點(diǎn)JdkRegexpMethodPointcut的實(shí)現(xiàn)原理為例,來具體了解切點(diǎn)Pointcut的工作原理。JdkRegexpMethodPointcut類完成通過正則表達(dá)式對方法名進(jìn)行匹配的功能。在JdkRegexpMethodPointcut的基類StaticMethod-MatcherPointcut的實(shí)現(xiàn)中可以看到,設(shè)置MethodMatcher為StaticMethodMatcher,同時(shí)JdkRegexpMethodPointcut也是這個(gè)MethodMatcher的子類,它的類層次關(guān)系如圖3-7所示。

圖3-7 StaticMethodMatcher的類層次關(guān)系

可以看到,在Pointcut中,通過這樣的類繼承關(guān)系,MethodMatcher對象實(shí)際上是可以被配置成JdkRegexpMethodPointcut來完成方法的匹配判斷的。在JdkRegexpMethodPointcut中,可以看到一個(gè)matches方法,這個(gè)matches方法是MethodMatcher定義的接口方法。在JdkRegexpMethodPointcut的實(shí)現(xiàn)中,這個(gè)matches方法就是使用正則表達(dá)式來對方法名進(jìn)行匹配的地方。關(guān)于在AOP框架中對matches方法的調(diào)用,會在下面的Spring AOP實(shí)現(xiàn)中介紹,這里只是先簡單提一下。要了解matches在AOP框架中的調(diào)用位置,比較簡單的方法就是以matches方法作為起始點(diǎn),對它的方法調(diào)用關(guān)系進(jìn)行追溯,可以看到對matches方法的調(diào)用關(guān)系如圖3-8所示。

圖3-8 對JdkRegexpMethodPointcut的matches方法的調(diào)用關(guān)系

在對matches方法的調(diào)用關(guān)系中可以看到,是在JdkDynamicAopProxy的invoke方法中觸發(fā)了對matches方法的調(diào)用。很明顯,熟悉Proxy使用的讀者一定會想到,這個(gè)invoke方法應(yīng)該就是Proxy對象進(jìn)行代理回調(diào)的入口方法,這個(gè)invoke回調(diào)的實(shí)現(xiàn)是使用JDK動(dòng)態(tài)代理完成AOP功能的一部分,關(guān)于這部分的實(shí)現(xiàn)原理,在下面AOP的實(shí)現(xiàn)分析中有詳細(xì)的闡述,這里就不進(jìn)行太多的說明了。這里重點(diǎn)了解Pointcut的實(shí)現(xiàn)原理,比如matches本身的實(shí)現(xiàn)。JdkRegexpMethodPointcut的matches方法的實(shí)現(xiàn)如代碼清單3-5所示。

代碼清單3-5 JdkRegexpMethodPointcut使用matches完成匹配

        protected boolean matches(String pattern, int patternIndex) {
            Matcher matcher = this.compiledPatterns[patternIndex].matcher(pattern);
            return matcher.matches();
        }

在JdkRegexpMethodPointcut中,通過JDK來實(shí)現(xiàn)正則表達(dá)式的匹配,這在代碼清單3-5中可以看得很清楚。如果要詳細(xì)了解使用JDK的正則表達(dá)式匹配功能,可以參考JDK的API文檔。接下來看看其他的Pointcut。

在Spring AOP中,還提供了其他的MethodPointcut,比如通過方法名匹配進(jìn)行Advice匹配的NameMatchMethodPointcut。它的matches方法實(shí)現(xiàn)很簡單,匹配的條件是方法名相同或者方法名相匹配,如代碼清單3-6所示。

代碼清單3-6 NameMatchMethodPointcut的matches

        public boolean matches(Method method, Class targetClass) {
            for (String mappedName : this.mappedNames) {
                if (mappedName.equals(method.getName()) || isMatch(method.getName(), mappedName)) {
                    return true;
                }
            }
            return false;
        }
        protected boolean isMatch(String methodName, String mappedName) {
            return PatternMatchUtils.simpleMatch(mappedName, methodName);
        }

3.1.4 Advisor通知器

完成對目標(biāo)方法的切面增強(qiáng)設(shè)計(jì)(Advice)和關(guān)注點(diǎn)的設(shè)計(jì)(Pointcut)以后,需要一個(gè)對象把它們結(jié)合起來,完成這個(gè)作用的就是Advisor(通知器)。通過Advisor,可以定義應(yīng)該使用哪個(gè)通知并在哪個(gè)關(guān)注點(diǎn)使用它,也就是說通過Advisor,把Advice和Pointcut結(jié)合起來,這個(gè)結(jié)合為使用IoC容器配置AOP應(yīng)用,或者說即開即用地使用AOP基礎(chǔ)設(shè)施,提供了便利。在Spring AOP中,我們以一個(gè)Advisor的實(shí)現(xiàn)(DefaultPointcutAdvisor)為例,來了解Advisor的工作原理。在DefaultPointcutAdvisor中,有兩個(gè)屬性,分別是advice和pointcut。通過這兩個(gè)屬性,可以分別配置Advice和Pointcut,DefaultPointcutAdvisor的實(shí)現(xiàn)如代碼清單3-7所示。

代碼清單3-7 DefaultPointcutAdvisor的實(shí)現(xiàn)

        public class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor implements
        Serializable {
            private Pointcut pointcut = Pointcut.TRUE;
            public DefaultPointcutAdvisor() {
            }
            public DefaultPointcutAdvisor(Advice advice) {
                this(Pointcut.TRUE, advice);
            }
            public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) {
                this.pointcut = pointcut;
                setAdvice(advice);
            }
            public void setPointcut(Pointcut pointcut) {
                this.pointcut = (pointcut != null ? pointcut : Pointcut.TRUE);
            }
            public Pointcut getPointcut() {
                return this.pointcut;
            }
            public String toString() {
                return getClass().getName() + ": pointcut [" + getPointcut() + "]; advice [" +
                getAdvice() + "]";
            }
        }

在DefaultPointcutAdvisor中,pointcut默認(rèn)被設(shè)置為Pointcut.True,這個(gè)Pointcut.True在Pointcut接口中被定義為:

        Pointcut TRUE = TruePointcut.INSTANCE;

TruePointcut的INSTANCE是一個(gè)單件。在它的實(shí)現(xiàn)中,可以看到單件模式的具體應(yīng)用和典型使用方法,比如使用static類變量來持有單件實(shí)例,使用private私有構(gòu)造函數(shù)來確保除了在當(dāng)前單件實(shí)現(xiàn)中,單件不會被再次創(chuàng)建和實(shí)例化,從而保證它的“單件”特性。在TruePointcut的methodMatcher實(shí)現(xiàn)中,使用TrueMethodMatcher作為方法匹配器。這個(gè)方法匹配器對任何的方法匹配都要求返回true的結(jié)果,也就是說對任何方法名的匹配要求,它都會返回匹配成功的結(jié)果。和TruePointcut一樣,TrueMethodMatcher也是一個(gè)單件實(shí)現(xiàn)。

TruePointcut和TrueMethodMatcher的實(shí)現(xiàn)如代碼清單3-8和代碼清單3-9所示。

代碼清單3-8 TruePointcut的實(shí)現(xiàn)

        class TruePointcut implements Pointcut, Serializable {
        public static final TruePointcut INSTANCE = new TruePointcut();
        //這里是單件模式的實(shí)現(xiàn)特點(diǎn),設(shè)置私有的構(gòu)造函數(shù),使其不能直接被實(shí)例化,
        //并設(shè)置一個(gè)靜態(tài)的類變量來保證該實(shí)例是唯一的
        private TruePointcut() {
        }
        public ClassFilter getClassFilter() {
            return ClassFilter.TRUE;
        }
        public MethodMatcher getMethodMatcher() {
            return MethodMatcher.TRUE;
        }

代碼清單3-9 TrueMethodMatcher的實(shí)現(xiàn)

        class TrueMethodMatcher implements MethodMatcher, Serializable {
        public static final TrueMethodMatcher INSTANCE = new TrueMethodMatcher();
        private TrueMethodMatcher() {
        }
        public boolean isRuntime() {
            return false;
        }
        public boolean matches(Method method, Class targetClass) {
            return true;
        }
        public boolean matches(Method method, Class targetClass, Object[] args) {
            throw new UnsupportedOperationException();
        }
        private Object readResolve() {
            return INSTANCE;
        }
        public String toString() {
            return "MethodMatcher.TRUE";
        }
        }
主站蜘蛛池模板: 金溪县| 诸暨市| 天长市| 垫江县| 德格县| 赤壁市| 隆尧县| 通化市| 丹巴县| 涡阳县| 平乡县| 安化县| 长垣县| 木里| 三亚市| 鹤山市| 合肥市| 青阳县| 阿巴嘎旗| 漳州市| 汝城县| 修水县| 沈阳市| 孟连| 伊宁市| 玛纳斯县| 遵义市| 龙州县| 平江县| 漳州市| 乌拉特后旗| 深水埗区| 平武县| 安宁市| 东兴市| 苏州市| 武汉市| 定兴县| 庄河市| 图木舒克市| 太仓市|