經過前面Spring(1)-粗解動態代理 - marigo - 部落格園 Spring(2)-粗解橫切關注點 - marigo - 部落格園兩篇內容,我們可以引入AOP了。
AOP的簡單理解
AOP 的全稱(aspect oriented programming) ,面向切面程式設計。
我們在此之前接觸的更多是OOP,也就是物件導向程式設計。OOP和AOP有什麼異同,網上有很多詳細地講解,本文不做贅述,直接對什麼是AOP進行說明。
在前面Spring(2)-粗解橫切關注點 - marigo - 部落格園中,比如有一個 A類,其中有很多方法,在使用的時候不是OOP那種 A.方法1()
的方式,而且透過動態代理,使用proxy.方法1()
的方式呼叫。又引出了橫切關注點的概念,有四個位置:前置通知、返回通知、異常通知、最終通知,這四個位置也就是代表著四個可能需要切入的方法。我們還寫了一個非常簡單的切面類,其中有很多方法,這些方法會被切入到四個橫切關注點的任意位置。
這就是AOP的比較直觀地理解,接下來就是AOP的快速入門。
AOP的快速入門
切面類中,框架 aspectj 宣告通知的方法:
1. 前置通知:@Before
2. 返回通知:@AfterReturning
3. 異常通知:@AfterThrowing
4. 後置通知(最終通知):@After
5. 環繞通知:@Around,可以將以上四個通知合併管理
例項
需求
同Spring(2)-粗解橫切關注點 - marigo - 部落格園的需求,大體要求就是列印輸出:
前置通知
方法內部列印:result = 21.1
返回通知
最終通知
AOP程式碼實現
- 建立 SmartAnimalable 介面
public interface SmartAnimalable {
float getSum(float i, float j);
float getSub(float i, float j);
}
- 建立 SmartAnimalable 介面的實現類 SmartDog
- 使用Spring框架,我們後面就不再手動new物件了,加入註解@Component,也就是當Spring啟動後,將SmartDog自動注入到容器中
@Component
public class SmartDog implements SmartAnimalable{
@Override
public float getSum(float i, float j) {
float result = i + j;
System.out.println("getSum() 方法內部列印 result= " + result);
return result;
}
@Override
public float getSub(float i, float j) {
float result = i - j;
System.out.println("getSub() 方法內部列印 result= " + result);
return result;
}
}
- 建立切面類 SmartAnimalAspect
- 如何讓Spring知道這是個切面類呢,需要加兩個註解@Aspect和@Component,前者表示這是一個切面類,後者是將這個切面類注入到容器中
- 以前置通知為例進行註釋講解,其他三個位置同理,不做展開
- 這裡就將前面寫過兩遍的 MyProxyProvider 動態代理類進行了封裝,寫起來簡單,而且功能更強大
@Aspect
@Component
public class SmartAnimalAspect {
/**
* 前置通知:希望在目標方法執行之前執行
* @Before() 參數列示我們要把方法切入到哪裡,格式是:訪問修飾符 返回值 包名.類名.方法名(引數型別)
* JoinPoint joinPoint:表示連線點物件,有很多有用的方法
* joinPoint.getSignature():獲取方法簽名物件
* joinPoint.getArgs():獲取方法引數
* 方法簽名就是目標方法的方法名:com.example.aspectj.SmartDog.getSum
*/
@Before(value = "execution(public float com.example.aspectj.SmartDog.getSum(float ,float))")
public void showBeginLog(JoinPoint joinPoint) {
System.out.println("前置通知");
Signature signature = joinPoint.getSignature();
// 1. 在呼叫目標方法之前列印“方法開始”日誌
System.out.println("日誌--方法名:" + signature.getName() + "--方法開始--引數:" + Arrays.asList(joinPoint.getArgs()));
}
}
- 配置bean.xml
- 自動掃描包
- 開啟基於註解的AOP功能
<context:component-scan base-package="com.example.aspectj"/>
<aop:aspectj-autoproxy/>
- 測試
public class ATest {
public static void main(String[] args) {
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans07.xml");
// 透過介面獲取注入的物件-本質上就是代理物件
SmartAnimalable bean = ioc.getBean(SmartAnimalable.class);
bean.getSum(1, 2);
}
}
至此,我們就完成了AOP的快速入門。
補充
- 切面類中的方法名不做要求,但是建議採用規範的方法名showBeginLog()、showSuccessEndLog()、showExceptionLog()、showFinallyEndLog()
- 切入表示式可以模糊配置:
@Before(value="execution(* com.example.aspectj.SmartDog.*(..))")
- 所有包下的所有類的所有方法都執行方法:
@Before(value="execution(* *.*(..))")
- 當 spring 容器開啟了 aop:aspectj-autoproxy/ , 我們獲取注入的物件, 需要以介面的型別來獲取, 因為我們注入的物件.getClass() 已經是代理型別了
- 當 spring 容器開啟了 aop:aspectj-autoproxy/ , 我們獲取注入的物件, 也可以透過 id 來獲取, 但是也要轉成介面型別
public class ATest {
public static void main(String[] args) {
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans07.xml");
// 透過介面獲取注入的物件-本質上就是代理物件
// SmartAnimalable bean = ioc.getBean(SmartAnimalable.class);
SmartAnimalable bean2 = (SmartAnimalable) ioc.getBean("smartDog");
bean2.getSum(1, 2);
}
}