Spring核心系列之AOP(二)

我又不是架構師發表於2018-01-11

Spring核心系列之AOP(二)

Hello,大家好,在Spring核心系列之AOP(一)中給大家講了Spring AOP的最強用的註解方式實現AOP(沒看的小夥伴最好先看一看,本文後面的例子大多都使用註解開發),這一篇就給大家分享一下Spring如何基於XML來做AOP,文章結構:

  1. Spring AOP - XML配置
  2. Aspect的優先順序
  3. Spring AOP實際運用場景
  4. Spring AOP 底層實現選擇

1. Spring AOP - XML配置

這裡直接以一個案例的形式對xml的開發形式進行簡要分析,定義一個切面類MyAspectXML:

public class MyAspectXML {
    
    public void before(){
        System.out.println("MyAspectXML====前置通知");
    }

    public void afterReturn(Object returnVal){
        System.out.println("後置通知-->返回值:"+returnVal);
    }

    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("MyAspectXML=====環繞通知前");
        Object object= joinPoint.proceed();
        System.out.println("MyAspectXML=====環繞通知後");
        return object;
    }

    public void afterThrowing(Throwable throwable){
        System.out.println("MyAspectXML======異常通知:"+ throwable.getMessage());
    }

    public void after(){
        System.out.println("MyAspectXML=====最終通知..來了");
    }
}
複製程式碼

注意這個類沒有加任何註解. 然後看下我們的XML檔案:

<!-- 把切面引入到Spring容器中 -->
    <bean name="myAspectXML" class="com.zdy.MyAspectXML" />
    <!-- 配置AOP 切面 -->
    <aop:config>
        <!-- 定義切點 -->
        <aop:pointcut id="pointcut" expression="execution(...)" />

        <!-- 定義其他切點函式 -->
        <aop:pointcut id="otherPointcut" expression="execution(...)" />

        <!-- 定義通知 order 定義優先順序,值越小優先順序越大-->
        <aop:aspect ref="myAspectXML" order="0">
            <!-- 定義通知
            method 指定通知方法名,必須與MyAspectXML中的相同
            pointcut 指定切點函式
            -->
            <aop:before method="before" pointcut-ref="pointcut" />

            <!-- 後置通知  returning="returnVal" 定義返回值 必須與類中宣告的名稱一樣-->
            <aop:after-returning method="afterReturn" pointcut-ref="pointcut"  returning="returnVal" />

            <!-- 環繞通知 -->
            <aop:around method="around" pointcut-ref="pointcut"  />

            <!--異常通知 throwing="throwable" 指定異常通知錯誤資訊變數,必須與類中宣告的名稱一樣-->
            <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="throwable"/>

            <!--
                 method : 通知的方法(最終通知)
                 pointcut-ref : 通知應用到的切點方法
                -->
            <aop:after method="after" pointcut-ref="otherPointcut"/>
        </aop:aspect>
    </aop:config>
複製程式碼

大家可以看到,<aop:aspect>這個標籤其實對應的就是MyAspectXML類,然後在類中組裝各種增強(建言)。其實如果在上一篇裡對註解的那種形式比較瞭解的話,可以看到XML註解無非就是把各種元素放到了XML裡去組裝。效果是一樣一樣的。xml配置在工作中用的比較少,一般都是註解。所以大家看了瞭解下就OK了 。也是比較簡單的。然後給XMl配置的傳送門,參考了看下吧:

2. Aspect的優先順序

首先說一下,設計到優先順序問題的前提是,增強的Pointcut有交集。 Aspect優先順序分兩種,一種是一個Aspect中定義多個增強。另一種是不同的Aspect中的多個增強.我先說下結論,然後針對不同情況搞倆例子。

  • 在目標方法前面的增強@Before,優先順序越高,越先執行。 在目標方法後面的增強@After,@AfterReturning,優先順序越高,越後執行。
  • 一個Aspect中定義多個增強。和定義的先後順序有關,越先出現,優先順序越高。
  • 不同的Aspect中的多個增強。根據Aspect實現Ordered介面,方法getOrder返回值有關,返回值越低,優先順序越高。

2.1 一個Aspect中定義多個增強

@Aspect
public class AspectOne {

    /**
     * Pointcut定義切點函式
     */
    @Pointcut("execution(...)")
    private void myPointcut(){}

    @Before("myPointcut()")
    public void beforeOne(){
        System.out.println("前置通知....執行順序1");
    }

    @Before("myPointcut()")
    public void beforeTwo(){
        System.out.println("前置通知....執行順序2");
    }

    @AfterReturning(value = "myPointcut()")
    public void AfterReturningThree(){
        System.out.println("後置通知....執行順序3");
    }

    @AfterReturning(value = "myPointcut()")
    public void AfterReturningFour(){
        System.out.println("後置通知....執行順序4");
    }
}
複製程式碼

列印結果:

前置通知....執行順序1
前置通知....執行順序2
後置通知....執行順序4
後置通知....執行順序3
複製程式碼

2.2 不同的Aspect中的多個增強

@Aspect
public class AspectOne implements Ordered {

    /**
     * Pointcut定義切點函式
     */
    @Pointcut("execution(...)")
    private void myPointcut(){}

    @Before("myPointcut()")
    public void beforeOne(){
        System.out.println("前置通知..AspectOne..執行順序1");
    }

    @Before("myPointcut()")
    public void beforeTwo(){
        System.out.println("前置通知..AspectOne..執行順序2");
    }

    @AfterReturning(value = "myPointcut()")
    public void AfterReturningThree(){
        System.out.println("後置通知..AspectOne..執行順序3");
    }

