前言
只有光頭才能變強
上一篇已經講解了Spring IOC知識點一網打盡!,這篇主要是講解Spring的AOP模組~
之前我已經寫過一篇關於AOP的文章了,那篇把比較重要的知識點都講解過了一篇啦:Spring【AOP模組】就這麼簡單,很榮幸被開源中國推薦過~~
- 如果沒有AOP的基礎,建議先看看上面那篇文章~
- 如果沒有代理模式基礎,建議先看看:給女朋友講解什麼是代理模式這篇文章
- 如果都看過了,這篇就放心食用吧!
這篇文章主要是補充和強化一些比較重要的知識點,並會把上面的兩本書關於AOP的知識點整理出來並畫成一個思維導圖來全面瞭解Spring AOP的知識點!
那麼接下來就開始吧,如果有錯的地方希望能多多包涵,並不吝在評論區指正!
一、Spring AOP全面認知
結合《Spring 實戰 (第4版)》和《精通Spring4.x 企業應用開發實戰》兩本書的AOP章節將其知識點整理起來~
1.1AOP概述
AOP稱為面向切面程式設計,那我們怎麼理解面向切面程式設計??
我們可以先看看下面這段程式碼:
我們學Java物件導向的時候,如果程式碼重複了怎麼辦啊??可以分成下面幾個步驟:
- 1:抽取成方法
- 2:抽取類
抽取成類的方式我們稱之為:縱向抽取
- 通過繼承的方式實現縱向抽取
但是,我們現在的辦法不行:即使抽取成類還是會出現重複的程式碼,因為這些邏輯(開始、結束、提交事務)依附在我們業務類的方法邏輯中!
現在縱向抽取的方式不行了,AOP的理念:就是將分散在各個業務邏輯程式碼中相同的程式碼通過橫向切割的方式抽取到一個獨立的模組中!
上面的圖也很清晰了,將重複性的邏輯程式碼橫切出來其實很容易(我們簡單可認為就是封裝成一個類就好了),但我們要將這些被我們橫切出來的邏輯程式碼融合到業務邏輯中,來完成和之前(沒抽取前)一樣的功能!這就是AOP首要解決的問題了!
1.2Spring AOP原理
被我們橫切出來的邏輯程式碼融合到業務邏輯中,來完成和之前(沒抽取前)一樣的功能
沒有學Spring AOP之前,我們就可以使用代理來完成。
- 如果看過我寫的給女朋友講解什麼是代理模式這篇文章的話,一定就不難理解上面我說的那句話了
- 代理能幹嘛?代理可以幫我們增強物件的行為!使用動態代理實質上就是呼叫時攔截物件方法,對方法進行改造、增強!
其實Spring AOP的底層原理就是動態代理!
來源《精通Spring4.x 企業應用開發實戰》一段話:
Spring AOP使用純Java實現,它不需要專門的編譯過程,也不需要特殊的類裝載器,它在執行期通過代理方式向目標類織入增強程式碼。在Spring中可以無縫地將Spring AOP、IoC和AspectJ整合在一起。
來源《Spring 實戰 (第4版)》一句話:
Spring AOP構建在動態代理基礎之上,因此,Spring對AOP的支援侷限於方法攔截。
在Java中動態代理有兩種方式:
- JDK動態代理
- CGLib動態代理
JDK動態代理是需要實現某個介面了,而我們類未必全部會有介面,於是CGLib代理就有了~~
- CGLib代理其生成的動態代理物件是目標類的子類
- Spring AOP預設是使用JDK動態代理,如果代理的類沒有介面則會使用CGLib代理。
那麼JDK代理和CGLib代理我們該用哪個呢??在《精通Spring4.x 企業應用開發實戰》給出了建議:
- 如果是單例的我們最好使用CGLib代理,如果是多例的我們最好使用JDK代理
原因:
- JDK在建立代理物件時的效能要高於CGLib代理,而生成代理物件的執行效能卻比CGLib的低。
- 如果是單例的代理,推薦使用CGLib
看到這裡我們就應該知道什麼是Spring AOP(面向切面程式設計)了:將相同邏輯的重複程式碼橫向抽取出來,使用動態代理技術將這些重複程式碼織入到目標物件方法中,實現和原來一樣的功能。
- 這樣一來,我們就在寫業務時只關心業務程式碼,而不用關心與業務無關的程式碼
1.3AOP的實現者
AOP除了有Spring AOP實現外,還有著名的AOP實現者:AspectJ,也有可能大家沒聽說過的實現者:JBoss AOP~~
我們下面來說說AspectJ擴充套件一下知識面:
AspectJ是語言級別的AOP實現,擴充套件了Java語言,定義了AOP語法,能夠在編譯期提供橫切程式碼的織入,所以它有專門的編譯器用來生成遵守Java位元組碼規範的Class檔案。
而Spring借鑑了AspectJ很多非常有用的做法,融合了AspectJ實現AOP的功能。但Spring AOP本質上底層還是動態代理,所以Spring AOP是不需要有專門的編輯器的~
1.4AOP的術語
嗯,AOP搞了好幾個術語出來~~兩本書都有講解這些術語,我會盡量讓大家看得明白的:
連線點(Join point):
- 能夠被攔截的地方:Spring AOP是基於動態代理的,所以是方法攔截的。每個成員方法都可以稱之為連線點~
切點(Poincut):
- 具體定位的連線點:上面也說了,每個方法都可以稱之為連線點,我們具體定位到某一個方法就成為切點。
增強/通知(Advice):
- 表示新增到切點的一段邏輯程式碼,並定位連線點的方位資訊。
- 簡單來說就定義了是幹什麼的,具體是在哪幹
- Spring AOP提供了5種Advice型別給我們:前置、後置、返回、異常、環繞給我們使用!
織入(Weaving):
- 將
增強/通知
新增到目標類的具體連線點上的過程。
引入/引介(Introduction):
引入/引介
允許我們向現有的類新增新方法或屬性。是一種特殊的增強!
切面(Aspect):
- 切面由切點和
增強/通知
組成,它既包括了橫切邏輯的定義、也包括了連線點的定義。
在《Spring 實戰 (第4版)》給出的總結是這樣子的:
通知/增強包含了需要用於多個應用物件的橫切行為;連線點是程式執行過程中能夠應用通知的所有點;切點定義了通知/增強被應用的具體位置。其中關鍵的是切點定義了哪些連線點會得到通知/增強。
總的來說:
- 這些術語可能翻譯過來不太好理解,但對我們正常使用AOP的話影響並沒有那麼大~~看多了就知道它是什麼意思了。
1.5Spring對AOP的支援
Spring提供了3種型別的AOP支援:
- 基於代理的經典SpringAOP
- 需要實現介面,手動建立代理
- 純POJO切面
- 使用XML配置,aop名稱空間
@AspectJ
註解驅動的切面- 使用註解的方式,這是最簡潔和最方便的!
二、基於代理的經典SpringAOP
這部分配置比較麻煩,用起來也很麻煩,這裡我就主要整理一下書上的內容,大家看看了解一下吧,我們實際上使用Spring AOP基本不用這種方式了!
首先,我們來看一下增強介面的繼承關係圖:
可以分成五類增強的方式:
Spring提供了六種的切點型別:
切面型別主要分成了三種:
- 一般切面
- 切點切面
- 引介/引入切面
一般切面,切點切面,引介/引入切面介紹:
對於切點切面我們一般都是直接用就好了,我們來看看引介/引入切面是怎麼一回事:
- 引介/引入切面是引介/引入增強的封裝器,通過引介/引入切面,可以更容易地為現有物件新增任何介面的實現!
繼承關係圖:
引介/引入切面有兩個實現類:
- DefaultIntroductionAdvisor:常用的實現類
- DeclareParentsAdvisor:用於實現AspectJ語言的DeclareParent註解表示的引介/引入切面
實際上,我們使用AOP往往是Spring內部使用BeanPostProcessor幫我們建立代理。
這些代理的建立器可以分成三類:
- 基於Bean配置名規則的自動代理建立器:BeanNameAutoProxyCreator
- 基於Advisor匹配機制的自動代理建立器:它會對容器所有的Advisor進行掃描,實現類為DefaultAdvisorAutoProxyCreator
- 基於Bean中的AspectJ註解標籤的自動代理建立器:AnnotationAwareAspectJAutoProxyCreator
對應的類繼承圖:
嗯,基於代理的經典SpringAOP就講到這裡吧,其實我是不太願意去寫這個的,因為已經幾乎不用了,在《Spring 實戰 第4版》也沒有這部分的知識點了。
- 但是通過這部分的知識點可以更加全面地認識Spring AOP的各種介面吧~
三、擁抱基於註解和命名空的AOP程式設計
Spring在新版本中對AOP功能進行了增強,體現在這麼幾個方面:
- 在XML配置檔案中為AOP提供了aop名稱空間
- 增加了AspectJ切點表示式語言的支援
- 可以無縫地整合AspectJ
那我們使用@AspectJ
來玩AOP的話,學什麼??其實也就是上面的內容,學如何設定切點、建立切面、增強的內容是什麼...
具體的切點表示式使用還是前往:Spring【AOP模組】就這麼簡單看吧~~
對應的增強註解:
3.1使用引介/引入功能實現為Bean引入新方法
其實前置啊、後置啊這些很容易就理解了,整篇文章看下來就只有這個引介/引入切面有點搞頭。於是我們就來玩玩吧~
我們來看一下具體的用法吧,現在我有個服務員的介面:
public interface Waiter {
// 向客人打招呼
void greetTo(String clientName);
// 服務
void serveTo(String clientName);
}
複製程式碼
一位年輕服務員實現類:
public class NaiveWaiter implements Waiter {
public void greetTo(String clientName) {
System.out.println("NaiveWaiter:greet to " + clientName + "...");
}
@NeedTest
public void serveTo(String clientName) {
System.out.println("NaiveWaiter:serving " + clientName + "...");
}
}
複製程式碼
現在我想做的就是:想這個服務員可以充當售貨員的角色,可以賣東西!當然了,我肯定不會加一個賣東西的方法到Waiter介面上啦,因為這個是暫時的~
所以,我搞了一個售貨員介面:
public interface Seller {
// 賣東西
int sell(String goods, String clientName);
}
複製程式碼
一個售貨員實現類:
public class SmartSeller implements Seller {
// 賣東西
public int sell(String goods,String clientName) {
System.out.println("SmartSeller: sell "+goods +" to "+clientName+"...");
return 100;
}
}
複製程式碼
此時,我們的類圖是這樣子的:
現在我想幹的就是:藉助AOP的引入/引介切面,來讓我們的服務員也可以賣東西!
我們的引入/引介切面具體是這樣乾的:
@Aspect
public class EnableSellerAspect {
@DeclareParents(value = "com.smart.NaiveWaiter", // 指定服務員具體的實現
defaultImpl = SmartSeller.class) // 售貨員具體的實現
public Seller seller; // 要實現的目標介面
}
複製程式碼
寫了這個切面類會發生什麼??
- 切面技術將SmartSeller融合到NaiveWaiter中,這樣NaiveWaiter就實現了Seller介面!!!!
是不是很神奇??我也覺得很神奇啊,我們來測試一下:
我們的bean.xml
檔案很簡單:
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<aop:aspectj-autoproxy/>
<bean id="waiter" class="com.smart.NaiveWaiter"/>
<bean class="com.smart.aspectj.basic.EnableSellerAspect"/>
</beans>
複製程式碼
測試一下:
public class Test {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("com/smart/aspectj/basic/beans.xml");
Waiter waiter = (Waiter) ctx.getBean("waiter");
// 呼叫服務員原有的方法
waiter.greetTo("Java3y");
waiter.serveTo("Java3y");
// 通過引介/引入切面已經將waiter服務員實現了Seller介面,所以可以強制轉換
Seller seller = (Seller) waiter;
seller.sell("水軍", "Java3y");
}
}
複製程式碼
具體的呼叫過程是這樣子的:
當引入介面方法被呼叫時,代理物件會把此呼叫委託給實現了新介面的某個其他物件。實際上,一個Bean的實現被拆分到多個類中
3.2在XML中宣告切面
我們知道註解很方便,但是,要想使用註解的方式使用Spring AOP就必須要有原始碼(因為我們要在切面類上新增註解)。如果沒有原始碼的話,我們就得使用XML來宣告切面了~
其實就跟註解差不多的功能:
我們就直接來個例子終結掉它吧:
首先我們來測試一下與傳統的SpringAOP結合的advisor是怎麼用的:
實現類:
xml配置檔案:
.......
一個一個來講解還是太花時間了,我就一次性用圖的方式來講啦:
最後還有一個切面型別總結圖,看完就幾乎懂啦:
三、總結
看起來AOP有很多很多的知識點,其實我們只要記住AOP的核心概念就行啦。
下面是我的簡要總結AOP:
- AOP的底層實際上是動態代理,動態代理分成了JDK動態代理和CGLib動態代理。如果被代理物件沒有介面,那麼就使用的是CGLIB代理(也可以直接配置使用CBLib代理)
- 如果是單例的話,那我們最好使用CGLib代理,因為CGLib代理物件執行速度要比JDK的代理物件要快
- AOP既然是基於動態代理的,那麼它只能對方法進行攔截,它的層面上是方法級別的
- 無論經典的方式、註解方式還是XML配置方式使用Spring AOP的原理都是一樣的,只不過形式變了而已。一般我們使用註解的方式使用AOP就好了。
- 註解的方式使用Spring AOP就瞭解幾個切點表示式,幾個增強/通知的註解就完事了,是不是賊簡單...使用XML的方式和註解其實沒有很大的區別,很快就可以上手啦。
- 引介/引入切面也算是一個比較亮的地方,可以用代理的方式為某個物件實現介面,從而能夠使用藉口下的方法。這種方式是非侵入式的~
- 要增強的方法還可以接收與被代理方法一樣的引數、繫結被代理方法的返回值這些功能...
最後,將我們上一次IOC的思維導圖補充AOP的知識點上去吧~~~
參考資料:
- 《Spring 實戰》
- 《精通Spring4.x 企業應用開發實戰》
如果文章有錯的地方歡迎指正,大家互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同學,可以關注微信公眾號:Java3y。
文章的目錄導航: