注:本文轉(zhuǎn)載自 航歌-開發(fā)者知識(shí)平臺(tái)
作者:hangge
[原帖地址]{https://www.hangge.com/blog/cache/detail_2527.html}
一、基本介紹
1亩进,什么是 AOP
(1)AOP 為 Aspect Oriented Programming 的縮寫灵汪,意為:面向切面編程牺汤,通過(guò)預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)咳短。
(2)利用 AOP 可以對(duì)業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行隔離,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低评也,提高程序的可重用性攒磨,同時(shí)提高了開發(fā)的效率。
一個(gè) AOP 的使用場(chǎng)景:
假設(shè)一個(gè)已經(jīng)上線的系統(tǒng)運(yùn)行出現(xiàn)問(wèn)題蜗字,有時(shí)運(yùn)行得很慢打肝。為了檢測(cè)出是哪個(gè)環(huán)節(jié)出現(xiàn)了問(wèn)題,就需要監(jiān)控每一個(gè)方法的執(zhí)行時(shí)間挪捕,再根據(jù)執(zhí)行時(shí)間進(jìn)行分析判斷粗梭。
由于整個(gè)系統(tǒng)里的方法數(shù)量十分龐大,如果一個(gè)個(gè)方法去修改工作量將會(huì)十分巨大级零,而且這些監(jiān)控方法在分析完畢后還需要移除掉断医,所以這種方式并不合適。
如果能夠在系統(tǒng)運(yùn)行過(guò)程中動(dòng)態(tài)添加代碼奏纪,就能很好地解決這個(gè)需求鉴嗤。這種在系統(tǒng)運(yùn)行時(shí)動(dòng)態(tài)添加代碼的方式稱為面向切面編程(AOP)
2,AOP 相關(guān)概念介紹
- Joinpoint(連接點(diǎn)):類里面可以被增強(qiáng)的方法即為連接點(diǎn)序调。例如醉锅,想要修改哪個(gè)方法的功能,那么該方法就是一個(gè)鏈接點(diǎn)发绢。
- Target(目標(biāo)對(duì)象):要增強(qiáng)的類成為 Target硬耍。
- Pointcut(切入點(diǎn)):對(duì) Jointpoint 進(jìn)行攔截的定義即為切入點(diǎn)。例如边酒,攔截所有以 insert 開始的方法经柴,這個(gè)定義即為切入點(diǎn)。
- Advice(通知):攔截到 Jointpoint 之后要做的事情就是通知甚纲。通知分為前置通知口锭、后置通知、異常通知介杆、最終通知和環(huán)繞通知鹃操。例如,前面說(shuō)到的打印日志監(jiān)控就是通知春哨。
- Aspect(切面):即 Pointcut 和 Advice 的結(jié)合荆隘。
3,安裝配置
Spring Boot 在 Spring 的基礎(chǔ)上對(duì) AOP 的配置提供了自動(dòng)化配置解決方案赴背,我們只需要修改 pom.xml 文件椰拒,添加 spring-boot-starter-aop 依賴即可晶渠。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
二、使用樣例
1燃观,創(chuàng)建 Service
首先創(chuàng)建一個(gè) UserService(假設(shè)在 com.example.demo.service)褒脯,內(nèi)容如下
@Service
public class UserService {
public String getUserById(Integer id) {
System.out.println("getUserById(" + id + ")...");
// 等待2秒
try {
Thread.sleep(2000);
}
catch(InterruptedException e) {
e.printStackTrace();
}
return "hangge";
}
}
2,創(chuàng)建切面
接著定義一個(gè)切面類缆毁,代碼如下
注解說(shuō)明:
(1)@Aspect 注解:表明這是一個(gè)切面類番川。
(2)@Pointcut 注解:表明這是一個(gè)切入點(diǎn)。
execution 中的第一個(gè) * 表示方法返回任意值
第二個(gè) * 表示 service 包下的任意類
第三個(gè) * 表示類中的任意方法脊框,括號(hào)中的兩個(gè)點(diǎn)表示方法參數(shù)任意颁督,即這里描述的切入點(diǎn)為 service 包下所有類中的所有方法。
(3)@Before 注解:表示這是一個(gè)前置通知浇雹,該方法在目標(biāo)方法之前執(zhí)行沉御。
通過(guò) JoinPoint 參數(shù)可以獲取目標(biāo)方法的方法名、修飾符等信息昭灵。
(4)@After 注解:表示這是一個(gè)后置通知吠裆,該方法在目標(biāo)執(zhí)行之后執(zhí)行。
(5)@AfterReturning 注解:表示這是一個(gè)返回通知烂完,在該方法中可以獲取目標(biāo)方法的返回值硫痰。
returning 參數(shù)是指返回值的變量名,對(duì)應(yīng)方法的參數(shù)窜护。
注意:本樣例在方法參數(shù)中定義 result 的類型為 Object效斑,表示目標(biāo)方法的返回值可以是任意類型。若 result 參數(shù)的類型為 Long柱徙,則該方法只能處理目標(biāo)方法返回值為 Long 的情況缓屠。
(6)@AfterThrowing 注解:表示這是一個(gè)異常通知,即當(dāng)目標(biāo)方法發(fā)生異常护侮,該方法會(huì)被調(diào)用敌完。
樣例中設(shè)置的異常類型為 Exception 表示所有的異常都會(huì)進(jìn)入該方法中執(zhí)行。
若異常類型為 ArithmeticException 則表示只有目標(biāo)方法拋出的 ArithmeticException 異常才會(huì)進(jìn)入該方法的處理羊初。
(7) @Around 注解:表示這是一個(gè)環(huán)繞通知滨溉。環(huán)繞通知是所有通知里功能最為強(qiáng)大的通知,可以實(shí)現(xiàn)前置通知长赞、后置通知晦攒、異常通知以及返回通知的功能。
目標(biāo)方法進(jìn)入環(huán)繞通知后得哆,通過(guò)調(diào)用 ProceedingJointPoint 對(duì)象的 proceed 方法使目標(biāo)方法繼續(xù)執(zhí)行脯颜,開發(fā)者可以在次修改目標(biāo)方法的執(zhí)行參數(shù)、返回值值贩据,并且可以在此目標(biāo)方法的異常
@Aspect
@Component
public class LogAspect {
// 定義一個(gè)切入點(diǎn)
@Pointcut("execution(* com.example.demo.service.*.*(..))")
public void pc1(){
}
// 前置通知
@Before(value = "pc1()")
public void before(JoinPoint jp) {
String name = jp.getSignature().getName();
System.out.println(name + "方法開始執(zhí)行...");
}
// 后置通知
@After(value = "pc1()")
public void after(JoinPoint jp) {
String name = jp.getSignature().getName();
System.out.println(name + "方法執(zhí)行結(jié)束...");
}
// 返回通知
@AfterReturning(value = "pc1()", returning = "result")
public void afterReturning(JoinPoint jp, Object result) {
String name = jp.getSignature().getName();
System.out.println(name + "方法返回值為:" + result);
}
// 異常通知
@AfterThrowing(value = "pc1()", throwing = "e")
public void afterThrowing(JoinPoint jp, Exception e) {
String name = jp.getSignature().getName();
System.out.println(name + "方法拋異常了栋操,異常是:" + e.getMessage());
}
// 環(huán)繞通知
@Around("pc1()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
String name = pjp.getSignature().getName();
// 統(tǒng)計(jì)方法執(zhí)行時(shí)間
long start = System.currentTimeMillis();
Object result = pjp.proceed();
long end = System.currentTimeMillis();
System.out.println(name + "方法執(zhí)行時(shí)間為:" + (end - start) + " ms");
return result;
}
}
3闸餐,創(chuàng)建 Controller
配置完成后,接下來(lái)在 Controller 中創(chuàng)建接口調(diào)用 UserService 中的方法矾芙。
@RestController
public class HelloController {
@Autowired
UserService userService;
@GetMapping("/test")
public String test(Integer id) {
return userService.getUserById(id);
}
}
4舍沙,運(yùn)行樣例
(1)使用瀏覽器訪問(wèn)如下地址:
-
http://localhost:8080/test?id=11
(2)查看控制臺(tái)信息,可以發(fā)現(xiàn) LogAspect 中的代碼動(dòng)態(tài)地嵌入目標(biāo)方法中執(zhí)行了
image.png