Spring實(shí)戰(zhàn)(四)-面向切面的Spring

本文基于《Spring實(shí)戰(zhàn)(第4版)》所寫炊苫。

在軟件開發(fā)中,散布于應(yīng)用中多的功能被稱為橫切關(guān)注點(diǎn)(cross-cutting concern)茬故。通常來講,這些橫切關(guān)注點(diǎn)從概念上是與應(yīng)用的業(yè)務(wù)邏輯相分離的(但往往會(huì)直接嵌入到應(yīng)用的業(yè)務(wù)邏輯之中)放棒。把這些橫切關(guān)注點(diǎn)與業(yè)務(wù)相分離正是面向切面編程(AOP)所要解決的問題姻报。

切面的應(yīng)用場(chǎng)景包括:日志、安全和事務(wù)管理等间螟。

面向切面編程的基本原理

在使用面向切面編程時(shí)吴旋,我們?nèi)匀辉谝粋€(gè)地方定義通用功能损肛,但是可以通過聲明的方式定義這個(gè)功能要以何種方式在何處應(yīng)用,而無需修改受影響的類荣瑟。橫切關(guān)注點(diǎn)可以被模塊化為特殊的類治拿,這些類被稱為切面(aspect)。

描述切面的常用術(shù)語有通知(advice)褂傀、切點(diǎn)(pointcut)和連接點(diǎn)(join point)忍啤。

通知(Advice)

在AOP術(shù)語中,切面的工作被稱為通知仙辟。
Spring切面可以應(yīng)用5種類型的通知:

  • 前置通知(Before):在目標(biāo)方法被調(diào)用之前調(diào)用通知功能同波;
  • 后置通知(After):在目標(biāo)方法完成之后調(diào)用通知,此時(shí)不會(huì)關(guān)心方法的輸出是什么叠国;
  • 返回通知(After-returning ):在目標(biāo)方法成功執(zhí)行之后調(diào)用通知未檩;
  • 異常通知(After-throwing):在目標(biāo)方法拋出異常后調(diào)用通知;
  • 環(huán)繞通知(Around):通知包裹了被通知的方法粟焊,在被通知的方法調(diào)用之前和調(diào)用之后執(zhí)行自定義的行為冤狡。

連接點(diǎn)(Join point)

應(yīng)用可能有數(shù)以千計(jì)的時(shí)機(jī)應(yīng)用通知。這些時(shí)機(jī)被稱為連接點(diǎn)项棠。連接點(diǎn)是在應(yīng)用執(zhí)行過程中能夠插入切面的一個(gè)點(diǎn)悲雳。這個(gè)點(diǎn)可以是調(diào)用方法時(shí)、拋出異常時(shí)香追、甚至修改一個(gè)字段時(shí)合瓢。切面代碼可以利用這些點(diǎn)插入到應(yīng)用的正常流程之中,并添加新的行為透典。

切點(diǎn)(Pointcut)

切點(diǎn)的定義會(huì)匹配通知所要織入的一個(gè)或多個(gè)連接點(diǎn)晴楔。我們通常使用明確的類和方法名稱,或是利用正則表達(dá)式定義所匹配的類和方法名稱來指定這些切點(diǎn)峭咒。

切面(Aspect)

切面是通知和切點(diǎn)的結(jié)合税弃。通知和切點(diǎn)共同定義了切面的全部內(nèi)容。

引入(Introduction)

引入允許我們向現(xiàn)有類添加新方法或?qū)傩浴?/p>

織入(Weaving)

織入是把切面應(yīng)用到目標(biāo)對(duì)象并創(chuàng)建新的代理對(duì)象的過程凑队。切面在指定的連接點(diǎn)被織入到目標(biāo)對(duì)象中则果。在目標(biāo)對(duì)象的生命周期里有多少個(gè)點(diǎn)可以進(jìn)行織入:

  • 編譯期:切面在目標(biāo)類編譯時(shí)被織入。AspectJ的織入編譯器是以這種方式織入切面的顽决。
  • 類加載期:切面在目標(biāo)類加載到JVM時(shí)被織入短条。需要特殊的類加載器,它可以在目標(biāo)類被引入應(yīng)用之前增強(qiáng)該目標(biāo)類的字節(jié)碼才菠。AspectJ5的加載時(shí)織入就支持以這種方式織入切面茸时。
  • 運(yùn)行期:切面在應(yīng)用運(yùn)行的某個(gè)時(shí)刻被織入。一般情況下赋访,在織入切面時(shí)可都,AOP容器會(huì)為目標(biāo)對(duì)象動(dòng)態(tài)地創(chuàng)建一個(gè)代理對(duì)象缓待。SpringAOP就是以這種方式織入切面。

Spring提供了4種類型的AOP支持:

  • 基于代理的經(jīng)典SpringAOP渠牲;
  • 純POJO切面旋炒;
  • @AspectJ注解驅(qū)動(dòng)的切面;
  • 注入式AspectJ切面签杈。

Spring在運(yùn)行時(shí)通知對(duì)象

通過在代理類中包裹切面瘫镇,Spring在運(yùn)行期把切面織入到Spring管理的bean中。代理封裝了目標(biāo)類答姥,并攔截被通知方法的調(diào)用铣除,再把調(diào)用轉(zhuǎn)發(fā)給真正的目標(biāo)bean。當(dāng)代理攔截到方法調(diào)用時(shí)鹦付,在調(diào)用目標(biāo)bean方法之前尚粘,會(huì)執(zhí)行切面邏輯。
直到應(yīng)用需要被代理的bean時(shí)敲长,Spring才創(chuàng)建代理對(duì)象郎嫁。如果使用的是ApplicationContext的話,在ApplicationContext從BeanFactory中加載所有bean的時(shí)候祈噪,Spring才會(huì)創(chuàng)建被代理的對(duì)象泽铛。因?yàn)镾pring運(yùn)行時(shí)才創(chuàng)建代理對(duì)象,所以我們不需要特殊的編譯器來織入SpringAOP的切面辑鲤。

Spring只支持方法級(jí)別的連接點(diǎn)

因?yàn)镾pring基于動(dòng)態(tài)代理厚宰,所以Spring只支持方法連接點(diǎn)。Spring缺少對(duì)字段連接點(diǎn)的支持遂填,而且它不支持構(gòu)造器連接點(diǎn)。

方法之外的連接點(diǎn)攔截功能澈蝙,我們可以利用Aspect來補(bǔ)充吓坚。

通過切點(diǎn)來選擇連接點(diǎn)

在Spring AOP中,要使用AspectJ的切點(diǎn)表達(dá)式語言來定義切點(diǎn)灯荧。

由于Sring是基于代理的礁击,而某切切點(diǎn)表達(dá)式是與基于代理的AOP無關(guān)的。下表列出了Spring AOP所支持的AspectJ切點(diǎn)指示器逗载。

AspectJ指示器 描述
arg() 限制連接點(diǎn)匹配參數(shù)為指定類型的執(zhí)行方法
@args() 限制連接點(diǎn)匹配參數(shù)有指定注解標(biāo)注的執(zhí)行方法
execution() 用于匹配是連接點(diǎn)的執(zhí)行方法
this() 限制連接點(diǎn)匹配AOP代理的bean引用為指定類型的類
target 限制連接點(diǎn)匹配目標(biāo)對(duì)象為指定類型的類
@target() 限制連接點(diǎn)匹配特定的執(zhí)行對(duì)象哆窿,這些對(duì)象對(duì)應(yīng)的類要具有指定類型的注解
within() 限制連接點(diǎn)匹配指定的類型
@within() 限制連接點(diǎn)匹配指定注解所標(biāo)注的類型(當(dāng)使用Spring AOP時(shí),方法定義在由指定的注解所標(biāo)注的類里)
@annotation 限制匹配帶有指定注解的連接點(diǎn)

在Spring中嘗試使用AspectJ其他指示器時(shí)厉斟,將會(huì)拋出IllegalArgument-Exception異常挚躯。

注意:如上所展示的這些Spring支持的指示器時(shí),注意只有execution指示器是實(shí)際執(zhí)行匹配的擦秽,而其他的指示器都是用來限制匹配的码荔。

編寫切點(diǎn)

為了闡述Spring中的切面漩勤,我們定義一個(gè)Performance接口:

package concert;

public interface Performance {
     public void perform() ;
}

假設(shè)我們想編寫Performance的perform()方法觸發(fā)的通知,下面展示了切點(diǎn)表達(dá)式的寫法

使用AspectJ切點(diǎn)表達(dá)式來選擇Performance的perform()方法

現(xiàn)在假設(shè)我們需要配置的切點(diǎn)僅匹配concert包缩搅。我們可以使用within()指示器來限制匹配越败,如下圖

使用within指示器限制切點(diǎn)范圍

注意:我們使用了“&&”操作符把execution()和within()指示器連接在一起形成與(可以用and代替)關(guān)系(切點(diǎn)必須匹配所有的指示器)。類似地硼瓣,我們可以使用“||操作符”來標(biāo)識(shí)或(可以用or代替)關(guān)系究飞,而使用“!”操作符來標(biāo)識(shí)非(可以用not代替)操作。

在切點(diǎn)中選擇bean

除了表所列的指示器外堂鲤,Spring還引入了一個(gè)新的bean()指示器亿傅,它允許我們?cè)谇悬c(diǎn)表達(dá)式中使用bean的ID來標(biāo)識(shí)bean。bean()使用beanID或bean名稱作為參數(shù)限制切點(diǎn)只匹配特定的bean筑累。

例如袱蜡,考慮如下的切點(diǎn):

execution(* concert.Performance.perform()) 
            and bean('woodstock')

還可以使用非操作為除了特定ID以外的其他bean應(yīng)用通知:

execution(* concert.Performance.perform()) 
            and !bean('woodstock')

使用注解創(chuàng)建切面

我們已經(jīng)定義了Performance接口,它是切面中切點(diǎn)的目標(biāo)對(duì)象÷冢現(xiàn)在坪蚁,讓我們使用AspectJ注解來定義切面

定義切面

我們將觀眾定義為一個(gè)切面,并將其應(yīng)用到演出上就是較為明智的做法镜沽。
下面為Audience類的代碼

package com.springinaction.perf;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;

//切面 POJO
@Aspect
public class Audience {

    //定義命名的切點(diǎn)
    @Pointcut("execution(** com.springinaction.perf.Performance.perform(..))")
    public void performance(){
    }

    //定義通知
    @Before("performance()")   // 表演之前
    public void silenceCellPhones(){
        System.out.println("Silencing cell phones");
    }

    @Before("performance()") // 表演之前
    public void takeSeats(){
        System.out.println("Taking seats");
    }

    @AfterReturning("performance()")  // 表演之后
    public void applause(){
        System.out.println("CLAP CLAP CLAP");
    }

    @AfterThrowing("performance()")   // 表演失敗之后
    public void demandRefund(){
        System.out.println("Demanding a refund");
    }

    @Around("performance()")  // 環(huán)繞通知方法
    public void watchPerformance(ProceedingJoinPoint jp){
        try {
            System.out.println("Silencing cell phones Again");
            System.out.println("Taking seats Again");
            jp.proceed();
            System.out.println("CLAP CLAP CLAP Again");
        }
        catch (Throwable e){
            System.out.println("Demanding a refund Again");
        }
    }

}

關(guān)于環(huán)繞通知敏晤,我們首先注意到它接受ProceedingJoinPoint作為參數(shù)。這個(gè)對(duì)象是必須要有的缅茉,因?yàn)橐谕ㄖ型ㄟ^它來調(diào)用被通知的方法嘴脾。

需要注意的是,一般情況下蔬墩,別忘記調(diào)用proceed()方法译打。如果不調(diào)用,那么通知實(shí)際上會(huì)阻塞對(duì)被通知方法的調(diào)用拇颅,也許這是所期望的效果奏司。當(dāng)然,也可以多次調(diào)用樟插,比如要實(shí)現(xiàn)一個(gè)場(chǎng)景是實(shí)現(xiàn)重試邏輯韵洋。

除了注解和沒有實(shí)際操作的performance()方法,Audience類依然是一個(gè)POJO黄锤,可以裝配為Spring中的bean

    @Bean
    public Audience audience(){   // 聲明Audience
        return new Audience();
    }

除了定義切面外搪缨,還需要啟動(dòng)自動(dòng)代理,才能使這些注解解析鸵熟。
如果使用JavaConfig的話副编,需要如下配置

package com.springinaction.perf;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan
@EnableAspectJAutoProxy   //啟動(dòng)AspectJ自動(dòng)代理
public class AppConfig {

    @Bean
    public Audience audience(){   // 聲明Audience
        return new Audience();
    }
}

假如在Spring中要使用XML來裝配bean的話,那么需要使用Spring aop命名空間中的<aop:aspectj-autoproxy>元素流强。

<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.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.xsd">

    <context:component-scan base-package="com.springinaction.perf" />
    <aop:aspectj-autoproxy />
    <bean id="audience" class="com.springinaction.perf.Audience" />
</beans>

無論使用JavaConfig還是XML齿桃,Aspect自動(dòng)代理都會(huì)使用@Aspect注解的bean創(chuàng)建一個(gè)代理惑惶,這個(gè)代理會(huì)圍繞著所有該切面的切點(diǎn)所匹配的bean了嚎。這種情況下前翎,將會(huì)為Concert的bean創(chuàng)建一個(gè)代理,Audience類中的通知方法將會(huì)在perform()調(diào)用前后執(zhí)行痕钢。

我們需要記住的是香到,Spring的AspectJ自動(dòng)代理僅僅使用@AspectJ作為創(chuàng)建切面的指導(dǎo)鱼冀,切面依然是基于代理的。本質(zhì)上悠就,它依然是Spring基于代理的切面千绪。

處理通知中的參數(shù)

目前為止,除了環(huán)繞通知梗脾,其他通知都沒有參數(shù)荸型。如果切面所通知的方法確實(shí)有參數(shù)該怎么辦呢?切面能訪問和使用傳遞給被通知方法的參數(shù)嗎炸茧?

為了闡述這個(gè)問題瑞妇,我們來重新看一下BlankDisc樣例。
假設(shè)你想記錄每個(gè)磁道被播放的次數(shù)梭冠。為了記錄次數(shù)辕狰,我們創(chuàng)建了TrackCounter類,它是通知playTrack()方法的一個(gè)切面控漠。

package com.springinaction.disc;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

import java.util.HashMap;
import java.util.Map;

@Aspect
public class TrackCounter {

    private Map<Integer, Integer> trackCounts = new HashMap<>();

    @Pointcut("execution(* com.springinaction.disc.CompactDisc.playTrack(int))" +
     "&& args(trackNumber)")  // 通知playTrack()方法
    public void trackPlayed(int trackNumber){}

    @Before("trackPlayed(trackNumber)")  // 在播放前蔓倍,為該磁道計(jì)數(shù)
    public void countTrack(int trackNumber){
        int currentCount = getPlayCount(trackNumber);
        trackCounts.put(trackNumber, currentCount + 1);
    }

    public int getPlayCount(int trackNumber){
        return trackCounts.containsKey(trackNumber) ? trackCounts.get(trackNumber) : 0;
    }
}

以下為切點(diǎn)表達(dá)式分解

在切點(diǎn)表達(dá)式中聲明參數(shù),這個(gè)參數(shù)傳入到通知方法中

其中args(trackNumber)限定符表明傳遞給playTrack()方法的int類型參數(shù)也會(huì)傳遞到通知中去盐捷。trackNumber也與切點(diǎn)方法簽名中的參數(shù)相匹配偶翅。切點(diǎn)定義中的參數(shù)與切點(diǎn)方法中的參數(shù)名稱是一樣的。

下面我們啟動(dòng)AspectJ自動(dòng)代理以及定義bean

package com.springinaction.disc;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by Shangnan on 2017/8/29.
 */
@Configuration
@EnableAspectJAutoProxy
public class TrackCounterConfig {

    @Bean
    public CompactDisc sgtPeppers(){
        BlankDisc cd = new BlankDisc();
        cd.setTitle("Sgt. Pepper's Lonely Hearts Club Band");
        cd.setArtist("The Beatles");
        List<String> tracks = new ArrayList<>();
        tracks.add("Sgt. Pepper's Lonely Hearts Club Band");
        tracks.add("With a Little Help from My Friends");
        tracks.add("Luck in the Sky with Diamonds");
        tracks.add("Getting Better");
        tracks.add("Fixing a Hole");
        tracks.add("Feel My Heart");
        tracks.add("L O V E");
        cd.setTracks(tracks);
        return cd;
    }

    @Bean
    public TrackCounter trackCounter(){
        return new TrackCounter();
    }

}

最后的簡單測(cè)試

package com.springinaction;

import static org.junit.Assert.*;
import com.springinaction.disc.CompactDisc;
import com.springinaction.disc.TrackCounter;
import com.springinaction.disc.TrackCounterConfig;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.contrib.java.lang.system.StandardOutputStreamLog;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * Created by Shangnan on 2017/8/29.
 */

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TrackCounterConfig.class)
public class TrackCounterTest {

    @Rule
    public final StandardOutputStreamLog log = new StandardOutputStreamLog();

    @Autowired
    private CompactDisc cd;

    @Autowired
    private TrackCounter counter;

    @Test
    public void testTrackCounter(){
        cd.playTrack(1);
        cd.playTrack(2);
        cd.playTrack(3);
        cd.playTrack(3);
        cd.playTrack(3);
        cd.playTrack(3);
        cd.playTrack(7);
        cd.playTrack(7);

        assertEquals(1,counter.getPlayCount(1));
        assertEquals(1,counter.getPlayCount(2));
        assertEquals(4,counter.getPlayCount(3));
        assertEquals(0,counter.getPlayCount(4));
        assertEquals(0,counter.getPlayCount(5));
        assertEquals(0,counter.getPlayCount(6));
        assertEquals(2,counter.getPlayCount(7));
    }
}

通過注解引入新功能

我們除了給已有的方法添加新功能外碉渡,還可以添加一些額外的功能倒堕。

回顧一下,在Spring中爆价,切面只是實(shí)現(xiàn)了它們所包裝bean相同的接口代理。如果除了實(shí)現(xiàn)這些接口媳搪,代理也能暴露新接口铭段。即便底層實(shí)現(xiàn)類并沒有實(shí)現(xiàn)這些接口,切面所通知的bean也能實(shí)現(xiàn)新的接口秦爆。下圖展示了它們是如何工作的序愚。

使用Spring AOP,我們可以為bean引入新的方法。代理攔截調(diào)用并委托給實(shí)現(xiàn)該方法的其他對(duì)象

需要注意的是等限,當(dāng)引入接口的方法被調(diào)用時(shí)爸吮,代理會(huì)把此調(diào)用委托給實(shí)現(xiàn)了新接口的某個(gè)其他對(duì)象芬膝。實(shí)際上,一個(gè)bean的實(shí)現(xiàn)被拆分到了多個(gè)類中形娇。

為了驗(yàn)證能行得通锰霜,我們?yōu)樗械腜erformance實(shí)現(xiàn)引入Encoreable接口

package com.springinaction.perf;

public interface Encoreable {
    void performEncore();
}

借助于AOP,我們創(chuàng)建一個(gè)新的切面

package com.springinaction.perf;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;

@Aspect
public class EncoreableIntroducer {   // 需要給Performance和其實(shí)現(xiàn)類額外添加方法的實(shí)現(xiàn)
    @DeclareParents(value = "com.springinaction.perf.Performance+",
                    defaultImpl = DefaultEncoreable.class)
    public static Encoreable encoreable;
}

其中@DeclareParents注解桐早,將Encoreable接口引入到Performance bean中癣缅。
@DeclareParents注解有三個(gè)部門組成:

  • value屬性指定了哪種類型的bean要引入該接口。(本例中哄酝,就是Performance友存,加號(hào)表示Performance的所有子類型)
  • defaultImpl屬性指定了為引入功能提供實(shí)現(xiàn)的類。
  • @DeclareParents注解所標(biāo)注的靜態(tài)屬性指明了要引入的接口陶衅。

然后需要在配置中聲明EncoreableIntroducer的bean

    @Bean
    public EncoreableIntroducer encoreableIntroducer(){
        return new EncoreableIntroducer();
    }

當(dāng)調(diào)用委托給被代理的bean或被引入的實(shí)現(xiàn)屡立,取決于調(diào)用的方法屬性被代理的bean還是屬性被引入的接口。

