Spring AOP切點(diǎn)表達(dá)式用法總結(jié)

1. 簡(jiǎn)介

???????面向?qū)ο缶幊蹋卜Q為OOP(即Object Oriented Programming)最大的優(yōu)點(diǎn)在于能夠?qū)I(yè)務(wù)模塊進(jìn)行封裝,從而達(dá)到功能復(fù)用的目的。通過(guò)面向?qū)ο缶幊蹋煌哪0蹇梢韵嗷ソM裝,從而實(shí)現(xiàn)更為復(fù)雜的業(yè)務(wù)模塊,其結(jié)構(gòu)形式可用下圖表示:

業(yè)務(wù)模塊

????????面向?qū)ο缶幊探鉀Q了業(yè)務(wù)模塊的封裝復(fù)用的問(wèn)題,但是對(duì)于某些模塊,其本身并不獨(dú)屬于摸個(gè)業(yè)務(wù)模塊,而是根據(jù)不同的情況,貫穿于某幾個(gè)或全部的模塊之間的。例如登錄驗(yàn)證,其只開(kāi)放幾個(gè)可以不用登錄的接口給用戶使用(一般登錄使用攔截器實(shí)現(xiàn),但是其切面思想是一致的);再比如性能統(tǒng)計(jì),其需要記錄每個(gè)業(yè)務(wù)模塊的調(diào)用,并且監(jiān)控器調(diào)用時(shí)間。可以看到,這些橫貫于每個(gè)業(yè)務(wù)模塊的模塊,如果使用面向?qū)ο蟮姆绞剑敲淳托枰谝逊庋b的每個(gè)模塊中添加相應(yīng)的重復(fù)代碼,對(duì)于這種情況,面向切面編程就可以派上用場(chǎng)了。

???????面向切面編程,也稱為AOP(即Aspect Oriented Programming),指的是將一定的切面邏輯按照一定的方式編織到指定的業(yè)務(wù)模塊中,從而將這些業(yè)務(wù)模塊的調(diào)用包裹起來(lái)。如下是其結(jié)構(gòu)示意圖:

切面編程模塊

2. AOP的各個(gè)扮演者

2.1 AOP的主要角色

  • 切面:使用切點(diǎn)表達(dá)式表示,指定了當(dāng)前切面邏輯所要包裹的業(yè)務(wù)模塊的范圍大小;
  • Advice:也即切面邏輯,指定了當(dāng)前用于包裹切面指定的業(yè)務(wù)模塊的邏輯。

2.2 Advice的主要類型

  • @Before:該注解標(biāo)注的方法在業(yè)務(wù)模塊代碼執(zhí)行之前執(zhí)行,其不能阻止業(yè)務(wù)模塊的執(zhí)行,除非拋出異常;
  • @AfterReturning:該注解標(biāo)注的方法在業(yè)務(wù)模塊代碼執(zhí)行之后執(zhí)行;
  • @AfterThrowing:該注解標(biāo)注的方法在業(yè)務(wù)模塊拋出指定異常后執(zhí)行;
  • @After:該注解標(biāo)注的方法在所有的Advice執(zhí)行完成后執(zhí)行,無(wú)論業(yè)務(wù)模塊是否拋出異常,類似于finally的作用;
  • @Around:該注解功能最為強(qiáng)大,其所標(biāo)注的方法用于編寫包裹業(yè)務(wù)模塊執(zhí)行的代碼,其可以傳入一個(gè)ProceedingJoinPoint用于調(diào)用業(yè)務(wù)模塊的代碼,無(wú)論是調(diào)用前邏輯還是調(diào)用后邏輯,都可以在該方法中編寫,甚至其可以根據(jù)一定的條件而阻斷業(yè)務(wù)模塊的調(diào)用;
  • @DeclareParents:其是一種Introduction類型的模型,在屬性聲明上使用,主要用于為指定的業(yè)務(wù)模塊添加新的接口和相應(yīng)的實(shí)現(xiàn)。
  • @Aspect:嚴(yán)格來(lái)說(shuō),其不屬于一種Advice,該注解主要用在類聲明上,指明當(dāng)前類是一個(gè)組織了切面邏輯的類,并且該注解中可以指定當(dāng)前類是何種實(shí)例化方式,主要有三種:singleton、perthis和pertarget,具體的使用方式后面會(huì)進(jìn)行講解。

????????這里需要說(shuō)明的是,@Before是業(yè)務(wù)邏輯執(zhí)行前執(zhí)行,與其對(duì)應(yīng)的是@AfterReturning,而不是@After,@After是所有的切面邏輯執(zhí)行完之后才會(huì)執(zhí)行,無(wú)論是否拋出異常。

3. 切點(diǎn)表達(dá)式

3.1 execution

???????由于Spring切面粒度最小是達(dá)到方法級(jí)別,而execution表達(dá)式可以用于明確指定方法返回類型,類名,方法名和參數(shù)名等與方法相關(guān)的部件,并且在Spring中,大部分需要使用AOP的業(yè)務(wù)場(chǎng)景也只需要達(dá)到方法級(jí)別即可,因而execution表達(dá)式的使用是最為廣泛的。如下是execution表達(dá)式的語(yǔ)法:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)

???????這里問(wèn)號(hào)表示當(dāng)前項(xiàng)可以有也可以沒(méi)有,其中各項(xiàng)的語(yǔ)義如下:

  • modifiers-pattern:方法的可見(jiàn)性,如public,protected;
  • ret-type-pattern:方法的返回值類型,如int,void等;
  • declaring-type-pattern:方法所在類的全路徑名,如com.spring.Aspect;
  • name-pattern:方法名類型,如buisinessService();
  • param-pattern:方法的參數(shù)類型,如java.lang.String;
  • throws-pattern:方法拋出的異常類型,如java.lang.Exception;

????????如下是一個(gè)使用execution表達(dá)式的例子:

execution(public * com.spring.service.BusinessObject.businessService(java.lang.String,..))

???????上述切點(diǎn)表達(dá)式將會(huì)匹配使用public修飾,返回值為任意類型,并且是com.spring.BusinessObject類中名稱為businessService的方法,方法可以有多個(gè)參數(shù),但是第一個(gè)參數(shù)必須是java.lang.String類型的方法。上述示例中我們使用了..通配符,關(guān)于通配符的類型,主要有兩種:

  • *通配符,該通配符主要用于匹配單個(gè)單詞,或者是以某個(gè)詞為前綴或后綴的單詞。

???????如下示例表示返回值為任意類型,在com.spring.service.BusinessObject類中,并且參數(shù)個(gè)數(shù)為零的方法:

execution(* com.spring.service.BusinessObject.*())

???????下述示例表示返回值為任意類型,在com.spring.service包中,以Business為前綴的類,并且是類中參數(shù)個(gè)數(shù)為零方法:

execution(* com.spring.service.Business*.*())
  • ..通配符,該通配符表示0個(gè)或多個(gè)項(xiàng),主要用于declaring-type-pattern和param-pattern中,如果用于declaring-type-pattern中,則表示匹配當(dāng)前包及其子包,如果用于param-pattern中,則表示匹配0個(gè)或多個(gè)參數(shù)。

???????如下示例表示匹配返回值為任意類型,并且是com.spring.service包及其子包下的任意類的名稱為businessService的方法,而且該方法不能有任何參數(shù):

execution(* com.spring.service..*.businessService())

???????這里需要說(shuō)明的是,包路徑service..*.businessService()中的..應(yīng)該理解為延續(xù)前面的service路徑,表示到service路徑為止,或者繼續(xù)延續(xù)service路徑,從而包括其子包路徑;后面的*.businessService(),這里的*表示匹配一個(gè)單詞,因?yàn)槭窃诜椒埃蚨硎酒ヅ淙我獾念悺?/p>

???????如下示例是使用..表示任意個(gè)數(shù)的參數(shù)的示例,需要注意,表示參數(shù)的時(shí)候可以在括號(hào)中事先指定某些類型的參數(shù),而其余的參數(shù)則由..進(jìn)行匹配:

execution(* com.spring.service.BusinessObject.businessService(java.lang.String,..))

