AOP實現(xiàn)可分為兩類(按AOP框架修改源代碼的時機):
- 靜態(tài)AOP實現(xiàn):AOP框架在編譯階段對程序進行修改摄欲,即實現(xiàn)對目標類的增強轿亮,生成靜態(tài)的AOP代理類(生成的*.class文件已經(jīng)被改掉了,需要使用特定的編輯器)胸墙。以AspectJ為代表我注。
- 動態(tài)AOP實現(xiàn):AOP框架在運行階段動態(tài)生成AOP代理(在內(nèi)存中以JDK動態(tài)代理或cglib動態(tài)代理生成AOP代理類)。以實現(xiàn)對目標類的增強迟隅。以Spring AOP為代表但骨。
AOP的基本概念:
AOP框架具有如下兩個特征:
- 個步驟之間的良好隔離性励七。
- 源代碼無關(guān)性。
關(guān)于面向切面編程的一些術(shù)語:
- 切面(Aspect):切面用于組織多個Advice奔缠,Advice放在切面中定義呀伙。
- 連接點(Joinpoint):程序執(zhí)行過程中明確的點,如方法的調(diào)用添坊,或者異常的拋出剿另。在Spring AOP中,連接點總是方法的調(diào)用贬蛙。
- 增強處理(Advice):AOP框架在特定的點執(zhí)行的增強處理雨女。處理有“before”,“around”阳准,“after”等氛堕。
- 切入點(Pointcut):可以插入增強處理的連接點。簡而言之野蝇,當某個連接點滿足指定要求時讼稚,該連接點將被添加增強處理,該連接點也就變成了切入點绕沈。
pointcut xxxPointcut():execution(void H*.say*())
如何使用表達式定義切入點是AOP的核心锐想,Spring默認使用AspectJ切入點語法:
- 引入:將方法或字段添加到被處理的類中。Spring允許將新的接口引入到任何被處理的對象中乍狐。例如赠摇,你可以使用一個引入,使任何對象實現(xiàn)IsModified接口浅蚪,以此來簡化緩存藕帜。
- 目標對象:被AOP框架進行增強處理的對象,也被稱為增強的對象惜傲。如果AOP框架采用的是動態(tài)AOP實現(xiàn)洽故,那么該對象就是一個被代理的對象。
- AOP代理:AOP框架創(chuàng)建的對象盗誊,代理就是對目標對象的增強时甚。Spring中的AOP代理可以是JDK動態(tài)代理,也可以是cglib代理浊伙。前者為實現(xiàn)接口的目標對象的代理撞秋,后者不實現(xiàn)接口的目標對象的代理。
- 織入(Weaving):將增強處理添加到目標對象中嚣鄙,并創(chuàng)建一個被增強的對象(AOP代理)的過程就是織入吻贿。植入有兩種實現(xiàn)方式---編譯時增強(如AspectJ)和運行時增強(如Spring AOP)。Spring和其他純Java AOP框架一樣哑子,在運行時完成織入舅列。
Spring的AOP支持:
Spring中的AOP代理由Spring的IOC容器負責生成肌割、管理,其依賴關(guān)系也由IOC容器負責管理帐要。AOP代理可以直接使用容器中的其他Bean實例作為目標把敞,這種關(guān)系可以由IOC容器的依賴注入提供。Spring默認使用Java動態(tài)代理來創(chuàng)建AOP代理榨惠。Spring目前僅支持將方法調(diào)用作為連接點(Joinpoint)奋早,如果需要把對成員變量的訪問和更新也作為增強處理的連接點,則可以考慮使用AspectJ赠橙。Spring側(cè)重于AOP實現(xiàn)和IOC容器之間的整合耽装,用于幫助解決企業(yè)級開發(fā)中常見問題。Spring AOP采用基于代理的AOP實現(xiàn)方案期揪,而AspectJ則采用編譯時增強的解決方案掉奄。
AOP編程中需要程序員參與的只有三個部分:
- 第一普通業(yè)務組件。
- 定義切入點凤薛,一個切入點可以橫切多個業(yè)務組件姓建。
- 定義增強處理,增強處理就是在AOP框架為普通業(yè)務組織織入的處理動作缤苫。
AOP代理方法=增強處理+目標對象的方法
Spring有如下兩種選擇來定義切入點和增強處理:
- 基于注解的“零配置”方式:使用@Aspect速兔、@Pointcut等注解來標注切入點和增強處理。
- 基于XML配置文件的管理方式:使用Spring配置文件來定義切入點和增強處理榨馁。
基于注解的“零配置”方式:
Spring依然采用運行時生成動態(tài)代理的方式來增強目標對象憨栽,所以它不需要增加額外的編譯帜矾,也不需要AspectJ的織入器支持翼虫;而AspectJ采用編譯時增強,所以AspectJ需要自己的編譯器來編譯Java文件屡萤,還需要織入器珍剑。
Spring中啟用對@AspectJ切面配置的支持:
<?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:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<!-- 啟動@AspectJ支持 -->
<aop:aspectj-autoproxy/>
</beans>
以及
<!-- 啟動@AspectJ支持 -->
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"/>
Spring應用中啟動@AspectJ支持還需要在應用的類加載路徑下添加AspectJ庫:
- aspectjweaver.jar
- aspectjrt.jar
以上兩個jar文件直接在AspectJ安裝目錄的lib中獲取,Spring AOP還需要依賴aopalliance.jar死陆。
1招拙、定義切面:
當啟動了@AspectJ支持后,只要在Spring容器中配置一個帶@AspectJ注解的Bean措译,Spring將會自動識別該Bean别凤,并將該Bean作為切面處理。
//使用@AspectJ定義一個切面類
@Aspect
public class LogAspect{
//定義該類的其他類容
.......
}
當使用@AspectJ來修飾一個Java類之后领虹,Spring不會把該Bean當成組件Bea處理规哪,因此負責增強后處理的Bean將會略過該Bean,不會對該Bean進行任何增強處理塌衰。
2诉稍、定義Before增強處理:
在一個切面類里使用@Before來修飾一個方法時蝠嘉,該方法將作為Before增強處理。使用@Before修飾時杯巨,通常需要指定一個value屬性蚤告,該屬性指定一個切入點表達式(既可以是一個已有的切入點,也可以直接定義切入點表達式)服爷,用于指定該增強處理將被織入哪些切入點杜恰。
AuthAspect.java
package entity;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class AuthAspect {
//匹配entity包下所有的類的所有方法作為切入點
@Before("execution(* entity.*.*(..))")
public void authority(){
System.out.println("模擬執(zhí)行權(quán)限檢查!");
}
}
HelloImpl.java
package entity;
import org.springframework.stereotype.Component;
import inter.Hello;
@Component("hello")
public class HelloImpl implements Hello{
//定義一個簡單方法仍源,模擬應用中的業(yè)務邏輯方法
public void foo() {
System.out.println("執(zhí)行Hello組件的foo()方法");
}
//定義一個addUser()方法箫章,模擬應用中添加用戶的方法
public void addUser(String name,String pass){
System.out.println("執(zhí)行Hello組件的addUser添加用戶:"+name);
}
}
beans.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:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<!-- 啟動@AspectJ支持 -->
<aop:aspectj-autoproxy/>
<!-- 指定自動搜索Bean文件,自動搜索切面文件 -->
<context:component-scan base-package="entity">
<context:include-filter type="annotation" expression="org.aspectj.lang.annotation.Aspect"/>
</context:component-scan>
</beans>
AspectjTest.java
package test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import entity.HelloImpl;
public class AspecjTest {
public static void main(String[] args) {
ApplicationContext ctx=new ClassPathXmlApplicationContext("beans.xml");
HelloImpl hello=(HelloImpl) ctx.getBean("hello");
hello.foo();
hello.addUser("張三", "123456");
}
}
輸出:
模擬執(zhí)行權(quán)限檢查镜会!
執(zhí)行Hello組件的foo()方法
模擬執(zhí)行權(quán)限檢查檬寂!
執(zhí)行Hello組件的addUser添加用戶:張三
使用Before增強處理只能在目標方法執(zhí)行之前織入增強,如果Before增強處理沒有特殊處理戳表,目標方法總會自動執(zhí)行桶至,如果Before處理需要阻止目標方法的執(zhí)行,可通過拋出一個異常來實現(xiàn)匾旭。Before增強處理時镣屹,目標方法還未獲得執(zhí)行的機會,所以Before增強處理無法訪問目標方法的返回值价涝。
3女蜈、定義AfterReturning增強處理:
AfterReturning增強處理將在目標方法正常完成后被織入。
@AfterReturning注解可指定如下兩個常用屬性:
- pointcut/value:它們都用于指定該切入點對應的切入點表達式色瘩。一樣即可是一個已有的切入點伪窖,也可以直接定義切入點表達式。當指定了pointcut屬性值后居兆,value屬性將被覆蓋覆山。
- returning:該屬性指定一個形參名,用于表示Advice方法中可定義與此同名的形參泥栖,該形參可用于訪問目標方法的返回值簇宽。除此之外,在Advice方法中定義該形參(代表目標方法的返回值)時指定的類型吧享,會限制目標方法必須返回指定類型的值或沒有返回值魏割。
//定義一個切面
@Aspect
public class LogAspect{
//匹配entity包下所有類的所有方法的執(zhí)行作為切入點
@AfterReturning(returning="rvt",pointcut="execution(* entity.*.*())")
//聲明rvt時指定的類型會限制目標方法必須返回指定類型的值或沒有返回值
//此處將rvt的類型聲明為Object,意味著對目標方法的返回值不加限制
public void log(Object rvt){
System.out.println("獲得目標方法返回值"+rvt);
System.out.println("模擬記錄日志功能....");
}
}
@AfterReturning注解的returning屬性所指定的形參名對應于增強處理中的一個形參名钢颂,當目標方法執(zhí)行返回后钞它,返回值作為相應的參數(shù)值傳入增強處理方法。使用returning屬性還有 一個額外的作用:它可用于限定切入點只匹配具有對應返回值類型的方法。
4须揣、定義AfterThrowing增強處理:
AfterThrowing增強處理主要用于處理程序中未處理的異常盐股。
使用@AfterThrowing注解時可指定如下兩個常用屬性:
- pointcut/value:都用于指定該切入點對應的切入表達式。
- throwing:該屬性值指定一個形參名耻卡,用于表示Advice方法中可定義與此同名的形參疯汁,該形參用于訪問目標方法拋出的異常。在Advice方法中定義該形參(代表目標方法拋出的異常)時指定的類型卵酪,會限制目標方法必須拋出指定類型的異常幌蚊。
//定義一個切面
@Aspect
public class RepairAspect{
//匹配entity包下所有類的所有方法的執(zhí)行作為切入點
@AfterThrowing(throwing="ex",pointcut="execution(* entity.*.*())")
//聲明ex時指定的類型會限制目標方法必須拋出指定的類型的異常
//此處將ex的類型聲明為Throwable,意味著對目標方法拋出的異常不加限制
public void doRecoveryActions(Throwable ex){
System.out.println("目標方法中拋出的異常"+ex);
System.out.println("模擬Advice對異常的修復....");
}
}
使用throwing屬性還用一個額外的作用:它可用于限定切入點只匹配指定類型的異常溃卡。
5溢豆、After增強處理:
- AfterReturning增強處理只有在目標方法成功完成后才會被織入。
- After增強處理不管目標方法如何結(jié)束(包括成功完成和遇到異常終止兩種情況)瘸羡,他都會被織入漩仙。
After增強處理必須準備處理正常返回或異常返回兩種情況,這種處理通常用于釋放資源犹赖。使用@After注解修飾一個方法队他,即可將該方法轉(zhuǎn)成After增強處理。使用@After注解時需要指定一個value屬性峻村,該屬性值用于指定該增強處理被織入的切入點麸折。
//定義一個切面
@Aspect
public class ReleaseAspect{
//匹配entity包下所有的類的所有方法的執(zhí)行作為切入點
@After("execution(* entity.*.*())")
public void release(){
System.out.println("模擬方法結(jié)束后的釋放資源...");
}
}
After增強處理的作用非常類似于異常處理中的finally塊的作用。
6粘昨、Around增強處理:
@Around注解用于修飾Around增強處理垢啼,Around增強處理是功能較強大得增強處理,它近似等于Before和AfterReturning增強處理的總和张肾,Around增強處理即可在執(zhí)行目標方法之前織入增強動作芭析,也可以在執(zhí)行目標方法之后織入增強處理。
Around增強處理可以決定目標方法在什么時候執(zhí)行捌浩,如何執(zhí)行放刨,甚至可以完全阻止目標方法的執(zhí)行。Around增強處理的功能雖然強大尸饺,但通常需要在線程安全的環(huán)境下使用。如果需要目標方法執(zhí)行之前和執(zhí)行之后共享某種狀態(tài)數(shù)據(jù)助币,則該考慮使用Around增強處理浪听;尤其是需要改變目標方法的返回值時,則只能使用Around增強處理眉菱。
當定義一個Around增強處理方法時迹栓,該方法的第一個形參必須是ProceedingJoinPoint類型(至少包含一個形參),在增強處理方法體內(nèi)俭缓,調(diào)用ProceedingJoinPoint參數(shù)的proceed()方法才會執(zhí)行目標方法---這就是Around增強處理可以完全控制目標方法的執(zhí)行時機克伊、如何執(zhí)行的關(guān)鍵酥郭;如果程序沒有調(diào)用proceedingJoinPoint參數(shù)的proceed()方法,則目標方法不會被執(zhí)行愿吹。
使用Around增強處理可以取得目標方法最大的控制權(quán)不从,既可以完全控制目標方法的執(zhí)行,一刻改變執(zhí)行目標方法的參數(shù)犁跪,還可改變目標方法的返回值椿息。
7、訪問目標方法的參數(shù):
訪問目標方法最簡單的做法是定義增強處理方法時將第一個參數(shù)定義為JoinPoint類型坷衍,當該增強處理方法被調(diào)用時寝优,該JoinPoint參數(shù)就代表了織入增強的連接點。JoinPoint里包含如下幾個常用的方法:
- Object[] getArgs():返回執(zhí)行目標方法時的參數(shù)枫耳。
- Signature getSignature():返回被增強的方法的相關(guān)信息乏矾。
- Object getTarget():返回被增強處理的目標對象。
- Object getThis():返回AOP框架為目標對象生成的代理對象迁杨。
當使用Around增強處理是妻熊,需要將第一個參數(shù)定義為ProceedingJoionPoint類型,該類型是JoinPoint類型的子類仑最。
Spring AOP 采用和AspectJ一樣的優(yōu)先順序來織入增強處理:在“進入連接點時扔役,具有最高優(yōu)先級的增強處理將先被織入。在“退出”連接點時警医,具有最高優(yōu)先級的增強處理會最后被織入亿胸。
當不同切面的兩個增強處理需要在同一個連接點被織入時,Spring AOP將以隨機的順序來織入這兩個增強處理预皇。如果應用需要指定不同切面類里增強處理的優(yōu)先級侈玄,Spring提供了如下兩種解決方案:
- 讓切面實現(xiàn)org.springframework.core.Ordered接口,實現(xiàn)該接口只需要實現(xiàn)一個int getOrder()方法吟温,該方法的返回值越小序仙,優(yōu)先級越高。
- 直接使用@Order注解來修飾一個切面類鲁豪,使用@Order注解時可指定一個int型的value屬性潘悼,該值越小,則優(yōu)先級越高爬橡。
8治唤、定義切入點:
定義切入點,其實質(zhì)就是為一個切入點表達式起一個名稱糙申,從而允許在多個增強處理中重用該名稱宾添。Spring AOP只支持將Spring Bean的方法執(zhí)行作為連接點,所以可以吧切入點看成所有能和切入點表達式匹配的Bean方法,切入點定義包含兩個部分:
- 一個切入點表達式缕陕。
- 一個包含名字和任意參數(shù)的方法名粱锐。
其中切入點表達式用于指定該切入點和哪些方法進行匹配,包含名字和任意參數(shù)的方法簽名將作為該切入點的名稱扛邑。
//使用@Pointcut注解定義切入點
@Pointcut("execution(* transfer(..))")
//使用一個返回值為void怜浅、方法體為空的方法來命名切入點
private void anyOldTransfer(){...}
如果需要使用本切面中的切入點,則可在使用@Before鹿榜、@After海雪、@Around等注解來定義Advice時,使用pointcut或value屬性值引入已有的切入點舱殿。
@AfterReturning(pointcut="myPointcut()",returning="retVal")
public void writeLog(String msg,Object retVal){...}
使用其他切面類中的切入點時奥裸,應該使用切面類作為前綴來限制切入點。
9沪袭、切入點指示符:
- execution:用于匹配執(zhí)行方法的連接點湾宙,這是Spring AOP中最主要的切入點指示符。
- within:用于限定匹配特定類型的連接點冈绊,當使用Spring AOP的時候侠鳄,只能匹配方法執(zhí)行的連接點。
//在entity包中的任意連接點(在Spring AOP中只是方法執(zhí)行的連接點)
within(entity.*)
//在entity包或子包中的任意連接點(在Spring AOP中只是方法執(zhí)行的連接點)
within(entity..*)
- this:用于限定AOP代理必須必須是指定類型的實例死宣,匹配該對象的所有連接點伟恶。當使用Spring AOP的時候,只能匹配方法執(zhí)行的連接點毅该。
- target:用于限定目標對象必須是指定類型的實例博秫,匹配該對象的所有連接點。當使用Spring AOP的時候眶掌,只能匹配方法執(zhí)行的連接點挡育。
- args:用于對連接點的參數(shù)類型進行限制,要求參數(shù)類型是指定類型的實例朴爬。當使用Spring AOP的時候即寒,只能匹配方法執(zhí)行的連接點。
- bean:用于限定只匹配指定Bean實例內(nèi)的連接點召噩,實際上只能使用方法執(zhí)行作為連接點母赵。定義表達式時需要傳入Bean的id或name屬性,表示值匹配該Bean實例內(nèi)的連接點蚣常。支持使用“*”通配符市咽。
//匹配tradeService Bean實例內(nèi)方法執(zhí)行的連接點
bean(tradeService)
//匹配名字以Service結(jié)尾的Bean實例內(nèi)方法執(zhí)行的連接點
bean(*Service)
10、組合切入點表達式:
- &&:要求連接點同時匹配兩個切入點表達式抵蚊。
- ||:只要求連接點匹配任意一個切入點表達式。
- !:要求切入點不匹配指定的切入點表達式贞绳。