1. 程式人生 > >我的Spring AOP例項

我的Spring AOP例項

AOP是OOP的延續,是Aspect Oriented Programming的縮寫,意思是面向切面程式設計。可以通過預編譯方式和執行期動態代理實現在不修改原始碼的情況下給程式動態統一新增功能的一種技術。AOP實際是GoF設計模式的延續,設計模式孜孜不倦追求的是呼叫者和被呼叫者之間的解耦,AOP可以說也是這種目標的一種實現。

我們現在做的一些非業務,如:日誌、事務、安全等都會寫在業務程式碼中(也即是說,這些非業務類橫切於業務類),但這些程式碼往往是重複,複製——貼上式的程式碼會給程式的維護帶來不便,AOP就實現了把這些業務需求與系統需求分開來做。這種解決的方式也稱代理機制。

先來了解一下AOP的相關概念,《Spring參考手冊》中定義了以下幾個AOP的重要概念,結合以上程式碼分析如下:

切面(Aspect):官方的抽象定義為“一個關注點的模組化,這個關注點可能會橫切多個物件”,在本例中,“切面”就是類TestAspect所關注的具體行為,例如,AServiceImpl.barA()的呼叫就是切面TestAspect所關注的行為之一。“切面”在ApplicationContext中來配置。
連線點(Joinpoint) :程式執行過程中的某一行為,例如,UserService.get的呼叫或者UserService.delete丟擲異常等行為。
通知(Advice) :“切面”對於某個“連線點”所產生的動作,例如,TestAspect中對com.spring.service包下所有類的方法進行日誌記錄的動作就是一個Advice。其中,一個“切面”可以包含多個“Advice”,例如ServiceAspect。
切入點(Pointcut) :匹配連線點的斷言,在AOP中通知和一個切入點表示式關聯。例如,TestAspect中的所有通知所關注的連線點,都由切入點表示式execution(* com.spring.service..

(..))來決定。
目標物件(Target Object) :被一個或者多個切面所通知的物件。例如,AServcieImpl和BServiceImpl,當然在實際執行時,Spring AOP採用代理實現,實際AOP操作的是TargetObject的代理物件。
AOP代理(AOP Proxy) :在Spring AOP中有兩種代理方式,JDK動態代理和CGLIB代理。預設情況下,TargetObject實現了介面時,則採用JDK動態代理,例如,AServiceImpl;反之,採用CGLIB代理,例如,BServiceImpl。強制使用CGLIB代理需要將 的 proxy-target-class屬性設為true。
通知(Advice)型別:

前置通知(Before advice):在某連線點(JoinPoint)之前執行的通知,但這個通知不能阻止連線點前的執行。ApplicationContext中在裡面使用元素進行宣告。例如,TestAspect中的doBefore方法。
後置通知(After advice):當某連線點退出的時候執行的通知(不論是正常返回還是異常退出)。ApplicationContext中在裡面使用元素進行宣告。例如,ServiceAspect中的returnAfter方法,所以Teser中呼叫UserService.delete丟擲異常時,returnAfter方法仍然執行。
返回後通知(After return advice):在某連線點正常完成後執行的通知,不包括丟擲異常的情況。ApplicationContext中在裡面使用元素進行宣告。
環繞通知(Around advice):包圍一個連線點的通知,類似Web中Servlet規範中的Filter的doFilter方法。可以在方法的呼叫前後完成自定義的行為,也可以選擇不執行。ApplicationContext中在裡面使用元素進行宣告。例如,ServiceAspect中的around方法。
丟擲異常後通知(After throwing advice):在方法丟擲異常退出時執行的通知。ApplicationContext中在裡面使用元素進行宣告。例如,ServiceAspect中的returnThrow方法。
注:可以將多個通知應用到一個目標物件上,即可以將多個切面織入到同一目標物件。

使用Spring AOP可以基於兩種方式,一種是比較方便和強大的註解方式,另一種則是中規中矩的xml配置方式。

先說註解,使用註解配置Spring AOP總體分為兩步,第一步是在xml檔案中宣告啟用自動掃描元件功能,同時啟用自動代理功能(同時在xml中新增一個UserService的普通服務層元件,來測試AOP的註解功能):

上例子:

匯入JAR包
aspectjrt-1.7.4.jar,aspectjweaver-1.7.4.jar,aopalliance-1.0.jar,cglib-nodep-3.1.jar

配置 applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <!-- 啟用元件掃描功能 -->
    <context:component-scan base-package="com.hr.core"/>
    <!-- 啟用自動代理功能 -->
    <aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>

建立Aspect切面類添加註解

package com.hr.core;

import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class AopInterceptor {

    private final static Logger log = Logger.getLogger(AopInterceptor.class);

    //配置切入點,該方法無方法體,主要為方便同類中其他方法使用此處配置的切入點
    @Pointcut("execution(* com.hr.service..*(..))")
    public void aspect(){   }

    /*
     * 配置前置通知,使用在方法aspect()上註冊的切入點
     * 同時接受JoinPoint切入點物件,可以沒有該引數
     */
    @Before("aspect()")
    public void before(JoinPoint joinPoint){
        if(log.isInfoEnabled()){
            log.info("before " + joinPoint);
        }
    }

    //配置後置通知,使用在方法aspect()上註冊的切入點
    @After("aspect()")
    public void after(JoinPoint joinPoint){
        if(log.isInfoEnabled()){
            log.info("after " + joinPoint);
        }
    }

    //配置環繞通知,使用在方法aspect()上註冊的切入點
    @Around("aspect()")
    public void around(JoinPoint joinPoint){
        long start = System.currentTimeMillis();
        try {
            ((ProceedingJoinPoint) joinPoint).proceed();
            long end = System.currentTimeMillis();
            if(log.isInfoEnabled()){
                log.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms!");
            }
        } catch (Throwable e) {
            long end = System.currentTimeMillis();
            if(log.isInfoEnabled()){
                log.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms with exception : " + e.getMessage());
            }
        }
    }

    //配置後置返回通知,使用在方法aspect()上註冊的切入點
    @AfterReturning("aspect()")
    public void afterReturn(JoinPoint joinPoint){
        if(log.isInfoEnabled()){
            log.info("afterReturn " + joinPoint);
        }
    }

    //配置丟擲異常後通知,使用在方法aspect()上註冊的切入點
    @AfterThrowing(pointcut="aspect()", throwing="ex")
    public void afterThrow(JoinPoint joinPoint, Exception ex){
        if(log.isInfoEnabled()){
            log.info("afterThrow " + joinPoint + "\t" + ex.getMessage());
        }
    } 

}