3.2 within

???????within表達(dá)式的粒度為類,其參數(shù)為全路徑的類名(可使用通配符),表示匹配當(dāng)前表達(dá)式的所有類都將被當(dāng)前方法環(huán)繞。如下是within表達(dá)式的語(yǔ)法:

within(declaring-type-pattern)

???????within表達(dá)式只能指定到類級(jí)別,如下示例表示匹配com.spring.service.BusinessObject中的所有方法:

within(com.spring.service.BusinessObject)

???????within表達(dá)式路徑和類名都可以使用通配符進(jìn)行匹配,比如如下表達(dá)式將匹配com.spring.service包下的所有類,不包括子包中的類:

within(com.spring.service.*)

???????如下表達(dá)式表示匹配com.spring.service包及子包下的所有類:

within(com.spring.service..*)

3.3 args

???????args表達(dá)式的作用是匹配指定參數(shù)類型和指定參數(shù)數(shù)量的方法,無(wú)論其類路徑或者是方法名是什么。這里需要注意的是,args指定的參數(shù)必須是全路徑的。如下是args表達(dá)式的語(yǔ)法:

args(param-pattern)

???????如下示例表示匹配所有只有一個(gè)參數(shù),并且參數(shù)類型是java.lang.String類型的方法:

args(java.lang.String)

???????也可以使用通配符,但這里通配符只能使用..,而不能使用*。如下是使用通配符的實(shí)例,該切點(diǎn)表達(dá)式將匹配第一個(gè)參數(shù)為java.lang.String,最后一個(gè)參數(shù)為java.lang.Integer,并且中間可以有任意個(gè)數(shù)和類型參數(shù)的方法:

args(java.lang.String,..,java.lang.Integer)

3.4 this和target

???????this和target需要放在一起進(jìn)行講解,主要目的是對(duì)其進(jìn)行區(qū)別。this和target表達(dá)式中都只能指定類或者接口,在面向切面編程規(guī)范中,this表示匹配調(diào)用當(dāng)前切點(diǎn)表達(dá)式所指代對(duì)象方法的對(duì)象,target表示匹配切點(diǎn)表達(dá)式指定類型的對(duì)象。比如有兩個(gè)類A和B,并且A調(diào)用了B的某個(gè)方法,如果切點(diǎn)表達(dá)式為this(B),那么A的實(shí)例將會(huì)被匹配,也即其會(huì)被使用當(dāng)前切點(diǎn)表達(dá)式的Advice環(huán)繞;如果這里切點(diǎn)表達(dá)式為target(B),那么B的實(shí)例也即被匹配,其將會(huì)被使用當(dāng)前切點(diǎn)表達(dá)式的Advice環(huán)繞。

???????在講解Spring中的this和target的使用之前,首先需要講解一個(gè)概念:業(yè)務(wù)對(duì)象(目標(biāo)對(duì)象)和代理對(duì)象。對(duì)于切面編程,有一個(gè)目標(biāo)對(duì)象,也有一個(gè)代理對(duì)象,目標(biāo)對(duì)象是我們聲明的業(yè)務(wù)邏輯對(duì)象,而代理對(duì)象是使用切面邏輯對(duì)業(yè)務(wù)邏輯進(jìn)行包裹之后生成的對(duì)象。如果使用的是Jdk動(dòng)態(tài)代理,那么業(yè)務(wù)對(duì)象和代理對(duì)象將是兩個(gè)對(duì)象,在調(diào)用代理對(duì)象邏輯時(shí),其切面邏輯中會(huì)調(diào)用目標(biāo)對(duì)象的邏輯;如果使用的是Cglib代理,由于是使用的子類進(jìn)行切面邏輯織入的,那么只有一個(gè)對(duì)象,即織入了代理邏輯的業(yè)務(wù)類的子類對(duì)象,此時(shí)是不會(huì)生成業(yè)務(wù)類的對(duì)象的。

???????在Spring中,其對(duì)this的語(yǔ)義進(jìn)行了改寫,即如果當(dāng)前對(duì)象生成的代理對(duì)象符合this指定的類型,那么就為其織入切面邏輯。簡(jiǎn)單的說(shuō)就是,this將匹配代理對(duì)象為指定類型的類。target的語(yǔ)義則沒(méi)有發(fā)生變化,即其將匹配業(yè)務(wù)對(duì)象為指定類型的類。如下是使用this和target表達(dá)式的簡(jiǎn)單示例:

this(com.spring.service.BusinessObject)
target(com.spring.service.BusinessObject)

???????通過(guò)上面的講解可以看出,this和target的使用區(qū)別其實(shí)不大,大部分情況下其使用效果是一樣的,但其區(qū)別也還是有的。Spring使用的代理方式主要有兩種:Jdk代理和Cglib代理(關(guān)于這兩種代理方式的講解可以查看本人的文章代理模式實(shí)現(xiàn)方式及優(yōu)缺點(diǎn)對(duì)比)。針對(duì)這兩種代理類型,關(guān)于目標(biāo)對(duì)象與代理對(duì)象,理解如下兩點(diǎn)是非常重要的:

  • 如果目標(biāo)對(duì)象被代理的方法是其實(shí)現(xiàn)的某個(gè)接口的方法,那么將會(huì)使用Jdk代理生成代理對(duì)象,此時(shí)代理對(duì)象和目標(biāo)對(duì)象是兩個(gè)對(duì)象,并且都實(shí)現(xiàn)了該接口;
  • 如果目標(biāo)對(duì)象是一個(gè)類,并且其沒(méi)有實(shí)現(xiàn)任意接口,那么將會(huì)使用Cglib代理生成代理對(duì)象,并且只會(huì)生成一個(gè)對(duì)象,即Cglib生成的代理類的對(duì)象。

???????結(jié)合上述兩點(diǎn)說(shuō)明,這里理解this和target的異同就相對(duì)比較簡(jiǎn)單了。我們這里分三種情況進(jìn)行說(shuō)明:

  • this(SomeInterface)或target(SomeInterface):這種情況下,無(wú)論是對(duì)于Jdk代理還是Cglib代理,其目標(biāo)對(duì)象和代理對(duì)象都是實(shí)現(xiàn)SomeInterface接口的(Cglib生成的目標(biāo)對(duì)象的子類也是實(shí)現(xiàn)了SomeInterface接口的),因而this和target語(yǔ)義都是符合的,此時(shí)這兩個(gè)表達(dá)式的效果一樣;
  • this(SomeObject)或target(SomeObject),這里SomeObject沒(méi)實(shí)現(xiàn)任何接口:這種情況下,Spring會(huì)使用Cglib代理生成SomeObject的代理類對(duì)象,由于代理類是SomeObject的子類,子類的對(duì)象也是符合SomeObject類型的,因而this將會(huì)被匹配,而對(duì)于target,由于目標(biāo)對(duì)象本身就是SomeObject類型,因而這兩個(gè)表達(dá)式的效果一樣;
  • this(SomeObject)或target(SomeObject),這里SomeObject實(shí)現(xiàn)了某個(gè)接口:對(duì)于這種情況,雖然表達(dá)式中指定的是一種具體的對(duì)象類型,但由于其實(shí)現(xiàn)了某個(gè)接口,因而Spring默認(rèn)會(huì)使用Jdk代理為其生成代理對(duì)象,Jdk代理生成的代理對(duì)象與目標(biāo)對(duì)象實(shí)現(xiàn)的是同一個(gè)接口,但代理對(duì)象與目標(biāo)對(duì)象還是不同的對(duì)象,由于代理對(duì)象不是SomeObject類型的,因而此時(shí)是不符合this語(yǔ)義的,而由于目標(biāo)對(duì)象就是SomeObject類型,因而target語(yǔ)義是符合的,此時(shí)this和target的效果就產(chǎn)生了區(qū)別;這里如果強(qiáng)制Spring使用Cglib代理,因而生成的代理對(duì)象都是SomeObject子類的對(duì)象,其是SomeObject類型的,因而this和target的語(yǔ)義都符合,其效果就是一致的。

???????關(guān)于this和target的異同,我們使用如下示例進(jìn)行簡(jiǎn)單演示:

// 目標(biāo)類
public class Apple {
  public void eat() {
    System.out.println("Apple.eat method invoked.");
  }
}
// 切面類
@Aspect
public class MyAspect {
  @Around("this(com.business.Apple)")
  public Object around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("this is before around advice");
    Object result = pjp.proceed();
    System.out.println("this is after around advice");
    return result;
  }
}
<!-- bean聲明文件 -->
<bean id="apple" class="chapter7.eg1.Apple"/>
<bean id="aspect" class="chapter7.eg6.MyAspect"/>
<aop:aspectj-autoproxy/>
// 驅(qū)動(dòng)類
public class AspectApp {
  public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    Apple fruit = (Apple) context.getBean("apple");
    fruit.eat();
  }
}

???????執(zhí)行驅(qū)動(dòng)類中的main方法,結(jié)果如下:

this is before around advice
Apple.eat method invoked.
this is after around advice

???????上述示例中,Apple沒(méi)有實(shí)現(xiàn)任何接口,因而使用的是Cglib代理,this表達(dá)式會(huì)匹配Apple對(duì)象。這里將切點(diǎn)表達(dá)式更改為target,還是執(zhí)行上述代碼,會(huì)發(fā)現(xiàn)結(jié)果還是一樣的:

target(com.business.Apple)

???????如果我們對(duì)Apple的聲明進(jìn)行修改,使其實(shí)現(xiàn)一個(gè)接口,那么這里就會(huì)顯示出this和target的執(zhí)行區(qū)別了:

public class Apple implements IApple {
  public void eat() {
    System.out.println("Apple.eat method invoked.");
  }
}
public class AspectApp {
  public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    Fruit fruit = (Fruit) context.getBean("apple");
    fruit.eat();
  }
}

???????我們還是執(zhí)行上述代碼,對(duì)于this表達(dá)式,其執(zhí)行結(jié)果如下:

Apple.eat method invoked.

???????對(duì)于target表達(dá)式,其執(zhí)行結(jié)果如下:

this is before around advice
Apple.eat method invoked.
this is after around advice

???????可以看到,這種情況下this和target表達(dá)式的執(zhí)行結(jié)果是不一樣的,這正好符合我們前面講解的第三種情況。

3.5 @within

???????前面我們講解了within的語(yǔ)義表示匹配指定類型的類實(shí)例,這里的@within表示匹配帶有指定注解的類,其使用語(yǔ)法如下所示:

@within(annotation-type)

???????如下所示示例表示匹配使用com.spring.annotation.BusinessAspect注解標(biāo)注的類:

@within(com.spring.annotation.BusinessAspect)

???????這里我們使用一個(gè)例子演示@within的用法(這里驅(qū)動(dòng)類和xml文件配置與3.4節(jié)使用的一致,這里省略):

// 注解類
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface FruitAspect {
}
// 目標(biāo)類
@FruitAspect
public class Apple {
  public void eat() {
    System.out.println("Apple.eat method invoked.");
  }
}
// 切面類
@Aspect
public class MyAspect {
  @Around("@within(com.business.annotation.FruitAspect)")
  public Object around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("this is before around advice");
    Object result = pjp.proceed();
    System.out.println("this is after around advice");
    return result;
  }
}

???????上述切面表示匹配使用FruitAspect注解的類,而Apple則使用了該注解,因而Apple類方法的調(diào)用會(huì)被切面環(huán)繞,執(zhí)行運(yùn)行驅(qū)動(dòng)類可得到如下結(jié)果,說(shuō)明Apple.eat()方法確實(shí)被環(huán)繞了:

this is before around advice
Apple.eat method invoked.
this is after around advice

3.6 @annotation

???????@annotation的使用方式與@within的相似,表示匹配使用@annotation指定注解標(biāo)注的方法將會(huì)被環(huán)繞,其使用語(yǔ)法如下:

@annotation(annotation-type)

???????如下示例表示匹配使用com.spring.annotation.BusinessAspect注解標(biāo)注的方法:

@annotation(com.spring.annotation.BusinessAspect)

???????這里我們繼續(xù)復(fù)用3.5節(jié)使用的例子進(jìn)行講解@annotation的用法,只是這里需要對(duì)Apple和MyAspect使用和指定注解的方式進(jìn)行修改,F(xiàn)ruitAspect不用修改的原因是聲明該注解時(shí)已經(jīng)指定了其可以使用在類,方法和參數(shù)上:

// 目標(biāo)類,將FruitAspect移到了方法上
public class Apple {
  @FruitAspect
  public void eat() {
    System.out.println("Apple.eat method invoked.");
  }
}
@Aspect
public class MyAspect {
  @Around("@annotation(com.business.annotation.FruitAspect)")
  public Object around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("this is before around advice");
    Object result = pjp.proceed();
    System.out.println("this is after around advice");
    return result;
  }
}

???????這里Apple.eat()方法使用FruitAspect注解進(jìn)行了標(biāo)注,因而該方法的執(zhí)行會(huì)被切面環(huán)繞,其執(zhí)行結(jié)果如下:

this is before around advice
Apple.eat method invoked.
this is after around advice

3.7 @args

???????@within和@annotation分別表示匹配使用指定注解標(biāo)注的類和標(biāo)注的方法將會(huì)被匹配,@args則表示使用指定注解標(biāo)注的類作為某個(gè)方法的參數(shù)時(shí)該方法將會(huì)被匹配。如下是@args注解的語(yǔ)法:

@args(annotation-type)

???????如下示例表示匹配使用了com.spring.annotation.FruitAspect注解標(biāo)注的類作為參數(shù)的方法:

@args(com.spring.annotation.FruitAspect)

???????這里我們使用如下示例對(duì)@args的用法進(jìn)行講解:

<!-- xml配置文件 -->
<bean id="bucket" class="chapter7.eg1.FruitBucket"/>
<bean id="aspect" class="chapter7.eg6.MyAspect"/>
<aop:aspectj-autoproxy/>
// 使用注解標(biāo)注的參數(shù)類
@FruitAspect
public class Apple {}
// 使用Apple參數(shù)的目標(biāo)類
public class FruitBucket {
  public void putIntoBucket(Apple apple) {
    System.out.println("put apple into bucket.");
  }
}
@Aspect
public class MyAspect {
  @Around("@args(chapter7.eg6.FruitAspect)")
  public Object around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("this is before around advice");
    Object result = pjp.proceed();
    System.out.println("this is after around advice");
    return result;
  }
}
// 驅(qū)動(dòng)類
public class AspectApp {
  public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    FruitBucket bucket = (FruitBucket) context.getBean("bucket");
    bucket.putIntoBucket(new Apple());
  }
}

???????這里FruitBucket.putIntoBucket(Apple)方法的參數(shù)Apple使用了@args注解指定的FruitAspect進(jìn)行了標(biāo)注,因而該方法的調(diào)用將會(huì)被環(huán)繞。執(zhí)行驅(qū)動(dòng)類,結(jié)果如下:

this is before around advice
put apple into bucket.
this is after around advice

3.8 @DeclareParents

???????@DeclareParents也稱為Introduction(引入),表示為指定的目標(biāo)類引入新的屬性和方法。關(guān)于@DeclareParents的原理其實(shí)比較好理解,因?yàn)闊o(wú)論是Jdk代理還是Cglib代理,想要引入新的方法,只需要通過(guò)一定的方式將新聲明的方法織入到代理類中即可,因?yàn)榇眍惗际切律傻念悾蚨椚脒^(guò)程也比較方便。如下是@DeclareParents的使用語(yǔ)法:

@DeclareParents(value = "TargetType", defaultImpl = WeaverType.class)
private WeaverInterface attribute;

???????這里TargetType表示要織入的目標(biāo)類型(帶全路徑),WeaverInterface中聲明了要添加的方法,WeaverType中聲明了要織入的方法的具體實(shí)現(xiàn)。如下示例表示在Apple類中織入IDescriber接口聲明的方法:

@DeclareParents(value = "com.spring.service.Apple", defaultImpl = DescriberImpl.class)
private IDescriber describer;

???????這里我們使用一個(gè)如下實(shí)例對(duì)@DeclareParents的使用方式進(jìn)行講解,配置文件與3.4節(jié)的一致,這里略:

// 織入方法的目標(biāo)類
public class Apple {
  public void eat() {
    System.out.println("Apple.eat method invoked.");
  }
}
// 要織入的接口
public interface IDescriber {
  void desc();
}
// 要織入接口的默認(rèn)實(shí)現(xiàn)
public class DescriberImpl implements IDescriber {
  @Override
  public void desc() {
    System.out.println("this is an introduction describer.");
  }
}
// 切面實(shí)例
@Aspect
public class MyAspect {
  @DeclareParents(value = "com.spring.service.Apple", defaultImpl = DescriberImpl.class)
  private IDescriber describer;
}
// 驅(qū)動(dòng)類
public class AspectApp {
  public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    IDescriber describer = (IDescriber) context.getBean("apple");
    describer.desc();
  }
}

???????在MyAspect中聲明了我們需要將IDescriber的方法織入到Apple實(shí)例中,在驅(qū)動(dòng)類中我們可以看到,我們獲取的是apple實(shí)例,但是得到的bean卻可以強(qiáng)轉(zhuǎn)為IDescriber類型,因而說(shuō)明我們的織入操作成功了。

3.9 perthis和pertarget

???????在Spring AOP中,切面類的實(shí)例只有一個(gè),比如前面我們一直使用的MyAspect類,假設(shè)我們使用的切面類需要具有某種狀態(tài),以適用某些特殊情況的使用,比如多線程環(huán)境,此時(shí)單例的切面類就不符合我們的要求了。在Spring AOP中,切面類默認(rèn)都是單例的,但其還支持另外兩種多例的切面實(shí)例的切面,即perthis和pertarget,需要注意的是perthis和pertarget都是使用在切面類的@Aspect注解中的。這里perthis和pertarget表達(dá)式中都是指定一個(gè)切面表達(dá)式,其語(yǔ)義與前面講解的this和target非常的相似,perthis表示如果某個(gè)類的代理類符合其指定的切面表達(dá)式,那么就會(huì)為每個(gè)符合條件的目標(biāo)類都聲明一個(gè)切面實(shí)例;pertarget表示如果某個(gè)目標(biāo)類符合其指定的切面表達(dá)式,那么就會(huì)為每個(gè)符合條件的類聲明一個(gè)切面實(shí)例。從上面的語(yǔ)義可以看出,perthis和pertarget的含義是非常相似的。如下是perthis和pertarget的使用語(yǔ)法:

perthis(pointcut-expression)
pertarget(pointcut-expression)

???????由于perthis和pertarget的使用效果大部分情況下都是一致的,我們這里主要講解perthis和pertarget的區(qū)別。關(guān)于perthis和pertarget的使用,需要注意的一個(gè)點(diǎn)是,由于perthis和pertarget都是為每個(gè)符合條件的類聲明一個(gè)切面實(shí)例,因而切面類在配置文件中的聲明上一定要加上prototype,否則Spring啟動(dòng)是會(huì)報(bào)錯(cuò)的。如下是我們使用的示例:

<!-- xml配置文件 -->
<bean id="apple" class="chapter7.eg1.Apple"/>
<bean id="aspect" class="chapter7.eg6.MyAspect" scope="prototype"/>
<aop:aspectj-autoproxy/>
// 目標(biāo)類實(shí)現(xiàn)的接口
public interface Fruit {
  void eat();
}
// 業(yè)務(wù)類
public class Apple implements Fruit {
  public void eat() {
    System.out.println("Apple.eat method invoked.");
  }
}
// 切面類
@Aspect("perthis(this(com.spring.service.Apple))")
public class MyAspect {

  public MyAspect() {
    System.out.println("create MyAspect instance, address: " + toString());
  }

  @Around("this(com.spring.service.Apple)")
  public Object around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("this is before around advice");
    Object result = pjp.proceed();
    System.out.println("this is after around advice");
    return result;
  }
}
// 驅(qū)動(dòng)類
public class AspectApp {
  public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    Fruit fruit = context.getBean(Fruit.class);
    fruit.eat();
  }
}

