Spring參考手冊 4 面向切面編程

翻譯自Spring官方文檔 4.1.2版本

相關文章:

一、簡介

面向切面編程英文全稱:Aspect-Oriented Programming (AOP)是面向對象編程(OOP)的補充。具體概念就不贅述。

1.1 AOP概念

我們首先學習一些AOP的核心概念和專業(yè)術語。這些并不是Spring特有的,不幸的是AOP的術語不是特別直觀,如果Spring用它自己的術語會更讓人難以理解。

  • Aspect 切面。事務管理是一個很好的例子。在Spring里切面實現特殊類或者使用@Aspect注解的類。
  • Join point 在Spring AOP中join point代表一個方法的執(zhí)行。
  • Advice 特定Join point產生的通知。例如:"around","before","after",就像攔截器。
  • Pointcut 一種匹配Join point的描述。Advice將會作用于所有符合Pointcut描述的Join point
  • Introduction 聲明一些方法或者成員變量在某方面為一個類型。

Advice的類型:

  • Before advice:在一個Join point(以后都說方法)前執(zhí)行,但是它沒有能力阻止不執(zhí)行這個方法,除非它發(fā)生異常。
  • After returning advice:當一個方法正常完成后執(zhí)行。
  • After throwing advice:當一個方法拋出異常后執(zhí)行。
  • After (finally) advice:當一個方法完成后執(zhí)行,不管正常還是異常。
  • Around advice:在一個方法執(zhí)行前和執(zhí)行后調用。

二、@AspectJ支持

Spring的注解和AspectJ 5的一樣,但是沒有任何依賴AspectJ編譯器。

2.1 啟用@AspectJ支持

通過Java配置

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}

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

    <aop:aspectj-autoproxy />

2.2 聲明一個切面

首先創(chuàng)建一個類:

package org.xyz;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class NotVeryUsefulAspect {

}

將它聲明為一個bean:

<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
    <!-- configure properties of aspect here as normal -->
</bean>

如果配置了自動掃描發(fā)現bean的方式也可以這樣:

package org.xyz;
import org.aspectj.lang.annotation.Aspect;

@Aspect
@Component
public class NotVeryUsefulAspect {

}

切面可以有其他的方法和成員變量,和其他的類一樣,只是多了@Aspect注解。

2.3 聲明一個切點

Spring AOP只支持在Spring beans的方法執(zhí)行聲明一個切點。有一個切點的聲明包含兩部分:一個方法簽名和一個切點表達式,準確的匹配哪些方法是我們感興趣的。在@AspectJ注解風格的切面編程里,@Pointcut注解表示切點(這個注解相關的方法必須使用void返回類型)。

下面是一個例子:

@Pointcut("execution(* transfer(..))")// 切點表達式
private void anyOldTransfer() {}// 切點方法簽名

這個切點將會匹配所有名字為transfer的方法(當然是在Spring beans里)。完整的切點語法規(guī)則參見AspectJ Programming Guide

支持的切點標識符

切點標識符就是上面例子中:execution部分,下面是支持的列表:

  • execution 用來匹配方法執(zhí)行,這個將會是你在Spring AOP里主要用到的標識符
  • within 只匹配指定類型(不太好理解,后面有例子,就是根據包名匹配)
  • this 只匹配引用是給定類型的一個實例的方法(還是看后面的例子吧)
  • target 只匹配目標對象是給定類型的一個實例的方法
  • args 只匹配參數是給定類型實例的方法

@target@args@within@annotation這些都是根據注解來匹配的。

注意,Spring AOP是一個基于代理的框架,被保護的方法不會被攔截,所以上面聲明的切點只會對public方法起作用。

聯合切點表達式

可以使用&&||!這些符號來聯合切點表達式。還可以通過切點的名字來引用切點。請看下面的例子:

@Pointcut("execution(public * (..))")
private void anyPublicOperation() {}

@Pointcut("within(com.xyz.someapp.trading..)")
private void inTrading() {}

@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}

