六、基于@Aspect注解編程(重點(diǎn))
1局服、說(shuō)明
Spring 使用了和AspectJ 一樣的注解并使用AspectJ來(lái)做切入點(diǎn)解析和匹配。但是驳遵,AOP在運(yùn)行時(shí)仍舊是純的Spring AOP淫奔,并不依賴于AspectJ的編譯器或者織入器(weaver)(編譯器與織入器暫時(shí)不要管)
2、啟用@AspectJ支持
- 說(shuō)明
為了在Spring中使用@AspectJ切面堤结,你首先必須啟用Spring對(duì)@AspectJ切面配置的支持唆迁,并確保開(kāi)啟自動(dòng)代理。自動(dòng)代理是指Spring會(huì)判斷一個(gè)bean是否使用了一個(gè)或多個(gè)切面通知竞穷,并據(jù)此自動(dòng)生成相應(yīng)的代理以攔截其方法調(diào)用唐责,并且確保通知在需要時(shí)執(zhí)行 - 新建spring-aspect.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" 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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd" <context:component-scan base-package="com.wener.example.aop.aspect"/ <!-- 有了這個(gè)Spring就能夠自動(dòng)掃描被@Aspect標(biāo)注的切面了 -- <!-- 開(kāi)啟自動(dòng)代理 -- <aop:aspectj-autoproxy/ </beans
2、聲明一個(gè)切面
- 說(shuō)明
在代碼中定義一個(gè)類任意在類上使用@Aspect注解 - 示例代碼
import org.aspectj.lang.annotation.Aspect; @Aspect public class LogAspect { }
3瘾带、聲明一個(gè)切入點(diǎn)
- 說(shuō)明
切入點(diǎn)決定了連接點(diǎn)關(guān)注的內(nèi)容鼠哥,使得我們可以控制通知什么時(shí)候執(zhí)行。Spring AOP只支持Spring bean的方法執(zhí)行連接點(diǎn)月弛。所以你可以把切入點(diǎn)看做是Spring bean上方法執(zhí)行的匹配肴盏。一個(gè)切入點(diǎn)聲明有兩個(gè)部分:-
包含名字和任意參數(shù)的簽名:一個(gè)切入點(diǎn)簽名通過(guò)一個(gè)普通的方法定義來(lái)提供,并且切入點(diǎn)表達(dá)式使用
@Pointcut
注解來(lái)表示(作為切入點(diǎn)簽名的方法必須返回void
類型) - 切入點(diǎn)表達(dá)式:切入點(diǎn)表達(dá)式?jīng)Q定了我們關(guān)注哪些方法的執(zhí)行,詳細(xì)表達(dá)式語(yǔ)法后面在說(shuō)帽衙。
-
包含名字和任意參數(shù)的簽名:一個(gè)切入點(diǎn)簽名通過(guò)一個(gè)普通的方法定義來(lái)提供,并且切入點(diǎn)表達(dá)式使用
- 語(yǔ)法格式
@Pointcut(value="", argNames = "")
- 參數(shù)說(shuō)明
- value
指定切入點(diǎn)表達(dá)式 - argNames
指定命名切入點(diǎn)方法參數(shù)列表參數(shù)名字菜皂,可以有多個(gè)用“,”分隔厉萝,這些參數(shù)將傳遞給通知方法同名的參數(shù)
- value
- 示例代碼
@Aspect public class LogAspect { // 也可以在通知上定義,當(dāng)需要復(fù)用切入點(diǎn)的時(shí)候 @Pointcut("execution(* com.wener.example.aop.aspect.*.*(..))") // 返回值 必須是void類型 public void log() { } }
- 備注
切入點(diǎn)的定義是非必要的,也可以直接在通知上使用切入點(diǎn)表達(dá)式
4恍飘、聲明通知
4.1榨崩、說(shuō)明
通知是跟一個(gè)切入點(diǎn)表達(dá)式關(guān)聯(lián)起來(lái)的,并且在切入點(diǎn)匹配的方法執(zhí)行之前或者之后或者前后運(yùn)行章母。 切入點(diǎn)表達(dá)式可能是指向已命名的切入點(diǎn)的簡(jiǎn)單引用或者是一個(gè)已經(jīng)聲明過(guò)的切入點(diǎn)表達(dá)式母蛛,通知的類型就是我們前面提到過(guò)的類型
4.2、前置通知
- 說(shuō)明
在關(guān)注點(diǎn)執(zhí)行前運(yùn)行的方法乳怎,切面里使用@Before
注解聲明前置通知 - 語(yǔ)法格式
@Before(value = "", argNames = "")
- 參數(shù)說(shuō)明
- value :指定切入點(diǎn)表達(dá)式或切入點(diǎn)名字彩郊;
- argNames: 用來(lái)接收AspectJ表達(dá)式中的參數(shù),并指定通知方法中的參數(shù)
- 示例代碼
import org.springframework.stereotype.Component; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.annotation.Before; @Aspect @Component public class LogAspect { /** * @Pointcut() 切入點(diǎn)表達(dá)式 */ @Pointcut("execution(* com.wener.example.aop.aspect.*.*(..))") public void logPointcut() { } /** * @Before 前置通知 * value:指定切入點(diǎn)表達(dá)式或命名切入點(diǎn); * argNames:與Schema方式配置中的同義蚪缀; */ @Before("logPointcut()") public void before() { System.out.println("前置通知"); } }
4.3秫逝、后置通知(最終通知)
- 說(shuō)明
不論一個(gè)方法是如何結(jié)束的,最終通知都會(huì)運(yùn)行询枚。使用@After
注解來(lái)聲明违帆。最終通知必須準(zhǔn)備處理正常返回和異常返回兩種情況。通常用它來(lái)釋放資源金蜀。相當(dāng)于異常處理里finally的代碼 - 語(yǔ)法格式
@After(value = "", argNames = "")
- 參數(shù)
- value :指定切入點(diǎn)表達(dá)式或切入點(diǎn)名字刷后;
- **argNames: **用來(lái)接收AspectJ表達(dá)式中的參數(shù),并指定通知方法中的參數(shù)
- 示例代碼
import org.springframework.stereotype.Component; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Before; @Aspect @Component public class LogAspect { /** * @Pointcut() 切入點(diǎn)表達(dá)式 */ @Pointcut("execution(* com.wener.example.aop.aspect.*.*(..))") public void logPointcut() { } /** * @After 后置通知 */ @After(value = "logPointcut()") public void after() { System.out.println("后置通知"); } }
4.4、返回通知
- 說(shuō)明
返回后通知通常在一個(gè)匹配的方法返回的時(shí)候執(zhí)行渊抄。使用@AfterReturning
注解來(lái)聲明 - 語(yǔ)法格式
@AfterReturning(value="",pointcut="",returning="",argNames="")
- 參數(shù)說(shuō)明
- value:指定切入點(diǎn)表達(dá)式或切入點(diǎn)名字尝胆;
- pointcut:指定切入點(diǎn)表達(dá)式或命名切入點(diǎn),如果指定了將覆蓋value屬性的护桦,pointcut具有高優(yōu)先級(jí)班巩;
- returning:如果你想獲取方法的返回值可以使用該參數(shù),在通知方法中定義參數(shù)就可以了
- argNames:用來(lái)接收AspectJ表達(dá)式中的參數(shù),并指定通知方法中的參數(shù)
- 示例代碼
import org.springframework.stereotype.Component; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.ProceedingJoinPoint; @Aspect @Component public class LogAspect { /** * @Pointcut() 切入點(diǎn)表達(dá)式 */ @Pointcut("execution(* com.wener.example.aop.aspect.*.*(..))") public void logPointcut() { } /** * 不獲取方法的返回值 */ @AfterReturning(value = "logPointcut()") public void AfterReturning1() { System.out.println("異常通知"); } /** * 獲取方法的返回值 * returning的賦值的名字,必須跟通知方法中參數(shù)的名字保持一致 */ @AfterReturning(value = "logPointcut()", returning = "val") public Object afterReturning(Object val) { System.out.println("返回后通知"); return val; } }
4.5、異常通知
- 說(shuō)明
拋出異常通知在一個(gè)方法拋出異常后執(zhí)行嘶炭。使用@AfterThrowing
注解來(lái)聲明 - 語(yǔ)法格式
@AfterThrowing(value="",pointcut="",throwing="",argNames="")
- 參數(shù)說(shuō)明
- value:指定切入點(diǎn)表達(dá)式或命名切入點(diǎn);
- pointcut:指定切入點(diǎn)表達(dá)式或命名切入點(diǎn)逊桦,如果指定了將覆蓋value屬性的眨猎,pointcut具有高優(yōu)先級(jí);
- throwing:異常類型强经;并且在通知方法中定義異常參數(shù)睡陪;
- argNames:用來(lái)接收AspectJ表達(dá)式中的參數(shù),并指定通知方法中的參數(shù);
- 示例代碼
import org.springframework.stereotype.Component; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.ProceedingJoinPoint; @Aspect @Component public class LogAspect { /** * @Pointcut() 切入點(diǎn)表達(dá)式 */ @Pointcut("execution(* com.wener.example.aop.aspect.*.*(..))") public void logPointcut() { } /** * @AfterThrowing 異常通知 * value:指定切入點(diǎn)表達(dá)式或命名切入點(diǎn)匿情; * throwing:異常類型兰迫。 */ @AfterThrowing("logPointcut()") public void afterThrowing() { System.out.println("異常通知"); } /** * 如果想要限制通知只在某種特定的異常被拋出的時(shí)候匹配,同時(shí)還想知道異常的一些信息炬称。 * 那我們就需要使用throwing屬性聲明響應(yīng) */ @AfterThrowing(value = "logPointcut()", throwing = "exception") public void afterThrowing(Exception exception) { System.out.println("異常通知"); } }
4.6汁果、環(huán)繞通知
- 說(shuō)明
環(huán)繞通知在一個(gè)方法執(zhí)行之前和之后執(zhí)行。它使得通知有機(jī)會(huì) 在一個(gè)方法執(zhí)行之前和執(zhí)行之后運(yùn)行玲躯。而且它可以決定這個(gè)方法在什么時(shí)候執(zhí)行据德,如何執(zhí)行鳄乏,甚至是否執(zhí)行。 環(huán)繞通知經(jīng)常在某線程安全的環(huán)境下棘利,你需要在一個(gè)方法執(zhí)行之前和之后共享某種狀態(tài)的時(shí)候使用橱野。 請(qǐng)盡量使用最簡(jiǎn)單的滿足你需求的通知。(比如如果簡(jiǎn)單的前置通知也可以適用的情況下不要使用環(huán)繞通知)善玫。- 使用
@Around
注解水援; - 環(huán)繞通知需要攜帶ProceedingJoinPoint類型的參數(shù);
- 且環(huán)繞通知必須有返回值茅郎,返回值即為有目標(biāo)方法的返回值蜗元。
- 使用
- 語(yǔ)法格式
@Around(value = "", argNames = "")
- 參數(shù)
- value :指定切入點(diǎn)表達(dá)式或切入點(diǎn)名字;
- **argNames: **用來(lái)接收AspectJ表達(dá)式中的參數(shù),并指定通知方法中的參數(shù)
- 示例代碼
import org.springframework.stereotype.Component; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.ProceedingJoinPoint; @Aspect @Component public class LogAspect { /** * @Pointcut() 切入點(diǎn)表達(dá)式 */ @Pointcut("execution(* com.wener.example.aop.aspect.*.*(..))") public void logPointcut() { } /** * @Around 環(huán)繞通知 * 比如 緩存切面只洒,如果緩存中有值许帐,就返回該值,否則調(diào)用proceed()方法 * value:指定切入點(diǎn)表達(dá)式或命名切入點(diǎn)毕谴; * 注意 第一個(gè)參數(shù)必須是 ProceedingJoinPoint對(duì)象 具體這個(gè)類的更多詳細(xì)使用看附錄: */ @Around(value = "logPointcut()") public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("環(huán)繞通知1"); Object obj = pjp.proceed(); System.out.println("環(huán)繞通知2"); return obj; } }
4.7成畦、通知參數(shù)
- 說(shuō)明
若想要在通知方法獲取被通知方法的參數(shù)共有兩種方式:自動(dòng)獲取、手動(dòng)指定- 自動(dòng)獲取參數(shù):通知類型可以通過(guò)參數(shù)JoinPoint或者 ProceedingJoinPoint 自動(dòng)獲取被通知方法的參數(shù)值并調(diào)用該方法
- 手動(dòng)指定參數(shù):即在配置切面時(shí)涝开,需在切面的通知與切面的切點(diǎn)中明確指定參數(shù)循帐。
- 手動(dòng)指定
- 在@pointcut中切入表達(dá)式中使用args聲明匹配的參數(shù),注意使用&&連接args
- 在@pointcut中切入表達(dá)式中使用參數(shù)argNames用來(lái)接收AspectJ表達(dá)式中的參數(shù),
argNames屬性是用于指定在表達(dá)式中應(yīng)用的參數(shù)名與Advice方法參數(shù)是如何對(duì)應(yīng)的 - 在通知方法中定義參數(shù)
- 手動(dòng)獲取指定參數(shù)
import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Aspect @Component public class LogAdviceParamsAspect { // 注意參數(shù)的個(gè)數(shù)必須一致,否則匹配不到 @Before(value = "execution(* com.wener.example.aop.aspect.*.*(..))&& args(id,name)", argNames = "id,name") public void testArgs(Object id, Object name) { System.out.println(id); System.out.println(name); } }
- 混用使用
當(dāng)同時(shí)采用自動(dòng)獲取參數(shù)與手動(dòng)指定參數(shù)時(shí)舀武,自動(dòng)獲取參數(shù)必須是第一個(gè)參數(shù)拄养,即ProceedingJoinPoint 等參數(shù)并需是通知方法定義的第一個(gè)參數(shù)import org.aopalliance.intercept.Joinpoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Aspect @Component public class LogAdviceParamsAspect { // args、argNames的參數(shù)名與testArgs()方法中參數(shù)名 保持一致 @Before(value = "execution(* com.wener.example.aop.aspect.*.*(..))&& args(id,name)", argNames = "id,name") public void testArgs(Object id, Object name) { System.out.println(id); System.out.println(name); } // 也可以不用argNames @Before(value = "execution(* com.wener.example.aop.aspect.*.*(..))&& args(id,name)") public void testArgs(Object id, Object name) { System.out.println(id); System.out.println(name); } @Around(value = "execution(* com.wener.example.aop.aspect.*.*(..))&&(args(id,name,..))", argNames = "pjp,id,name") public Object testAroundArgs(ProceedingJoinPoint pjp, Object id, Object name) throws Throwable { System.out.println("Around之前"); Object obj = pjp.proceed(); System.out.println(); return obj; } }
4.8 银舱、引入
- 說(shuō)明
有時(shí)候有一組共享公共行為類瘪匿。在OOP中,它們必須擴(kuò)展相同的基類或者實(shí)現(xiàn)相同的接口寻馏。此外棋弥,Java的單繼承機(jī)制僅允許一個(gè)類最多擴(kuò)展一個(gè)基類。所以诚欠,不能同時(shí)從多個(gè)實(shí)現(xiàn)類中繼承行為顽染。
解決方案:引入是AOP中的一種特殊的通知。它允許為一個(gè)接口提供實(shí)現(xiàn)類轰绵,使對(duì)象動(dòng)態(tài)的實(shí)現(xiàn)接口粉寞。就像對(duì)象在運(yùn)行時(shí)擴(kuò)展了實(shí)現(xiàn)類。而且左腔,可以用多個(gè)實(shí)現(xiàn)類將多個(gè)接口同時(shí)引入對(duì)象唧垦。這可以實(shí)現(xiàn)與多重繼承相同的效果。 - 在開(kāi)發(fā)中用的不是很多,所以不做過(guò)多的分析
5翔悠、聲明代理類
- 說(shuō)明
被代理的對(duì)象,跟前面說(shuō)的一樣,代理接口或者類都可以 - 示例代碼
public interface AspectDao { public void test(); public void testParams(int id, String name); public void testParams(Joinpoint jp, int id, String name); } @Component("aspectDao") public class AspectDaoImpl implements AspectDao { @Override public void test() { System.out.println("核心測(cè)試方法"); } @Override public void testParams(int id, String name) { System.out.println("帶參數(shù)的方法:" + "ID:" + id + "name:" + name); } }
6业崖、測(cè)試
- 示例代碼
ApplicationContext context = new ClassPathXmlApplicationContext("spring-aspect.xml"); AspectDao dao = (AspectDao) context.getBean("aspectDao"); dao.test(); dao.testParams(1,"hello");
7野芒、總結(jié)
- 使用@Aspect將POJO聲明為切面;
- 在切面類中使用@Pointcut進(jìn)行命名切入點(diǎn)聲明双炕;
- 定義通知方法,使用5中注解聲明狞悲,其中value用于定義切入點(diǎn)表達(dá)式或引用命名切入點(diǎn);
- 配置文件需要使用
<aop:aspectj-autoproxy/
來(lái)開(kāi)啟注解風(fēng)格的@AspectJ支持妇斤; - 將切面類和POJO類注冊(cè)到Spring容器中
七摇锋、基于xml的AOP編程(掌握)
1、說(shuō)明
如果比較喜歡使用XML格式站超,Spring2.0也提供了使用新的"aop"命名空間來(lái)定義一個(gè)切面荸恕。 和使用@AspectJ風(fēng)格完全一樣,切入點(diǎn)表達(dá)式和通知類型同樣得到了支持
AOP配置元素 | 用途 |
---|---|
<aop:config |
頂層的AOP配置元素,大多數(shù)的<aop:* 必須包含在<aop:config 元素內(nèi) |
<aop:aspect |
定義一個(gè)切面 |
<aop:pointcut |
定義一個(gè)切點(diǎn) |
<aop:advisor |
定義AOP通知器 |
<aop:before |
定義AOP前置通知 |
<aop:around |
定義AOP環(huán)繞通知 |
<aop:after-returning |
定義AOP返回通知 |
<aop:after-throwing |
定義AOP異常通知 |
<aop:after |
定義AOP后置通知(不管被通知的方法是否執(zhí)行成功) |
<aop:aspectj-autoproxy |
啟用@Aspect注解的切面 |
<aop:declare-parents |
以透明的方式為被通知的對(duì)象引入額外的接口 |
2死相、引入aop命名空間標(biāo)簽
- 說(shuō)明
在beans元素下 引入aop融求,聲明<aop-config
,在配置文件中算撮,我們可以聲明多個(gè)<aop-config
生宛。
注意:- 所有的切面和通知都必須定義在
<aop:config
元素內(nèi)部。 - 一個(gè)
<aop:config
可以包含pointcut肮柜,advisor和aspect元素 (注意這三個(gè)元素必須按照這個(gè)順序進(jìn)行聲明)
- 所有的切面和通知都必須定義在
- 示例代碼
<beans ... xmlns:aop="http://www.springframework.org/schema/aop" ... <aop:config </aop:config </beans
3陷舅、聲明一個(gè)切面
- 說(shuō)明
切面使用<aop:aspect
來(lái)聲明 - 示例代碼
<aop:config <aop:aspect id="myAspect" ref="myBean" ... </aop:aspect </aop:config <bean id="myBean" class="..." ... </bean
4、聲明一個(gè)切入點(diǎn)
- 說(shuō)明
一個(gè)命名切入點(diǎn)可以在<aop:config
元素中定義审洞,使用<aop:pointcut
聲明莱睁,這樣多個(gè)切面和通知就可以共享該切入點(diǎn),你也可以在切面中定義 - 示例代碼
<aop:config <aop:pointcut id="servicePointcut" expression="execution(* *.*(..))"/ </aop:config
<aop:config <aop:aspect id="myAspect" ref="myBean" <!--這個(gè)切入點(diǎn)只能在該 切面中使用 -- <aop:pointcut id="servicePointcut" expression="execution(* *.*(..))"/ </aop:aspect </aop:config
5芒澜、聲明通知
5.1仰剿、說(shuō)明
和@AspectJ風(fēng)格一樣,基于xml的風(fēng)格也支持5種通知類型并且兩者具有同樣的語(yǔ)義
5.2痴晦、前置通知
- 說(shuō)明
前置通知在匹配方法執(zhí)行前運(yùn)行酥馍。在<aop:aspect
中使用<aop:before
元素來(lái)聲明它 - 示例代碼
<aop:config <aop:pointcut id="servicePointcut" expression="execution(* *.*(..))"/ <aop:aspect id="beforeExample" ref="myBean" <aop:before pointcut-ref="servicePointcut" method="doBefore"/ </aop:aspect </aop:config
5.3、后置通知
- 說(shuō)明
后置通知在匹配的方法完全執(zhí)行后運(yùn)行阅酪。和前置通知一樣,在<aop:aspect
里面使用<aop:after-returning
聲明汁针,通知方法可以得到返回值术辐。使用returning屬性來(lái)指定傳遞返回值的參數(shù)名。 - 示例代碼
<aop:aspect id="afterReturningExample" ref="myBean" <aop:after-returning pointcut-ref="servicePointcut" method="doAfterReturning"/ ... </aop:aspect
<aop:aspect id="afterReturningExample" ref="myBean" <aop:after-returning pointcut-ref="servicePointcut" method="doAfterReturning"/ ... </aop:aspect
5.4施无、異常通知
- 說(shuō)明
異常通知在匹配方法拋出異常退出時(shí)執(zhí)行辉词。在<aop:aspect
中使用<after-throwing
元素來(lái)聲明,還可以使用throwing屬性來(lái)指定傳遞異常的參數(shù)名 - 示例代碼
<!-- 無(wú)返回值 -- <aop:aspect id="afterThrowingExample" ref="myBean" <aop:after-throwing pointcut-ref="servicePointcut" throwing="exception" method="doAfterThrowing"/ ... </aop:aspect
5.5猾骡、最終通知
- 說(shuō)明
最終通知無(wú)論如何都會(huì)在匹配方法退出后執(zhí)行瑞躺。在<aop:aspect
中使用<aop:after
元素來(lái)聲明 - 示例代碼
<aop:aspect id="afterFinallyExample" ref="myBean" <aop:after pointcut-ref="servicePointcut" method="doAfter"/ ... </aop:aspect
5.6敷搪、環(huán)繞通知
- 說(shuō)明
環(huán)繞通知在匹配方法運(yùn)行期的“周?chē)眻?zhí)行。 它有機(jī)會(huì)在目標(biāo)方法的前面和后面執(zhí)行幢哨,并決定什么時(shí)候運(yùn)行赡勘,怎么運(yùn)行,甚至是否運(yùn)行捞镰。環(huán)繞通知經(jīng)常在需要在一個(gè)方法執(zhí)行前后共享狀態(tài)信息闸与,并且是在線程安全的情況下使用 - 示例代碼
<aop:aspect id="aroundExample" ref="myBean" <aop:around pointcut-ref="servicePointcut" method="doAround"/ ... </aop:aspect
八、切面的優(yōu)先級(jí)
1岸售、說(shuō)明
在同一個(gè)連接點(diǎn)上應(yīng)用不止一個(gè)切面時(shí), 除非明確指定, 否則它們的優(yōu)先級(jí)是不確定的
- 切面的優(yōu)先級(jí)可以通過(guò)實(shí)現(xiàn) Ordered 接口或利用 @Order 注解指定.
實(shí)現(xiàn) Ordered 接口, getOrder() 方法的返回值越小, 優(yōu)先級(jí)越高. - 若使用 @Order 注解, 序號(hào)出現(xiàn)在注解中践樱,值越小優(yōu)先級(jí)越高
2、示例代碼
- 基于實(shí)現(xiàn)接口(了解)
@Aspect @Component public class LoggingAspect implements Ordered { @Override public int getOrder() { return 2; } }
@Aspect @Component public class ValidateAspect implements Ordered { @Override public int getOrder() { return 1; } }
- 基于注解
@Order(2) @Aspect @Component public class LoggingAspect { }
@Order(1) @Aspect @Component public class ValidateAspect { }
九凸丸、簡(jiǎn)單總結(jié)
- 切面的內(nèi)容可以復(fù)用
- 避免使用Proxy拷邢、CGLIB生成代理,這方面的工作全部框架去實(shí)現(xiàn)屎慢,開(kāi)發(fā)者可以專注于切面內(nèi)容本身
- 代碼與代碼之間沒(méi)有耦合瞭稼,如果攔截的方法有變化修改配置文件即可
十、附錄
1抛人、獲取目標(biāo)對(duì)象信息
1.1弛姜、JoinPoint 對(duì)象
- 說(shuō)明
JoinPoint對(duì)象封裝了SpringAop中切面方法的信息,在切面方法中添加JoinPoint參數(shù),就可以獲取到封裝了該方法信息的JoinPoint對(duì)象 - 重要方法
方法 說(shuō)明 Signature getSignature(); 獲取封裝了署名信息的對(duì)象,在該對(duì)象中可以獲取到目標(biāo)方法名,所屬類的Class等信息 Object[] getArgs(); 獲取連接點(diǎn)方法運(yùn)行時(shí)的入?yún)⒘斜?/strong> Object getTarget(); 獲取連接點(diǎn)所在的目標(biāo)對(duì)象 Object getThis(); 獲取代理對(duì)象本身
1.2、ProceedingJoinPoint
- 說(shuō)明
ProceedingJoinPoint繼承JoinPoint子接口妖枚,并且只能用于@Around的切面方法中 - 新增方法
方法名 功能 Object proceed() throws Throwable 執(zhí)行目標(biāo)方法 Object proceed(Object[] var1) throws Throwable 傳入的新的參數(shù)去執(zhí)行目標(biāo)方法
2廷臼、示例代碼
- 案例1
Aspect @Component public class JoinPointerAspect { /** * 定義一個(gè)切入點(diǎn)表達(dá)式,用來(lái)確定哪些類需要代理 */ @Pointcut("execution(* com.wener.example.aop.aspect.*.*(..))") public void declareJoinPointer() {} /** * 前置方法,在目標(biāo)方法執(zhí)行前執(zhí)行 * @param joinPoint 封裝了代理方法信息的對(duì)象,若用不到則可以忽略不寫(xiě) */ @Before("declareJoinPointer()") public void beforeMethod(JoinPoint joinPoint){ System.out.println("目標(biāo)方法名:" + joinPoint.getSignature().getName()); System.out.println("目標(biāo)方法所屬類的名:" + joinPoint.getSignature().getDeclaringType().getSimpleName()); System.out.println("目標(biāo)方法聲明類型:" + Modifier.toString(joinPoint.getSignature().getModifiers())); //獲取傳入目標(biāo)方法的參數(shù) Object[] args = joinPoint.getArgs(); for (int i = 0; i < args.length; i++) { System.out.println("第" + (i+1) + "個(gè)參數(shù)為:" + args[i]); } System.out.println("被代理的對(duì)象:" + joinPoint.getTarget()); System.out.println("代理對(duì)象自己:" + joinPoint.getThis()); } /** * 環(huán)繞方法,可自定義目標(biāo)方法執(zhí)行的時(shí)機(jī) * @param pjd JoinPoint的子接口,添加了 * Object proceed() throws Throwable 執(zhí)行目標(biāo)方法 * Object proceed(Object[] var1) throws Throwable 傳入的新的參數(shù)去執(zhí)行目標(biāo)方法 * * @return 此方法需要返回值,返回值視為目標(biāo)方法的返回值 */ @Around("declareJoinPointer()") public Object aroundMethod(ProceedingJoinPoint pjd){ Object result = null; try { //前置通知 System.out.println("目標(biāo)方法執(zhí)行前..."); //執(zhí)行目標(biāo)方法 //result = pjd.proeed(); //用新的參數(shù)值執(zhí)行目標(biāo)方法 result = pjd.proceed(new Object[]{"hello","world"}); //返回通知 System.out.println("目標(biāo)方法返回結(jié)果后..."); } catch (Throwable e) { //異常通知 System.out.println("執(zhí)行目標(biāo)方法異常后..."); throw new RuntimeException(e); } //后置通知 System.out.println("目標(biāo)方法執(zhí)行后..."); return result; } }
- 執(zhí)行流程
- AOP定義了一個(gè)切面(Aspect),一個(gè)切面包含了切入點(diǎn)绝页,通知荠商,引入,這個(gè)切面上定義了許多的切入點(diǎn)(Pointcut)续誉,一旦訪問(wèn)過(guò)程中有對(duì)象的方法跟切入點(diǎn)匹配那么就會(huì)被AOP攔截莱没。
- 此時(shí)該對(duì)象就是目標(biāo)對(duì)象(Target Object)而匹配的方法就是連接點(diǎn)(Join Point)。
- 緊接著AOP會(huì)用過(guò)JDK動(dòng)態(tài)代理或者CGLIB生成一個(gè)目標(biāo)對(duì)象的代理對(duì)象(AOP proxy)酷鸦,這個(gè)過(guò)程就是織入(Weaving)饰躲。
- 這個(gè)時(shí)候我們就可以按照我們的需求對(duì)連接點(diǎn)進(jìn)行一些攔截處理。
- 可以看到臼隔,我們可以引入(Introduction)一個(gè)新的接口嘹裂,讓代理對(duì)象來(lái)實(shí)現(xiàn)這個(gè)接口來(lái),以實(shí)現(xiàn)額外的方法和字段摔握。也可以在連接點(diǎn)上進(jìn)行通知(Advice)寄狼,通知的類型包括了前置通知,返回后通知氨淌,拋出異常后通知泊愧,后置通知伊磺,環(huán)繞通知。
- 最后也是最騷的是整個(gè)過(guò)程不會(huì)改變代碼原有的邏輯