Spring AOP學習筆記01:AOP概述_台中搬家

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

台中搬家公司推薦超過30年經驗,首選台中大展搬家

1. AOP概述

  軟件開發一直在尋求更加高效、更易維護甚至更易擴展的方式。為了提高開發效率,我們對開發使用的語言進行抽象,走過了從彙編時代到現在各種高級語言繁盛之時期;為了便於維護和擴展,我們對某些相同的功能進行歸類並使之模塊化,衝出了最初的”原始部落”,走過了從過程化編程到面向對象編程(OOP)的”短暫而漫長”的歷程。但不管走過的路有多長,多麼坎坷,我們一直沒有停止尋找更加完美、更加高效的軟件開發方法,過去如此,現在亦然。

  當OOP被提出來,以取代過去基於過程化編程的開發方法時,或許那個時代的人都會以為,面向對象編程和面向對象的軟件開發就是我們一直追求的那顆能夠搞定一切的”銀彈”。但不得不承認的是,即使面向對象的軟件開發模式,依然不能很好地解決軟件開發中的所有問題。

  軟件開發的目的,最終是為了解決各種需求,包括業務需求和系統需求。使用面向對象方法,我們可以對業務需求等普通關注點進行很好的抽象和封裝,並且使之模塊化。但對於系統需求(比如日誌記錄、權限驗證、事務管理等)一類的關注點來說,情況卻有所不同。

  對於業務需求而言,需求與其具體實現之間的關係基本上是一對一的。我們可以在系統中某一個確定的點找到針對這種需求的實現,無論從開發還是維護的角度,都比較方便。比如電商系統中的賬戶管理模塊、訂單模塊、支付模塊等,可以很容易地按照功能劃分模塊並完成開發。

  但是,事情並沒有結束!開發中為了調試或在進入生產環境後為了對系統進行監控,我們需要為這些業務需求的實現對象添加日誌記錄功能;或者,業務方法的執行需要一定的權限限制,那麼方法執行前肯定需要有相應的安全檢查功能。而這些則屬於系統需求的範疇。雖然需求都很明確(加入日誌記錄、加入安全檢查),但是要將這些需求以面向對象的方式實現並集成到整個的系統中去,可就不是一個需求對應一個實現那麼簡單了,系統中的每個業務對象都需要加入日誌記錄,加入相應的安全檢查,那麼,這些需求的實現代碼就會遍及所有業務對象。

  對於系統中普通的業務關注點,OOP可以很好地對其進行分解並使之模塊化,但卻無法更好地避免類似於系統需求的實現在系統中各處散落這樣的問題。所以,我們要尋求一種更好的方法,它可以在OOP的基礎上更上一層樓,提出一套全新的方法論來避免以上問題,也可以提供某種方法對基於OOP的開發模式做一個補足,幫助OOP以更好的方式解決以上問題。迄今為止,我們還找不到比OOP更加有效的軟件開發模式。不過,我們找到了後者,那就是AOP,對OOP的補足。

  AOP全稱為Aspect-Oriented Programming,中文通常翻譯為面向方面編程。使用AOP,我們可以對類似於Logging和Security等系統需求進行模塊化的組織,簡化系統需求與實現之間的對比關係,進而使得整個系統的實現更具模塊化。

  對於一個軟件系統而言,日誌記錄、安全檢查、事務管理等系統需求就像一把把刀“惡狠狠”地橫切到我們組織良好的各個業務功能模塊之上。以AOP的行話來說,這些系統需求是系統中的橫切關注點(cross-cutting concern)。使用傳統方法,我們無法更好地以模塊化的方式,對這些橫切關注點進行組織和實現。所以AOP引入了Aspect的概念,用來以模塊化的形式對系統中的橫切關注點進行封裝。Aspect 之對於AOP,就相當於Class之對於OOP。我們說過AOP僅是對OOP方法的一種補足,當我們把以Class形式模塊化的業務需求和以Aspect形式模塊化的系統需求拼裝到一起的時候,整個系統就算完成了。

 

2. AOP相關概念

  在進一步學習Spring AOP之前,我們還需要了解一下AOP涉及的相關概念:

2.1 切點(JoinPoint)

  在系統運行之前,AOP的功能模塊都需要織入到OOP的功能模塊中。所以,要進行這種織入過程,我們需要知道在系統的哪些執行點上進行織入操作,這些將要在其之上進行織入操作的系統執行點就稱之為切點(Joinpoint)。對應到spring中可以理解為具體攔截的某個業務點。

  以下是一些較為常見的Joinpoint類型

  • 方法調用(Method Call)。當某個方法被調用的時候所處的程序執行點。
  • 方法調用執行(Method Call execution)。也可以稱之為方法執行,該Joinpoint類型代表的是某個方法內部執行開始時點,這需要與上面的方法調用類型的Jointpoint進行區分。方法調用(method call)是在調用對象上的執行點,而方法執行(method execution)則是在被調用到的方法邏輯執行的時點,對於同一對象,方法調用要先於方法執行。
  • 構造方法調用(Constructor Call)。程序執行過程中對某個對象調用其構造方法進行初始化的時點。
  • 構造方法執行(Constructor Call Execution)。構造方法執行和構造方法調用之間的關係類似於方法執行和方法調用之間的關係,指的是某個對象構造方法內部執行的開始時點。
  • 字段設置(Field Set)。對象的某個屬性通過setter方法被設置或者直接被設置的時點。
  • 字段獲取(Field Get)。對象的某個屬性通過getter方法獲取或者直接訪問的時點。
  • 異常處理(Exception Handler Execution)。在某些類型異常拋出后,對應的異常處理邏輯執行的時點。
  • 類初始化(Class initialization)。類中某些靜態類型或者靜態塊的初始化時點。

  基本上程序執行過程中你認為必要的執行時點都可以作為Joinpoint,但是對於一些位置,具體的AOP實現產品在捕捉的時候可能存在一定的困難,或者能夠實現但付出太多卻可能收效甚微。在Spring AOP中最常見的就是前面的方法執行類型的Joinpoint。

2.2 切面(Pointcut)

  Pointcut概念代表的是JointPoint的表述方式。將橫切邏輯織入當前系統的過程中,需要參照Pointcut規定的Jointpoint信息,才可以知道應該往系統的哪些Joinpoint上織入橫切邏輯。

一個Pointcut可以指定系統中符合條件的一組Joinpoint,但是其是如何來指定的呢?通常有如下幾種方式:

  • 直接指定Joinpoint所在方法名稱。這種形式的Pointcut表述方式比較簡單,而且功能單一,通常只限於支持方法級別Joinpoint的AOP框架。並且這種方式只能一個一個指定,所以通常只限於Joinpoint較少且較為簡單的情況。

  • 正則表達式。這是比較普遍的Pointcut表達方式,可以充分利用正則表達式的強大功能來歸納表述符合某種條件的多組Joinpoint。幾乎現在大部分的Java平台的AOP產品都支持這種形式的Pointcut表達形式,包括Jboss AOP、Spring AOP以及AspectWerkz等。

  • 使用特定的Pointcut表述語言。這是一種最為強大的表達Pointcut的方式,很靈活,但具體實現起來可能會很複雜,需要設計該表述語言的語法,實現相應的解釋器等許多工作。AspectJ使用這種方式來指定Pointcut,它提供了一種類似於正則表達式的針對Pointcut的表述語言,在表達Pointcut方面支持比較完善,而且Spring 2.0之後也是支持這種方式。

2.3 通知(Advice)

  Advice是單一橫切關注點邏輯的載體,它代表將會織入到Joinpoint的橫切邏輯。如果將Aspect比作OOP中的Class,那麼Advice就相當於Class中的Method。

  按照Advice在Jointpoint位置執行時機的差異或者完成功能的不同,Advice可以分成多種具體形式。

  • Before Advice

  Before Advice是在Joinpoint指定位置之前執行的Advice類型。通常,它不會中斷程序執行流程,但如果必要,可以通過在Before Advice中拋出異常的方式來中斷當前程序流程。如果當前Before Advice將被織入到方法執行類型的Joinpoint,那麼這個Before Advice就會先於方法執行而執行。   通常,可以使用Before Advice做一些系統的初始化工作,比如設置系統初始值,獲取必要系統資源。

  • After Advice

  顧名思義,After Advice就是在相應連接點之後執行的Advice類型,但該類型的Advice還可以細分為三種:

  After returning Advice。只有當前Joinpoint處執行流程正常完成后,After returning Advice才會執行。

  After throwing Advice。又稱Throws Advice,只有在當前Joinpoint執行過程中拋出異常的情況下,才會執行。比如某個方法執行類型的Joinpoint拋出某異常而沒有正常返回。

  After Advice。或許叫After (Finally) Advice更為確切,該類型Advice不管Joinpoint處執行流程是正常終了還是拋出異常都會執行,就好像Java中的finally塊一樣。

  • Around Advice

  Around Advice對附加其上的Joinpoint進行”包裹”,可以在Joinpoint之前和之後都指定相應的邏輯,甚至於中斷或者忽略Joinpoint處原來程序流程的執行。

