4. Spring AOP基本使用

1 AOP的概念

  1. AOP:Aspect Oriented Programming 面向切面編程
  2. OOP:Object Oriented Programming 面向對象編程
  3. 所謂"面向切面編程",通俗點說的話就是在程序運行期間,將某段代碼動態切入到指定方法的指定位置進行運行的這種編程方式。主要應用於處理日誌、安全管理、事務管理等方面
  4. AOP的應用場景
    1. 日誌管理
    2. 權限認證
    3. 安全檢查
    4. 事務控制

2 爲何使用AOP

  1. 在Calculator實現類MyCalculator的"加減乘除"方法前後加入日誌
  2. Calculator.java
package com.mashibing.inter;

public interface Calculator {

    public int add(int i,int j);

    public int sub(int i,int j);

    public int mult(int i,int j);

    public int div(int i,int j);
}
  1. MyCalculator.java
package com.mashibing.inter;

public class MyCalculator implements Calculator {
    public int add(int i, int j) {
        int result = i + j;
        return result;
    }

    public int sub(int i, int j) {
        int result = i - j;
        return result;
    }

    public int mult(int i, int j) {
        int result = i * j;
        return result;
    }

    public int div(int i, int j) {
        int result = i / j;
        return result;
    }
}
  1. MyTest.java
public class MyTest {
    public static void main(String[] args) throws SQLException {
        MyCalculator myCalculator = new MyCalculator();
        System.out.println(myCalculator.add(1, 2));
    }
}
  1. MyCalculator.java:這種日誌加入方法,非常麻煩,冗餘代碼角度,且難以維護
package com.mashibing.inter;

public class MyCalculator implements Calculator {
    public int add(int i, int j) {
        System.out.println("add 方法開始執行,參數爲:"+i+","+j);
        int result = i + j;
        System.out.println("add 方法開始完成結果爲:"+result);
        return result;
    }

    public int sub(int i, int j) {
        System.out.println("sub 方法開始執行,參數爲:"+i+","+j);
        int result = i - j;
        System.out.println("add 方法開始完成結果爲:"+result);
        return result;
    }

    public int mult(int i, int j) {
        System.out.println("mult 方法開始執行,參數爲:"+i+","+j);
        int result = i * j;
        System.out.println("add 方法開始完成結果爲:"+result);
        return result;
    }

    public int div(int i, int j) {
        System.out.println("div 方法開始執行,參數爲:"+i+","+j);
        int result = i / j;
        System.out.println("add 方法開始完成結果爲:"+result);
        return result;
    }
}
  1. LogUtil.java:將日誌的處理抽象出來,變成工具類來進行實現,LogUtil爲工具類
package com.mashibing.util;

import java.util.Arrays;

public class LogUtil {

    public static void start(Object ... objects){
        System.out.println("XXX方法開始執行,使用的參數是:"+ Arrays.asList(objects));
    }

    public static void stop(Object ... objects){
        System.out.println("XXX方法執行結束,結果是:"+ Arrays.asList(objects));
    }
}
  1. MyCalculator.java:使用工具類替代代碼
package com.mashibing.inter;

import com.mashibing.util.LogUtil;

public class MyCalculator implements Calculator {
    public int add(int i, int j) {
        LogUtil.start(i,j);
        int result = i + j;
        LogUtil.stop(result);
        return result;
    }

    public int sub(int i, int j) {
        LogUtil.start(i,j);
        int result = i - j;
        LogUtil.stop(result);
        return result;
    }

    public int mult(int i, int j) {
        LogUtil.start(i,j);
        int result = i * j;
        LogUtil.stop(result);
        return result;
    }

    public int div(int i, int j) {
        LogUtil.start(i,j);
        int result = i / j;
        LogUtil.stop(result);
        return result;
    }
}
  1. 按照上述方式抽象之後,代碼確實簡單很多,但是大家應該已經發現在輸出的信息中並不包含具體的方法名稱,我們更多的是想要在程序運行過程中動態的獲取方法的名稱及參數、結果等相關信息,此時可以通過使用動態代理的方式來進行實現。

  2. CalculatorProxy.java:產生代理對象的類

package com.mashibing.proxy;

import com.mashibing.inter.Calculator;

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

/**
 * 幫助Calculator生成代理對象的類
 */
public class CalculatorProxy {

    /**
     *
     *  爲傳入的參數對象創建一個動態代理對象
     * @param calculator 被代理對象
     * @return
     */
     //後面匿名內部類中想使用外部的calculator參數,因此calculator必須設置爲final否則編譯報錯
    public static Calculator getProxy(final Calculator calculator){

        //被代理對象的類加載器
        ClassLoader loader = calculator.getClass().getClassLoader();
        //被代理對象的接口
        Class<?>[] interfaces = calculator.getClass().getInterfaces();
        //方法執行器,執行被代理對象的目標方法
        InvocationHandler h = new InvocationHandler() {
            /**
             *  執行目標方法
             * @param proxy 代理對象,給jdk使用,任何時候都不要操作此對象
             * @param method 當前將要執行的目標對象的方法
             * @param args 這個方法調用時外界傳入的參數值
             * @return
             * @throws Throwable
             */
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //利用反射執行目標方法,目標方法執行後的返回值
//                System.out.println("這是動態代理執行的方法");
                Object result = null;
                try {
                    System.out.println(method.getName()+"方法開始執行,參數是:"+ Arrays.asList(args));
                    //反射調用被代理對象的方法
                    result = method.invoke(calculator, args);
                    System.out.println(method.getName()+"方法執行完成,結果是:"+ result);
                } catch (Exception e) {
                    System.out.println(method.getName()+"方法出現異常:"+ e.getMessage());
                } finally {
                    System.out.println(method.getName()+"方法執行結束了......");
                }
                //將結果返回回去
                return result;
            }
        };
        Object proxy = Proxy.newProxyInstance(loader, interfaces, h);
        return (Calculator) proxy;
    }
}
  1. 我們可以看到這種方式更加靈活,而且不需要在業務方法中添加額外的代碼,這纔是常用的方式。如果想追求完美的同學,還可以使用上述的日誌工具類來完善。

  2. LogUtil.java

package com.mashibing.util;

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

public class LogUtil {

    public static void start(Method method, Object ... objects){
//        System.out.println("XXX方法開始執行,使用的參數是:"+ Arrays.asList(objects));
        System.out.println(method.getName()+"方法開始執行,參數是:"+ Arrays.asList(objects));
    }

    public static void stop(Method method,Object ... objects){
//        System.out.println("XXX方法執行結束,結果是:"+ Arrays.asList(objects));
        System.out.println(method.getName()+"方法開始執行,參數是:"+ Arrays.asList(objects));

    }

    public static void logException(Method method,Exception e){
        System.out.println(method.getName()+"方法出現異常:"+ e.getMessage());
    }
    
    public static void end(Method method){
        System.out.println(method.getName()+"方法執行結束了......");
    }
}
  1. CalculatorProxy.java
package com.mashibing.proxy;

import com.mashibing.inter.Calculator;
import com.mashibing.util.LogUtil;

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

/**
 * 幫助Calculator生成代理對象的類
 */
public class CalculatorProxy {

    /**
     *
     *  爲傳入的參數對象創建一個動態代理對象
     * @param calculator 被代理對象
     * @return
     */
    public static Calculator getProxy(final Calculator calculator){


        //被代理對象的類加載器
        ClassLoader loader = calculator.getClass().getClassLoader();
        //被代理對象的接口
        Class<?>[] interfaces = calculator.getClass().getInterfaces();
        //方法執行器,執行被代理對象的目標方法
        InvocationHandler h = new InvocationHandler() {
            /**
             *  執行目標方法
             * @param proxy 代理對象,給jdk使用,任何時候都不要操作此對象
             * @param method 當前將要執行的目標對象的方法
             * @param args 這個方法調用時外界傳入的參數值
             * @return
             * @throws Throwable
             */
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //利用反射執行目標方法,目標方法執行後的返回值
//                System.out.println("這是動態代理執行的方法");
                Object result = null;
                try {
                    LogUtil.start(method,args);
                    result = method.invoke(calculator, args);
                    LogUtil.stop(method,args);
                } catch (Exception e) {
                    LogUtil.logException(method,e);
                } finally {
                    LogUtil.end(method);
                }
                //將結果返回回去
                return result;
            }
        };
        Object proxy = Proxy.newProxyInstance(loader, interfaces, h);
        return (Calculator) proxy;
    }
}
  1. 此處動態代理的實現方式調用的是jdk的基本實現,如果需要代理的目標對象的方法,並不是其實現的某個接口的方法,那麼是無法爲他創建代理對象的,這是因爲jdk創建的代理類必須繼承Proxy類,而java中爲單繼承,繼承了Proxy就無法繼承原本被代理的類,那麼代理對象也就無法轉爲被代理對象,更無法調用被代理對象的方法
  2. 動態代理還有第二種方式,cglib,cglib不要求被代理對象繼承接口
  3. 使用Spring完成動態代理時,只需要利用AOP,就能夠輕輕鬆鬆實現上述功能,而不需要編寫上述如此複雜的代碼,當然,Spring AOP的底層實現也依賴的是動態代理

3 AOP的核心概念及術語

在這裏插入圖片描述

  1. 切面(Aspect):LogUtil就是切面類
  2. 連接點(Join point):圖中交匯的圓圈稱爲連接點,通俗來說就是每一個方法外可以填入額外代碼的地方都叫連接點
  3. 切點(Pointcut):實際填入額外代碼的位置,切點是連接點的子集,我們可以通過表達式決定選取哪個連接點作爲切點
  4. 通知(Advice):切面類中的方法start、stop、logException、logFinally稱爲通知方法
  5. 引入(Introduction)
  6. 目標對象(Target object):被代理的對象
  7. AOP代理(AOP proxy):建立的代理對象,在Spring中,AOP代理可以是JDK動態代理或CGLIB代理
  8. 織入(Weaving):完成切入功能的過程,叫織入。Spring是在運行時完成織入的

4 Spring AOP的簡單配置

4.1 添加pom依賴
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
<dependency>
    <groupId>aopalliance</groupId>
    <artifactId>aopalliance</artifactId>
    <version>1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.3.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.5.RELEASE</version>
</dependency>
4.2 編寫配置
  1. 將目標類和切面類加入到IOC容器中,在對應的類上添加組件註解

    1. 給LogUtil添加@Component註解
    2. 給MyCalculator添加@Service註解
    3. 添加自動掃描的配置
    <!--別忘了添加context命名空間-->
    <context:component-scan base-package="com.mashibing"></context:component-scan>
    
  2. 設置程序中的切面類

    1. 在LogUtil.java中添加@Aspect註解
  3. 設置切面類中的方法是什麼時候在哪裏執行

package com.mashibing.util;

import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

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

@Component
@Aspect
public class LogUtil {

    /*
    設置下面方法在什麼時候運行
        @Before:在目標方法之前運行:前置通知
        @After:在目標方法之後運行:後置通知
        @AfterReturning:在目標方法正常返回之後:返回通知
        @AfterThrowing:在目標方法拋出異常後開始運行:異常通知
        @Around:環繞:環繞通知

        當編寫完註解之後還需要設置在哪些方法上執行,使用表達式
        execution(訪問修飾符  返回值類型 方法全稱)
        注意方法的參數列表中不要隨意添加參數值,會有異常信息
     */
    @Before("execution( public int com.mashibing.inter.MyCalculator.*(int,int))")
    public static void start(){
//        System.out.println("XXX方法開始執行,使用的參數是:"+ Arrays.asList(objects));
//        System.out.println(method.getName()+"方法開始執行,參數是:"+ Arrays.asList(objects));
        System.out.println("方法開始執行,參數是:");
    }

    @AfterReturning("execution( public int com.mashibing.inter.MyCalculator.*(int,int))")
    public static void stop(){
//        System.out.println("XXX方法執行結束,結果是:"+ Arrays.asList(objects));
//        System.out.println(method.getName()+"方法執行結束,結果是:"+ Arrays.asList(objects));
        System.out.println("方法執行完成,結果是:");

    }

    @AfterThrowing("execution( public int com.mashibing.inter.MyCalculator.*(int,int))")
    public static void logException(){
//        System.out.println(method.getName()+"方法出現異常:"+ e.getMessage());
        System.out.println("方法出現異常:");
    }

    @After("execution( public int com.mashibing.inter.MyCalculator.*(int,int))")
    public static void end(){
//        System.out.println(method.getName()+"方法執行結束了......");
        System.out.println("方法執行結束了......");
    }
}
  1. 開啓基於註解的aop的功能
<?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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       https://www.springframework.org/schema/aop/spring-aop.xsd
">
    <context:component-scan base-package="com.mashibing"></context:component-scan>
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
4.3 測試
  1. MyTest.java
import com.mashibing.inter.Calculator;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
    public static void main(String[] args){
        ApplicationContext context = new ClassPathXmlApplicationContext("aop.xml");
        //也可以按bean的id獲取
        //Calculator bean = (Calculator) context.getBean("myCalculator");
        Calculator bean = context.getBean(Calculator.class);
        bean.add(1,1);
        //class com.sun.proxy.$Proxy21
        //由於被代理對象MyCalculator擁有接口Calculator,因此Spring會默認使用jdk的動態代理,因此打印出的對象類型爲com.sun.proxy.$Proxy21
        System.out.println(bean.getClass());
    }
}
4.4 通過cglib來創建代理對象
  1. 被代理類未實現接口,就會使用cglib的代理
  2. 早期時,cglib效率比jdk高,但隨着jdk發展,二者現在效率差不太多
  3. MyCalculator.java
package com.mashibing.inter;

import org.springframework.stereotype.Service;

@Service
public class MyCalculator {
    public int add(int i, int j) {
        int result = i + j;
        return result;
    }

    public int sub(int i, int j) {
        int result = i - j;
        return result;
    }

    public int mult(int i, int j) {
        int result = i * j;
        return result;
    }

    public int div(int i, int j) {
        int result = i / j;
        return result;
    }
}
  1. MyTest.java
public class MyTest {
    public static void main(String[] args){
        ApplicationContext context = new ClassPathXmlApplicationContext("aop.xml");
        MyCalculator bean = context.getBean(MyCalculator.class);
        bean.add(1,1);
        System.out.println(bean);
        //class com.mashibing.inter.MyCalculator$$EnhancerBySpringCGLIB$$b6c27344
        System.out.println(bean.getClass());
    }
}

5 切入點表達式

5.1 切入點表達式中使用通配符
  1. 通配符:*
//1. 匹配一個或者多個字符
execution( public int com.mashibing.inter.My*alculator.*(int,int))
//2. 匹配任意一個參數
execution( public int com.mashibing.inter.MyCalculator.*(int,*))
//3. 只能匹配一層路徑,如果項目路徑下有多層目錄,那麼*只能匹配一層路徑,但如果以*開頭,就可以匹配多層路徑
//第二個*,由於它前面什麼都沒有,因此可以替代所有路徑下所有類的所有方法
execution(*  *(..)) 
//4. 權限位置不能使用*,如果想表示全部權限,那麼不寫即可
execution( * com.mashibing.inter.MyCalculator.*(int,*))
  1. 通配符:…
//1. 匹配多個參數,任意類型參數
execution( * com.mashibing.inter.MyCalculator.*(..))
//2. 匹配任意多層路徑
execution( * com.mashibing..MyCalculator.*(..))
  1. 最偷懶與最精確的寫法
    1. 使用通配符時,不是越簡單越好,更多的是要選擇符合要求或符合規則的匹配方式
    2. 因此就要求在定義標識符時(類名、類所在包),必須遵守項目規範,否則難以匹配想要的切點
//最偷懶
execution(*  *(..)) 
execution(*  *.*(..))
//最精確
execution( public int com.mashibing.inter.MyCalculator.add(int,int))
  1. 表達式中使用&&、||、!
execution( public int com.mashibing.inter.MyCalculator.*(..)) && execution(\* \*.\*(int,int) )
//注意如果返回值類型爲Integer,那麼此處寫int是無法匹配的
execution( public int com.mashibing.inter.MyCalculator.*(..)) && execution(\* \*(..) )
execution( public int com.mashibing.inter.MyCalculator.*(..)) && execution(\* \*.\*(int,int) )
execution( public int com.mashibing.inter.MyCalculator.*(..))
5.2 表達式的抽取
  1. 如果在實際使用過程中,多個方法的表達式完全相同,那麼可以考慮將切入點表達式抽取出來
  2. 表達式抽取步驟
    1. 隨便聲明一個沒有實現的返回void的空方法
    2. 給空方法上標註@Potintcut註解
    3. 通知方法上面改爲@Before(value = “myPoint()”)
  3. LogUtil
package com.mashibing.util;

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

import java.util.Arrays;

@Component
@Aspect
public class LogUtil {
    
    //一個類中可以定義多個Pointcut
    @Pointcut("execution( public int com.mashibing.inter.MyCalculator.*(int,int))")
    public void myPoint(){}
    
    @Before("myPoint()")
    public static void start(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法開始執行,參數是:"+ Arrays.asList(args));
    }

    @AfterReturning(value = "myPoint()",returning = "result")
    public static void stop(JoinPoint joinPoint,Object result){
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法執行完成,結果是:"+result);

    }

    @AfterThrowing(value = "myPoint()",throwing = "exception")
    public static void logException(JoinPoint joinPoint,Exception exception){
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法出現異常:"+exception.getMessage());
    }

    @After("myPoint()")
    private int end(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法執行結束了......");
        return 0;
    }
}

6 AOP中的通知

6.1 通知類型
  1. 前置通知(Before advice):@Before
  2. 後置返回通知(After returning advice):@AfterReturning
  3. 後置異常通知(After throwing advice):@AfterThrowing
  4. 後置通知(總會執行)(After (finally) advice):@After
  5. 環繞通知(Around Advice):@Around
6.2 通知方法執行順序
  1. 正常情況
    在這裏插入圖片描述
  2. 異常情況
    在這裏插入圖片描述
  3. 多切面
    1. 在spring中,默認是按照切面名稱的字典順序進行執行的,但是如果想自己改變具體的執行順序的話,可以在切面類上使用@Order註解來解決,數值越小,優先級越高
    2. 每個切面中,都必須遵守如下順序:@Around–@Before–下個切面/方法–@Around
      在這裏插入圖片描述
  4. SecurityAspect.java
package com.mashibing.util;

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;

@Component
@Aspect
@Order(1)
public class SecurityAspect {

    @Before("com.mashibing.util.LogUtil.myPoint()")
    public static void start(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        String name = joinPoint.getSignature().getName();
        System.out.println("Security:"+name+"方法開始執行,參數是:"+ Arrays.asList(args));
    }

    @AfterReturning(value = "com.mashibing.util.LogUtil.myPoint()",returning = "result")
    public static void stop(JoinPoint joinPoint,Object result){
        String name = joinPoint.getSignature().getName();
        System.out.println("Security:"+name+"方法執行完成,結果是:"+result);

    }

    @AfterThrowing(value = "com.mashibing.util.LogUtil.myPoint()",throwing = "exception")
    public static void logException(JoinPoint joinPoint,Exception exception){
        String name = joinPoint.getSignature().getName();
        System.out.println("Security:"+name+"方法出現異常:"+exception.getMessage());
    }

    @After("com.mashibing.util.LogUtil.myPoint()")
    private int end(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        System.out.println("Security:"+name+"方法執行結束了......");
        return 0;
    }

    /**
     * 環繞通知是spring中功能最強大的通知
     * @param proceedingJoinPoint
     * @return
     */
    //@Around("myPoint()")
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint){
        Object[] args = proceedingJoinPoint.getArgs();
        String name = proceedingJoinPoint.getSignature().getName();
        Object proceed = null;
        try {
            System.out.println("環繞前置通知:"+name+"方法開始,參數是"+Arrays.asList(args));
            //利用反射調用目標方法,就是method.invoke()
            proceed = proceedingJoinPoint.proceed(args);
            System.out.println("環繞返回通知:"+name+"方法返回,返回值是"+proceed);
        } catch (Throwable e) {
            System.out.println("環繞異常通知"+name+"方法出現異常,異常信息是:"+e);
        }finally {
            System.out.println("環繞後置通知"+name+"方法結束");
        }
        return proceed;
    }
}
  1. LogUtil.java
package com.mashibing.util;

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;

@Component
@Aspect
@Order(2)
public class LogUtil {
    @Pointcut("execution( public int com.mashibing.inter.MyCalculator.*(int,int))")
    public void myPoint(){}
    @Before("myPoint()")
    public static void start(JoinPoint joinPoint){
//        System.out.println("XXX方法開始執行,使用的參數是:"+ Arrays.asList(objects));
//        System.out.println(method.getName()+"方法開始執行,參數是:"+ Arrays.asList(objects));
        Object[] args = joinPoint.getArgs();
        String name = joinPoint.getSignature().getName();
        System.out.println("Log:"+name+"方法開始執行,參數是:"+ Arrays.asList(args));
    }

