《Spring3實戰》摘要(4-2)--Spring配置切面

4.3 在XML中聲明切面

Spring 的 AOP 配置命名空間中,提供了對聲明式切面的支持。

配置AOP命名空間:

<?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/beans 
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd" default-lazy-init="true">

</beans>

這裏寫圖片描述

正常使用AOP功能,需要導入以下jar包:

  • spring-aop.jar
  • aopalliance.jar
  • aspectjrt.jar
  • aspectjweaver.jar

4.3.1 聲明前置和後置通知

<!-- 使用Spring的AOP配置元素聲明一個audience切面 -->
<aop:config><!-- AOP配置 -->
    <!-- 聲明一個切面 -->
    <aop:aspect ref="audience">
        <!-- 配置切面的通知類型及織入切點,即何時和何處執行何種方法 -->
        <!-- 通知在切點執行前 -->
        <aop:before pointcut=
            "execution(* com.spring.springdemo.bean.springidol2.Performer.perform(..))" 
            method="takeSeats"/>
        <!-- 通知在切點執行前 -->   
        <aop:before pointcut=
            "execution(* com.spring.springdemo.bean.springidol2.Performer.perform(..))"
            method="turnOffCellPhones"/>
        <!-- 通知在切點成功執行後 -->
        <aop:after-returning pointcut=
            "execution(* com.spring.springdemo.bean.springidol2.Performer.perform(..))"
            method="applaud"/>
        <!-- 通知在切點執行報錯時 -->
        <aop:after-throwing pointcut=
            "execution(* com.spring.springdemo.bean.springidol2.Performer.perform(..))"
            method="demandRefund"/>
    </aop:aspect>
</aop:config>

/**
 * 作爲切面的Bean ID 爲audience的類
 */
import org.springframework.stereotype.Component;

@Component
public class Audience {
    //表演之前
    public void takeSeats(){
        System.out.println("The audience is taking their seats.");
    }
    //表演之前
    public void turnOffCellPhones(){
        System.out.println("The audience is turning off their cellphones");
    }
    //表演之後
    public void applaud(){
        System.out.println("CLAP CLAP CLAP CLAP");
    }
    //表演失敗之後
    public void demandRefund(){
        System.out.println("Boo! We want our money back!");
    }
}

上例中,所有通知的切點都是相同的,爲了避免重複定義切點,可以使用<aop:pointcut>元素定義一個命名切點。如果<aop:pointcut>放在<aop:aspect>元素中,則所有該<aop:aspect>元素中的通知都可以引用該切點。如果<aop:pointcut>放在<aop:config>元素中,則該切點能夠在該<aop:config>元素中定義的所有切面中被引用。

<aop:config>
    <aop:aspect ref="audience">
        <aop:pointcut expression=
            "execution(* com.spring.springdemo.bean.springidol2.Performer.perform(..))" 
            id="performance"/>
        <aop:before pointcut-ref="performance" method="takeSeats"/>

        <aop:before pointcut-ref="performance" method="turnOffCellPhones"/>

        <aop:after-returning pointcut-ref="performance" method="applaud"/>

        <aop:after-throwing pointcut-ref="performance" method="demandRefund"/>
    </aop:aspect>
</aop:config>

4.3.2 聲明環繞通知

使用環繞通知可以完成之前前置通知和後置通知所實現的相同功能,但是隻需要在一個方法中實現。因爲整個
通知邏輯是在一個方法內實現的,所以不需要使用成員變量保存狀態。

示例如下:

//環繞通知的方法
public void watchPerformance(ProceedingJoinPoint joinpoint){
    try {
        System.out.println("arround---The audience is taking their seats.");
        System.out.println("arround---The audience is turning off their cellphones");
        long start = System.currentTimeMillis();
        /*proceed()方法可以在通知方法裏調用被通知的方法,在環繞通知中,proceed()方法是必須調用的,否則通知將會阻止被通知的方法的調用。*/
        joinpoint.proceed();
        long end =System.currentTimeMillis();
        System.out.println("arround---CLAP CLAP CLAP CLAP");
        System.out.println("The performance took "+(end-start)+" milliseconds.");
    } catch (Throwable e) {
        System.out.println("arround---Boo! We want our money back!");
    }
}

-----------------------------------------------------------

<!-- XML配置文件中聲明切面使用環繞通知 -->
<aop:config>
    <aop:aspect ref="audience">
        <aop:pointcut expression=
            "execution(* com.spring.springdemo.bean.springidol2.Performer.perform(..))" 
            id="performance"/>
        <aop:around pointcut-ref="performance" method="watchPerformance"/>
    </aop:aspect>
</aop:config>

通知執行順序: before -> arround -> after-returning -> after
這裏寫圖片描述

4.3.3 爲通知傳遞參數

舉例:Magician(切面)讀取Volunteer(被監聽類)的方法參數

/**
 * spring配置,需要額外配置 name 爲 magician 的 Bean
 */
<aop:config>
    <aop:aspect ref="magician">
        <aop:pointcut id="thinking" 
            expression="execution(* com.springinaction.springidol.Thinker.thinkOfSomething(String)) and args(thoughts)" />
        <aop:before method="interceptThoughts" pointcut-ref="thinking" arg-names="thoughts" />
    </aop:aspect>
</aop:config>

/**
 * Volunteer(被監聽類)
 */
@Component
public class Volunteer implements Thinker{
    private String thoughts;
    @Override
    public void thinkOfSomething(String thoughts) {
        this.thoughts = thoughts;
    }
    public String getThoughts(){
        return thoughts;
    }   
}
/**
 * Magician類(切面類)
 */
 @Controller
public class Magician implements MindReader{
    private String thoughts;
    @Override
    public void interceptThoughts(String thoughts) {
        System.out.println("Intercepting volunteer's thoughts:"+thoughts);
        this.thoughts = thoughts;
    }
    @Override
    public String getThoughts() {
        return thoughts;
    }
}

4.3.4 通過切面引入新功能

利用被稱爲“引用”的 AOP 概念,切面可以爲 Spring Bean 添加新方法。
需要使用<aop:declare-parents>元素。

舉例如下:

/**
 * spring 配置
 * <aop:declare-parents>元素聲明瞭此切面所通知的Bean在它的對象層次結構中擁有新的父類型。
 * 本示例中,類型匹配 Performer 接口(由types-matching屬性制定)的那些Bean會實現 Contestant 接口(由implement-interface屬性指定)。最後要解決的問題是Contestant接口中的方法實現來自何處。
 * 本例中,使用default-impl屬性通過它的全限定類名來顯式制定Contestant的實現。還可以使用default-ref屬性來標識。
 * default-ref屬性引用了一個 Spring Bean 作爲引入的委託。該引入的Bean 本身可以被注入,被通知,或者使用其他的Spring配置。
 */
<aop:aspect>
    <aop:declare-parents 
        types-matching="com.springinaction.springidol.Performer+" 
        implement-interface="com.springinaction.springidol.Contestant"
        default-impl="com.springinaction.springidol.GraciousContestant"/>
</aop:aspect>

4.4 註解切面

使用註解來創建切面是 AspectJ 5 所引入的關鍵特性。AspectJ 面向註解的模型可以非常簡便地通過少量註解把任意類型轉變爲切面。這種新特性通常稱爲 @AspectJ 。該註解位於aspectjrt.jar 中。

import org.aspectj.lang.ProceedingJoinPoint;
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;
/**
 * 將4.3.1的切面配置示例用註解來實現
 */
@Aspect
public class Audience {
    //定義切點,切點的名稱來源於註解所應用的方法名稱。因此該切點的名稱爲"performance()"。
    //performance()方法的實現內容並不重要,該方法只是一個標識,供@Pointcut依賴。
    @Pointcut("execution(* com.spring.springdemo.bean.springidol2.Performer.perform(..))")
    public void performance(){

    }