在Spring中搀军,注解和自動(dòng)代理提供了一種很便利的方式來創(chuàng)建切面膨俐。但有一個(gè)劣勢(shì):必須能夠?yàn)橥ㄖ愄砑幼⒔猓性创a奕巍。

如果沒有源碼或者不想注解到你的代碼中吟策,能可選擇Spring XML配置文件中聲明切面。

在XML中聲明切面

如果聲明切面的止,但不能為通知類添加注解時(shí)檩坚,需要轉(zhuǎn)向XML配置了。

在Spring的aop命名空間中诅福,提供了多個(gè)元素用來在XML中聲明切面匾委,如下表所示

AOP配置元素 用途
<aop:advisor> 定義AOP通知器
<aop:after> 定義AOP后置通知(不管被通知的方法是否執(zhí)行成功)
<aop:after-returning> 定義AOP返回通知
<aop:after-throwing> 定義AOP異常通知
<aop:around> 定義AOP環(huán)繞通知
<aop:aspect> 定義一個(gè)切面
<aop:aspectj-autoproxy> 啟用@AspectJ注解驅(qū)動(dòng)的切面
<aop:before> 定義AOP前置通知
<aop:config> 頂層的AOP配置元素。大多數(shù)的<aop:*>元素必須包含在<aop:config>元素內(nèi)
<aop:declare-parents> 以透明的方式為被通知的對(duì)象引入額外的接口
<aop:pointcut> 定義一個(gè)切點(diǎn)

我們重新看一下Audience類氓润,這一次我們將它所有的AspectJ注解全部移除掉:

package com.springinaction.perf;

public class Audience {

    public void silenceCellPhones(){
        System.out.println("Silencing cell phones");
    }

    public void takeSeats(){
        System.out.println("Taking seats");
    }

    public void applause(){
        System.out.println("CLAP CLAP CLAP");
    }

    public void demandRefund(){
        System.out.println("Demanding a refund");
    }

    public void watchPerformance(ProceedingJoinPoint jp){
        try {
            System.out.println("Silencing cell phones Again");
            System.out.println("Taking seats Again");
            jp.proceed();
            System.out.println("CLAP CLAP CLAP Again");
        }
        catch (Throwable e){
            System.out.println("Demanding a refund Again");
        }
    }
}

聲明前置赂乐、后置以及環(huán)繞通知

下面展示了所需要的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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="audience" class="com.springinaction.perf.Audience" />
    <bean id="performance" class="com.springinaction.perf.Concert"/>

    <aop:config>
        <aop:aspect ref="audience">
            <aop:pointcut id="perf" expression="execution(* com.springinaction.perf.Performance.perform(..))" />
            <aop:before pointcut-ref="perf" method="silenceCellPhones" />
            <aop:before pointcut-ref="perf" method="takeSeats" />
            <aop:after-returning pointcut-ref="perf" method="applause" />
            <aop:after-throwing pointcut-ref="perf" method="demandRefund"/>
            <aop:around pointcut-ref="perf" method="watchPerformance"/>
        </aop:aspect>
    </aop:config>

</beans>

為通知傳遞參數(shù)

我們使用XML來配置BlankDisc。
首先咖气,移除掉TrackCounter上所有的@AspectJ注解挨措。

package com.springinaction.disc;
import java.util.HashMap;
import java.util.Map;

public class TrackCounter {

    private Map<Integer, Integer> trackCounts = new HashMap<>();

    // 在播放前,為該磁道計(jì)數(shù)
    public void countTrack(int trackNumber){
        int currentCount = getPlayCount(trackNumber);
        trackCounts.put(trackNumber, currentCount + 1);
    }

    public int getPlayCount(int trackNumber){
        return trackCounts.containsKey(trackNumber) ? trackCounts.get(trackNumber) : 0;
    }
}

下面展示了在XML中將TrackCounter配置為參數(shù)化的切面