anyPublicOperation切點匹配所有public的方法。

inTrading切點匹配所有com.xyz.someapp.trading包下的方法。

tradingOperation切點是上面兩個切點的交集。

最佳實踐是用小的切點表達式來構成復雜的切點表達式,就像上面的例子。

一些通用的切點定義

在企業(yè)環(huán)境中,你會經常引用應用程序的各個模塊。我們推薦定義一個"系統架構"切面,捕獲通用的切點。一個典型的切面看起來像下面這個類:

package com.xyz.someapp;

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

@Aspect
public class SystemArchitecture {

    /
     * 一個web層的切點,匹配任何在com.xyz.someapp.web包下的方法
     */
    @Pointcut("within(com.xyz.someapp.web..)")
    public void inWebLayer() {}

    /
     * 一個service層的切點,匹配任何在com.xyz.someapp.service包下的方法
     */
    @Pointcut("within(com.xyz.someapp.service..)")
    public void inServiceLayer() {}

    /
     * 一個數據訪問層的切點,匹配任何在com.xyz.someapp.dao包下的方法
     */
    @Pointcut("within(com.xyz.someapp.dao..)")
    public void inDataAccessLayer() {}

    /
     * 匹配在service包下的方法,例如com.xyz.someapp.user.service.addUser()這個方法
     */
    @Pointcut("execution( com.xyz.someapp..service..(..))")
    public void businessService() {}

    /*
     * 匹配在dao包下的方法
     */
    @Pointcut("execution( com.xyz.someapp.dao..(..))")
    public void dataAccessOperation() {}

}

2.4 一些列子

Spring AOP的用戶可能和切點表達式交往最頻繁。一個方法執(zhí)行表達式的格式為:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? 
    name-pattern(param-pattern) throws-pattern?)

?表示是可選的。

  • modifiers-pattern? 修飾符部分
  • ret-type-pattern 返回類型部分
  • declaring-type-pattern? 聲明部分
  • name-pattern 名稱部分
  • param-pattern 參數部分
  • throws-pattern? 異常拋出部分

*表示匹配某任意部分。(..)表示任意數量的參數。(*,String)表示匹配參數數量是2,第一個參數是任意類型,第二個參數必須是String的方法。

下面是一些常見的切點表達式:

  • 任意公開方法的執(zhí)行
execution(public * *(..))
  • 以set開頭的任意方法
execution(* set*(..))
  • 定義在service包下的任意方法
execution(* com.xyz.service..(..))
  • 定義在service包以及子包下的任意方法
execution(* com.xyz.service...(..))
  • service包里的任意切點 (method execution only in Spring AOP)
within(com.xyz.service.*)
  • 一個參數是Serializable的任意切點 (method execution only in Spring AOP)
args(java.io.Serializable)
  • 切點匹配的方法上有@Transactional的注解
@annotation(org.springframework.transaction.annotation.Transactional)

譯者注:很多我也沒實踐過,以后實踐了再加些更通俗的解釋吧。

2.5 聲明通知

通知(Advice)是和切點表達式關聯的。在切點表達式匹配的方法之前、之后或者前后執(zhí)行。

前置通知

前置通知在一個切面里使用@Before注解:

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

@Aspect
public class BeforeExample {

    @Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }

}

com.xyz.myapp.SystemArchitecture是上面的一個例子:

@Pointcut("execution( com.xyz.someapp.dao..(..))")
public void dataAccessOperation() {}

或者直接用切點的表達式:

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

@Aspect
public class BeforeExample {

    @Before("execution(* com.xyz.myapp.dao..(..))")
    public void doAccessCheck() {
        // ...
    }

}

方法返回后通知

如其字面意思,當一個匹配的方法執(zhí)行并正常返回后執(zhí)行。它使用@AfterReturning注解聲明:

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

@Aspect
public class AfterReturningExample {

    @AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }

}

注意:通知和切點完全可以寫在一個切面里。

有時你需要訪問方法返回的值。你可以使用下面這種形式綁定返回值:

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

@Aspect
public class AfterReturningExample {

    @AfterReturning(
        pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
        returning="retVal")
    public void doAccessCheck(Object retVal) {
        // ...
    }

}

returning屬性的名字必須與通知方法的參數名字保持一致。returning項目還會約束方法返回值的類型(Object在這里將會匹配任何返回值,如果是String,將只會匹配String類型的返回值)

方法拋出異常后通知

當切點匹配的方法拋出異常后執(zhí)行的通知。使用@AfterThrowing注解:

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

@Aspect
public class AfterThrowingExample {

    @AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doRecoveryActions() {
        // ...
    }

}

如果想只獲得指定類型的異常,可以使用throwing屬性,和上面的returning類似:

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

@Aspect
public class AfterThrowingExample {

    @AfterThrowing(
        pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
        throwing="ex")
    public void doRecoveryActions(DataAccessException ex) {
        // ...
    }

}

后置通知

只要是一個切點匹配的方法退出了就會執(zhí)行方法后通知,不管是否成功返回。它使用@After注解。你必須同時考慮到正常和異常的情況。一個典型的應用就是釋放資源等:

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

@Aspect
public class AfterFinallyExample {

    @After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doReleaseLock() {
        // ...
    }

}

環(huán)繞通知

環(huán)繞通知經常被用在需要在一個線程安全的方法執(zhí)行前后共享狀態(tài)。總是使用能滿足你需求的能力最弱的切面(例如,如果前置通知就可以完成的不要使用環(huán)繞通知)。

環(huán)繞通知使用@Around注解來聲明。通知方法的第一個參數必須是ProceedingJoinPoint類型。在通知方法體內調用ProceedingJoinPointproceed()方法會執(zhí)行切點所匹配的方法。proceed()方法也可以接收一個Object[]類型的參數給切點所匹配的方法。

譯者注:proceed()方法之前相當于前置通知處理的范圍,proceed()方法之后相當于后置通知處理的范圍。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;

@Aspect
public class AroundExample {

    @Around("com.xyz.myapp.SystemArchitecture.businessService()")
    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
        // retVal是方法的返回值
        Object retVal = pjp.proceed();

        return retVal;
    }

}

訪問當前的切點

任何通知的方法都可以聲明一個org.aspectj.lang.JoinPoint類型的參數作為它的第一個參數(環(huán)繞通知中的ProceedingJoinPointJoinPoint的子類,所以可以作為環(huán)繞通知的第一個參數)。JoinPoint接口提供了很多實用的方法例如:

  • getArgs() 返回切點匹配方法的參數
  • getThis() 返回代理對象
  • getTarget() 返回目標對象
  • getSignature() 返回通知關聯的切點方法的簽名

未完,待續(xù)...

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,362評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 99,577評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,486評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,852評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,600評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,944評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,944評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,108評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 49,652評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,385評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,616評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,111評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,798評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,205評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,537評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,334評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,570評論 2 379

推薦閱讀更多精彩內容

  • 本章內容: 面向切面編程的基本原理 通過POJO創(chuàng)建切面 使用@AspectJ注解 為AspectJ切面注入依賴 ...
    謝隨安閱讀 3,180評論 0 9
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發(fā)現,斷路器,智...
    卡卡羅2017閱讀 134,828評論 18 139
  • 在生活中,監(jiān)控用電量是一個很重要的功能,但并不是大多數家庭重點關注的問題。軟件系統的一些功能就像家里的電表一樣,這...
    yjaal閱讀 596評論 0 3
  • 你會那樣嗎?刻意讓自己不要變得太深沉,因為你知道越深奧的地方越是人少,想到害怕孤獨,不想讓自己讀太多書,去太多地方...
    柯柯醬koo閱讀 254評論 0 0
  • 《源泉》25周年再版序言 《源泉》一書25年來連續(xù)再版,很多人詢問我對此有何感受。除了藏在心底的滿足感之外,還能有...
    溪水旁閱讀 1,175評論 0 1