    @AfterReturning(value = "myPoint()",returning = "result")
    public static void stop(JoinPoint joinPoint,Object result){
//        System.out.println("XXX方法執行結束,結果是:"+ Arrays.asList(objects));
//        System.out.println(method.getName()+"方法執行結束,結果是:"+ Arrays.asList(objects));
        String name = joinPoint.getSignature().getName();
        System.out.println("Log:"+name+"方法執行完成,結果是:"+result);

    }

    @AfterThrowing(value = "myPoint()",throwing = "exception")
    public static void logException(JoinPoint joinPoint,Exception exception){
//        System.out.println(method.getName()+"方法出現異常:"+ e.getMessage());
        String name = joinPoint.getSignature().getName();
        System.out.println("Log:"+name+"方法出現異常:"+exception.getMessage());
    }

    @After("myPoint()")
    private int end(JoinPoint joinPoint){
//        System.out.println(method.getName()+"方法執行結束了......");
        String name = joinPoint.getSignature().getName();
        System.out.println("Log:"+name+"方法執行結束了......");
        return 0;
    }

    /**
     * 環繞通知是spring中功能最強大的通知
     * @param proceedingJoinPoint
     * @return
     */
    //@Around("myPoint()")
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint){
        Object[] args = proceedingJoinPoint.getArgs();
        String name = proceedingJoinPoint.getSignature().getName();
        Object proceed = null;
        try {
            System.out.println("環繞前置通知:"+name+"方法開始,參數是"+Arrays.asList(args));
            //利用反射調用目標方法,就是method.invoke()
            proceed = proceedingJoinPoint.proceed(args);
            System.out.println("環繞返回通知:"+name+"方法返回,返回值是"+proceed);
        } catch (Throwable e) {
            System.out.println("環繞異常通知"+name+"方法出現異常,異常信息是:"+e);
        }finally {
            System.out.println("環繞後置通知"+name+"方法結束");
        }
        return proceed;
    }
}
6.3 spring對通知方法的要求
  1. spring對於通知方法的要求並不是很高,你可以任意改變方法的返回值和方法的訪問修飾符,但是唯一不能修改的就是方法的參數,會出現參數綁定的錯誤,原因在於通知方法是spring利用反射調用的,每次方法調用得確定這個方法的參數的值。

  2. LogUtil.java

@After("execution( public int com.mashibing.inter.MyCalculator.*(int,int))")
private int end(JoinPoint joinPoint,String aa){
//        System.out.println(method.getName()+"方法執行結束了......");
    String name = joinPoint.getSignature().getName();
    System.out.println(name+"方法執行結束了......");
    return 0;
}
6.4 環繞通知
  1. 環繞通知如果處理了異常,那麼@AfterThrowing就就接收不到了,只會由@AfterReturning接收,因此最好在環繞異常通知捕獲到異常時,再將該異常拋出,方便@AfterThrowing接收
  2. 儘量使用環繞通知,這樣會很清楚的定義整個流程
  3. LogUtil.java
package com.mashibing.util;

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

import java.util.Arrays;

@Component
@Aspect
public class LogUtil {
    @Pointcut("execution( public int com.mashibing.inter.MyCalculator.*(int,int))")
    public void myPoint(){}
    
    /**
     * 環繞通知是spring中功能最強大的通知
     * @param proceedingJoinPoint
     * @return
     */
    @Around("myPoint()")
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint){
        Object[] args = proceedingJoinPoint.getArgs();
        String name = proceedingJoinPoint.getSignature().getName();
        Object proceed = null;
        try {
            System.out.println("環繞前置通知:"+name+"方法開始,參數是"+Arrays.asList(args));
            //利用反射調用目標方法,就是method.invoke(),可以自己修改返回結果值
            proceed = proceedingJoinPoint.proceed(args);
            //return 1000;
            System.out.println("環繞返回通知:"+name+"方法返回,返回值是"+proceed);
        } catch (Throwable e) {
            System.out.println("環繞異常通知"+name+"方法出現異常,異常信息是:"+e);
        }finally {
            System.out.println("環繞後置通知"+name+"方法結束");
        }
        return proceed;
    }
}

7 獲取方法的詳細信息

  1. LogUtil.java:獲取方法名與方法參數值等方法詳細信息,添加JoinPoint參數,注意JoinPoint必須是第一個參數
package com.mashibing.util;

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

import java.util.Arrays;

@Component
@Aspect
public class LogUtil {

    @Before("execution( public int com.mashibing.inter.MyCalculator.*(int,int))")
    public static void start(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法開始執行,參數是:"+ Arrays.asList(args));
    }

    @AfterReturning("execution( public int com.mashibing.inter.MyCalculator.*(int,int))")
    public static void stop(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法執行完成,結果是:");

    }

    @AfterThrowing("execution( public int com.mashibing.inter.MyCalculator.*(int,int))")
    public static void logException(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法出現異常:");
    }

    @After("execution( public int com.mashibing.inter.MyCalculator.*(int,int))")
    public static void end(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法執行結束了......");
    }
}
  1. LogUtil.java:獲取方法結果,添加returning參數指定返回值名稱,並在通知方法形參列表中添加同名Object類型參數
//returning參數的值,必須和形參列表中參數名一致    
@AfterReturning(value = "execution( public int com.mashibing.inter.MyCalculator.*(int,int))",returning = "result")
    public static void stop(JoinPoint joinPoint,Object result){
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法執行完成,結果是:"+result);

    }
  1. LogUtil.java:添加throwing參數指定拋出的異常對象名稱,並在通知方法形參列表中添加同名Exception類型參數
    @AfterThrowing(value = "execution( public int com.mashibing.inter.MyCalculator.*(int,int))",throwing = "exception")
    public static void logException(JoinPoint joinPoint,Exception exception){
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法出現異常:"+exception);
    }
  1. LogUtil.java:便捷獲取目標方法中的參數信息,使用JoinPoint也可以獲取,但比較麻煩
//1. args中的..表示可以匹配更多參數
//2. 要注意,通知方法中,i、j類型可以隨意指定,但如果指定爲非Object,那麼就會限制該切入點只匹配參數類型與指定的完全相同的方法
//3. 例如如果start形參列表中爲String i,int j,那麼只會去匹配MyCalculator類中,形參列表爲String i,int j的方法,在本例中也就不會生效
@Before("execution( public int com.mashibing.inter.MyCalculator.*(int,int)) && args(i,j,..)")
public static void start(JoinPoint joinPoint,Object i ,int j){
    Object[] args = joinPoint.getArgs();
    String name = joinPoint.getSignature().getName();
    System.out.println(name+"方法開始執行,參數是:"+ Arrays.asList(args));
    System.out.println(name+"方法開始執行,參數是:"+ i+","+j);
}

8 基於XML的AOP配置

  1. 將所有的註解都進行刪除
  2. aop.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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       https://www.springframework.org/schema/aop/spring-aop.xsd
">

    <bean id="logUtil" class="com.mashibing.util.LogUtil2"></bean>
    <bean id="securityAspect" class="com.mashibing.util.SecurityAspect"></bean>
    <bean id="myCalculator" class="com.mashibing.inter.MyCalculator"></bean>
    
    <aop:config>
        <!--現在定義在外面還是裏面沒有區別-->
        <aop:pointcut id="globalPoint" expression="execution(public int com.mashibing.inter.MyCalculator.*(int,int))"/>
        <aop:aspect ref="logUtil">
            <aop:pointcut id="mypoint" expression="execution(public int com.mashibing.inter.MyCalculator.*(int,int))"/>
            <aop:before method="start" pointcut-ref="mypoint"></aop:before>
            <aop:after method="end" pointcut-ref="mypoint"></aop:after>
            <aop:after-returning method="stop" pointcut-ref="mypoint" returning="result"></aop:after-returning>
            <aop:after-throwing method="logException" pointcut-ref="mypoint" throwing="exception"></aop:after-throwing>
            <aop:around method="myAround" pointcut-ref="mypoint"></aop:around>
        </aop:aspect>
        <aop:aspect ref="securityAspect">
            <aop:before method="start" pointcut-ref="globalPoint"></aop:before>
            <aop:after method="end" pointcut-ref="globalPoint"></aop:after>
            <aop:after-returning method="stop" pointcut-ref="globalPoint" returning="result"></aop:after-returning>
            <aop:after-throwing method="logException" pointcut-ref="globalPoint" throwing="exception"></aop:after-throwing>
            <aop:around method="myAround" pointcut-ref="mypoint"></aop:around>
        </aop:aspect>
    </aop:config>
</beans>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章