???????這里我們使用的切面表達(dá)式語(yǔ)法為perthis(this(com.spring.service.Apple)),這里this表示匹配代理類是Apple類型的類,perthis則表示會(huì)為這些類的每個(gè)實(shí)例都創(chuàng)建一個(gè)切面類。由于Apple實(shí)現(xiàn)了Fruit接口,因而Spring使用Jdk動(dòng)態(tài)代理為其生成代理類,也就是說(shuō)代理類與Apple都實(shí)現(xiàn)了Fruit接口,但是代理類不是Apple類型,因而這里聲明的切面不會(huì)匹配到Apple類。執(zhí)行上述驅(qū)動(dòng)類,結(jié)果如下:

Apple.eat method invoked.

???????結(jié)果表明Apple類確實(shí)沒(méi)有被環(huán)繞。如果我們講切面類中的perthis和this修改為pertarget和target,效果如何呢:

@Aspect("pertarget(target(com.spring.service.Apple))")
public class MyAspect {

  public MyAspect() {
    System.out.println("create MyAspect instance, address: " + toString());
  }

  @Around("target(com.spring.service.Apple)")
  public Object around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("this is before around advice");
    Object result = pjp.proceed();
    System.out.println("this is after around advice");
    return result;
  }
}

???????執(zhí)行結(jié)果如下:

create MyAspect instance, address: chapter7.eg6.MyAspect@48fa0f47
this is before around advice
Apple.eat method invoked.
this is after around advice

???????可以看到,Apple類被切面環(huán)繞了。這里target表示目標(biāo)類是Apple類型,雖然Spring使用了Jdk動(dòng)態(tài)代理實(shí)現(xiàn)切面的環(huán)繞,代理類雖不是Apple類型,但是目標(biāo)類卻是Apple類型,符合target的語(yǔ)義,而pertarget會(huì)為每個(gè)符合條件的表達(dá)式的類實(shí)例創(chuàng)建一個(gè)代理類實(shí)例,因而這里Apple會(huì)被環(huán)繞。

???????由于代理類與目標(biāo)類的差別非常小,因而與this和target一樣,perthis和pertarget的區(qū)別也非常小,大部分情況下其使用效果是一致的。關(guān)于切面多實(shí)例的創(chuàng)建,其演示比較簡(jiǎn)單,我們可以將xml文件中的Apple實(shí)例修改為prototype類型,并且在驅(qū)動(dòng)類中多次獲取Apple類的實(shí)例:

<!-- xml配置文件 -->
<bean id="apple" class="chapter7.eg1.Apple" scope="prototype"/>
<bean id="aspect" class="chapter7.eg6.MyAspect" scope="prototype"/>
<aop:aspectj-autoproxy/>
public class AspectApp {
  public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    Fruit fruit = context.getBean(Fruit.class);
    fruit.eat();
    fruit = context.getBean(Fruit.class);
    fruit.eat();
  }
}

???????執(zhí)行結(jié)果如下:

create MyAspect instance, address: chapter7.eg6.MyAspect@48fa0f47
this is before around advice
Apple.eat method invoked.
this is after around advice
create MyAspect instance, address: chapter7.eg6.MyAspect@56528192
this is before around advice
Apple.eat method invoked.
this is after around advice

???????執(zhí)行結(jié)果中兩次打印的create MyAspect instance表示當(dāng)前切面實(shí)例創(chuàng)建了兩次,這也符合我們進(jìn)行的兩次獲取Apple實(shí)例。

4. 小結(jié)

???????本文首先對(duì)AOP進(jìn)行了簡(jiǎn)單介紹,然后介紹了切面中的各個(gè)角色,最后詳細(xì)介紹了切點(diǎn)表達(dá)式中各個(gè)不同類型表達(dá)式的語(yǔ)法。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,208評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 175,746評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 62,666評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,477評(píng)論 6 407
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 54,960評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 42,200評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,726評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,617評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,807評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,049評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 34,425評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 35,674評(píng)論 1 281
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,432評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,769評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容