2.4 Aspect

  Aspect是對系統中的橫切關注點邏輯進行模塊化封裝的AOP概念實體,可以理解為攔截器類,其中會定義切點以及攔截處理邏輯。通常情況下,Aspect可以包含多個Pointcut以及相關Advice定義。在Spring中,是通過使用@AspectJ註解並結合普通POJO來聲明Aspect的。

@AspectJ
public class AspectClass{
    // pointcut 定義

    // advice 定義
}

2.5 目標對象

  符合Pointcut所指定的條件,將在織入過程中被織入橫切邏輯的對象,稱為目標對象(Target Object)。

 

3. Spring AOP

  AOP只是一種理念,要實現這種理念,通常需要一種現實的方式。Spring AOP就是一款AOP的實現產品,Spring AOP是Spring核心框架的重要組成部分,通常認為它與Spring的IoC容器以及Spring框架對其他JavaEE服務的集成共同組成了Spring框架的”質量三角”,足見其地位之重要。

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

  在Java語言的基礎之上,Spring AOP對AOP的概念進行了適當的抽象和實現,使得每個AOP的概念都可以落到實處,在詳細學習Spring AOP概念實體之前,我們有必要先看一下其是如何運作的。

  Spring AOP從最初發布以來,一直延續了最初的設計,也就是採用動態代理機制和字節碼生成技術來實現基於Java語言的簡單而強大的AOP框架。與最初的AspectJ採用編譯器將橫切邏輯織入目標對象不同,動態代理機制和字節碼生成都是在運行期間為目標對象生成一個代理對象,再將橫切邏輯織入到這個代理對象中,系統最終使用的是織入了橫切邏輯的代理對象,而不是真正的目標對象。

  要理解這種差別以及最終可以達到的效果,有必要先從動態代理機制的根源–代理模式(Proxy Pattern)開始說起。。。

 

3.1 設計模式之代理模式

  說到代理,舉幾個簡單的例子,比如房地產中介就是一種代理,我們偶爾使用的網絡代理也是一種代理,類似例子很多,就不一一列舉了。代理處於訪問者與被訪問者之間,可以隔離這兩者之間的直接交互,訪問者與代理打交道就好像在跟被訪問者在打交道一樣,因為代理通常幾乎會全權擁有被代理者的職能,代理能夠處理的訪問請求就不必要勞煩被訪問者來處理了。從這個角度來講,有兩個好處:

  • 代理可以減少被訪問者的負擔;
  • 即使代理最終要將訪問請求轉發給真正的被訪問者,它也可以在轉發訪問請求之前或者之後加入特定的邏輯,比如安全訪問限制;

  在軟件系統中,代理機制的實現有現成的設計模式支持,即代理模式。在代理模式中通常涉及4種角色:

  • ISubject。該接口是對被訪問者或者被訪問資源的抽象。在嚴格的設計模式中,這樣的抽象接口是必須的。
  • SubjectImpl。這是被訪問者或者被訪問資源的具體實現類。如果你要訪問某位明星,那麼SubjectImpl就是你想要訪問的明星;如果你想要買房子,那麼SubjectImpl就是房主。
  • SubjectProxy。這是被訪問者或者被訪問資源的代理實現類,該類持有一個ISubject接口的具體實例。在這個場景中,我們要對SubjectImpl進行代理,那麼SubjectProxy現在持有的就是SubjectImpl的實例。
  • Client。這代表訪問者的抽象角色,Client將會訪問ISubject類型的對象或者資源。在這個場景中,Client將會請求具體的SubjectImpl實例,但Client無法直接請求其真正要訪問的資源SubjectImpl,而是必須通過ISubject資源的訪問代理類SubjectProxy進行。

  SubjectImpl和SubjectProxy都實現了相同的接口ISubject,而SubjectProxy內部持有SubjectImpl的引用。當Client通過request()請求服務的時候,SubjectProxy將轉發該請求給SubjectImpl。從這個角度來說,SubjectProxy反而有多此一舉之嫌了,不過SubjectProxy的作用不只局限於請求的轉發,更多時候是對請求添加更多訪問限制。SubjectImpl和SubjectProxy之間的調用關係如下代碼所示:

public class SubjectProxy implements ISubject{
    private ISubject subject;   // Inject SubjectImpl to SubjectProxy
    public String request(){
        // add pre-process logic if necessary

        String originalResult = subject.request();

        // add post process logic if necessary

        return "Proxy:" + originalResult;
    }
    public ISubject getSubject(){
        return subject;
    }
    public void setSubject(ISubject subject){
        this.subject = subject;
    }
}

public class SubjectImpl implements ISubject{
    public String request(){
        // process logic
        return "OK";
    }
}

  在將請求轉發給被代理對象SubjectImpl之前或者之後,都可以根據情況插入其他處理邏輯,比如在轉發之前記錄方法執行開始時間,在轉發之後記錄結束時間,這樣就能夠對SubjectImpl的request()執行的時間進行檢測。或者,可以只在轉發之後對SubjectImpl的request()方法返回結果進行覆蓋,返回不同的值。甚至,可以不做請求轉發,這樣,就不會有SubjectImpl的訪問發生。

  代理對象SubjectProxy就像是SubjectImpl的影子,只不過這個影子通常擁有更多的功能。如果SubjectImpl是系統中Jointpoint所在的對象(即目標對象),那麼就可以為這個目標對象創建一個代理對象,然後將橫切邏輯添加到這個代理對象中。當系統使用這個代理對象的時候,原有邏輯的實現和橫切邏輯就完全融合到一個系統中。

  Spring AOP本質上就是採用這種代理機制實現的,但是,具體實現細節上有所不同。我們來看一下上面的代理實現,我們是將代理類直接寫好,然後在代碼中手動初始化代理類並通過調用代理類來實現代理功能,發現沒有,如果系統裏面有很多類需要代理相同的能,那麼我們就要寫很多的代理類,儘管它們代理的內容是一樣的,這樣是有問題的。上面這種為對應的目標對象創建靜態代理的方法,原理上是可行的,但具體應用上存在問題,所以要尋找其他方法,那有沒有呢,答案是肯定有的,就是接下來我們要講的動態代理。

 

3.2 動態代理

  JDK1.3之後,引入了動態代理(Dynamic Proxy)機制,可以在運行期間,為相應的接口(Interface)動態生成對應的代理對象,從而幫助我們走出最初使用靜態代理實現AOP的窘境。

  動態代理機制的實現主要由一個類和一個接口組成,即java.lang.reflect.Proxy類和java.lang.reflect.InvocationHandler接口。InvacationHandler就是我們實現橫切邏輯的地方,它是橫切邏輯的載體,作用跟Advice是一樣的。所以在使用動態代理機制實現AOP的過程中,我們可以在InvocationHandler的基礎上細化程序結構,根據Advice的類型,分化出對應不同的Advice類型的程序結構。

  所以,我們可以將橫切關注點邏輯封裝到動態代理的InvocationHandler中,然後在系統運行期間,根據橫切關注點需要織入的模塊位置,將橫切邏輯織入到相應的代理類中。以動態代理類為載體的橫切邏輯,現在當然就可以與系統其他實現模塊一起工作了。

  動態代理雖好,但不能滿足所有的需求,這種方式實現的唯一缺點或者說優點就是,所有需要織入橫切關注點邏輯的模塊類都得實現相應的接口,因為動態代理機制只針對接口有效。如果某個類沒有實現任何的接口,就無法使用動態代理機製為其生成相應的動態代理對象。對於沒有實現任何接口的目標對象我們需要尋找其他方式為其動態的生成代理對象。

  默認情況下,Spring AOP發現目標對象實現了相應接口,則採用動態代理機製為其生成代理對象實例。而如果目標對象沒有實現任何接口,Spring AOP則會嘗試使用一個稱為CGLIB(Code Generation Library)的開源的動態字節碼生成類庫,為目標對象生成動態的代理對象實例。

 

