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)形式可用下圖表示:
????????面向?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ǔ)法。