前言
只有光頭才能變強
上一篇已經講解了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。為了大家方便,剛新建了一下qq群:742919422,大家也可以去交流交流。謝謝支持了!希望能多介紹給其他有需要的朋友
文章的目錄導航:
