Spring框架底層原理- AOP

1、概述

1. 什麼是AOP

AOP(Aspect Oriented Programming):面向切面編程,指在程序運行期間動態的將某段代碼切入到指定方法指定位置進行運行的操做。如:性能監控、日誌記錄、權限控制等,經過AOP解決代碼耦合問題,讓職責更加單一。html

AOP技術它利用一種稱爲**「橫切」**的技術,剖解開封裝的對象內部,並將那些影響了多個類的公共行爲封裝到一個可重用模塊,並將其命名爲」Aspect」,即切面。所謂」切面」,簡單說就是那些與業務無關,卻爲業務模塊所共同調用的邏輯或責任封裝起來,便於減小系統的重複代碼,下降模塊之間的耦合度,並有利於將來的可操做性和可維護性java

2、案例實現推導

爲了更好的理解aop的原理,咱們經過案例來一步步,首先,咱們先用spring框架,使用配置類註解方式注入bean,UserService做爲業務代碼:spring

  • UserService業務代碼:
@Service
public class UserService {
    public void queryAll(){
        System.out.println("業務代碼:查詢全部數據");
    }
}
複製代碼
  • 配置類注入bean
@Configuration
@ComponentScan("com.star")
public class AppConfig {
}
複製代碼
  • 經過反射獲取字節碼建立對象
@Test
public void AOPTest(){
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
    UserService bean = ac.getBean(UserService.class);
    bean.queryAll();
}
複製代碼

咱們運行 AOPTest 方法,能夠看到控制檯打印出:數據庫

image-20210107221911049

我們就在以上代碼的基礎上對功能進行加強。編程

1. 直接加強

如今咱們須要給業務代碼執行先後加上打印日誌,沒有aop的時候,我們能夠直接在 service 業務中增長相關方法進行加強:設計模式

@Service
public class UserService {
    public void queryAll(){
        System.out.println("before----業務代碼執行前打印日誌.....");
        System.out.println("業務代碼:查詢全部數據");
        System.out.println("after----業務代碼執行前打印日誌.....");
    }
}
複製代碼

這樣一來,就把加強代碼和業務代碼放到了一塊兒,這是很不合理的,而且增長了耦合,不利於代碼的拓展。數組

2. 動態代理加強

所謂的動態代理,須要一個代理類,這個代理類是動態生成的,字節碼要用的時候就建立,要用的時候就加載,在不修改源碼的基礎上對方法進行加強。有兩種代理機制,一種是基於JDK的動態代理,另外一種是基於CGLib的動態代理,bean沒有接口時使用 CGLib 代理,bean有接口則使用 JDK 代理。因爲上面的案例中沒有使用接口,因此這裏用CGLib代理。markdown

有關動態代理能夠參考以前的博客:blog.csdn.net/One_L_Star/…框架

@Test
public void AOPTest1(){
    final UserService bean = new UserService();
    UserService cglibProducer = (UserService) Enhancer.create(bean.getClass(), new MethodInterceptor(){
        /** * 做用:執行被代理對象的任何藉口方法都會通過該方法 * @param proxy:代理對象的引用 * @param method:當前執行的方法 * @param args:當前執行方法所需的參數 * @return:和被代理對象方法有相同的返回值 * @throws Throwable */
        @Override
        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            System.out.println("記錄日誌");
            Object result = method.invoke(bean, args);
            return result;
        }
    });
    cglibProducer.queryAll();
}
複製代碼

執行後打印以下:ide

image-20210108105149157

能夠看到,通過CGLib代理後,不修改業務代碼的基礎上,對方法進行了加強,而在spring aop 的底層,也是使用的動態代理,不過要遠遠複雜於上面的代碼,若是要深究,須要查看spring的源碼,這裏只講基本原理,源碼有點太費頭髮。

3. AOP切面加強

最後,我們來看看spring是如何加強的,AOP是一個標準規範,而爲了實現這個標準規範,有幾種方式:

  1. 基於代理的AOP
  2. @AspectJ註解驅動的切面
  3. 純POJO切面
  4. 注入式AspectJ切面

這四種方式都是實現aop的方法,這裏講一下經過AspectJ提供的註解實現AOP,但在spring官網中,有AspectJ 的概念,主要是由於在spring2.x的時候,spring aop的語法過於複雜,spring想進行改進,而改進的時候就藉助了AspectJ 的語法、編程風格來完場aop的配置功能,這裏使用AspectJ 註解方式來實現。

  • @EnableAspectJAutoProxy註解

在配置類中添加@EnableAspectJAutoProxy註解,開啓切面編程功能,添加後以下:

@Configuration
@ComponentScan("com.star")
@EnableAspectJAutoProxy
public class AppConfig {
}
複製代碼
  • 增長切面類

使用@Aspect註解聲明一個切面,並使用@Before、@After等註解代表鏈接點

@Aspect
@Component
public class LogAspect {

    @Pointcut("execution(* com.star.service..*.*(..))")
    public void pointCut(){};

    @Before("pointCut()")
    public void logStart(){
        System.out.println("查詢以前打印日誌....");
    }

    @After("pointCut()")
    public void logEnd(){
        System.out.println("查詢以後打印日誌....");
    }

    @AfterReturning("pointCut()")
    public void logReturn(){
        System.out.println("查詢以後正常返回....");
    }

    @AfterThrowing("pointCut()")
    public void logException(){
        System.out.println("查詢以後返回異常....");
    }
}
複製代碼
  • 測試運行類不變
@Test
public void AOPTest(){
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
    UserService bean = ac.getBean(UserService.class);
    bean.queryAll();
}
複製代碼

直接運行測試類,能夠看到對方法進行了加強

image-20210108113000239

直接獲取一個代理對象 ,首先產生一個目標對象,而後對目標對象進行代理,返回代理對象,把目標對象放到了map中

在spring初始化的時候就已經完成了代理,也就是執行下面代碼的時候就完成了代理

AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AopConfig.class);
複製代碼

3、AOP原理理解

1. AOP術語

術語的理解參考:yq.aliyun.com/articles/63…

在上面,咱們已經經過實例實現了經過AOP對方法進行加強,如今咱們來理解一下,首先,咱們必需要了解AOP的術語,這些術語在上面的AOP切面加強案例中都有體現,這裏結合案例來理解一下。

  • 鏈接點(Joinpoint):鏈接點的最小單位稱之爲方法,每個方法稱之爲鏈接點,如類開始初始化前、類初始化後、類某個方法調用前、調用後、方法拋出異常後。一個類或一段程序代碼擁有一些具備邊界性質的特定點,這些點中的特定點就稱爲「鏈接點」。在上面的案例中,@Before、@After等註解所在的方法都稱之爲鏈接點
  • 切點(Pointcut):切點是鏈接點的集合,每一個程序類都擁有多個鏈接點,如一個擁有兩個方法的類,這兩個方法都是鏈接點,即鏈接點是程序類中客觀存在的事物。AOP經過「切點」定位特定的鏈接點。鏈接點至關於數據庫中的記錄,而切點至關於查詢條件。切點和鏈接點不是一對一的關係,一個切點能夠匹配多個鏈接點。在上面案例中,@Pointcut("execution( com.star.service...*(..))")就是一個切點**。

鏈接點是一個比較空泛的概念,就是定義了哪一些地方是能夠切入的,也就是全部容許你通知的地方。

切點就是定義了通知被應用的位置 (配合通知的方位信息,能夠肯定具體鏈接點)

  • 通知(Advice):切入鏈接點的時機和切入鏈接點的內容稱爲通知,Spring切面能夠應用5種類型的通知:

  • 前置通知(Before):在目標方法被調用以前調用通知功能;

  • 後置通知(After):在目標方法完成以後調用通知,此時不會關心方法的輸出是什麼;

  • 返回通知(After-returning):在目標方法成功執行以後調用通知;

  • 異常通知(After-throwing):在目標方法拋出異常後調用通知;

  • 環繞通知(Around):通知包裹了被通知的方法,在被通知的方法調用以前和調用以後執行自定義的行爲。

通知就定義了,須要作什麼,以及在某個鏈接點的何時作。 上面的切點定義了在哪裏作。

  • 目標對象(Target):指的是被加強的對象,也就是被通知的對象,也就是真正的業務邏輯,在上面案例中,UserService就是目標對象

  • 引介(Introduction):容許咱們向現有的類添加新方法屬性。經過引介,咱們能夠動態地爲該業務類添加接口的實現邏輯,讓業務類成爲這個接口的實現類。

  • 織入(Weaving):織入是將通知添加到目標類具體鏈接點上的過程。AOP像一臺織布機,將目標類、通知或引介經過AOP這臺織布機完美無缺地編織到一塊兒。根據不一樣的實現技術,AOP有三種織入的方式:

    1. 編譯期織入,這要求使用特殊的Java編譯器。

    2. 類裝載期織入,這要求使用特殊的類裝載器。

    3. 動態代理織入,在運行期爲目標類添加通知生成子類的方式。

把切面應用到目標對象來建立新的代理對象的過程,Spring採用動態代理織入,而AspectJ採用編譯期織入和類裝載期織入。

  • 代理對象(Proxy):一個類被AOP織入通知後,就產出了一個結果類,這個類就是代理對象,它是融合了原類和通知邏輯的代理類。
  • 切面(Aspect):鏈接點和切點以及通知所在的那個類稱爲切面,它既包括了橫切邏輯的定義,也包括了鏈接點的定義,Spring AOP就是負責實施切面的框架,它將切面所定義的橫切邏輯織入到切面所指定的鏈接點中。

切點的通知的結合,切面知道全部它須要作的事:什麼時候/何處/作什麼

2. AOP實現原理

原理實現參考:www.cnblogs.com/stateis0/p/…

【1】AOP的設計

  1. 在Spring的底層,若是咱們配置了代理模式,Spring會爲每個Bean建立一個對應的ProxyFactoryBean的FactoryBean來建立某個對象的代理對象。
  2. 每一個 Bean 都會被 JDK 或者 Cglib 代理。取決因而否有接口。
  3. 每一個 Bean 會有多個「方法攔截器」。注意:攔截器分爲兩層,外層由 Spring 內核控制流程,內層攔截器是用戶設置,也就是 AOP。
  4. 當代理方法被調用時,先通過外層攔截器,外層攔截器根據方法的各類信息判斷該方法應該執行哪些「內層攔截器」。內層攔截器的設計就是職責連的設計。

【2】代理的建立

  1. 首先,須要建立代理工廠,代理工廠須要 3 個重要的信息:攔截器數組,目標對象接口數組,目標對象。
  2. 建立代理工廠時,默認會在攔截器數組尾部再增長一個默認攔截器 —— 用於最終的調用目標方法。
  3. 當調用 getProxy 方法的時候,會根據接口數量大餘 0 條件返回一個代理對象(JDK or Cglib)。

注意:建立代理對象時,同時會建立一個外層攔截器,這個攔截器就是 Spring 內核的攔截器。用於控制整個 AOP 的流程。

【3】代理的調用

  1. 當對代理對象進行調用時,就會觸發外層攔截器。
  2. 外層攔截器根據代理配置信息,建立內層攔截器鏈。建立的過程當中,會根據表達式判斷當前攔截是否匹配這個攔截器。而這個攔截器鏈設計模式就是職責鏈模式。
  3. 當整個鏈條執行到最後時,就會觸發建立代理時那個尾部的默認攔截器,從而調用目標方法。最後返回。

如圖:

image-20210108234011964

調用過程:

image-20210108234159287

相關文章
相關標籤/搜索