    @AfterReturning(value = "myPointcut()")
    public void AfterReturningFour(){
        System.out.println("後置通知..AspectOne..執行順序4");
    }

    /**
     * 定義優先順序,值越低,優先順序越高
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}


//切面類 AspectTwo.java
@Aspect
public class AspectTwo implements Ordered {

    /**
     * Pointcut定義切點函式
     */
    @Pointcut("execution(...)")
    private void myPointcut(){}

    @Before("myPointcut()")
    public void beforeOne(){
        System.out.println("前置通知....執行順序1--AspectTwo");
    }

    @Before("myPointcut()")
    public void beforeTwo(){
        System.out.println("前置通知....執行順序2--AspectTwo");
    }

    @AfterReturning(value = "myPointcut()")
    public void AfterReturningThree(){
        System.out.println("後置通知....執行順序3--AspectTwo");
    }

    @AfterReturning(value = "myPointcut()")
    public void AfterReturningFour(){
        System.out.println("後置通知....執行順序4--AspectTwo");
    }

    /**
     * 定義優先順序,值越低,優先順序越高
     * @return
     */
    @Override
    public int getOrder() {
        return 1;
    }
}
複製程式碼

輸出結果:

前置通知..AspectOne..執行順序1
前置通知..AspectOne..執行順序2
前置通知....執行順序1--AspectTwo
前置通知....執行順序2--AspectTwo
後置通知....執行順序4--AspectTwo
後置通知....執行順序3--AspectTwo
後置通知..AspectOne..執行順序4
後置通知..AspectOne..執行順序3
複製程式碼

好了,關於AspectJ的優先順序分享完了,大家參照著前面總結的結論,再看下兩個例子,應該是可以搞懂的。不過說實在的,用的比較少。(^_^)

3. Spring AOP實際運用場景

Spring AOP的實際運用場景其實還是比較多的,別的不說,Spring自己的事務管理其實就運用了AOP,這裡我來一個相對而言靠近開發者的案例,效能監控。其實說是效能監控,沒那麼高大上,無非就是計算一下一個Web介面呼叫的時間。然後打日誌或者寫入到其他監控平臺(granafa等).廢話少說,直接擼程式碼:

先定義一個實體類,用於存監控的一些資訊:

public class MonitorData {
    //類名
    private String className;
    //方法名
    private String methodName;
    //消耗時間
    private String consumeTime;
    //記錄時間
    private Date logTime;
    
    //gettter setter toString什麼的省略
    ....
}
複製程式碼
@Aspect
@Component
public class MonitorAspectJ {

    /**
     * 定義切點函式,過濾controller包下的名稱以Controller結尾的類所有方法
     */
    @Pointcut("execution(* com..*Controller.*(..))")
    void timer() {
    }

    @Around("timer()")
    public Object logTimer(ProceedingJoinPoint thisJoinPoint) throws Throwable {

        MonitorData monitorData=new MonitorData();
        //獲取目標類名稱
        String clazzName = thisJoinPoint.getTarget().getClass().getName();
        //獲取目標類方法名稱
        String methodName = thisJoinPoint.getSignature().getName();
        
        
        //記錄類名稱
        monitorData.setClassName(clazzName);
        //記錄對應方法名稱
        monitorData.setMethodName(methodName);
        //記錄時間
        monitorData.setLogTime(new Date());

        // 計時並呼叫目標函式
        long start = System.currentTimeMillis();
        Object result = thisJoinPoint.proceed();
        Long time = System.currentTimeMillis() - start;

        //設定消耗時間
        monitorData.setConsumeTime(time.toString());
        //把monitorTime記錄的資訊上傳給監控系統,並沒有實現,需要自行實現即可
        //MonitoruUtils.report(monitorTime)
        System.out.println(monitorData.toString());
        return result;
    }
}
複製程式碼

其實還是比較好理解的,無非就是在把所有以Controller結尾的類的所有方法上加上環繞,記錄時間。然後儲存下來(我這是列印了一下).

AOP的應用遠不止這兩種,諸如快取,許可權驗證、內容處理、事務控制等都可以使用AOP實現,其中事務控制Spring中提供了專門的處理方式,限於篇幅就先聊到這。

4. Spring AOP 底層實現選擇

Spring AOP的底層實現有兩種可選,一種是JDK動態代理,一種是CGLib動態代理。先說下結論,如果要代理的target有介面,則預設採用JDK動態代理。如果沒有,則採用CGLib動態代理。當然也可以強制指定使用CGLib動態代理。方法:

  • XML配置AOP: <aop:config proxy-target-class="true">
  • 註解配置AOP: <aop:aspectj-autoproxy proxy-target-class="true"/>

如果要代理的物件沒有實現任何介面,則必須使用CGLIb代理,如果有,則可以使用JDK代理和CGLib代理,至於為什麼,其實有點一言難盡,這裡不準備展開。後期有機會專門寫動態代理了再展開說。對於我們在Web專案中常用的單例類,儘量使用CGLib動態代理來實現Spring AOP.

CGlib特點如下:位元組碼技術,啟動慢,為每一個方法做索引,效率高。 而JDK動態代理特點則是:啟動快,每個方法通過反射呼叫,效率低,方法沒有索引。

結語

好了,Spring AOP和大家分享完了,有點遺憾的是關於它底層的JDK動態代理和CGLib動態代理沒有展開來講。不過沒關係,後期有時間一定專門出一篇動態代理的文章,通過Spring AOP 的兩篇文章,希望大家至少能夠在使用層面掌握好AOP這種思想,並能運用到工作當中去。Spring,以及Spring boot其實很多地方都運用到了Spring AOP,後面的文章如果涉及到會給大家點出來。Over ,Have a good day .

相關文章