    //表演之前
    @Before("performance()")
    public void takeSeats(){
        System.out.println("before---The audience is taking their seats.");
    }
    //表演之前
    @Before("performance()")
    public void turnOffCellPhones(){
        System.out.println("before---The audience is turning off their cellphones");
    }
    //表演之後
    @AfterReturning("performance()")
    public void applaud(){
        System.out.println("after-----CLAP CLAP CLAP CLAP");
    }
    //表演失敗之後
    @AfterThrowing("performance()")
    public void demandRefund(){
        System.out.println("after-throwing----Boo! We want our money back!");
    }
    //環繞通知
    @Around("performance()")
    public void watchPerformance(ProceedingJoinPoint joinpoint){
        try {
            System.out.println("arround---The audience is taking their seats.");
            System.out.println("arround---The audience is turning off their cellphones");
            long start = System.currentTimeMillis();
            joinpoint.proceed();
            long end =System.currentTimeMillis();
            System.out.println("arround---CLAP CLAP CLAP CLAP");
            System.out.println("The performance took "+(end-start)+" milliseconds.");
        } catch (Throwable e) {
            System.out.println("arround---Boo! We want our money back!");
        }
    }
}

有了上面 @AspectJ 標識的切面類 Audience 之後,還需要讓 Spring 將Audience 應用爲一個切面。我們可以在 Spring 上下文中把 AnnotationAwareAspectJAutoProxyCreator 註冊爲一個Bean,該 Bean 知道如何把 @AspectJ 註解所標註的 Bean 轉變爲代理通知。

Spring在aop命名空間中提供了一個自定義的配置元素,<aop:aspectj-autoproxy />將在 Spring 上下文中創建一個 AnnotationAwareAspectJAutoProxyCreator 類,他會自動代理一些 Bean ,這些Bean的方法需要與使用 @Aspect 註解的 Bean 中所定義的切點相匹配,而這些切點又是使用 @Pointcut 註解定義出來的。

4.4.2 傳遞參數給所標註的通知

/**
 * 將4.3.3的切面配置示例用註解來實現
 */
@Aspect
public class Magician implements MindReader{
    private String thoughts;

    @Pointcut("execution(* com.spring.springdemo.bean.springidol2.Thinker."
            +"thinkOfSomething(String)) and args(thoughts)")
    public void thinking(String thoughts){

    }
    //這裏不需要與<aop:before>元素對應的arg-names熟悉,@AspectJ能夠依靠Java語法來判斷爲通知所傳遞參數的細節。
    @Before("thinking(thoughts)")
    public void interceptThoughts(String thoughts) {
        System.out.println("Intercepting volunteer's thoughts:"+thoughts);
        this.thoughts = thoughts;
    }

    @Override
    public String getThoughts() {
        return thoughts;
    }
}

4.4.3 標註引入

等價於<aop:declare-parents>的註解是 @AspectJ 的 @DeclareParents 。在基於 @AspectJ 註解所標註的類內使用時,@DeclareParents 工作方式幾乎等同於<aop:declare-parents>

/**
 * 將4.3.4的切面配置示例用註解來實現
 */
@Aspect
public class ContestantIntroducer {
    @DeclareParents(value="com.spring.springdemo.bean.springidol3.Performer+"
    ,defaultImpl= GraciousContestant.class)
    public static Contestant contestant;
}

@DeclareParents註解由3部分組成

  • value 屬性等同於 <aop:declare-parents> 的 types-matching 屬性。它標識應該被引入指定接口的Bean的類型。
  • defaultImpl 屬性等同於 <aop:declare-parents> 的 default-impl屬性。它標識該類提供了所引入接口的實現。
  • 由 @DeclareParents 註解所標註的 static屬性指定了將被引入的接口。

4.5 注入 AspectJ 切面

雖然 Spring AOP 能夠滿足許多應用的切面需求,但與 AspectJ 相比,Spring AOP 是一個功能比較弱的 AOP 解決方案。AspectJ 提供了 Spring AOP 所不能支持的許多類型的切點。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章