<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="trackCounter" class="com.springinaction.disc.TrackCounter" />

    <bean id="cd" class="com.springinaction.disc.BlankDisc" >
        <property name="title" value="Sgt. Pepper's Lonely Hearts Club Band" />
        <property name="artist" value="The Beatles" />
        <property name="tracks">
            <list>
                <value>Sgt. Pepper's Lonely Hearts Club Band</value>
                <value>With a Little Help from My Friends</value>
                <value>Lucy in the Sky with Diamonds</value>
                <value>Getting Better</value>
                <value>Fixing a Hole</value>
                <value>Feel My Heart</value>
                <value>L O V E</value>
            </list>
        </property>
    </bean>

    <aop:config>
        <aop:aspect ref="trackCounter">
            <aop:pointcut id="trackPlayed" expression="
            execution(* com.springinaction.disc.CompactDisc.playTrack(int))
            and args(trackNumber)" />

            <aop:before pointcut-ref="trackPlayed" method="countTrack"/>
        </aop:aspect>
    </aop:config>


</beans>

注意:在XML中崩溪,“&”符號(hào)會(huì)被解析為實(shí)體的開始浅役,所用“and”關(guān)鍵字。

通過切面引入新的功能

使用Spring aop命名空間中的<aop:declare-parents>元素伶唯,我們可以實(shí)現(xiàn)相同的功能觉既。

<aop:aspect>
      <aop:declare-parents types-matching="com.springinaction.perf.Performance+"
                                 implement-interface="com.springinaction.perf.Encoreable"
                                 default-impl="com.springinaction.perf.DefaultEncoreable" />
</aop:aspect>

我們還可以使用delegate-ref屬性來標(biāo)識(shí)

        <aop:aspect>
            <aop:declare-parents types-matching="com.springinaction.perf.Performance+"
                                 implement-interface="com.springinaction.perf.Encoreable"
                                 delegate-ref="defaultEncoreable" />
        </aop:aspect>

delegate-ref屬性引用了一個(gè)Spring bean作為引入的委托。

<bean id="defaultEncoreable" class="com.springinaction.perf.DefaultEncoreable" />

注入AspectJ切面

AspectJ提供了Spring AOP所不能支持的許多類型的切點(diǎn)。例如:構(gòu)造器切點(diǎn)就非常方便瞪讼。

為了演示钧椰,我們新創(chuàng)建一個(gè)切面,我們以切面的方式創(chuàng)建一個(gè)評(píng)論員的角色符欠,演出后提一些批評(píng)意見嫡霞。
首先創(chuàng)建這樣的一個(gè)切面

package com.springinaction.perf;

public aspect CriticAspect {
    public  CriticAspect(){}

    pointcut performance() : execution(* perform(..));

    after() returning : performance() {
       System.out.println(criticismEngine.getCriticism());
    }

    private CriticismEngine criticismEngine;

    public CriticismEngine getCriticismEngine() {
        return criticismEngine;
    }

    public void setCriticismEngine(CriticismEngine criticismEngine) {
        this.criticismEngine = criticismEngine;
    }
}

然后是CriticismEngine的接口

package com.springinaction.perf;

public interface CriticismEngine {
    String getCriticism();
}

CriticismEngine的實(shí)現(xiàn)類

package com.springinaction.perf;

public class CriticismEngineImpl implements CriticismEngine {

    public CriticismEngineImpl(){}

    @Override
    public String getCriticism() {
        int i = (int) (Math.random() * criticismPool.length);
        return criticismPool[i];
    }

    private String[] criticismPool;
    public void setCriticismPool(String[] criticismPool){
        this.criticismPool = criticismPool;
    }
}

為CriticismEngineImpl注入list

    <bean id="criticismEngine" class="com.springinaction.perf.CriticismEngineImpl">
        <property name="criticismPool">
            <list>
                <value>Worst performance ever!</value>
                <value>I laughed, I cried, then I realized I was at the wrong show.</value>
                <value>A must see show!</value>
            </list>
        </property>
    </bean>

在展示如何實(shí)現(xiàn)注入之前,我們必須清楚AspectJ切面根本不需要Spring就可以織入到我們的應(yīng)用中背亥。如果想使用Spring的依賴注入秒际,那就需要把切面聲明為一個(gè)Spring配置中的<bean>。

   <bean class="com.springinaction.perf.CriticAspect" factory-method="aspectOf">
       <property name="criticismEngine" ref="criticismEngine"/>
   </bean>

