1 AOP術(shù)語(yǔ)
AOP的術(shù)語(yǔ)主要有如下幾個(gè):
- 通知(Advice)
- 連接點(diǎn)(Join point)
- 切點(diǎn)(Pointcut)
- 切面(Aspect)
- 引入(Introduction)
- 織入(Weaving)
- 通知
在AOP術(shù)語(yǔ)中屹篓,切面的工作被稱為通知。通知定義了切面是什么以及何時(shí)使用。除了描述切面要完成的工作,通知還解決了何時(shí)執(zhí)行這個(gè)工作的問(wèn)題。
Spring切面可以應(yīng)用5種類型的通知:
- 前置通知(Before):在目標(biāo)方法被調(diào)用之前調(diào)用通知功能;
- 后置通知(After):在目標(biāo)方法完成之后調(diào)用通知,此時(shí)不會(huì)關(guān)心方法的輸出是什么背伴;
- 返回通知(After-returning):在目標(biāo)方法成功執(zhí)行之后調(diào)用通知;
- 異常通知(After-throwing):在目標(biāo)方法拋出異常后調(diào)用通知峰髓;
- 環(huán)繞通知(Around):通知包裹了被通知的方法傻寂,在被通知的方法調(diào)用之前和調(diào)用之后執(zhí)行自定義的行為。
- 連接點(diǎn)
連接點(diǎn)是在應(yīng)用執(zhí)行過(guò)程中能夠插入切面的一個(gè)點(diǎn)携兵。這個(gè)點(diǎn)可以是調(diào)用方法時(shí)疾掰、拋出異常時(shí)、甚至修改一個(gè)字段時(shí)眉孩。切面代碼可以利用這些點(diǎn)插入到應(yīng)用的正常流程之中个绍,并添加新的行為。 - 切點(diǎn)
如果說(shuō)通知定義了切面的“什么”和“何時(shí)”的話浪汪,那么切點(diǎn)就定義了“何處”巴柿。切點(diǎn)的定義會(huì)匹配通知所要織入的一個(gè)或多個(gè)連接點(diǎn)。 - 切面
切面是通知和切點(diǎn)的結(jié)合死遭。通知和切點(diǎn)共同定義了切面的全部?jī)?nèi)容——它是什么广恢,在何時(shí)和何處完成其功能。 - 引入
引入允許我們向現(xiàn)有的類添加新方法或?qū)傩浴?/li> - 織入
織入是把切面應(yīng)用到目標(biāo)對(duì)象并創(chuàng)建新的代理對(duì)象的過(guò)程呀潭。切面在指定的連接點(diǎn)被織入到目標(biāo)對(duì)象中钉迷。在目標(biāo)對(duì)象的生命周期里有多個(gè)點(diǎn)可以進(jìn)行織入:
- 編譯期:切面在目標(biāo)類編譯時(shí)被織入。這種方式需要特殊的編譯器钠署。AspectJ的織入編譯器就是以這種方式織入切面的糠聪。
- 類加載期:切面在目標(biāo)類加載到JVM時(shí)被織入。這種方式需要特殊的類加載器(ClassLoader)谐鼎,它可以在目標(biāo)類被引入應(yīng)用之前增強(qiáng)該目標(biāo)類的字節(jié)碼舰蟆。AspectJ 5的加載時(shí)織入(load-time weaving,LTW)就支持以這種方式織入切面。
- 運(yùn)行期:切面在應(yīng)用運(yùn)行的某個(gè)時(shí)刻被織入身害。一般情況下味悄,在織入切面時(shí),AOP容器會(huì)為目標(biāo)對(duì)象動(dòng)態(tài)地創(chuàng)建一個(gè)代理對(duì)象塌鸯。Spring AOP就是以這種方式織入切面的侍瑟。
2 Spring AOP 簡(jiǎn)介
Spring提供了4種類型的AOP支持:
- 基于代理的經(jīng)典Spring AOP;
- 純POJO切面丙猬;
- @AspectJ注解驅(qū)動(dòng)的切面涨颜;
- 注入式AspectJ切面(適用于Spring各版本)
Spring AOP的特點(diǎn):
- 通知是使用標(biāo)準(zhǔn)的Java類寫的
- 在運(yùn)行時(shí)通知對(duì)象,通過(guò)在代理類中包裹切面淮悼,Spring在運(yùn)行期把切面織入到Spring管理的bean中咐低。
- 只支持方法級(jí)別的連接點(diǎn)揽思。因?yàn)镾pring基于動(dòng)態(tài)代理袜腥,所以Spring只支持方法連接點(diǎn)。
3 切點(diǎn)的編寫
Spring AOP支持的AspectJ切點(diǎn)指示器.JPG
其中最為常用的是exection()钉汗,其使用方法為:
exection()使用方法.JPG
還可以使用
&&
, ||
, !
等操作符羹令,在XML中,則以and, or, not來(lái)表示损痰。匹配指定包下所有的方法:
execution("* com.demo.controller.*(..))
匹配指定包以及其子包下的所有方法:
execution("* com.demo..*(..)")
選擇特定的bean(
bean()
使用bean ID或bean名稱作為參數(shù)來(lái)限制切點(diǎn)只匹配特定的bean):exection(* concert.Performance.perform(..) && bean('woodstock'))
4 定義切面
聲明通知的注解.JPG
@Aspect
public class HelloAspect {
@Pointcut("execution(* me.ye.springinaction.controller.Controller.hello(..))")
public void hello() {}
@Before("hello()")
public void beforeHello() {
System.out.println("ready for hello");
}
@AfterReturning("hello()")
public void afterHello() {
System.out.println("after hello");
}
@AfterThrowing("hello()")
public void errorWhenHello() {
System.out.println("error when hello");
}
}
可以使用@Pointcut
來(lái)定義切點(diǎn)福侈,也可以直接在通知的注解中直接以切點(diǎn)的表達(dá)式作為參數(shù)。
定義了切面之后卢未,還要注入相應(yīng)的bean肪凛,以及在配置類中啟用自動(dòng)代理@EnableAspectJAutoProxy
@ComponentScan
@Configuration
@EnableAspectJAutoProxy
public class AspectConfig {
@Bean
public HelloAspect helloAspect() {
return new HelloAspect();
}
}
@Around
的用法:
public class HelloAspect {
@Pointcut("execution(* me.ye.springinaction.controller.Controller.hello(..))")
public void hello() {}
@Around("hello()")
public void helloAspect(ProceedingJoinPoint joinPoint) {
try {
System.out.println("ready for hello");
joinPoint.proceed();
System.out.println("after hello");
} catch(Throwable ex) {
System.out.println("error when hello");
}
}
}
當(dāng)要將控制權(quán)交給被通知的方法時(shí),需要調(diào)用ProceedingJoinPoint的proceed()方法辽社。
5 通過(guò)切面引入新功能
切面還可以為bean添加來(lái)自其他接口的方法伟墙,而并不需要真正實(shí)現(xiàn)其他接口。通過(guò)代理滴铅,引入其他接口戳葵,當(dāng)調(diào)用到引入接口的方法時(shí),代理會(huì)將調(diào)用委托給實(shí)現(xiàn)了該接口的其他對(duì)象汉匙。
使用Spring AOP為bean引入新的方法.JPG
@Aspect
public class DeclareParentsAspect {
@DeclareParents(value = "me.ye.springinaction.service.DemoService", defaultImpl = CommonParentImpl.class)
private CommonParent commonParent;
}
@DeclareParents注解由三部分組成:
- value屬性指定了哪種類型的bean要引入該接口拱烁。
- defaultImpl屬性指定了為引入功能提供實(shí)現(xiàn)的類。
- @DeclareParents注解所標(biāo)注的屬性指明了要引入的接口噩翠。
使用的時(shí)候可以將bean進(jìn)行類型轉(zhuǎn)換為要引入的接口戏自,再調(diào)用要引入的方法即可。
((CommonParent)service).doSomething();