前言
只有光頭才能變強(qiáng)
上一篇已經(jīng)講解了Spring IOC知識點(diǎn)一網(wǎng)打盡!,這篇主要是講解Spring的AOP模塊~
之前我已經(jīng)寫過一篇關(guān)于AOP的文章了,那篇把比較重要的知識點(diǎn)都講解過了一篇啦:Spring【AOP模塊】就這么簡單,很榮幸被開源中國推薦過~~
- 如果沒有AOP的基礎(chǔ),建議先看看上面那篇文章~
- 如果沒有代理模式基礎(chǔ),建議先看看:給女朋友講解什么是代理模式這篇文章
- 如果都看過了,這篇就放心食用吧!
這篇文章主要是補(bǔ)充和強(qiáng)化一些比較重要的知識點(diǎn),并會把上面的兩本書關(guān)于AOP的知識點(diǎn)整理出來并畫成一個思維導(dǎo)圖來全面了解Spring AOP的知識點(diǎn)!
那么接下來就開始吧,如果有錯的地方希望能多多包涵,并不吝在評論區(qū)指正!
一、Spring AOP全面認(rèn)知
結(jié)合《Spring 實(shí)戰(zhàn) (第4版)》和《精通Spring4.x 企業(yè)應(yīng)用開發(fā)實(shí)戰(zhàn)》兩本書的AOP章節(jié)將其知識點(diǎn)整理起來~
1.1AOP概述
AOP稱為面向切面編程,那我們怎么理解面向切面編程??
我們可以先看看下面這段代碼:
我們學(xué)Java面向?qū)ο蟮臅r候,如果代碼重復(fù)了怎么辦啊??可以分成下面幾個步驟:
- 1:抽取成方法
- 2:抽取類
抽取成類的方式我們稱之為:縱向抽取
- 通過繼承的方式實(shí)現(xiàn)縱向抽取
但是,我們現(xiàn)在的辦法不行:即使抽取成類還是會出現(xiàn)重復(fù)的代碼,因?yàn)檫@些邏輯(開始、結(jié)束、提交事務(wù))依附在我們業(yè)務(wù)類的方法邏輯中!
現(xiàn)在縱向抽取的方式不行了,AOP的理念:就是將分散在各個業(yè)務(wù)邏輯代碼中相同的代碼通過橫向切割的方式抽取到一個獨(dú)立的模塊中!
上面的圖也很清晰了,將重復(fù)性的邏輯代碼橫切出來其實(shí)很容易(我們簡單可認(rèn)為就是封裝成一個類就好了),但我們要將這些被我們橫切出來的邏輯代碼融合到業(yè)務(wù)邏輯中,來完成和之前(沒抽取前)一樣的功能!這就是AOP首要解決的問題了!
1.2Spring AOP原理
被我們橫切出來的邏輯代碼融合到業(yè)務(wù)邏輯中,來完成和之前(沒抽取前)一樣的功能
沒有學(xué)Spring AOP之前,我們就可以使用代理來完成。
- 如果看過我寫的給女朋友講解什么是代理模式這篇文章的話,一定就不難理解上面我說的那句話了
- 代理能干嘛?代理可以幫我們增強(qiáng)對象的行為!使用動態(tài)代理實(shí)質(zhì)上就是調(diào)用時攔截對象方法,對方法進(jìn)行改造、增強(qiáng)!
其實(shí)Spring AOP的底層原理就是動態(tài)代理!
來源《精通Spring4.x 企業(yè)應(yīng)用開發(fā)實(shí)戰(zhàn)》一段話:
Spring AOP使用純Java實(shí)現(xiàn),它不需要專門的編譯過程,也不需要特殊的類裝載器,它在運(yùn)行期通過代理方式向目標(biāo)類織入增強(qiáng)代碼。在Spring中可以無縫地將Spring AOP、IoC和AspectJ整合在一起。
來源《Spring 實(shí)戰(zhàn) (第4版)》一句話:
Spring AOP構(gòu)建在動態(tài)代理基礎(chǔ)之上,因此,Spring對AOP的支持局限于方法攔截。
在Java中動態(tài)代理有兩種方式:
- JDK動態(tài)代理
- CGLib動態(tài)代理
JDK動態(tài)代理是需要實(shí)現(xiàn)某個接口了,而我們類未必全部會有接口,于是CGLib代理就有了~~
- CGLib代理其生成的動態(tài)代理對象是目標(biāo)類的子類
- Spring AOP默認(rèn)是使用JDK動態(tài)代理,如果代理的類沒有接口則會使用CGLib代理。
那么JDK代理和CGLib代理我們該用哪個呢??在《精通Spring4.x 企業(yè)應(yīng)用開發(fā)實(shí)戰(zhàn)》給出了建議:
- 如果是單例的我們最好使用CGLib代理,如果是多例的我們最好使用JDK代理
原因:
- JDK在創(chuàng)建代理對象時的性能要高于CGLib代理,而生成代理對象的運(yùn)行性能卻比CGLib的低。
- 如果是單例的代理,推薦使用CGLib
看到這里我們就應(yīng)該知道什么是Spring AOP(面向切面編程)了:將相同邏輯的重復(fù)代碼橫向抽取出來,使用動態(tài)代理技術(shù)將這些重復(fù)代碼織入到目標(biāo)對象方法中,實(shí)現(xiàn)和原來一樣的功能。
- 這樣一來,我們就在寫業(yè)務(wù)時只關(guān)心業(yè)務(wù)代碼,而不用關(guān)心與業(yè)務(wù)無關(guān)的代碼
1.3AOP的實(shí)現(xiàn)者
AOP除了有Spring AOP實(shí)現(xiàn)外,還有著名的AOP實(shí)現(xiàn)者:AspectJ,也有可能大家沒聽說過的實(shí)現(xiàn)者:JBoss AOP~~
我們下面來說說AspectJ擴(kuò)展一下知識面:
AspectJ是語言級別的AOP實(shí)現(xiàn),擴(kuò)展了Java語言,定義了AOP語法,能夠在編譯期提供橫切代碼的織入,所以它有專門的編譯器用來生成遵守Java字節(jié)碼規(guī)范的Class文件。
而Spring借鑒了AspectJ很多非常有用的做法,融合了AspectJ實(shí)現(xiàn)AOP的功能。但Spring AOP本質(zhì)上底層還是動態(tài)代理,所以Spring AOP是不需要有專門的編輯器的~
1.4AOP的術(shù)語
嗯,AOP搞了好幾個術(shù)語出來~~兩本書都有講解這些術(shù)語,我會盡量讓大家看得明白的:
連接點(diǎn)(Join point):
- 能夠被攔截的地方:Spring AOP是基于動態(tài)代理的,所以是方法攔截的。每個成員方法都可以稱之為連接點(diǎn)~
切點(diǎn)(Poincut):
- 具體定位的連接點(diǎn):上面也說了,每個方法都可以稱之為連接點(diǎn),我們具體定位到某一個方法就成為切點(diǎn)。
增強(qiáng)/通知(Advice):
- 表示添加到切點(diǎn)的一段邏輯代碼,并定位連接點(diǎn)的方位信息。
- 簡單來說就定義了是干什么的,具體是在哪干
- Spring AOP提供了5種Advice類型給我們:前置、后置、返回、異常、環(huán)繞給我們使用!
織入(Weaving):
- 將
增強(qiáng)/通知
添加到目標(biāo)類的具體連接點(diǎn)上的過程。
引入/引介(Introduction):
-
引入/引介
允許我們向現(xiàn)有的類添加新方法或?qū)傩?/strong>。是一種特殊的增強(qiáng)!
切面(Aspect):
- 切面由切點(diǎn)和
增強(qiáng)/通知
組成,它既包括了橫切邏輯的定義、也包括了連接點(diǎn)的定義。
在《Spring 實(shí)戰(zhàn) (第4版)》給出的總結(jié)是這樣子的:
通知/增強(qiáng)包含了需要用于多個應(yīng)用對象的橫切行為;連接點(diǎn)是程序執(zhí)行過程中能夠應(yīng)用通知的所有點(diǎn);切點(diǎn)定義了通知/增強(qiáng)被應(yīng)用的具體位置。其中關(guān)鍵的是切點(diǎn)定義了哪些連接點(diǎn)會得到通知/增強(qiáng)。
總的來說:
- 這些術(shù)語可能翻譯過來不太好理解,但對我們正常使用AOP的話影響并沒有那么大~~看多了就知道它是什么意思了。
1.5Spring對AOP的支持
Spring提供了3種類型的AOP支持:
- 基于代理的經(jīng)典SpringAOP
- 需要實(shí)現(xiàn)接口,手動創(chuàng)建代理
- 純POJO切面
- 使用XML配置,aop命名空間
-
@AspectJ
注解驅(qū)動的切面- 使用注解的方式,這是最簡潔和最方便的!
二、基于代理的經(jīng)典SpringAOP
這部分配置比較麻煩,用起來也很麻煩,這里我就主要整理一下書上的內(nèi)容,大家看看了解一下吧,我們實(shí)際上使用Spring AOP基本不用這種方式了!
首先,我們來看一下增強(qiáng)接口的繼承關(guān)系圖:
可以分成五類增強(qiáng)的方式:
Spring提供了六種的切點(diǎn)類型:
切面類型主要分成了三種:
- 一般切面
- 切點(diǎn)切面
- 引介/引入切面
一般切面,切點(diǎn)切面,引介/引入切面介紹:
對于切點(diǎn)切面我們一般都是直接用就好了,我們來看看引介/引入切面是怎么一回事:
- 引介/引入切面是引介/引入增強(qiáng)的封裝器,通過引介/引入切面,可以更容易地為現(xiàn)有對象添加任何接口的實(shí)現(xiàn)!
繼承關(guān)系圖:
引介/引入切面有兩個實(shí)現(xiàn)類:
- DefaultIntroductionAdvisor:常用的實(shí)現(xiàn)類
- DeclareParentsAdvisor:用于實(shí)現(xiàn)AspectJ語言的DeclareParent注解表示的引介/引入切面
實(shí)際上,我們使用AOP往往是Spring內(nèi)部使用BeanPostProcessor幫我們創(chuàng)建代理。
這些代理的創(chuàng)建器可以分成三類:
- 基于Bean配置名規(guī)則的自動代理創(chuàng)建器:BeanNameAutoProxyCreator
- 基于Advisor匹配機(jī)制的自動代理創(chuàng)建器:它會對容器所有的Advisor進(jìn)行掃描,實(shí)現(xiàn)類為DefaultAdvisorAutoProxyCreator
- 基于Bean中的AspectJ注解標(biāo)簽的自動代理創(chuàng)建器:AnnotationAwareAspectJAutoProxyCreator
對應(yīng)的類繼承圖:
嗯,基于代理的經(jīng)典SpringAOP就講到這里吧,其實(shí)我是不太愿意去寫這個的,因?yàn)橐呀?jīng)幾乎不用了,在《Spring 實(shí)戰(zhàn) 第4版》也沒有這部分的知識點(diǎn)了。
- 但是通過這部分的知識點(diǎn)可以更加全面地認(rèn)識Spring AOP的各種接口吧~
三、擁抱基于注解和命名空的AOP編程
Spring在新版本中對AOP功能進(jìn)行了增強(qiáng),體現(xiàn)在這么幾個方面:
- 在XML配置文件中為AOP提供了aop命名空間
- 增加了AspectJ切點(diǎn)表達(dá)式語言的支持
- 可以無縫地集成AspectJ
那我們使用@AspectJ
來玩AOP的話,學(xué)什么??其實(shí)也就是上面的內(nèi)容,學(xué)如何設(shè)置切點(diǎn)、創(chuàng)建切面、增強(qiáng)的內(nèi)容是什么...
具體的切點(diǎn)表達(dá)式使用還是前往:Spring【AOP模塊】就這么簡單看吧~~
對應(yīng)的增強(qiáng)注解:
3.1使用引介/引入功能實(shí)現(xiàn)為Bean引入新方法
其實(shí)前置啊、后置啊這些很容易就理解了,整篇文章看下來就只有這個引介/引入切面有點(diǎn)搞頭。于是我們就來玩玩吧~
我們來看一下具體的用法吧,現(xiàn)在我有個服務(wù)員的接口:
public interface Waiter {
// 向客人打招呼
void greetTo(String clientName);
// 服務(wù)
void serveTo(String clientName);
}
一位年輕服務(wù)員實(shí)現(xiàn)類:
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 + "...");
}
}
現(xiàn)在我想做的就是:想這個服務(wù)員可以充當(dāng)售貨員的角色,可以賣東西!當(dāng)然了,我肯定不會加一個賣東西的方法到Waiter接口上啦,因?yàn)檫@個是暫時的~
所以,我搞了一個售貨員接口:
public interface Seller {
// 賣東西
int sell(String goods, String clientName);
}
一個售貨員實(shí)現(xiàn)類:
public class SmartSeller implements Seller {
// 賣東西
public int sell(String goods,String clientName) {
System.out.println("SmartSeller: sell "+goods +" to "+clientName+"...");
return 100;
}
}
此時,我們的類圖是這樣子的:
現(xiàn)在我想干的就是:借助AOP的引入/引介切面,來讓我們的服務(wù)員也可以賣東西!
我們的引入/引介切面具體是這樣干的:
@Aspect
public class EnableSellerAspect {
@DeclareParents(value = "com.smart.NaiveWaiter", // 指定服務(wù)員具體的實(shí)現(xiàn)
defaultImpl = SmartSeller.class) // 售貨員具體的實(shí)現(xiàn)
public Seller seller; // 要實(shí)現(xiàn)的目標(biāo)接口
}
寫了這個切面類會發(fā)生什么??
- 切面技術(shù)將SmartSeller融合到NaiveWaiter中,這樣NaiveWaiter就實(shí)現(xiàn)了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");
// 調(diào)用服務(wù)員原有的方法
waiter.greetTo("Java3y");
waiter.serveTo("Java3y");
// 通過引介/引入切面已經(jīng)將waiter服務(wù)員實(shí)現(xiàn)了Seller接口,所以可以強(qiáng)制轉(zhuǎn)換
Seller seller = (Seller) waiter;
seller.sell("水軍", "Java3y");
}
}
具體的調(diào)用過程是這樣子的:
當(dāng)引入接口方法被調(diào)用時,代理對象會把此調(diào)用委托給實(shí)現(xiàn)了新接口的某個其他對象。實(shí)際上,一個Bean的實(shí)現(xiàn)被拆分到多個類中
3.2在XML中聲明切面
我們知道注解很方便,但是,要想使用注解的方式使用Spring AOP就必須要有源碼(因?yàn)槲覀円谇忻骖惿咸砑幼⒔?。如果沒有源碼的話,我們就得使用XML來聲明切面了~
其實(shí)就跟注解差不多的功能:
我們就直接來個例子終結(jié)掉它吧:
首先我們來測試一下與傳統(tǒng)的SpringAOP結(jié)合的advisor是怎么用的:
實(shí)現(xiàn)類:
xml配置文件:
.......
一個一個來講解還是太花時間了,我就一次性用圖的方式來講啦:
最后還有一個切面類型總結(jié)圖,看完就幾乎懂啦:
三、總結(jié)
看起來AOP有很多很多的知識點(diǎn),其實(shí)我們只要記住AOP的核心概念就行啦。
下面是我的簡要總結(jié)AOP:
- AOP的底層實(shí)際上是動態(tài)代理,動態(tài)代理分成了JDK動態(tài)代理和CGLib動態(tài)代理。如果被代理對象沒有接口,那么就使用的是CGLIB代理(也可以直接配置使用CBLib代理)
- 如果是單例的話,那我們最好使用CGLib代理,因?yàn)镃GLib代理對象運(yùn)行速度要比JDK的代理對象要快
- AOP既然是基于動態(tài)代理的,那么它只能對方法進(jìn)行攔截,它的層面上是方法級別的
- 無論經(jīng)典的方式、注解方式還是XML配置方式使用Spring AOP的原理都是一樣的,只不過形式變了而已。一般我們使用注解的方式使用AOP就好了。
- 注解的方式使用Spring AOP就了解幾個切點(diǎn)表達(dá)式,幾個增強(qiáng)/通知的注解就完事了,是不是賊簡單...使用XML的方式和注解其實(shí)沒有很大的區(qū)別,很快就可以上手啦。
- 引介/引入切面也算是一個比較亮的地方,可以用代理的方式為某個對象實(shí)現(xiàn)接口,從而能夠使用借口下的方法。這種方式是非侵入式的~
- 要增強(qiáng)的方法還可以接收與被代理方法一樣的參數(shù)、綁定被代理方法的返回值這些功能...
最后,將我們上一次IOC的思維導(dǎo)圖補(bǔ)充AOP的知識點(diǎn)上去吧~~~
參考資料:
- 《Spring 實(shí)戰(zhàn)》
- 《精通Spring4.x 企業(yè)應(yīng)用開發(fā)實(shí)戰(zhàn)》
如果文章有錯的地方歡迎指正,大家互相交流。習(xí)慣在微信看技術(shù)文章,想要獲取更多的Java資源的同學(xué),可以關(guān)注微信公眾號:Java3y。為了大家方便,剛新建了一下qq群:742919422,大家也可以去交流交流。謝謝支持了!希望能多介紹給其他有需要的朋友
文章的目錄導(dǎo)航: