1. 程式人生 > >Spring-AOP切面程式設計總結

Spring-AOP切面程式設計總結

寫在前面:
  之前寫了三篇JAVA基礎進階、一篇JAVA原始碼解析,今天又過來寫框架,大家別擔心,另外兩個以後還會繼續寫的,給大家預告下,下一篇部落格會寫JAVA基礎進階的《JAVA反射機制》,然後會寫JAVA原始碼解析的集合原始碼解析,那塊應該一兩個集合型別就是一篇部落格了吧。近期打算把部落格搬到github上面去了,但是這裡以後還會繼續更新的,兩邊都一樣。

.1 AOP簡介

  AOP是面向切面程式設計(Aspect-Oriented Programming)的縮寫,是對傳統的面向物件程式設計的一種補充,下面來介紹下在AOP中經常用到的一些術語。

  • 切面(Aspect):橫切關注點(跨越應用程式多個模組的功能)被模組化的特殊物件

  • 通知(Advice):切面必須要完成的工作

  • 目標(Target):被通知的物件

  • 代理(Proxy): 向目標物件應用通知之後建立的物件

  • 連線點(Joinpoint):程式執行的某個特定位置:如類某個方法呼叫前、呼叫後、方法丟擲異常後等。

  • 切點(pointcut):一個類中可以有多個連線點,就和每個類中可以有多個方法一樣,AOP通過切點定位到特定的連線點。可以這麼理解:連線點相當於資料庫中的記錄,切點則相當於查詢條件,切點使用類和方法作為連線點的查詢條件。

  AOP切面程式設計是圍繞著通知註解這個東西展開的,在SpringAOP中共有五種型別的通知註解:

  • @Before: 前置通知,在方法執行之前執行

  • @After: 後置通知,在方法執行之後執行

  • @AfterRunning:返回通知,在方法返回結果之後執行

  • @AfterThrowing:異常通知,在方法丟擲異常之後

  • @Around:環繞通知,圍繞著方法執行 (並不常用)

.2 一個小需求

   我通過一個小小的需求來向大家展示SpringAOP程式設計的各個細節,比如說,我們寫了兩個方法:一個方法將兩個整形的數字做加法運算、一個方法將兩個整形數字做除法運算,這樣一個需求看起來非常簡單。但是,我們要求,在方法開始時、結束時、返回時、發生異常時,分別在控制檯上輸出一段文字來說明當前的狀態,這時該怎麼辦呢??
   我們通過AOP可以很輕鬆的解決這個問題。

.3 通過註解的方式配置AOP

   首先建立一個JAVA專案,在專案下匯入Spring的開發包,然後建立一個介面ArithmeticCalculator,在介面中定義兩個方法,add()和div()。程式碼如下:

package com.byh.aop.hello;

public interface ArithmeticCalculator {

    int add(int i,int j);
    int div(int i,int j);

}

   在src下建立Spring的配置檔案applicationContext.xml,並在Namespaces選項中勾選aop和context這兩個選項,之後在applicationContext.xml中啟用 AspectJ 註解支援,並指定IOC容器的掃描範圍。程式碼如下:

    <context:component-scan base-package="com.byh.aop.hello"></context:component-scan>  <!-- 指定IOC容器的掃描範圍 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy> <!-- 啟用 AspectJ 註解支援 -->

   新增該介面的實現類ArithmeticCalculatorImpl,實現ArithmeticCalculator介面的兩個方法。程式碼如下:

package com.byh.aop.hello;
import org.springframework.stereotype.Component;

@Component //將該類注入到IOC容器中
public class ArithmeticCalculatorImpl implements ArithmeticCalculator  {

    @Override
    public int add(int i, int j) {

        int result = i+j;

        return result;
    }



    @Override
    public int div(int i, int j) {

        int result = i/j;

        return result;
    }

}

   寫一個測試類來測試,程式碼如下:

package com.byh.aop.hello;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

    public static void main(String[] args) {

        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        ArithmeticCalculator arithmeticCalculator = ctx.getBean(ArithmeticCalculator.class);

        int result = arithmeticCalculator.add(3, 6);
        System.out.println("result:"+result);

        result = arithmeticCalculator.div(12, 6);
        System.out.println("result:"+result);
    }

}

   執行之後我們發現執行是成功的,但是這還沒有達到我們的要求,在需求中我們要求在方法開始時、結束時、返回時、發生異常時,分別在控制檯上輸出一段文字來說明當前的狀態,現在我們就用AOP來實現。

.3-1 前置通知

   首先我們來想如何讓方法開始時在控制檯輸出一段話呢?這是就要用到AOP的前置通知了。前置通知就是在方法執行之前執行的通知,前置通知使用@Before註解,並將切入點表示式的值也就是該方法的路徑,作為其註解值。如果要把一個類宣告為一個切面的話,只要在該類的前面加上@Aspect註解就可以了。可以在通知方法中宣告一個型別為 JoinPoint 的引數.。然後就能訪問連結細節. 如方法名稱和引數值.。
我們來新建一個類名為LoggingAspect,並在該類中實現前置通知,程式碼如下:

@Component //將該類注入到IOC容器 
@Aspect  //將該類生命為切面
public class LoggingAspect {

    @Before(value = "execution(public int com.byh.aop.hello.ArithmeticCalculator.*(..))")//將該方法宣告為前置通知,切入點表示式的值為連線點的路徑
    public void beforeMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println("前置通知:   The method "+methodName+" begins with "+args);
    }

}

.3-2 後置通知

  後置通知是在連線點完成之後執行的, 即連線點返回結果或者丟擲異常的時候,。一個切面可以包括一個或者多個通知,所以根據我們的需求,我們在剛才前置通知的基礎上在LoggingAspect類中新增實現後置通知的方法即可,程式碼如下:

@After(value = "execution(public int com.byh.aop.hello.ArithmeticCalculator.*(..))")
    public void afterMethod(JoinPoint joinPoint){

        String methodName = joinPoint.getSignature().getName();
        System.out.println("後置通知:   end method "+methodName);

    }

  這時不知道大家有沒有發現一個問題,我們在每一個通知的前面都要寫上切點表示式,而且切點表示式的內容是一樣的,這樣就不符合我們編碼的風格了,該如何進行簡化呢?我們可以宣告一個類去指定切入點表示式的值,這個方法中什麼都不用寫,只需要在方法前使用@Pointcut註解來指定切點表示式即可,程式碼如下。

@Pointcut("execution(public int com.byh.aop.hello.ArithmeticCalculator.*(..))")
    public void declareJointPointExpression(){}

  將該方法加入到LoggingAspect類中,並且寫在所有通知之前,這樣一來,當需要生命切入點表示式時,直接將該方法的名字寫上就好了,我們修改之前寫的前置通知和後置通知的切入點表示式宣告方法,程式碼如下:

@Before("declareJointPointExpression()")//修改切入點表示式的宣告方法
    public void beforeMethod(JoinPoint joinPoint){
        ...
    }

.3-3 返回通知

  無論連線點是正常返回還是丟擲異常, 後置通知都會執行.。如果想在連線點返回的時候記錄日誌, 應使用返回通知。程式碼如下:

@AfterReturning(value="declareJointPointExpression()",
            returning="result")//returning指定返回的引數
    public void afterReturning(JoinPoint joinPoint,Object result){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("返回通知:   The method "+methodName+" ends with "+result);
    }

.3-4 異常通知

  異常通知只在連線點丟擲異常時才會執行,將 throwing 屬性新增到 @AfterThrowing 註解中, 也可以訪問連線點丟擲的異常..Throwable 是所有錯誤和異常類的超類.。所以在異常通知方法可以捕獲到任何錯誤和異常。如果只對某種特殊的異常型別感興趣, 可以將引數宣告為其他異常的引數型別, 然後通知就只在丟擲這個型別及其子類的異常時才被執行。程式碼如下:

@AfterThrowing(value="declareJointPointExpression()",
            throwing="ex")
    public void afterThrowing(JoinPoint joinPoint,Exception ex){

        String methodName = joinPoint.getSignature().getName();
        System.out.println("異常通知:   The method "+methodName+" occurs exception: "+ex);

    }

  這時我們再去執行下測試方法,發現執行的結果已經變成了這樣:

前置通知:   The method add begins with [3, 6]
後置通知:   end method add
返回通知:   The method add ends with 9
result:9
前置通知:   The method div begins with [12, 6]
後置通知:   end method div
返回通知:   The method div ends with 2
result:2

  這就表明我們寫的幾個通知已經起到它該有的作用了,測試異常通知的話,只需將除法的被除數改為0即可,這裡就不做演示了,至此我們已經瞭解AOP到底是如何使用的了。

.4 通過基於 XML 的配置來配置AOP

  除了使用註解的方式去配置之外,我們還可以使用配置檔案的方式去配置。
  在 Bean 配置檔案中, 所有的 Spring AOP 配置都必須定義在 aop:config 元素內部。
  對於每個切面而言, 都要建立一個 aop:aspect 元素來為具體的切面實現引用後端 Bean 例項。
  切入點使用 aop:pointcut 元素宣告,切入點必須定義在 aop:aspect 元素下, 或者直接定義在 aop:config 元素下。區別是定義在 aop:aspect 元素下: 只對當前切面有效,定義在 aop:config 元素下: 對所有切面都有效。
  修改applicationContext.xml,程式碼如下:

<context:component-scan base-package="com.byh.aop.hello"></context:component-scan>

    <!-- 配置AOP -->

<aop:config>
    <!-- 配置切點表示式 -->
    <aop:pointcut expression="execution(* com.byh.aop.hello.ArithmeticCalculator.*(..))" id="pointcut"/>
    <!--宣告切面-->
    <aop:aspect ref="loggingAspect">
        <!--前置通知-->
        <aop:before method="beforeMethod" pointcut-ref="pointcut"/>
        <!--後置通知-->
        <aop:after method="afterMethod" pointcut-ref="pointcut"/>
        <!--返回通知-->
        <aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/>
        <!--異常通知-->
        <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="ex"/>
    </aop:aspect>


</aop:config>

  然後我們將LoggingAspect類中所有關於AOP的註解都刪除掉,刪除之後程式碼如下:

@Component
public class LoggingAspect {

    public void beforeMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println("前置通知:   The method "+methodName+" begins with "+args);
    }

    public void afterMethod(JoinPoint joinPoint){

        String methodName = joinPoint.getSignature().getName();
        System.out.println("後置通知:   end method "+methodName);

    }

    public void afterReturning(JoinPoint joinPoint,Object result){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("返回通知:   The method "+methodName+" ends with "+result);
    }

    public void afterThrowing(JoinPoint joinPoint,Exception ex){

        String methodName = joinPoint.getSignature().getName();
        System.out.println("異常通知:   The method "+methodName+" occurs exception: "+ex);

    }

}

  之後執行測試方法,發現執行結果與使用註解進行配置的方式是一樣的。

筆者水平有限,若有錯漏,歡迎指正,如果轉載以及CV操作,請務必註明出處,謝謝!