Spring4(二)AOP

需求:假如,一個業務類有四個方法(加減),我們要在每個方法都加上日誌操作。

1、沒有AOP,實現日誌

1.1 定義接口

定義一個數字運算的接口,其中有兩個方法:加法運算和減法運算:

package com.spring.aop;

public interface MathCalculator {
    public int add(int a,int b);
    public int sub(int a,int b);
}

1.2 定義實現類

定義實現類,除了進行運算,還要打印簡單的日誌功能:

package com.spring.aop;

public class NonAopMathCalculator implements MathCalculator {
    @Override
    public int add(int a, int b) {
        System.out.println("方法執行前參數是:"+a+","+b);
        int result = a+b;
        System.out.println("方法執行後結果是:"+result);
        return result;
    }

    @Override
    public int sub(int a, int b) {
        System.out.println("方法執行前參數是:"+a+","+b);
        int result = a-b;
        System.out.println("方法執行後結果是:"+result);
        return result;
    }
}

1.3 測試

package com.spring.aop;

public class MathCalculatorTest {
    public static void main(String[] args) {
        MathCalculator mathCalculator = new NonAopMathCalculator();
        int add = mathCalculator.add(9, 6);
        System.out.println("加法計算結果:"+add);
        int sub = mathCalculator.sub(9, 6);
        System.out.println("減法計算結果:"+sub);
    }
}

打印結果:

方法執行前參數是:9,6
方法執行後結果是:15
加法計算結果:15
方法執行前參數是:9,6
方法執行後結果是:3
減法計算結果:3

1.4 存在的問題

代碼混亂:越來越多的非業務需求(日誌和驗證等)加入後原有的業務方法急劇膨脹 每個方法在處理核心邏輯的同時還必須兼顧其他多個關注點

代碼分散以日誌需求爲例只是爲了滿足這個單一需求就不得不在多個模塊(方法)裏多次重複相同的日誌代碼如果日誌需求發生變化必須修改所有模塊。

 

2、通過動態代理實現日誌

代理設計模式的原理使用一個代理將對象包裝起來然後用該代理對象取代原始對象任何對原始對象的調用都要通過代理代理對象決定是否以及何時將方法調用轉到原始對象上。這種方式可以解決上述存在的問題:

接口還是上面的,不用改變,現在我們使用動態代理的方式來實現,代碼如下:

2.1 創建接口實現

創建接口的實現類,實現類中只做業務相關的事情,日誌的事情不需要管:

package com.spring.aop;

public class MathCalculatorImpl implements MathCalculator {
    @Override
    public int add(int a, int b) {
        return a+b;
    }

    @Override
    public int sub(int a, int b) {
        return a-b;
    }
}

2.2 創建代理類

創代理類,代理類持有被代理對象的引用

package com.spring.aop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class MathCalculatorProxyImpl {
    //持有被代理對象的引用
    private MathCalculatorImpl target;

    //通過構造方法傳入被代理對象
    public MathCalculatorProxyImpl(MathCalculatorImpl target) {
        this.target = target;
    }

    //返回代理對象
    public MathCalculator getLoggingProxy(){
        MathCalculator proxy = null;
        ClassLoader classLoader = target.getClass().getClassLoader();//類加載器
        Class[] interfaces = new Class[]{MathCalculator.class};//被代理對象實現的接口的數組
        //處理器對象,進行日誌操作,被代理對象的所有方法執行都要先經過該類的invoke方法
        InvocationHandler handler = new InvocationHandler() {
            /**
             * proxy: 代理對象。 一般不使用該對象
             * method: 正在被調用的方法
             * args: 調用方法傳入的參數
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String methodName = method.getName();
                //打印日誌
                System.out.println("[before] The method " + methodName + " begins with " + Arrays.asList(args));

                //調用目標方法
                Object result = null;//目錄方法返回值
                try {
                    //前置通知
                    result = method.invoke(target, args);
                    //返回通知, 可以訪問到方法的返回值
                } catch (NullPointerException e) {
                    e.printStackTrace();
                    //異常通知, 可以訪問到方法出現的異常
                }
                //後置通知. 因爲方法可以能會出異常, 所以訪問不到方法的返回值
                //打印日誌
                System.out.println("[after] The method ends with " + result);
                return result;
            }
        };
        /**
         * classLoader: 代理對象使用的類加載器。
         * interfaces: 指定代理對象的類型. 即代理代理對象中可以有哪些方法.
         * handler: 當具體調用代理對象的方法時, 應該如何進行響應, 實際上就是調用 InvocationHandler 的 invoke 方法
         */
        proxy = (MathCalculator) Proxy.newProxyInstance(classLoader,interfaces,handler);
        return proxy;
    }
}

2.3 測試

package com.spring.aop;

public class MathCalculatorProxyTest {
    public static void main(String[] args) {
        //創建被代理對象
        MathCalculatorImpl mathCalculatorImpl = new MathCalculatorImpl();
        //創建代理對象
        MathCalculatorProxyImpl mathCalculatorProxy = new MathCalculatorProxyImpl(mathCalculatorImpl);
        MathCalculator proxy = mathCalculatorProxy.getLoggingProxy();
        int add = proxy.add(9, 6);
        System.out.println("加法計算結果:"+add);
        int sub = proxy.sub(9, 6);
        System.out.println("減法計算結果:"+sub);
    }
}

打印結果:

[before] The method add begins with [9, 6]
[after] The method ends with 15
加法計算結果:15
[before] The method sub begins with [9, 6]
[after] The method ends with 3
減法計算結果:3

2.4 總結

動態代理的方式可以實現日誌操作,這樣實現起來很統一,就算目標類中加了其他方法,不需要任何操作,就能實現日誌功能。但是如果每次這樣手動實現,也會比較麻煩,所以Spring給我們提供了AOP模塊。

 

3、AOP

3.1 AOP簡介

AOP(Aspect-Oriented Programming, 面向切面編程): 是一種新的方法論是對傳統 OOP(Object-Oriented Programming, 面向對象編程) 的補充
AOP 的主要編程對象是切面(aspect),切面模塊化橫切關注點
在應用 AOP 編程時仍然需要定義公共功能但可以明確的定義這個功能在哪裏以什麼方式應用並且不必修改受影響的類這樣一來橫切關注點就被模塊化到特殊的對象(切面)
AOP 的好處
每個事物邏輯位於一個位置代碼不分散便於維護和升級。

                 業務模塊更簡潔,只包含核心業務代碼。

3.2 AOP術語

切面(Aspect):  橫切關注點(跨越應用程序多個模塊的功能)被模塊化的特殊對象
通知(Advice):  切面必須要完成的工作
目標(Target):   被通知的對象
代理(Proxy):    向目標對象應用通知之後創建的對象
連接點(Joinpoint): 程序執行的某個特定位置:如類某個方法調用前、調用後、方法拋出異常後等連接點由兩個信息                                        確定:方法表示的程序執行點;相對點表示的方位。例如 ArithmethicCalculator#add() 方法執行前的                                  連接點,執行點爲 ArithmethicCalculator#add(); 方位爲該方法執行前的位置
切點(pointcut):每個類都擁有多個連接點:例如 ArithmethicCalculator 的所有方法實際上都是連接點,即連接點是程序                                  類中客觀存在的事務AOP 通過切點定位到特定的連接點。類比:連接點相當於數據庫中的記錄,切                                     點相當於查詢條件。切點和連接點不是一對一的關係,一個切點匹配多個連接點,切點通過                                                    org.springframework.aop.Pointcut 接口進行描述,它使用類和方法作爲連接點的查詢條件

 

3.3 AOP的實現AspectJ使用

AspectJJava 社區裏最完整最流行的 AOP 框架.
Spring2.0 以上版本可以使用基於 AspectJ 註解或基於 XML 配置的 AOP。
 
AspectJ需要的jar包:com.springsource.org.aopalliance-1.0.0.jar    com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar   spring-aop-4.0.0.RELEASE.jar    spring-aspects-4.0.0.RELEASE.jar

3.3.1 註解配置使用AspectJ實現AOP

首先要把jar包導入到工程中

1、創建接口和實現類,並在實現類加上Spring註解

package com.spring.aspectjAop.annotation;

public interface MathCalculator {
    public int add(int a, int b);
    public int sub(int a, int b);
}

package com.spring.aspectjAop.annotation;

import org.springframework.stereotype.Component;

@Component("mathCalculator")
public class MathCalculatorImpl implements MathCalculator {
    @Override
    public int add(int a, int b) {
        return a+b;
    }

    @Override
    public int sub(int a, int b) {
        return a-b;
    }
}

2、創建AspectJ代理類

package com.spring.aspectjAop.annotation;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

@Component//首先切面也是spring的一個Bean
@Aspect//表明這是一個AspectJ切面
public class LoggingAspectJAop {

    /**
     *     標識這個方法是個前置通知,  切點表達式表示執行任意類的任意方法. 第
     *     一個 * 代表匹配任意修飾符及任意返回值,  第二個 * 代表任意類的對象,
     *     第三個 * 代表任意方法, 參數列表中的 ..  匹配任意數量的參數
     */
    @Before("execution(* com.spring.aspectjAop.annotation.*.*(..))")
    public void beforeMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println("執行的方法是:"+methodName+",參數是:"+args);
    }
}

