Java開發學習(十八)----AOP通知獲取資料(引數、返回值、異常)

2022-07-28 06:00:53

前面的部落格我們寫AOP僅僅是在原始方法前後追加一些操作,接下來我們要說說AOP中資料相關的內容,我們將從獲取引數獲取返回值獲取異常三個方面來研究切入點的相關資訊。

前面我們介紹通知型別的時候總共講了五種,那麼對於這五種型別都會有引數,返回值和異常嗎?

我們先來一個個分析下:

  • 獲取切入點方法的引數,所有的通知型別都可以獲取引數

    • JoinPoint:適用於前置、後置、返回後、丟擲異常後通知

    • ProceedingJoinPoint:適用於環繞通知

  • 獲取切入點方法返回值,前置和丟擲異常後通知是沒有返回值,後置通知可有可無,所以不做研究

    • 返回後通知

    • 環繞通知

  • 獲取切入點方法執行異常資訊,前置和返回後通知是不會有,後置通知可有可無,所以不做研究

    • 丟擲異常後通知

    • 環繞通知

一、環境準備

  • 建立一個Maven專案

  • pom.xml新增Spring依賴

    <dependencies>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-context</artifactId>
          <version>5.2.10.RELEASE</version>
        </dependency>
        <dependency>
          <groupId>org.aspectj</groupId>
          <artifactId>aspectjweaver</artifactId>
          <version>1.9.4</version>
        </dependency>
      </dependencies>
  • 新增BookDao和BookDaoImpl類

    public interface BookDao {
        public String findName(int id);
    }
    @Repository
    public class BookDaoImpl implements BookDao {
    ​
        public String findName(int id) {
            System.out.println("id:"+id);
            return "itcast";
        }
    }
  • 建立Spring的設定類

    @Configuration
    @ComponentScan("com.itheima")
    @EnableAspectJAutoProxy
    public class SpringConfig {
    }
  • 編寫通知類

    @Component
    @Aspect
    public class MyAdvice {
        @Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
        private void pt(){}
    ​
        @Before("pt()")
        public void before() {
            System.out.println("before advice ..." );
        }
    ​
        @After("pt()")
        public void after() {
            System.out.println("after advice ...");
        }
    ​
        @Around("pt()")
        public Object around() throws Throwable{
            Object ret = pjp.proceed();
            return ret;
        }
        @AfterReturning("pt()")
        public void afterReturning() {
            System.out.println("afterReturning advice ...");
        }
    ​
    ​
        @AfterThrowing("pt()")
        public void afterThrowing() {
            System.out.println("afterThrowing advice ...");
        }
    }
  • 編寫App執行類

    public class App {
        public static void main(String[] args) {
            ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
            BookDao bookDao = ctx.getBean(BookDao.class);
            String name = bookDao.findName(100);
            System.out.println(name);
        }
    }

最終建立好的專案結構如下:

二、獲取引數

非環繞通知獲取方式

在方法上新增JoinPoint,通過JoinPoint來獲取引數

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
    private void pt(){}
​
    @Before("pt()")
    public void before(JoinPoint jp) 
        Object[] args = jp.getArgs();
        System.out.println(Arrays.toString(args));
        System.out.println("before advice ..." );
    }
    //...其他的略
}

執行App類,可以獲取如下內容,說明引數100已經被獲取

思考:方法的引數只有一個,為什麼獲取的是一個陣列?

因為引數的個數是不固定的,所以使用陣列更通配些。

如果將引數改成兩個會是什麼效果呢?

(1)修改BookDao介面和BookDaoImpl實現類

public interface BookDao {
    public String findName(int id,String password);
}
@Repository
public class BookDaoImpl implements BookDao {
​
    public String findName(int id,String password) {
        System.out.println("id:"+id);
        return "itcast";
    }
}

(2)修改App類,呼叫方法傳入多個引數

public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = ctx.getBean(BookDao.class);
        String name = bookDao.findName(100,"itheima");
        System.out.println(name);
    }
}

(3)執行App,檢視結果,說明兩個引數都已經被獲取到

說明:

使用JoinPoint的方式獲取引數適用於前置後置返回後丟擲異常後通知。

環繞通知獲取方式

環繞通知使用的是ProceedingJoinPoint,因為ProceedingJoinPoint是JoinPoint類的子類,所以對於ProceedingJoinPoint類中應該也會有對應的getArgs()方法,我們去驗證下:

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
    private void pt(){}
​
    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp)throws Throwable {
        Object[] args = pjp.getArgs();
        System.out.println(Arrays.toString(args));
        Object ret = pjp.proceed();
        return ret;
    }
    //其他的略
}

執行App後檢視執行結果,說明ProceedingJoinPoint也是可以通過getArgs()獲取引數

注意:

  • pjp.proceed()方法是有兩個構造方法,分別是:

    • 呼叫無引數的proceed,當原始方法有引數,會在呼叫的過程中自動傳入引數

    • 所以呼叫這兩個方法的任意一個都可以完成功能

    • 但是當需要修改原始方法的引數時,就只能採用帶有引數的方法,如下:

      @Component
      @Aspect
      public class MyAdvice {
          @Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
          private void pt(){}
      ​
          @Around("pt()")
          public Object around(ProceedingJoinPoint pjp) throws Throwable{
              Object[] args = pjp.getArgs();
              System.out.println(Arrays.toString(args));
              args[0] = 666;
              Object ret = pjp.proceed(args);
              return ret;
          }
          //其他的略
      }

      有了這個特性後,我們就可以在環繞通知中對原始方法的引數進行攔截過濾,避免由於引數的問題導致程式無法正確執行,保證程式碼的健壯性。

三、獲取返回值

對於返回值,只有返回後AfterReturing和環繞Around這兩個通知型別可以獲取,具體如何獲取?

環繞通知獲取返回值
@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
    private void pt(){}
​
    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable{
        Object[] args = pjp.getArgs();
        System.out.println(Arrays.toString(args));
        args[0] = 666;
        Object ret = pjp.proceed(args);
        return ret;
    }
    //其他的略
}

上述程式碼中,ret就是方法的返回值,我們是可以直接獲取,不但可以獲取,如果需要還可以進行修改。

返回後通知獲取返回值
@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
    private void pt(){}
​
    @AfterReturning(value = "pt()",returning = "ret")
    public void afterReturning(Object ret) {
        System.out.println("afterReturning advice ..."+ret);
    }
    //其他的略
}

注意:

(1)引數名的問題

(2)afterReturning方法引數型別的問題

引數型別可以寫成String,但是為了能匹配更多的引數型別,建議寫成Object型別

(3)afterReturning方法引數的順序問題

執行App後檢視執行結果,說明返回值已經被獲取到

四、獲取異常

對於獲取丟擲的異常,只有丟擲異常後AfterThrowing和環繞Around這兩個通知型別可以獲取,具體如何獲取?

環繞通知獲取異常

這塊比較簡單,以前我們是丟擲異常,現在只需要將異常捕獲,就可以獲取到原始方法的異常資訊了

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
    private void pt(){}
​
    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp){
        Object[] args = pjp.getArgs();
        System.out.println(Arrays.toString(args));
        args[0] = 666;
        Object ret = null;
        try{
            ret = pjp.proceed(args);
        }catch(Throwable throwable){
            t.printStackTrace();
        }
        return ret;
    }
    //其他的略
}

在catch方法中就可以獲取到異常,至於獲取到異常以後該如何處理,這個就和你的業務需求有關了。

丟擲異常後通知獲取異常
@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
    private void pt(){}
​
    @AfterThrowing(value = "pt()",throwing = "t")
    public void afterThrowing(Throwable t) {
        System.out.println("afterThrowing advice ..."+t);
    }
    //其他的略
}

如何讓原始方法丟擲異常,方式有很多,

@Repository
public class BookDaoImpl implements BookDao {
​
    public String findName(int id,String password) {
        System.out.println("id:"+id);
        if(true){
            throw new NullPointerException();
        }
        return "itcast";
    }
}

注意:

執行App後,檢視控制檯,就能看的異常資訊被列印到控制檯