Spring(3)-AOP快速入手

marigo發表於2024-04-27

經過前面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()的方式呼叫。又引出了橫切關注點的概念,有四個位置:前置通知、返回通知、異常通知、最終通知,這四個位置也就是代表著四個可能需要切入的方法。我們還寫了一個非常簡單的切面類,其中有很多方法,這些方法會被切入到四個橫切關注點的任意位置。
image.png
這就是AOP的比較直觀地理解,接下來就是AOP的快速入門。

AOP的快速入門

切面類中,框架 aspectj 宣告通知的方法:
1. 前置通知:@Before
2. 返回通知:@AfterReturning
3. 異常通知:@AfterThrowing
4. 後置通知(最終通知):@After
5. 環繞通知:@Around,可以將以上四個通知合併管理

例項

需求

Spring(2)-粗解橫切關注點 - marigo - 部落格園的需求,大體要求就是列印輸出:

前置通知
方法內部列印:result = 21.1
返回通知
最終通知

AOP程式碼實現

  1. 建立 SmartAnimalable 介面
public interface SmartAnimalable {  
    float getSum(float i, float j);  
    float getSub(float i, float j);  
}
  1. 建立 SmartAnimalable 介面的實現類 SmartDog
    1. 使用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;  
    }  
}
  1. 建立切面類 SmartAnimalAspect
    1. 如何讓Spring知道這是個切面類呢,需要加兩個註解@Aspect和@Component,前者表示這是一個切面類,後者是將這個切面類注入到容器中
    2. 以前置通知為例進行註釋講解,其他三個位置同理,不做展開
    3. 這裡就將前面寫過兩遍的 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()));  
    }  
}
  1. 配置bean.xml
    1. 自動掃描包
    2. 開啟基於註解的AOP功能
<context:component-scan base-package="com.example.aspectj"/>  
<aop:aspectj-autoproxy/>
  1. 測試
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的快速入門。

補充

  1. 切面類中的方法名不做要求,但是建議採用規範的方法名showBeginLog()、showSuccessEndLog()、showExceptionLog()、showFinallyEndLog()
  2. 切入表示式可以模糊配置:@Before(value="execution(* com.example.aspectj.SmartDog.*(..))")
  3. 所有包下的所有類的所有方法都執行方法:@Before(value="execution(* *.*(..))")
  4. 當 spring 容器開啟了 aop:aspectj-autoproxy/ , 我們獲取注入的物件, 需要以介面的型別來獲取, 因為我們注入的物件.getClass() 已經是代理型別了
  5. 當 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);  
    }  
}

相關文章