Java開發學習(十八)—-AOP通知獲取數據(參數、返回值、異常)
前面的部落格我們寫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後,查看控制台,就能看的異常資訊被列印到控制台