1. 程式人生 > >Spring 學習之路(九):Spring 中的AOP(二):事務通知

Spring 學習之路(九):Spring 中的AOP(二):事務通知

AspectJ

  • 目前,spring 框架中我們可以使用基於 AspectJ 註解或者是基於XML配置的 AOP(主流是使用 AspectJ ,簡單,方便)
  • 如何配置AspectJ

    1. 簡單理解,AspectJ 就是一個支援 aop 的第三方元件,spring 提供了很好的支援,我們只需要將 對應的 jar 包加入我們的專案即可(對應 jar 包可以在我的原始碼下載)
    2. 如圖:
      mark

    3. 配置檔案中宣告 使用 AspectJ 註解

      1. 引入aop名稱空間
        mark
      2. 使 AspectJ 註解生效
        mark
  • AspectJ 註解工作流程

    1. 在 Spring 中宣告 AspectJ 切面, 只需要在 IOC 容器中將切面宣告為 Bean 例項. 當在 Spring IOC 容器中初始化 AspectJ 切面之後, Spring IOC 容器就會為那些與 AspectJ 切面相匹配的 Bean 建立代理
    2. 在 AspectJ 註解中, 切面只是一個帶有 @Aspect 註解的 Java 類

    3. 通知是標註有某種註解的簡單的 Java 方法

    4. 對以上流程不清楚的話,我們直接看程式碼

前置通知

//把該類宣告為一個切面:需要把該類放入到ioc容器中,然後再宣告為一個切面
@Aspect
@Component
public class LogginAspect {

    // 宣告該方法是一個前置通知,在目標方法開始之前執行
    @Before("execution(void com.zc.cris.beans.spring.aop.impl.Chinese.*(String))")
    public
void beforeMethod(JoinPoint joinPoint) { // 獲取方法簽名和引數集合 System.out.println(joinPoint.getSignature().getName() + "-----" + Arrays.asList(joinPoint.getArgs())); System.out.println("我是方法的前置通知"); } - 測試程式碼 @Test void testProxy() { //建立ioc容器 ApplicationContext context = new
ClassPathXmlApplicationContext("applicationContext.xml"); //獲取bean People bean = context.getBean(People.class); // System.out.println(bean); // System.out.println(bean.getClass().getName()); //System.out.println(bean instanceof Chinese); //false //使用bean bean.eat("筷子"); bean.say("中文"); }

console:

mark

後置通知

// 宣告該方法是一個後置通知,在目標方法執行後執行(無論目標方法是否發生異常)
    // 且後置通知無法訪問目標方法的返回值
    @After("execution(* com.zc.cris.beans.spring.aop.impl.*.*(String))")
    public void afterMethod(JoinPoint joinPoint) {

        System.out.println("我是方法的後置通知");
    }

- 測試程式碼同上

console:

mark

返回通知

    // 宣告該方法為返回通知:方法正常執行結束後執行的程式碼
    // 返回 通知是可以訪問到方法的返回值的!
    /*切點表示式表示執行任意類的任意方法. 第
    一個 * 代表匹配任意修飾符及任意返回值,  第二個 * 代表任意類的物件,
    第三個 * 代表任意方法, 引數列表中的 ..  匹配任意數量的引數
    */
    @AfterReturning(value = "execution(public void com.zc.cris.beans.spring.aop.impl.*.*(..))", returning = "result")
    public void afterRetruning(JoinPoint joinPoint, Object result) {

        System.out.println("我是方法的返回通知" + joinPoint.getSignature().getName() + "^^^^"
                + Arrays.asList(joinPoint.getArgs() + "我是方法的返回值" + result));
    }


- 測試程式碼同上

console:

mark

異常通知

//目標方法出現異常才會指定的程式碼
    //可以訪問到異常物件,且可以指定出現特定的異常(NullPointException)才會執行
    @AfterThrowing(value = "execution(public void com.zc.cris.beans.spring.aop.impl.*.*(..))",
            throwing="e")
    public void afterThrowing(JoinPoint joinPoint, Exception e) {
        System.out.println("我是目標方法發生異常才執行的通知:"+e.getMessage());
    }

- 測試程式碼同上

console:

mark

  • 通過以上四種通知型別的應用我們大致瞭解Spring 的aop通過 AspectJ 元件是如何完成的,讓我們再 修改之前的代理類,加深理解

mark

環繞通知

    //環繞通知:必須攜帶 ProceedingJoinPoint 型別的引數
    //環繞通知類似於動態代理的全過程:ProceedingJoinPoint 型別的引數可以決定目標方法的執行,
    //環繞通知必須要有返回值,返回值其實就是目標方法的返回值
    @Around(value = "execution(public void com.zc.cris.beans.spring.aop.impl.*.*(..))")
    public Object around(ProceedingJoinPoint pjt) {

        Object result = null;
        String methodName = pjt.getSignature().getName();

        try {
            //前置通知
            System.out.println("我是環繞通知的前置通知!!!!!");

            //執行目標方法
            result = pjt.proceed();

            //返回通知
            System.out.println("我是環繞通知的後置通知");
        } catch (Throwable e) {
            //異常通知
            System.out.println("我是環繞通知的異常通知"+e.getMessage());
            throw new RuntimeException(e);
        }
        //後置通知
        System.out.println("我是環繞通知的後置通知");
        return result;
    }

console:

mark

切面的優先順序
  • 假如我們現在有兩個切面類,一個負責引數驗證,一個負責日誌記錄,那麼我們如何確定這兩個切面類誰先執行,誰後執行呢?
//使用 @Order(1) 註解指定切面的優先順序,數字越小,優先順序越高
@Order(1)
@Aspect
@Component
public class ValidationAspect {

    @Before(value = "execution(* com.zc.cris.beans.spring.aop.impl.*.*(..))")
    public void validate(JoinPoint joinPoint) {

        System.out.println("------- validation-----"+ Arrays.asList(joinPoint.getArgs()));
    }
}

@Order(2)
//把該類宣告為一個切面:需要把該類放入到ioc容器中,然後再宣告為一個切面
@Aspect
@Component
public class LogginAspect {

console:

mark

切面表示式的重用
  • 通過上面的測試,我們發現每個通知的註解裡都需要寫相同的切面表示式,這明顯不符合我們的風格,著名程式設計大師馬丁·富勒 就曾經說過,程式碼有很多種壞味道,而重複是最壞的一種,事實上通過一個小小的註解就可以搞定
    /*
     * 定義一個方法,專門用來宣告切入點表示式,一般的,該方法中不需要再寫任何程式碼
     * 使用@Pointcut 註解來宣告
     * 後面的其他通知直接使用該方法名來引用當前的切入點表示式即可
     */
    @Pointcut("execution(* com.zc.cris.beans.spring.aop.impl.*.*(String))")
    public void declaredJointPointExpresson() {};


    // 宣告該方法是一個前置通知,在目標方法開始之前執行
    @Before("declaredJointPointExpresson()")


    @Before(value = "com.zc.cris.beans.spring.aop.impl.LogginAspect.declaredJointPointExpresson()")
  • 我們通過@Pointcut 註解對切面表示式進行了重構,當前類的通知或者其他包的類的通知,都可以使用,以達到簡潔,高效的目的
通過xml配置檔案來配置spring的事務通知(不推薦,看完程式碼你就知道為什麼了,瞭解即可)
  • applicationContext.aopXML.xml
<bean id="chinese" class="com.zc.cris.beans.spring.aop.impl.xml.Chinese"></bean>

    <bean id="validationAspect" class="com.zc.cris.beans.spring.aop.impl.xml.ValidationAspect"></bean>

    <bean id="logginAspect" class="com.zc.cris.beans.spring.aop.impl.xml.LogginAspect"></bean>

    <!-- 配置aop -->
    <aop:config>
        <!-- 定義切入點表示式 -->
        <aop:pointcut expression="execution(* com.zc.cris.beans.spring.aop.impl.xml.*.*(String))" id="pointCut"/>
        <!-- 定義一個切面物件 -->
        <aop:aspect ref="logginAspect" order="2">
            <!-- 定義各種通知 -->
            <aop:before method="beforeMethod" pointcut-ref="pointCut"/>
            <aop:after method="afterMethod" pointcut-ref="pointCut"/>
            <aop:after-returning method="afterRetruning" returning="result" pointcut-ref="pointCut"/>
            <aop:after-throwing method="afterThrowing" throwing="e" pointcut-ref="pointCut"/>
            <!-- <aop:around method="around"/> -->
        </aop:aspect>
        <aop:aspect ref="validationAspect" order="1">
            <aop:before method="validate" pointcut-ref="pointCut"/>
        </aop:aspect>       
    </aop:config>
  • 取消我們aop切面類上的所有切面註解,然後進行測試發現console列印的和之前測試一毛一樣

  • 原始碼點我