通常情況下狡汉,Spring bean由Spring容器初始化娄徊,但是AspectJ切面AspectJ在運(yùn)行期創(chuàng)建的。等到Spring有機(jī)會(huì)為CriticAspect注入CriticismEngine時(shí)盾戴,CriticAspect已經(jīng)被實(shí)例化了寄锐。

因?yàn)镾pring不能負(fù)責(zé)創(chuàng)建CriticAspect,那就不能在Spring中簡單地把CriticAspect聲明為一個(gè)bean尖啡。相反橄仆,我們需要一種方式為Spring獲得已經(jīng)有AspectJ創(chuàng)建的CriticAspect實(shí)例的句柄,從而可以注入CriticismEngine衅斩。幸好盆顾,所有AspectJ切面都提供了一個(gè)靜態(tài)的aspectOf()方法,該方法返回切面的一個(gè)單例畏梆。所有為了獲得切面的實(shí)例您宪,我們必須使用factory-method來調(diào)用aspectOf()而不是調(diào)用CriticAspect的構(gòu)造器方法。

簡而言之奠涌,Spring不能像之前那樣使用<bean>聲明來創(chuàng)建一個(gè)CriticAspect實(shí)例-它已經(jīng)在運(yùn)行時(shí)有AspectJ創(chuàng)建完成了宪巨。Spring需要通過aspectOf()工廠方法獲得切面的引用,然后像<bean>元素規(guī)定的那樣在該對(duì)象上執(zhí)行依賴注入溜畅。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末捏卓,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子慈格,更是在濱河造成了極大的恐慌怠晴,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件浴捆,死亡現(xiàn)場(chǎng)離奇詭異蒜田,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)汤功,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來溜哮,“玉大人滔金,你說我怎么就攤上這事色解。” “怎么了餐茵?”我有些...
    開封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵科阎,是天一觀的道長。 經(jīng)常有香客問我忿族,道長锣笨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任道批,我火速辦了婚禮错英,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘隆豹。我一直安慰自己椭岩,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開白布璃赡。 她就那樣靜靜地躺著判哥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪碉考。 梳的紋絲不亂的頭發(fā)上塌计,一...
    開封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天,我揣著相機(jī)與錄音侯谁,去河邊找鬼锌仅。 笑死,一個(gè)胖子當(dāng)著我的面吹牛良蒸,可吹牛的內(nèi)容都是我干的技扼。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼嫩痰,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼剿吻!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起串纺,我...
    開封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤丽旅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后纺棺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體榄笙,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年祷蝌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了茅撞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖米丘,靈堂內(nèi)的尸體忽然破棺而出剑令,到底是詐尸還是另有隱情,我是刑警寧澤拄查,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布吁津,位于F島的核電站,受9級(jí)特大地震影響堕扶,放射性物質(zhì)發(fā)生泄漏碍脏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一稍算、第九天 我趴在偏房一處隱蔽的房頂上張望典尾。 院中可真熱鬧,春花似錦邪蛔、人聲如沸急黎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽勃教。三九已至,卻和暖如春匠抗,著一層夾襖步出監(jiān)牢的瞬間故源,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來泰國打工汞贸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留绳军,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓矢腻,卻偏偏與公主長得像门驾,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子多柑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

推薦閱讀更多精彩內(nèi)容

  • 本章內(nèi)容: 面向切面編程的基本原理 通過POJO創(chuàng)建切面 使用@AspectJ注解 為AspectJ切面注入依賴 ...
    謝隨安閱讀 3,158評(píng)論 0 9
  • 1.1 spring IoC容器和beans的簡介 Spring 框架的最核心基礎(chǔ)的功能是IoC(控制反轉(zhuǎn))容器奶是,...
    simoscode閱讀 6,721評(píng)論 2 22
  • 今天去城里做頭發(fā),認(rèn)識(shí)了一個(gè)小孩竣灌。 小男孩兒給我提了一個(gè)詞:文藝聂沙! 這個(gè)詞引起了我多少回憶!...
    冒牌文人閱讀 273評(píng)論 3 1
  • 第一章 先說說全棧初嘹。 硅谷推崇全棧及汉,一個(gè)人快速實(shí)現(xiàn),我沒去過硅谷屯烦,聽別人這么講的坷随。 于是我回來問暗滅大人:“暗滅大...
    xdyl閱讀 498評(píng)論 0 4