3、配置文件applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <!--配置包掃描-->
    <context:component-scan base-package="com.spring.aspectjAop.annotation"/>

    <!--開啓AspectJ的註解-->
    <aop:aspectj-autoproxy/>

</beans>

4、測試

package com.spring.aspectjAop.annotation;

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

public class MainTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring/applicationContext.xml");
        MathCalculator mathCalculator = (MathCalculator) applicationContext.getBean("mathCalculator");
        int add = mathCalculator.add(9, 3);
        System.out.println("加法結果:"+add);
        int sub = mathCalculator.sub(9, 3);
        System.out.println("減法結果:"+sub);
    }
}

打印結果:

執行的方法是:add,參數是:[9, 3]
加法結果:12
執行的方法是:sub,參數是:[9, 3]
減法結果:6

4、總結

1、要使用AspectJ,就要先導入相關的jar包;

2、applicationContext.xml文件中,要加入aop名稱空間,啓用 AspectJ 註解支持, 只要Bean 配置文件中定義一個空的 XML 元素 <aop:aspectj-autoproxy>;Spring IOC 容器偵測到 Bean 配置文件中的 <aop:aspectj-autoproxy> 元素時, 會自動爲與 AspectJ 切面匹配的 Bean 創建代理。

3、切面類要使用註解@Components和@Aspect

4、定義方法,方法上使用註解表明這個方法的執行時機,有如下註解:

@Before: 前置通知, 在方法執行之前執行
@After: 後置通知, 在方法執行之後執行(無法是否發生異常)
@AfterRunning: 返回通知, 在方法返回結果之後執行
@AfterThrowing: 異常通知, 在方法拋出異常之後
@Around: 環繞通知, 圍繞着方法執行

5、切面方法的註解中,要編寫AspectJ表達式,告訴該方法要監聽哪些方法;

6、如果要拿到目標方法的方法名稱和參數,就可以在方法形參中直接寫上:JoinPoint joinPoint

5、方法簽名編寫 AspectJ 切入點表達式

最典型的切入點表達式時根據方法的簽名來匹配各種方法:
execution * com.atguigu.spring.ArithmeticCalculator.*(..): 匹配 ArithmeticCalculator 中聲明的所有方法,第一個 * 代表任意修飾符及任意返回值. 第二個 * 代表任意方法. .. 匹配任意數量的參數. 若目標類與接口與該切面在同一個包中, 可以省略包名.
execution public * ArithmeticCalculator.*(..): 匹配 ArithmeticCalculator 接口的所有公有方法.
execution public double ArithmeticCalculator.*(..): 匹配 ArithmeticCalculator 返回 double 類型數值的方法
execution public double ArithmeticCalculator.*(double, ..): 匹配第一個參數爲 double 類型的方法, .. 匹配任意數量任意類型的參數

                  execution public double ArithmeticCalculator.*(double, double): 匹配參數類型爲 double, double 類型的方法

AspectJ 切入點表達式可以通過操作符 &&,||, ! 結合起來:

6、後置通知

在剛纔的LoggingAspectJAop加上後置通知 @After:

 /**
     * 後置通知是在連接點完成之後執行的, 即連接點返回結果或者拋出異常的時候, 下面的後置通知記錄了方法的終止
     * @param joinPoint
     */
    @After("execution(* com.spring.aspectjAop.annotation.*.*(..))")
    public void afterMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println(methodName+"執行完了");
    }

7、返回通知

無論連接點是正常返回還是拋出異常後置通知都會執行如果只想在連接點返回的時候(方法正常結束)記錄日誌應使用返回通知代替後置通知。返回通知可以訪問方法的返回值。

返回通知中, 只要將 returning 屬性添加到 @AfterReturning 註解中就可以訪問連接點的返回值,該屬性的值即爲用來傳入返回值的參數名稱;必須在通知方法的簽名中添加一個同名參數在運行時,Spring AOP 會通過這個參數傳遞返回值;原始切點表達式需要出現在 pointcut 屬性中

在剛纔的LoggingAspectJAop加上返回通知 @AfterReturning:

@AfterReturning(value = "execution(* com.spring.aspectjAop.annotation.*.*(..))",returning = "result")
    public void afterRunningMethod(JoinPoint joinPoint, Object result){
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println(methodName+"方法的返回值是:"+result);
    }

8、異常通知

只在連接點拋出異常時才執行異常通知;

將throwing 屬性添加到 @AfterThrowing 註解中也可以訪問連接點拋出的異常Throwable 是所有錯誤和異常類的超類所以在異常通知方法可以捕獲到任何錯誤和異常。

如果只對某種特殊的異常類型感興趣可以將參數聲明爲其他異常的參數類型然後通知就只在拋出這個類型及其子類的異常時才被執行。

/**
     * 異常通知
     */
    @AfterThrowing(value = "execution(* com.spring.aspectjAop.annotation.*.*(..))",throwing = "exception")
    public void afterThrowableMethod(JoinPoint joinPoint, Exception exception){
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println(methodName+"方法的出現了異常,異常信息是:"+exception.getMessage());
    }
 

9、環繞通知

環繞通知是所有通知類型中功能最爲強大的能夠全面地控制連接點甚至可以控制是否執行連接點;

對於環繞通知來說連接點的參數類型必須是 ProceedingJoinPoint它是 JoinPoint 的子接口允許控制何時執行是否執行連接點;

在環繞通知中需要明確調用 ProceedingJoinPoint 的 proceed() 方法來執行被代理的方法, 如果忘記這樣做就會導致通知被執行了,但目標方法沒有被執行

注意:環繞通知的方法 需要返回目標方法執行之後的結果即調用 joinPoint.proceed(); 的返回值否則會出現空指針異常

/**
     * 環繞通知需要攜帶 ProceedingJoinPoint 類型的參數. 
     * 環繞通知類似於動態代理的全過程: ProceedingJoinPoint 類型的參數可以決定是否執行目標方法.
     * 且環繞通知必須有返回值, 返回值即爲目標方法的返回值
     */
    @Around(value = "execution(* com.spring.aspectjAop.annotation.*.*(..))")
    public Object aroundMethod(ProceedingJoinPoint pjd){
        Object result = null;//定義返回值
        String methodName = pjd.getSignature().getName();
        List<Object> args = Arrays.asList(pjd.getArgs());
        try {
            //前置通知
            System.out.println(methodName+"方法執行之前,參數是:"+args);
            result = pjd.proceed();
            //後置通知
            System.out.println(methodName+"方法執行完成,結果是:"+result);
        } catch (Throwable throwable) {
            //異常通知
            System.out.println(methodName+"方法執行出現了異常,異常信息:"+throwable.getMessage());
            throw  new RuntimeException(throwable);
        }
        //返回通知
        System.out.println(methodName+"執行沒有發生異常,一切正常返回,返回結果是:"+result);
        return result;
    }

10、切面的優先級

在同一個連接點上應用不止一個切面時除非明確指定否則它們的優先級是不確定的
切面的優先級可以通過實現 Ordered 接口或利用 @Order 註解指定
實現 Ordered 接口getOrder() 方法的返回值越小優先級越高
若使用 @Order 註解序號出現在註解中。

11、重(chong)用切入點定義

1、在編寫AspectJ 切面時可以直接在通知註解中書寫切入點表達式但同一個切點表達式可能會在多個通知中重複出現;

2、在AspectJ 切面中可以通過 @Pointcut 註解將一個切入點聲明成簡單的方法切入點的方法體通常是空的因爲將切入點定義與應用程序邏輯混在一起是不合理的;

3、切入點方法的訪問控制符同時也控制着這個切入點的可見性如果切入點要在多個切面中共用最好將它們集中在一個公共的類中在這種情況下它們必須被聲明爲 public;在引入這個切入點時必須將類名也包括在內如果類沒有與這個切面放在同一個包中還必須包含包名;

4、其他通知可以通過方法名稱引入該切入點。

切面的完整代碼:

package com.spring.aspectjAop.annotation;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

@Order(0)//可以使用 @Order 註解指定切面的優先級, 值越小優先級越高
@Component//首先切面也是spring的一個Bean
@Aspect//表明這是一個AspectJ切面
public class LoggingAspectJAop {

    /**
     * 定義一個方法, 用於聲明切入點表達式. 一般地, 該方法中再不需要添入其他的代碼.
     * 使用 @Pointcut 來聲明切入點表達式.
     * 後面的其他通知直接使用方法名來引用當前的切入點表達式.
     */
    @Pointcut("execution(* com.spring.aspectjAop.annotation.*.*(..))")
    public void JoinPointCut(){}

    /**
     *     標識這個方法是個前置通知,  切點表達式表示執行任意類的任意方法. 第
     *     一個 * 代表匹配任意修飾符及任意返回值,  第二個 * 代表任意類的對象,
     *     第三個 * 代表任意方法, 參數列表中的 ..  匹配任意數量的參數
     */
    @Before("JoinPointCut()")
    public void beforeMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println("執行的方法是:"+methodName+",參數是:"+args);
    }

    /**
     * 後置通知是在連接點完成之後執行的, 即連接點返回結果或者拋出異常的時候, 下面的後置通知記錄了方法的終止
     * @param joinPoint
     */
    @After("JoinPointCut()")
    public void afterMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println(methodName+"執行完了");
    }

    @AfterReturning(pointcut = "JoinPointCut()",returning = "result")
    public void afterRunningMethod(JoinPoint joinPoint, Object result){
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println(methodName+"方法的返回值是:"+result);
    }

    /**
     * 異常通知
     */
    @AfterThrowing(pointcut = "JoinPointCut()",throwing = "exception")
    public void afterThrowableMethod(JoinPoint joinPoint, Exception exception){
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println(methodName+"方法的出現了異常,異常信息是:"+exception.getMessage());
    }

    /**
     * 環繞通知需要攜帶 ProceedingJoinPoint 類型的參數.
     * 環繞通知類似於動態代理的全過程: ProceedingJoinPoint 類型的參數可以決定是否執行目標方法.
     * 且環繞通知必須有返回值, 返回值即爲目標方法的返回值
     */
    @Around(value = "JoinPointCut()")
    public Object aroundMethod(ProceedingJoinPoint pjd){
        Object result = null;//定義返回值
        String methodName = pjd.getSignature().getName();
        List<Object> args = Arrays.asList(pjd.getArgs());
        try {
            //前置通知
            System.out.println(methodName+"方法執行之前,參數是:"+args);
            result = pjd.proceed();
            //後置通知
            System.out.println(methodName+"方法執行完成,結果是:"+result);
        } catch (Throwable throwable) {
            //異常通知
            System.out.println(methodName+"方法執行出現了異常,異常信息:"+throwable.getMessage());
            throw  new RuntimeException(throwable);
        }
        //返回通知
        System.out.println(methodName+"執行沒有發生異常,一切正常返回,返回結果是:"+result);
        return result;
    }
}

 

12、引入通知

       引入通知是一種特殊的通知類型. 它通過爲接口提供實現類, 允許對象動態地實現接口, 就像對象已經在運行時擴展了實現類一樣。

 