3.3 動態字節碼增強

  使用動態字節碼生成技術擴展對象行為的原理是,我們可以對目標對象進行繼承擴展,為其生成相應的子類,而子類可以通過覆寫來擴展父類的行為,只要將橫切邏輯的實現放到子類中,然後讓系統使用擴展后的目標對象的子類,就可以達到與代理模式相同的效果了。

  但是使用繼承的方式來擴展對象定義,也不能像靜態代理模式那樣,為每個不同類型的目標對象都單獨創建相應的擴展子類。所以,我們要藉助於CGLIB這樣的動態字節碼生成庫,在系統運行期間動態地為目標對象生成相應的擴展子類。

  我們知道,Java虛擬機加載的文件都是符合一定規範的,所以,只要交給Java虛擬機運行的文件符合Java class規範,程序的運行就沒有問題。通常的class文件都是從Java源代碼文件使用Javac編譯器編譯而成的,但只要符合Java class規範,我們也可以使用ASM或者CGLiB等Java工具庫,在程序運行期間,動態構建字節碼的class文件。

  在這樣的前提下,我們可以為需要織入橫切邏輯的模塊類在運行期間,通過動態字節碼增強技術,為這些系統模塊類生成相應的子類,而將橫切邏輯加到這些子類中,讓應用程序在執行期間使用從這些動態生成的子類,從而達到將橫切邏輯織入系統的目的。   使用動態字節碼增強技術,即使模塊類沒有實現相應的接口,我們依然可以對其進行擴展,而不用像動態代理那樣受限於接口。不過,這種實現機制依然存在不足,如果需要擴展的類以及類中的實例方法等聲明為final的話,則無法對其進行子類化的擴展。

 

3.4 一個spring aop示例

  上面說了這麼多,下面就來看一個簡單的例子,體會一下aop的魔法吧。如果只是引用了spring-context,那麼還需要引入spring-aspects:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>3.2.18.RELEASE</version>
</dependency>

  這裏我們採用xml配置的方式來開啟aop功能,在resources目錄下添加一個xml配置文件,其中<aop:aspectj-autoproxy/>是用來開啟aop的:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop = "http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
     http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
     http://www.springframework.org/schema/aop
     http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
     
     <aop:aspectj-autoproxy/>
     
     <bean id = "test" class = "spring.aop.TestAopBean"/>
     <bean class = "spring.aop.AspectJTest"/>
</beans>

  添加Aspect:

@Aspect
public class AspectJTest {

    @Pointcut("execution(* *.test(..))")
    public void test(){
        
    }

    @Before("test()")
    public void beforeTest(){
        System.out.println("beforeTest");
    }
    
    @After("test()")
    public void afterTest(){
        System.out.println("afterTest");
    }

    @Around("test()")
    public Object aroundTest(ProceedingJoinPoint p){
        System.out.println("before1");
        Object o = null;
        try{
            o = p.proceed();
        }catch (Throwable e){
            e.printStackTrace();
        }
        System.out.println("after1");
        return o;
    }
}

  添加測試類:

public class TestAopBean {

    private String testStr = "testStr";

    public String getTestStr(){
        return testStr;
    }

    public void setTestStr(String testStr){
        this.testStr = testStr;
    }

    public void test(){
        System.out.println("hello test");
    }
    
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("aspectJTest.xml");
        TestAopBean test = (TestAopBean)ctx.getBean("test");
        test.test();
    }
}

  可以看到輸出結果:

before1
beforeTest
hello test
after1
afterTest

  這是一個aop簡單示例,我們寫了一個切面(Pointcut),用來指定Joinpoint的位置在執行test()方法時;同時分別定義了三個Advice(Before、After、Around)用來指定要織入的動作,最後將Pointcut和Advice封裝到一個Aspect中,這樣就完成了橫切邏輯的織入。

 

4. 總結

  在深入學習Spring AOP之前,我們先對AOP的概況進行了介紹,接着一起探索了Spring AOP的實現機制,包括最原始的代理模式,直至最終的動態代理與動態字節碼生成技術。

  • AOP是能夠讓我們在不影響系統原有功能前提下,為軟件系統橫向擴展功能;
  • Spring AOP通過兩種方式實現:JDK動態代理、動態字節碼增強;

  在了解了這些內容之後,我們將繼續深入學習Spring AOP。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司