Spring AOP的使用詳解
什么是AOP
AOP(Aspect Oriented Programming 面向切面編程),通過(guò)預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)。AOP是OOP的延續(xù),是軟件開(kāi)發(fā)中的一個(gè)熱點(diǎn),也是Spring框架中的一個(gè)重要內(nèi)容,是函數(shù)式編程的一種衍生范型。利用AOP可以對(duì)業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行隔離,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低,提高程序的可重用性,同時(shí)提高了開(kāi)發(fā)的效率。
常用于日志記錄,性能統(tǒng)計(jì),安全控制,事務(wù)處理,異常處理等等。
定義AOP術(shù)語(yǔ)
切面(Aspect):切面是一個(gè)關(guān)注點(diǎn)的模塊化,這個(gè)關(guān)注點(diǎn)可能是橫切多個(gè)對(duì)象;
連接點(diǎn)(Join Point):連接點(diǎn)是指在程序執(zhí)行過(guò)程中某個(gè)特定的點(diǎn),比如某方法調(diào)用的時(shí)候或者處理異常的時(shí)候;
通知(Advice):指在切面的某個(gè)特定的連接點(diǎn)上執(zhí)行的動(dòng)作。Spring切面可以應(yīng)用5中通知:
- 前置通知(Before):在目標(biāo)方法或者說(shuō)連接點(diǎn)被調(diào)用前執(zhí)行的通知;
- 后置通知(After):指在某個(gè)連接點(diǎn)完成后執(zhí)行的通知;
- 返回通知(After-returning):指在某個(gè)連接點(diǎn)成功執(zhí)行之后執(zhí)行的通知;
- 異常通知(After-throwing):指在方法拋出異常后執(zhí)行的通知;
- 環(huán)繞通知(Around):指包圍一個(gè)連接點(diǎn)通知,在被通知的方法調(diào)用之前和之后執(zhí)行自定義的方法。
切點(diǎn)(Pointcut):指匹配連接點(diǎn)的斷言。通知與一個(gè)切入點(diǎn)表達(dá)式關(guān)聯(lián),并在滿足這個(gè)切入的連接點(diǎn)上運(yùn)行,例如:當(dāng)執(zhí)行某個(gè)特定的名稱的方法。
引入(Introduction):引入也被稱為內(nèi)部類型聲明,聲明額外的方法或者某個(gè)類型的字段。
目標(biāo)對(duì)象(Target Object):目標(biāo)對(duì)象是被一個(gè)或者多個(gè)切面所通知的對(duì)象。
AOP代理(AOP Proxy):AOP代理是指AOP框架創(chuàng)建的對(duì)對(duì)象,用來(lái)實(shí)現(xiàn)切面契約(包括通知方法等功能)
織入(Wearving):指把切面連接到其他應(yīng)用出程序類型或者對(duì)象上,并創(chuàng)建一個(gè)被通知的對(duì)象?;蛘哒f(shuō)形成代理對(duì)象的方法的過(guò)程。
Spring對(duì)AOP的支持
- 基于代理的經(jīng)典SpringAOP;
- 純POJO切面;
- @AspectJ注解驅(qū)動(dòng)的切面;
- 注入式AspectJ切面(適用于Spring各版本);
前三種都是SpringAOP實(shí)現(xiàn)的變體,SpringAOP構(gòu)建在動(dòng)態(tài)代理基礎(chǔ)之上,因此,Spring對(duì)AOP的支持局限于方法的攔截。
切入點(diǎn)表達(dá)式
使用SpringAOP
SpringAOP的支持必須呀導(dǎo)入spring-aspects的jar包
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>4.3.5.RELEASE</version> </dependency>
使用注解定義切面
采用注解的方式定義切面以及通知
@Aspect public class Audience { //使用@Pointcut注解聲明頻繁使用的切入點(diǎn)表達(dá)式 @Pointcut("execution(* com.wqh.concert.Performance.perform(..))") public void performance(){} @Before("performance()") public void silenceCellPhones(){ System.out.println("Sillencing cell phones"); } @Before("performance()") public void takeSeats(){ System.out.println("Task Seat"); } @AfterReturning("performance()") public void applause(){ System.out.println("CLAP CLAP CLAP"); } @AfterThrowing("performance()") public void demandRefund(){ System.out.println("Demand a Refund"); } }
另外需要在applicationContext.xml也就是spring的配置文件中添加配置:
<!--啟用AspectJ的自動(dòng)代理--> <aop:aspectj-autoproxy/> <!--聲明bean--> <bean class="com.wqh.concert.Audience"/>
在XML中聲明切面
定義pojo類,這里只是把上面定義的注解全public class AudienceXML {
public void silenceCellPhones() { System.out.println("Sillencing cell phones"); } public void takeSeats() { System.out.println("Task Seat"); } public void applause() { System.out.println("CLAP CLAP CLAP"); } public void demandRefund() { System.out.println("Demand a Refund"); }
applicationContext.xml配置
<!--聲明bean--> <bean name="audienceXML" class="com.wqh.concert.AudienceXML"/> <aop:config> <!--引入bean--> <aop:aspect ref="audienceXML"> <!--定義切點(diǎn)--> <aop:pointcut id="perform" expression="execution(* com.wqh.concert.Performance.perform(..))"/> <!--定義通知 method:通知,也就是具體的方法 pointcut-ref:引用的切點(diǎn) pointcut:切點(diǎn)--> <aop:before method="silenceCellPhones" pointcut-ref="perform"/> <aop:before method="takeSeats" pointcut-ref="perform"/> <aop:after-returning method="applause" pointcut-ref="perform"/> <aop:after-throwing method="demandRefund" pointcut="execution(* com.wqh.concert.Performance.perform(..))"/> </aop:aspect> </aop:config>
環(huán)繞通知
在springAOP中有五種通知,環(huán)繞通知是最為強(qiáng)大的通知。它能夠讓你編寫(xiě)的邏輯將被通知的目標(biāo)方法完全包裝起來(lái)。實(shí)際上就像在一個(gè)通知方法中同時(shí)編寫(xiě)前置通知和后置通知。
本片文章具體講解環(huán)繞通知的使用。
使用注解
使用環(huán)繞通知定義切面:
@Aspect public class AudienceAround { //使用@Pointcut注解聲明頻繁使用的切入點(diǎn)表達(dá)式 @Pointcut("execution(* com.wqh.concert.Performance.perform(..))") public void performance(){} @Around("performance()") public void watchPerformance(ProceedingJoinPoint joinPoint){ try { System.out.println("Silencing cell phones"); System.out.println("Taking seats"); joinPoint.proceed(); System.out.println("Demanding a refund"); } catch (Throwable throwable) { throwable.printStackTrace(); } } }
可以看到在上面的代碼中,定義通知的時(shí)候在通知方法中添加了入?yún)ⅲ篜roceedingJoinPoint。在創(chuàng)建環(huán)繞通知的時(shí)候,這個(gè)參數(shù)是必須寫(xiě)的。因?yàn)樵谛枰谕ㄖ惺褂肞roceedingJoinPoint.proceed()方法調(diào)用被通知的方法。
另外,如果忘記調(diào)用proceed()方法,那么通知實(shí)際上會(huì)阻塞對(duì)被通知方法的調(diào)用。
在XML中定義
首先去掉上面類的所有注解:這里為了區(qū)別就重新創(chuàng)建一個(gè)類
public class AudienceAroundXML { public void watchPerformance(ProceedingJoinPoint joinPoint){ try { System.out.println("Silencing cell phones"); System.out.println("Taking seats"); joinPoint.proceed(); System.out.println("Demanding a refund"); } catch (Throwable throwable) { throwable.printStackTrace(); } } }
配置:
<!--聲明bean--> <bean name="audienceAroundXML" class="com.wqh.concert.AudienceAroundXML"/> <!--配置切面及通知--> <aop:config> <aop:aspect ref="audienceAroundXML"> <aop:pointcut id="performance" expression="execution(* com.wqh.concert.Performance.perform(..))"/> <aop:around method="watchPerformance" pointcut-ref="performance"/> </aop:aspect> </aop:config>
處理通知中的參數(shù)
Spring借助AspectJ的切點(diǎn)表達(dá)式語(yǔ)言來(lái)定義Spring切面
在spring中嘗試使用其他指示器時(shí),會(huì)拋出IllegalArgument-Exception異常。
如上的這些指示器,只有exception指示器是實(shí)際執(zhí)行匹配的,而其他都是用來(lái)限制匹配的。
切面表達(dá)式分析
帶參數(shù)的切點(diǎn)表達(dá)式分解
在該切點(diǎn)表達(dá)式中使用了args(trackNumber)限定符。表示傳遞給playTrack()方法的int類型參數(shù)也會(huì)傳遞到通知中去。參數(shù)名trackNumber也與切點(diǎn)方法簽名中的參數(shù)相匹配。
創(chuàng)建切面
@Aspect public class TrackCounter { @Pointcut("execution(* com.wqh.aop.CompactDisc.playTrack(int))&&args(trackNumber)") public void trackPlayder(int trackNumber){} @Before("trackPlayder(trackNumber)") public void countTrack(int trackNumber) { System.out.println("前置通知:targetNumber=" + trackNumber); } }
連接點(diǎn)類
@Service public class CompactDisc { public void playTrack(int trackNumber){ System.out.println("trackNumber =" + trackNumber); } }
XML配置
<!--啟用AspectJ的自動(dòng)代理--> <aop:aspectj-autoproxy/> <!--聲明bean--> <bean class="com.wqh.aop.TrackCounter"/> <!--自動(dòng)掃描包下的類--> <context:component-scan base-package="com.wqh.aop"/>
測(cè)試
@Test public void testT(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext( new String[]{"classpath:/spring/applicationContext.xml"}); CompactDisc compactDisc = (CompactDisc) applicationContext.getBean("compactDisc"); compactDisc.playTrack(12); }
上面給指定方法傳入的參數(shù)是12,在通知中獲取到了該參數(shù)
另外:在xml中配置切面來(lái)處理通知中的參數(shù),其實(shí)也差不多,只是把切點(diǎn)表達(dá)式放到了XML配置文件中。
給類添加新的功能
引入Spring實(shí)戰(zhàn)中的知識(shí)
在SpringAOP中,我們可以為Bean引入新的方法。代理攔截器調(diào)用并委托給實(shí)現(xiàn)該方法的其他對(duì)象。
當(dāng)引入接口的方法被調(diào)用時(shí),代理會(huì)把此調(diào)用委托給實(shí)現(xiàn)了新接口的某給其他對(duì)象。
使用注解方式引入
代碼
首先是連接點(diǎn)的接口及其實(shí)現(xiàn)類
public interface Person { void say(); }
public class ChinesePerson implements Person { @Override public void say() { System.out.println("說(shuō)中文"); } }
創(chuàng)建需要添加的功能,這里個(gè)人類擴(kuò)展一個(gè)吃的功能
public interface Food { void eat(); }
public class ChineseFood implements Food { @Override public void eat() { System.out.println("吃中餐"); } }
編寫(xiě)切面
@Aspect public class addFuction { @DeclareParents(value = "com.wqh.addfunction.Person+",defaultImpl = ChineseFood.class) public static Food food; }
注意這里的表達(dá)式使用的式@DeclareParents注解;該注解所標(biāo)注的靜態(tài)屬性指明了要引入的接口。
注解中使用的value屬性指定哪種類型的bean要引入該接口,這里Person后后面的“+”號(hào)表示所有子類型,而不是該類的本身。defaultImpl,指定了為引入功能提供實(shí)現(xiàn)的類。
使用XML配置bean:
<!--啟用AspectJ的自動(dòng)代理--> <aop:aspectj-autoproxy/> <!--聲明bean--> <bean class="com.wqh.addfunction.addFuction"/> <bean name="chinesePerson" class="com.wqh.addfunction.ChinesePerson"/>
測(cè)試
@Test public void testAdd(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext( "classpath:spring/applicationContext.xml"); Person person = (Person) applicationContext.getBean("chinesePerson"); person.say(); //這里可以將chinesePerson bean轉(zhuǎn)換為Food類,所以添加成功 Food food = (Food) applicationContext.getBean("chinesePerson"); food.eat(); }
在XML中引入
首先將上面的addFuction注解全部刪除,其他不變;然后在xml中添加相應(yīng)的配置:
<!--啟用AspectJ的自動(dòng)代理--> <aop:aspectj-autoproxy/> <!--聲明bean--> <bean name="chinesePerson" class="com.wqh.addfunction.ChinesePerson"/> <aop:config> <aop:aspect> <aop:declare-parents types-matching="com.wqh.addfunction.Person+" implement-interface="com.wqh.addfunction.Food" default-impl="com.wqh.addfunction.ChineseFood"/> </aop:aspect> </aop:config>
這里的types-matching與上面的vale作用一樣;
default-impl與defaultImpl作用一樣,這也可以使用delegate-ref;當(dāng)然如果使用delegate-ref則是要引用SpringBean;
implement-interface則是要引入的接口
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
使用SpringBoot發(fā)送郵箱驗(yàn)證碼的簡(jiǎn)單實(shí)現(xiàn)
這篇文章主要介紹了使用SpringBoot發(fā)送郵箱驗(yàn)證碼的簡(jiǎn)單實(shí)現(xiàn),咱們今天來(lái)講使用QQ郵箱來(lái)發(fā)送和接收驗(yàn)證碼,首先來(lái)介紹一下它在SpringBoot項(xiàng)目中的具體應(yīng)用,需要的朋友可以參考下2023-04-04使用JWT作為Spring?Security?OAuth2的token存儲(chǔ)問(wèn)題
這篇文章主要介紹了使用JWT作為Spring?Security?OAuth2的token存儲(chǔ),大家經(jīng)常使用的方法有兩種一種是使用JWT作為T(mén)oken傳遞,一種是使用Redis存儲(chǔ)Token,資源服務(wù)器本地訪問(wèn)Redis校驗(yàn)Token,需要的朋友可以參考下2021-12-12Java棧和基礎(chǔ)隊(duì)列的實(shí)現(xiàn)詳解
這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)中的棧與隊(duì)列,在Java的時(shí)候,對(duì)于棧與隊(duì)列的應(yīng)用需要熟練的掌握,這樣才能夠確保Java學(xué)習(xí)時(shí)候能夠有扎實(shí)的基礎(chǔ)能力。本文小編就來(lái)詳細(xì)說(shuō)說(shuō)Java中的棧與隊(duì)列,需要的朋友可以參考一下2022-02-02Java利用Request請(qǐng)求獲取IP地址的方法詳解
在開(kāi)發(fā)中我們經(jīng)常需要獲取用戶IP地址,通過(guò)地址來(lái)實(shí)現(xiàn)一些功能,下面這篇文章主要給大家介紹了關(guān)于Java利用Request請(qǐng)求獲取IP地址的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-10-10spring boot security 沒(méi)有合適的構(gòu)造器問(wèn)題
這篇文章主要介紹了spring boot security 沒(méi)有合適的構(gòu)造器問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12重新啟動(dòng)IDEA時(shí)maven項(xiàng)目SSM框架文件變色所有@注解失效
這篇文章主要介紹了重新啟動(dòng)IDEA時(shí)maven項(xiàng)目SSM框架文件變色所有@注解失效,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03詳解Java中NullPointerException異常的原因詳解以及解決方法
這篇文章主要介紹了詳解Java中NullPointerException異常的原因詳解以及解決方法。文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08