看了絕對不會後悔之:spring AOP原理

假不理發表於2018-08-24

引子:上一篇文章已經講了spring的核心思想Ioc(看了絕對不會後悔之:spring Ioc原理),那作為spring的兩大核心思想的另一個思想AOP,當然也不能缺席啦。

那麼,什麼是AOP呢? 我們都知道Java的核心思想是物件導向OOP,而OOP的核心是封裝、繼承、多型。 什麼是封裝?

封裝就是把物件的屬性和操作(或服務)結合為一個獨立的整體,並儘可能隱藏物件的內部實現細節。

由此可見封裝的好處在於把整個專案中相同的業務抽成成一個個獨立的類/方法,使得相同的程式碼有複用性,這樣的做法可以降低程式碼的複雜度,但是也存在一些問題,比如我們都知道日誌是開發中必不可少的邏輯,當我們把一塊塊的程式碼封裝到不同的類中之後,一旦需要新增日誌時,就需要在每個類中都寫入相應的日誌程式碼,有人會說,可以把日誌程式碼封裝成一個類來呼叫,在這些需要日誌的類中呼叫日誌收集的方法即可,但這樣一來,日誌類和業務邏輯就產生了耦合,當日志類發生變化時,導致業務邏輯的程式碼都需要修改。 這..怎麼辦呢? 當OOP滿足不了的時候,AOP就來救場了。

AOP:面向切面程式設計,作為OOP程式設計的一種補充,它的核心思想是在執行時動態地將程式碼切入到類的指定方法的指定位置上。

因此用AOP的思想問題就很容易解決了,我們把日誌收集邏輯作為一個切面,切入到指定的類/方法中,按照我們需求執行即可,我們的業務邏輯既不會因為這個日誌類產生變動而收到影響,也不會因為它的存在而產生耦合現象。

知道原理就好辦了,下面我們來舉個栗子:

例如:很多時候,API的每個介面,我們都需要知道它的執行時間,這樣方便我們做效能監控和優化

下面是常規操作:

 1public class Api {
 2    public void test(){
 3        // 開始
 4        long begin=(new Date()).getTime();
 5        System.out.println("開始執行....");
 6
 7        //執行業務邏輯
 8
 9        // 結束
10        long end=(new Date()).getTime();
11        //統計耗時
12        System.out.println("結束執行....");
13        System.out.println(" 執行完成,耗時:"+(end-begin)+"毫秒");
14    }
15}
複製程式碼

可以看到,這樣確實可以獲取到一個介面執行時長,但問題是,每個方法都要加入這些程式碼,那就很悲催了。。

下面,我們用spring的AOP來實現同樣的功能 首先,建立一個切面的類:

 1package com.test.aoptest;
 2
 3import java.util.Date;
 4
 5import org.aspectj.lang.ProceedingJoinPoint;
 6import org.aspectj.lang.annotation.Around;
 7import org.aspectj.lang.annotation.Aspect;
 8import org.aspectj.lang.annotation.Pointcut;
 9import org.springframework.stereotype.Component;
10import org.springframework.util.StopWatch;
11
12
13@Component
14@Aspect
15public class AopAspect {
16    //within 用於匹配指定型別內的方法執行
17    //設定切入點
18    @Pointcut("within(AopService)")
19    //切點簽名方法,作用是使得通知的註解可以通過這個切點簽名方法連線到切點,
20    //通過解釋切點表示式找到需要被切入的連線點。
21    //最終的目的都是為了找到需要被切入的連線點
22    public void pointcut(){
23        System.out.println("I am pointcut...");
24    }
25//    @Before:前置通知,在呼叫目標方法之前執行通知定義的任務
26//    @After:後置通知,在目標方法執行結束後,無論執行結果如何都執行通知定義的任務
27//    @After-returning:後置通知,在目標方法執行結束後,如果執行成功,則執行通知定義的任務
28//    @After-throwing:異常通知,如果目標方法執行過程中丟擲異常,則執行通知定義的任務
29//    @Around:環繞通知,在目標方法執行前和執行後,都需要執行通知定義的任務
30    @Around("pointcut()")
31    public Object invokeMethod(ProceedingJoinPoint pjp) throws Throwable {
32        // 開始
33        long begin=(new Date()).getTime();
34        System.out.println("開始執行....");
35        //執行業務邏輯
36        Object retVal = pjp.proceed();
37        // 結束
38        long end=(new Date()).getTime();
39        //統計耗時
40        System.out.println("結束執行....");
41        System.out.println("方法:"+pjp.getSignature().toShortString()+" 執行完成,耗時:"+(end-begin)+"毫秒");
42        return retVal;
43    }
44
45}
複製程式碼

其次,建立一個業務類:

1package com.test.aoptest;
2import org.springframework.stereotype.Service;
3@Service
4public class AopService {
5    public void test(){
6        System.out.println("執行業務邏輯。。。");
7    }
8}
複製程式碼

接下來,我們測試一下:

 1package com.test.aoptest;
 2import javax.annotation.PostConstruct;
 3import org.springframework.beans.factory.annotation.Autowired;
 4import org.springframework.boot.SpringApplication;
 5import org.springframework.boot.autoconfigure.SpringBootApplication;
 6import org.springframework.context.annotation.EnableAspectJAutoProxy;
 7
 8@EnableAspectJAutoProxy //開啟AOP
 9@SpringBootApplication //把啟動類注入到容器
10public class AopTest {
11    @Autowired
12    AopService service;
13    public static void main(String[] args) {
14        SpringApplication.run(AopTest.class, args);
15    }
16    @PostConstruct //用於在依賴關係注入完成之後需要執行的方法上,以執行任何初始化
17    public void test() {
18        service.test();
19    }
20    @PostConstruct
21    public void test2(){
22        service.test();
23    }
24}
複製程式碼

看看輸出:

1開始執行....
2執行業務邏輯。。。
3結束執行....
4方法:AopService.test() 執行完成,耗時:17毫秒
5開始執行....
6執行業務邏輯。。。
7結束執行....
8方法:AopService.test() 執行完成,耗時:0毫秒
複製程式碼

可以清楚的看到,它按照我們的預想輸出了,而我們的業務類,沒有任何日誌的程式碼侵入,並且日誌類的變動,也不會對業務類產生影響,因此,這就是AOP的優勢之處了。 本文只是用了一些通俗的話語來簡述AOP的原理,並且用一個最常見最簡單的例子來分析理解AOP,實際上AOP還有非常多的知識點和用途,這就要求大家多學多寫了。

覺得本文對你有幫助?請分享給更多人

關注「程式設計無界」,提升裝逼技能

看了絕對不會後悔之:spring AOP原理

相關文章