1.AOP的做用java
在OOP中,正是這種分散在各處且與對象核心功能無關的代碼(橫切代碼)的存在,使得模塊複用難度增長。AOP則將封裝好的對象剖開,找出其中對多個對象產生影響的公共行爲,並將其封裝爲一個可重用的模塊,這個模塊被命名爲「切面」(Aspect),切面將那些與業務無關,卻被業務模塊共同調用的邏輯提取並封裝起來,減小了系統中的重複代碼,下降了模塊間的耦合度,同時提升了系統的可維護性。正則表達式
2.DI 和 IOC 概念spring
依賴注入或控制反轉的定義中,調用者不負責被調用者的實例建立工做,該工做由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),下面先開始只使用加強,切點暫且不加入。spa
5.編程式加強3d
這裏我先用「編程式」的方法,也就是暫且不用Spring的配置文件去定義Bean對象,不把代碼中的new操做取代。
(1)建立一個接口和實現類
(2)編寫前置加強和後置加強(這裏我將兩個加強合併,即實現兩個接口)
(3)JUnit來測試
(3)環繞加強(當把兩個接口合併時,其實徹底能夠用一個接口就行)
環繞加強類須要實現 org.aopalliance.intercept.MethodInterceptor 接口。注意,這個接口不是 Spring 提供的,它是 AOP 聯盟寫的,Spring 只是借用了它。
以後再JUnit中添加
6. 聲明式加強
如今經過Spring配置文件配置bean。同時使用Bean掃描,能夠不用在配置文件中配置<bean id="..." class="..."/>.
(1)Spring配置文件(加強類爲環繞加強)
(2)在相應的實現類和加強類上添加Component註解
(3)JUnit測試
從 Context 中根據 id 獲取 Bean 對象(其實就是一個代理),調用代理的方法。
獲得結果
7.Introduction Advice(引入加強)
上面的加強僅僅是對方法加強,也就是織入,對類的加強才能叫作引入加強,好比說我不想讓GreetingImpl去直接實現Greeting接口,由於這樣的話,我就必須去實現他的方法。這時我就能靠Spring引入加強來幫我動態實現。
(1)定義一個新接口Love
(2)定義受權引入加強類
定義一個受權引入加強類,實現Love接口,用以豐富GreetingImpl類的功能,這樣GreetingImpl就能很巧妙的使用Love接口裏的方法而不用去implement。
配置以下:
proxyTargetClass屬性表示是否代理目標類,默認是false,也就是代理接口,上面一個例子的配置就是沒有這一項屬性因此用JDK動態代理,如今是true即便用CGLib動態代理。因此在測試方法中是GreetingImpl greetingImpl = (GreetingImpl)context.getBean("beans.xml"),而不會是Greeting greeting = (Greeting)context.getBean("beans.xml"),由於如今是代理目標類而不是代理接口。
(3)JUnit測試
注意:這裏的Love love = (Love)greetingImpl 是將目標類強制向上轉型爲Love接口,
這就是
引入加強(DelegatingIntroductionInterceptor)的特性--「接口動態實現」功能
。因此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()不攔截。
在上面的InterceptorNames屬性再也不是原來的加強,而是一個定義好的切面greetingAdvisor,切面裏面還用正則表達式定義了一個切點,即攔截GreetingImpl類中以good開頭的方法。
JUnit測試:
11.AOP自動代理
(1)Spring框架自動生成代理。
屬性optimize意思是對代理生成策略是否優化,true表示若是目標類有接口則代理接口(JDK動態代理),若是沒有則代理類(CGLib動態代理),這樣即可以取代前面強制代理類的proxyTargetClass屬性。
此時由於是自動代理,getBean()中的值再也不是原來代理id(greetingProxy),而是目標類GreetingImpl的Bean的id(greetingImpl),他一樣也是一個代理對象。
(2)spring根據Bean名稱來生成自動代理
beanNames屬性表明只爲bean的id後綴是「Impl」生成代理。
12. AspectJ execution 表達式攔截
定義一個切面類,實現環繞加強。@Aspect註解就不須要類再實現接口,@Around註解爲AspectJ切點表達式,參數ProceedingJoinPoint的對象即爲鏈接點,此鏈接點能夠取得方法名,參數等等。
這樣兩行配置,節約了配置大量代理和切面的時間,proxy-target-class爲true表示代理目標類。
以前的切點表達式定義了攔截類中全部方法,因此每一個方法都被加強。同時在ApplicationContext中獲取的greetingImpl代理對象,可轉型爲本身靜態實現的接口Greeting也能夠是實現類GreetingImpl。屬性proxy-target-class默認爲false,表明只代理接口,也就是說只能將代理轉型爲Greeting,而不能是GreetingImpl
實現類GreetingImpl:
P.s 若是將切面類裏的切點從原來的實現類GreetingImpl改成接口Greeting又會發生什麼呢?
改成:
結果發現,實現類中的實現接口的方法被加強了,而本身建立的good方法沒有被加強,這就是由於切點設置爲Greeting接口裏面全部方法被增強,因此實現了這個接口中的方法被加強了。
13. AspectJ @DeclareParents 註解(引入加強)
定義一個切面類AroundAspect:value屬性指定了哪一種類型的bean要引入該接口。defaultImpl屬性指定了爲引入功能提供實現的類,@DeclareParents註解所標註的屬性指明要引入的接口。
LoveImpl實現類:將這個實現類引入目標類GreetingImpl中,就能使用display方法。
JUnit測試:
注意:在ApplicationContext中獲取的greetingImpl對象是個代理對象,可轉型爲本身靜態實現的接口Greeting,也能夠轉型爲本身動態實現的接口Love,可隨意切換。如今的AspectJ的引入加強跟上面的SpringAOP的引入加強只能面向實現類相比,還可面向接口編程。因此有兩種方式實現:
控制檯輸出:
而對於SpringAOP引入的加強,則只能面向實現類:
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:
咱們發現原來的兩條配置均可以刪除,可是要注意,沒有顯式配置<aop:aspectj-autoproxy/>不表明不使用自動代理,這條配置默認屬性爲「false」,表示只代理接口(JDK動態代理),因此若是隻想代理接口,能夠不用顯式寫出。
若是想要使用CGLib動態代理,則增長
這時又能夠代理目標類了:
16. END
這篇文章是我對《Spring實戰》和《AOP那點事兒》的一些知識的整理和例子的實現,但願你也能一塊兒實現一下,若是你以爲還不錯的話,請點個贊或關注我,之後會有更多的知識分享!