1. 程式人生 > 其它 >跟著柴毛毛學Spring(4)——面向切面程式設計![這裡寫圖片描述](http://img.blog.csdn.net/20171031111402095)

跟著柴毛毛學Spring(4)——面向切面程式設計![這裡寫圖片描述](http://img.blog.csdn.net/20171031111402095)

面向切面程式設計簡介

1. 什麼是面向切面程式設計?

  面向切面程式設計是Spring的第二大特性,它能將一個函式中非主體但有很必要的程式碼封裝到一個單獨的類中,在程式執行的時候再把它們插入到函式中。這樣能使程式猿只關注函式的主體功能,而且寫出來的程式碼具有具有較強的可讀性,簡約明瞭。

2. 面向切面程式設計的優點

面向切面程式設計的優點有兩個: 1. 一個函式中所有額外的功能都被封裝在一個類中,而不是分散在函式的各處。 2. 由於將非主體功能的程式碼轉移到其他類中,因此函式的程式碼將更佳簡潔。

3. Spring的面向切面程式設計特點

目前面向切面(AOP)市場的三足鼎立格局是這樣的: - AspectJ - JBoss Aspect - Spring Aspect Spring Aspect借鑑AspectJ。

  所謂面向切面,前面已經介紹過,就是將函式的一部分程式碼抽取出來,封裝到一個類中,然後在某一時刻將其插入到程式中去。 AOP世界中,將切面插入的到程式中的時機有以下三種: 1. 編譯時插入 2. 類被載入的時候插入 3. 程式執行時被插入

  Spring是在程式執行期間將切面插入到相關函式中去的。   此外,在編譯時插入需要特殊的編譯器,在類載入的時候插入需要特殊的類載入器,而在執行時插入不需要額外的軟體支援。

開始使用

  假設現在有一個函式,用來查詢資料庫中名字為name的Person物件:

    public String queryPersons(String name){
        //查詢的具體操作……
        //此處省略100行程式碼……
    }

  我們需要在查詢開始前判斷name是否為空,在查詢結束後記錄操作日誌,在查詢發生異常時記錄異常日誌。 在過去,我們把這些程式碼都直接寫在這個函式中,這樣會導致這個函式很長,可讀性很差。下面我們使用面向切面程式設計的思想解決這個問題。

1. 定義切面程式碼

  首先需要將這個函式中額外的功能封裝到一個類中,這個類就是一個普通類,功能封裝在函式中:

class PersonAspect(){
    /**
      * 判斷引數是否為空
      */
    public void verifyNull(String name){
        //判斷引數是否為空
    }

    /**
      * 記錄操作日誌
      */
    public void logInfo(){

    }

    /**
      * 記錄異常日誌
      */
    public void logError(){

    }
}

2. 將切面宣告為bean

在Spring的配置檔案中新增bean的宣告:

    <bean id="personAspect" class="com.njupt.PersonAspect">
    </bean>

或者直接在PersonAspect類上使用@Componet標註:

@Componet("personAspect")
class PersonAspect(){
    /**
      * 判斷引數是否為空
      */
    public void verifyNull(String name){
        //判斷引數是否為空
    }

    /**
      * 記錄操作日誌
      */
    public void logInfo(){

    }

    /**
      * 記錄異常日誌
      */
    public void logError(){

    }
}

3. 宣告通知

  定義好了額外的功能之後,接下來我們需要告訴Spring,這些功能需要在何時新增到哪個函式的什麼位置,因此我們需要在Spring的配置檔案中作如下宣告:

    <aop:config>
        <aop:aspect ref="personAspect">
            <!-- 在queryPersons函式執行之前先執行函式verifyNull -->
            <aop:before pointcut="excution(* com.njupt.Person.queryPersons(..))" method="verifyNull" />

            <!-- 在queryPersons函式執行之後再執行函式logInfo -->
            <aop:after-returning pointcut="excution(* com.njupt.Person.queryPersons(..))" method="logInfo" />

            <!-- 在queryPersons函式發生異常時執行函式logError -->
            <aop:throwing pointcut="excution(* com.njupt.Person.queryPersons(..))" method="logError" />
    </aop:config>

到此為止,Spring AOP已經可以運行了!

4. 宣告環繞通知

  在上述示例中,函式前插入一段程式碼,函式後插入一段程式碼,函式異常時插入一段程式碼,這些程式碼塊都是獨立的,無法共享資料。如果需要實現函式前後程式碼塊的資料共享,那就要使用環繞通知。 說來也很簡單,首先需要在PersonAspect類中加上一個引數為ProceedingJoinPoint的函式,如下所示:

@Componet("personAspect")
class PersonAspect(){
    /**
      * 判斷引數是否為空
      */
    public void verifyNull(String name){
        //判斷引數是否為空
    }

    /**
      * 記錄操作日誌
      */
    public void logInfo(){

    }

    /**
      * 記錄異常日誌
      */
    public void logError(){

    }

    /**
      * 環繞通知
      */
    public void xxx(ProceedingJoinPoint joinPoint){
        int num = 0;
        //在函式前執行一些功能,修改num引數

        //執行函式
        joinPoint.proceed();

        //在函式後處理num
        num++;
        //……
    }
}

然後在配置檔案中把這個函式宣告為環繞通知即可:

    <aop:config>
        <aop:aspect ref="personAspect">
            <!-- 在queryPersons函式執行之前先執行函式verifyNull -->
            <aop:before pointcut="excution(* com.njupt.Person.queryPersons(..))" method="verifyNull" />

            <!-- 在queryPersons函式執行之後再執行函式logInfo -->
            <aop:after-returning pointcut="excution(* com.njupt.Person.queryPersons(..))" method="logInfo" />

            <!-- 在queryPersons函式發生異常時執行函式logError -->
            <aop:throwing pointcut="excution(* com.njupt.Person.queryPersons(..))" method="logError" />

            <!-- 環繞通知 -->
            <aop:around pointcut="excution(* com.njupt.Person.queryPersons(..))" method="xxx()" />
        </aop:aspect>
    </aop:config>

到此為止,Spring的AOP介紹完畢,下面介紹如何使用註解取代XML。

使用註解代替XML

1. 註解切面

  首先需要在切面類上加上註解@Aspect,告訴Spring這個類是個切面類;   其次需要在切面類中建立一個函式,用於定義切點。該函式的名字即為切點名,函式返回值和引數均為空。

@Aspect
class PersonAspect(){
    /**
      * 定義切點
      */
    @Pointcut("excution(* com.njupt.Person.queryPersons(..))")
    public void personAspect(){
    }

    /**
      * 判斷引數是否為空
      */
    @Before("personAspect()")
    public void verifyNull(String name){
        //判斷引數是否為空
    }

    /**
      * 記錄操作日誌
      */
    @AfterReturning("personAspect()")
    public void logInfo(){

    }

    /**
      * 記錄異常日誌
      */
    @AfterThrowing("personAspect()")
    public void logError(){

    }

    /**
      * 環繞通知
      */
    @Around("personAspect()")
    public void xxx(ProceedingJoinPoint joinPoint){
        int num = 0;
        //在函式前執行一些功能,修改num引數

        //執行函式
        joinPoint.proceed();

        //在函式後處理num
        num++;
        //……
    }
}