AOP(Aspect Oriented Programming)镀梭,即面向切面編程屋确。
是OOP(Object Oriented Programming瘤睹,面向?qū)ο缶幊蹋┑难a(bǔ)充和完善。
AOP 要達(dá)到的效果是番电,在不修改源代碼的前提下岗屏,為業(yè)務(wù)組件添加某種通用功能。所以AOP 的本質(zhì)就是由 AOP 框架修改業(yè)務(wù)組件的源代碼漱办。
按照 修改源代碼的時(shí)機(jī),AOP 框架分為靜態(tài) AOP 實(shí)現(xiàn) 和動(dòng)態(tài) AOP 實(shí)現(xiàn)兩類(lèi)婉烟。
- 靜態(tài) AOP 實(shí)現(xiàn)娩井, 通常使用AspectJ
- 動(dòng)態(tài) AOP 實(shí)現(xiàn),通常使用 JDK 動(dòng)態(tài)代理似袁,或 CGlib 動(dòng)態(tài)代理
Spring AOP是基于動(dòng)態(tài)代理實(shí)現(xiàn)的洞辣。Spring AOP會(huì)看看你的類(lèi)有沒(méi)有實(shí)現(xiàn)接口咐刨,有的話(huà)使用動(dòng)態(tài)代理,沒(méi)有的話(huà)使用cglib扬霜。
1 AspectJ
AOP 框架在編譯階段對(duì)程序目標(biāo)類(lèi)進(jìn)行修改定鸟,生成了靜態(tài)的 AOP 代理類(lèi)。使AOP 框架使用特定的編譯器著瓶,使生成的 *.class 文件被改掉了联予。
AOP相關(guān)的代碼,和目標(biāo)類(lèi)的結(jié)合過(guò)程叫做織入(weave)材原。AspectJ的織入過(guò)程沸久,有可能發(fā)生在三個(gè)階段:
- 編譯時(shí)織入(Compile-time weaving)
用AspectJ的編譯器ajc,在項(xiàng)目編譯的階段就將代碼織入目標(biāo)類(lèi) - 編譯后織入(Post-compile weaving)
用AspectJ的編譯器ajc余蟹,向javac編譯出來(lái)的.class或者.jar織入代碼 - 加載時(shí)織入(Load-time weaving)
類(lèi)加載器將字節(jié)碼加載到JVM前織入
2 AOP核心概念
2.1 AOP相關(guān)術(shù)語(yǔ)
連接點(diǎn)(Joinpoint)
Joinpoint是程序在運(yùn)行過(guò)程中能夠插入Aspect的地點(diǎn)卷胯,比如方法調(diào)用、異常拋出威酒、字段修改等窑睁。 在 Spring AOP 中,連接點(diǎn)總是方法的調(diào)用葵孤。切入點(diǎn)(Pointcut)
Pointcut用于定義Advice應(yīng)該切入到哪些Joinpoint上卵慰, 根據(jù)Pointcut的通配或者正則表達(dá)式來(lái)定義要攔截的Joinpoint。通知(Advice)
Advice定義了在Pointcut上執(zhí)行的增強(qiáng)處理佛呻,是攔截到Joinpoint之后要執(zhí)行的代碼裳朋。切面(Aspect)
Aspect通常是一個(gè)類(lèi),可以定義Pointcut和Advice吓著。Aspect指明在哪個(gè)Pointcut執(zhí)行什么Advice鲤嫡。目標(biāo)對(duì)象(Target)
目標(biāo)對(duì)象是指代理的目標(biāo)對(duì)象,是指要織入Advice的對(duì)象绑莺。織入(weaving)
通過(guò)Pointcut切入伟姐,將Aspect應(yīng)用到Target并創(chuàng)建代理對(duì)象創(chuàng)建引入(Introduction)
在不修改目標(biāo)對(duì)象的前提下,引入可以在運(yùn)行期為類(lèi)動(dòng)態(tài)地添加一些方法或字段
2.2 通知Advice 的類(lèi)型
Advice可以分為前置通知Before慨丐、后置通知AfterReturning菩貌、異常通知AfterThrowing、最終通知After欺缘、環(huán)繞通知Around五類(lèi)栋豫。
- 前置通知 before
在Joinpoint前被執(zhí)行的Advice - 后置通知 AfterReturning
在一個(gè)Joinpoint 正常返回后執(zhí)行的Advice - 異常通知 AfterThrowing
當(dāng)一個(gè)Joinpoint 拋出異常后執(zhí)行的Advice - 最終通知 after
Joinpoint 無(wú)論是正常退出還是發(fā)生了異常,都會(huì)被執(zhí)行的 advice. - 環(huán)繞通知 around
在Joinpoint前和Joinpoint退出后都執(zhí)行的Advice谚殊。 這個(gè)是最常用的 Advice. - introduction
introduction可以為原有的對(duì)象增加新的屬性和方法
各個(gè)通知的執(zhí)行順序如圖所示丧鸯。
3 Spring AOP的代碼demo
Spring借用了@AspectJ的注解來(lái)定義切面。在Spring Boot中引入AOP Starter依賴(lài):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
AopAutoConfiguration會(huì)添加@EnableAspectJAutoProxy注解以開(kāi)啟AspectJ注解的使用嫩絮,也就是說(shuō)加了@Aspect注解的切面類(lèi)丛肢,一放到容器中围肥,Spring AOP就自動(dòng)完成織入。
3.1 定義注解MyLogger
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyLogger {
}
3.2 定義Pointcut
/**
* 定義切點(diǎn)蜂怎,匹配所有使用了@MyLogger注解的方法
*/
@Pointcut("@annotation( com.dc.artemis.server.qian.MyLogger)")
public void logPointcut() {
}
3.3 定義Advice
/**
* 這里使用環(huán)繞通知
*/
@Around("logPointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("@Around before");
Object returnValue = joinPoint.proceed();
log.info("@Around after");
return returnValue;
}
3.4 切面Aspect完整的代碼
@Slf4j
@Aspect // 使用注解定義切面
@Component
@EnableAspectJAutoProxy // 開(kāi)啟AOP
public class LogAspect {
/**
* 定義切點(diǎn)穆刻,匹配所有使用了@MyLogger注解的方法
*/
@Pointcut("@annotation( com.dc.artemis.server.qian.MyLogger)")
public void logPointcut() {
}
@Around("logPointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("@Around before");
Object returnValue = joinPoint.proceed();
log.info("@Around after");
return returnValue;
}
}
3.5 測(cè)試
調(diào)用
@Test
void testMyService() {
myService.myMethod();
}
輸出
2022-10-15 17:34:04.470 INFO 87770 --- [main] com.dc.artemis.server.qian.LogAspect : @Around before
2022-10-15 17:34:04.496 INFO 87770 --- [main] com.dc.artemis.server.qian.MyService: 執(zhí)行方法
2022-10-15 17:34:04.497 INFO 87770 --- [main] com.dc.artemis.server.qian.LogAspect: @Around after
由于MyService沒(méi)有實(shí)現(xiàn)接口,可以看到Spring使用了CGLIB生成代理對(duì)象