引入通知可以使用兩個實現類 MaxCalculatorImpl MinCalculatorImpl, ArithmeticCalculatorImpl 動態地實現 MaxCalculator MinCalculator 接口. 而這與從 MaxCalculatorImpl MinCalculatorImpl 中實現多繼承的效果相同. 但卻不需要修改 ArithmeticCalculatorImpl 的源代碼
引入通知也必須在切面中聲明
在切面中, 通過爲任意字段添加@DeclareParents 註解來引入聲明.
註解類型的 value 屬性表示哪些類是當前引入通知的目標. value 屬性值也可以是一個 AspectJ 類型的表達式, 以將一個即可引入到多個類中defaultImpl 屬性中指定這個接口使用的實現類

引入通知示例代碼:

3.3.2 基於xml配置文件使用AspectJ實現AOP

1、業務接口和業務實現類

package com.spring.aspectAop.xml;

public interface MathCalculator {
    public int add(int a, int b);
    public int sub(int a, int b);
}


package com.spring.aspectAop.xml;

import org.springframework.stereotype.Component;

public class MathCalculatorImpl implements MathCalculator {
    @Override
    public int add(int a, int b) {
        return a+b;
    }

    @Override
    public int sub(int a, int b) {
        return a-b;
    }
}

2、切面類

package com.spring.aspectAop.xml;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

public class LoggingAspectJAop {

    public void beforeMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println("執行的方法是:"+methodName+",參數是:"+args);
    }

    public void afterMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println(methodName+"執行完了");
    }

    public void afterRunningMethod(JoinPoint joinPoint, Object result){
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println(methodName+"方法的返回值是:"+result);
    }

    public void afterThrowableMethod(JoinPoint joinPoint, Exception exception){
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println(methodName+"方法的出現了異常,異常信息是:"+exception.getMessage());
    }

    public Object aroundMethod(ProceedingJoinPoint pjd){
        Object result = null;//定義返回值
        String methodName = pjd.getSignature().getName();
        List<Object> args = Arrays.asList(pjd.getArgs());
        try {
            //前置通知
            System.out.println(methodName+"方法執行之前,參數是:"+args);
            result = pjd.proceed();
            //後置通知
            System.out.println(methodName+"方法執行完成,結果是:"+result);
        } catch (Throwable throwable) {
            //異常通知
            System.out.println(methodName+"方法執行出現了異常,異常信息:"+throwable.getMessage());
            throw  new RuntimeException(throwable);
        }
        //返回通知
        System.out.println(methodName+"執行沒有發生異常,一切正常返回,返回結果是:"+result);
        return result;
    }
}

3、配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <!--配置業務類Bean-->
    <bean id="mathCalculator" class="com.spring.aspectAop.xml.MathCalculatorImpl"/>

    <!--配置切面的 bean-->
    <bean id="loggingAspectJAop" class="com.spring.aspectAop.xml.LoggingAspectJAop"/>

    <!--配置AOP-->
    <aop:config>
        <!--配置切點表達式-->
        <aop:pointcut id="joinPointCut" expression="execution(* com.spring.aspectAop.xml.*.*(..))"/>

        <!--配置切面及通知-->
        <aop:aspect ref="loggingAspectJAop" order="0">
            <!--前置通知-->
            <aop:before method="beforeMethod" pointcut-ref="joinPointCut"/>
            <!--後置通知-->
            <aop:after method="afterMethod" pointcut-ref="joinPointCut"/>
            <!--異常通知-->
            <aop:after-throwing method="afterThrowableMethod" pointcut-ref="joinPointCut" throwing="exception"/>
            <!--返回通知-->
            <aop:after-returning method="afterRunningMethod" pointcut-ref="joinPointCut" returning="result"/>
            <!--  環繞通知
            <aop:around method="aroundMethod" pointcut-ref="joinPointCut"/>
            -->
        </aop:aspect>
    </aop:config>
</beans>

4、測試

package com.spring.aspectAop.xml;

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

public class MainTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring/applicationContext-xml.xml");
        MathCalculator mathCalculator = (MathCalculator) applicationContext.getBean("mathCalculator");
        int add = mathCalculator.add(9, 3);
        System.out.println("加法結果:"+add);
        int sub = mathCalculator.sub(9, 3);
        System.out.println("減法結果:"+sub);
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章