SpringBoot中切面的解釋和實(shí)例

注:本文轉(zhuǎn)載自 航歌-開(kāi)發(fā)者知識(shí)平臺(tái)

作者:hangge

[原帖地址]{https://www.hangge.com/blog/cache/detail_2527.html}

一、基本介紹

1,什么是 AOP

(1)AOP 為 Aspect Oriented Programming 的縮寫(xiě),意為:面向切面編程,通過(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í)提高了開(kāi)發(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 開(kāi)始的方法,這個(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í)行,開(kāi)發(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 + "方法開(kāi)始執(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
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容