天天看點

Spring之AOP由淺入深

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)建立一個接口和實作類

Spring之AOP由淺入深
Spring之AOP由淺入深

(2)編寫前置增強和後置增強(這裡我将兩個增強合并,即實作兩個接口)

Spring之AOP由淺入深

(3)JUnit來測試

Spring之AOP由淺入深
Spring之AOP由淺入深

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

  環繞增強類需要實作 org.aopalliance.intercept.MethodInterceptor 接口。注意,這個接口不是 Spring 提供的,它是 AOP 聯盟寫的,Spring 隻是借用了它。

Spring之AOP由淺入深

之後再JUnit中添加

Spring之AOP由淺入深

6. 聲明式增強

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

(1)Spring配置檔案(增強類為環繞增強)

Spring之AOP由淺入深

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

Spring之AOP由淺入深
Spring之AOP由淺入深

(3)JUnit測試

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

Spring之AOP由淺入深

得到結果

 7.Introduction Advice(引入增強)

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

(1)定義一個新接口Love

Spring之AOP由淺入深

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

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

Spring之AOP由淺入深

配置如下:

Spring之AOP由淺入深

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

Spring之AOP由淺入深
Spring之AOP由淺入深

注意:這裡的Love love = (Love)greetingImpl 是<code>将目标類強制向上轉型為Love接口,</code>這<code>就是</code>引入增強(DelegatingIntroductionInterceptor)的特性--<code>“接口動态實作”功能</code>。是以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()不攔截。<code></code><code></code>

Spring之AOP由淺入深
Spring之AOP由淺入深

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

JUnit測試:

Spring之AOP由淺入深
Spring之AOP由淺入深

 11.AOP自動代理

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

Spring之AOP由淺入深

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

Spring之AOP由淺入深

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

Spring之AOP由淺入深

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

Spring之AOP由淺入深

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

12. AspectJ execution 表達式攔截

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

Spring之AOP由淺入深

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

Spring之AOP由淺入深

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

Spring之AOP由淺入深

實作類GreetingImpl:

Spring之AOP由淺入深

P.s 如果将切面類裡的切點從原來的實作類GreetingImpl改為接口Greeting又會發生什麼呢?

Spring之AOP由淺入深

改為:

Spring之AOP由淺入深

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

Spring之AOP由淺入深

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

  定義一個切面類AroundAspect:value屬性指定了哪種類型的bean要引入該接口。defaultImpl屬性指定了為引入功能提供實作的類,@DeclareParents注解所标注的屬性指明要引入的接口。

Spring之AOP由淺入深

LoveImpl實作類:将這個實作類引入目标類GreetingImpl中,就能使用display方法。

Spring之AOP由淺入深

注意:在ApplicationContext中擷取的greetingImpl對象是個代理對象,可轉型為自己靜态實作的接口Greeting,也可以轉型為自己動态實作的接口Love,可随意切換。現在的AspectJ的引入增強跟上面的SpringAOP的引入增強隻能面向實作類相比,還可面向接口程式設計。是以有兩種方式實作:

Spring之AOP由淺入深
Spring之AOP由淺入深

控制台輸出:

Spring之AOP由淺入深

而對于SpringAOP引入的增強,則隻能面向實作類:

Spring之AOP由淺入深

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:

Spring之AOP由淺入深

我們發現原來的兩條配置都可以删除,但是要注意,沒有顯式配置&lt;aop:aspectj-autoproxy/&gt;不代表不使用自動代理,這條配置預設屬性為“false”,表示隻代理接口(JDK動态代理),是以如果隻想代理接口,可以不用顯式寫出。

Spring之AOP由淺入深

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

Spring之AOP由淺入深

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

Spring之AOP由淺入深

16. END

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

作者:六月的餘晖

出處:http://www.cnblogs.com/zhaozihan/

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連結,否則保留追究法律責任的權利。