spring-core-5 AOP

5.1 介紹

AOP(Aspect-Oriented Progerammint) 是對OOP編程的一種補充. OOP編程的關(guān)鍵單元是類(Class), 而AOP編程的關(guān)鍵單元是切面(aspect),方面支持關(guān)注點模塊化,如橫跨多個類和對象的事務(wù)管理, 這些關(guān)注點在AOP中常被稱為橫切關(guān)注點.
AOP框架是Spring的一個關(guān)鍵組件,雖然Spring IoC容器不依賴AOP铣口,這意味著如果您不想使用AOP,就不需要使用AOP,但是AOP補充了Spring IoC,提供了一個非常有用的中間件解決方案。

在spring 2.0中引入了基于xml或@AspectJ注解的方式使用AOP

AOP在spring框架中的使用:

  • 提供聲明性企業(yè)服務(wù)振惰,特別是作為EJB聲明性服務(wù)的替代。最重要的服務(wù)是聲明性事務(wù)管理
  • 允許用戶實現(xiàn)自定義方面凿叠,對OOP進行補充。
5.1.1 AOP概念
  • Aspect: 切面, 即跨多個類的關(guān)注點模塊嚼吞。Java企業(yè)應用程序中的事務(wù)管理就是一個很好的例子盒件。在Spring AOP中,Aspect是使用常規(guī)類(基于xml的方法)或使用@Aspect注釋的常規(guī)類(@AspectJ樣式)實現(xiàn)的舱禽。
  • Join point: 連接點, 即程序執(zhí)行過程中的一個點炒刁,如方法的執(zhí)行或異常的處理, 在Spring AOP中,連接點總是用一個方法表示誊稚。
  • Advice: 通知, 切面在特定的連接點上執(zhí)行的操作.分為around, before, after, 許多AOP框架翔始,包括Spring,都將通知建模為攔截器里伯,維護一個圍繞連接點的攔截器鏈城瞎。
  • Pointcut: 切入點, 即匹配連接點的定義.通知與切入點表達式相關(guān)聯(lián),并在與切入點匹配的任何連接點上運行, 連接點與切入點表達式匹配的概念是AOP的核心疾瓮,Spring默認使用AspectJ切入點表達式語言脖镀。
  • Introduction: 引入, 為一個類添加新的方法或字段, Spring AOP允許您向任何被通知的對象引入新的接口(以及相應的實現(xiàn)), 例如,可以通過引入使bean實現(xiàn)IsModified接口狼电,以簡化緩存.
  • Target object: 目標對象, 被一個或多個切面通知的對象, 也稱為被通知對象认然。由于Spring AOP是使用運行時代理實現(xiàn)的补憾,所以這個對象將始終是代理對象。
  • AOP proxy: AOP代理, AOP框架為了實現(xiàn)方面契約(建議方法執(zhí)行等等)而創(chuàng)建的一個對象卷员,在Spring框架中盈匾,AOP代理為JDK動態(tài)代理或CGLIB代理。
  • Weaving: 織入, 即將切面與其他應用程序類型或?qū)ο箧溄颖下猓詣?chuàng)建通知的對象. 這可以在編譯時(例如削饵,使用AspectJ編譯器)、加載時或運行時完成未巫。與其他純Java AOP框架一樣窿撬,Spring AOP在運行時完成。

通知的類型:

  • Before advice: 在連接點之前執(zhí)行的通知叙凡,但不能阻止執(zhí)行流繼續(xù)到連接點(除非拋出異常)劈伴。
  • After (finally) advice: 無論連接點以何種方式退出,都會執(zhí)行的通知.
  • After throwing advice: 如果方法拋出異常,則要執(zhí)行的通知握爷。
  • After returning advice: 在連接點正常完成后執(zhí)行的通知
  • Around advice: 圍繞連接點的通知跛璧。Around通知可以在連接點調(diào)用前后執(zhí)行自定義行為。它還負責選擇是繼續(xù)到連接點新啼,還是通過返回它自己的返回值或拋出異常來簡化通知的方法執(zhí)行追城。
    環(huán)繞通知是功能最強的通知, 在AOP以及其他AOP框架中都提供了完整的通知類型, 但建議在使用通知時盡可能使用功能最弱的通知, 例如,如果只需要用方法的返回值更新緩存燥撞,那么最好實現(xiàn)after return通知座柱,而不是around通知,盡管around通知可以完成相同的任務(wù)物舒。使用最合適的通知類型可以提供更簡單的編程模型色洞,出錯的可能性更小。
5.1.2 Spring AOP的功能和目標

Spring AOP是用純Java實現(xiàn)的冠胯。Spring AOP目前只支持方法作為連接點(建議在Spring bean上執(zhí)行方法). 沒有實現(xiàn)字段攔截锋玲,如果需要建議字段訪問和更新連接點,請考慮AspectJ之類的語言涵叮。

5.1.3 AOP代理

AOP默認使用標準JDK動態(tài)代理。這允許代理任何接口(或一組接口)伞插。
Spring AOP還可以使用CGLIB代理割粮。這對于代理類是必要的, 如果業(yè)務(wù)對象沒有實現(xiàn)接口,則默認使用CGLIB媚污。

5.2 @AspectJ支持

@AspectJ是一個將常規(guī)JAVA類聲明為切面的注解. @AspectJ樣式是AspectJ項目作為AspectJ 5發(fā)行版的一部分引入的舀瓢。Spring使用了與AspectJ 5相同的注解樣式, 但是Spring AOP運行時仍然是純Spring AOP,并且不依賴于AspectJ編譯器或編織器耗美。

5.2.1 開啟@AspectJ支持

可以通過XML或Java樣式配置啟用@AspectJ支持京髓。在這兩種情況下航缀,還需要確保AspectJ的aspectjweaver.jar庫位于應用程序的類路徑上(版本1.8或更高).
java方式開啟:

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}

xml方式開啟:

<aop:aspectj-autoproxy/>
5.2.2 聲明切面

啟用@AspectJ支持后,在應用程序上下文中使用@AspectJ方面(具有@Aspect注釋)類定義的任何bean都將被Spring自動檢測到堰怨,并用于配置Spring AOP芥玉。
示例:
應用程序上下文中的常規(guī)bean定義,指向具有@Aspect注釋的bean類

<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
    <!-- configure properties of aspect here as normal -->
</bean>
// 
package org.xyz;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class NotVeryUsefulAspect {

}

切面也可以通過組件掃描自動發(fā)現(xiàn), 由于組件掃描不能自動發(fā)現(xiàn)@Aspect注解, 因此要加上@Component注解.
在Spring AOP中备图,不可能讓方面本身成為來自其他方面的通知的目標灿巧。類上的@Aspect注釋將其標記為一個方面,spring會將其排除在自動代理之外揽涮。

5.2.3 聲明切入點

Spring AOP只支持Spring bean的方法執(zhí)行連接點抠藕,所以可以將切入點看作是匹配Spring bean上方法的執(zhí)行。切入點聲明有兩部分:一個包含名稱和任何參數(shù)的簽名蒋困,以及一個確定我們對哪個方法執(zhí)行感興趣的切入點表達式盾似。在AOP的@AspectJ注釋風格中,切入點簽名由一個正則方法定義提供雪标,切入點表達式使用@Pointcut注釋表示(作為切入點簽名的方法必須有一個void返回類型)零院。
下面的例子定義了一個名為“anyOldTransfer”的切入點,它將匹配任何名為“transfer”的方法的執(zhí)行:

@Pointcut("execution(* transfer(..))")// the pointcut expression
private void anyOldTransfer() {}// the pointcut signature

切入點標識符
Spring AOP支持在切入點表達式中使用的以下AspectJ切入點標識符(PCD):

  • execution: 匹配方法執(zhí)行連接點.
  • within: 將匹配限制為特定類型中的連接點(當使用Spring AOP時汰聋,只需執(zhí)行在匹配類型中聲明的方法)
  • this: 將匹配限制為連接點(使用Spring AOP時方法的執(zhí)行)门粪,其中bean引用(Spring AOP代理)是給定類型的實例
  • target: 將匹配限制為連接點(使用Spring AOP時方法的執(zhí)行),其中目標對象(代理的應用程序?qū)ο?是給定類型的實例
  • args: 將匹配限制為連接點(使用Spring AOP時方法的執(zhí)行)烹困,其中的參數(shù)是給定類型的實例
  • @target: 將匹配限制為連接點(使用Spring AOP時方法的執(zhí)行)玄妈,其中執(zhí)行對象的類具有給定類型的注釋
  • @args: 將匹配限制為連接點(使用Spring AOP時方法的執(zhí)行),其中傳遞的實際參數(shù)的運行時類型具有給定類型的注釋
  • @within: 限制對具有給定注釋的類型中的連接點的匹配(使用Spring AOP時髓梅,使用給定注釋在類型中聲明的方法的執(zhí)行)
  • @annotation: 將匹配限制為連接點的主題(在Spring AOP中執(zhí)行的方法)具有給定注釋的連接點

Spring AOP還支持一個名為bean的PCD, 這允許您將連接點的匹配限制為特定名稱的一個或一組bean(使用通配符時)拟蜻。其定義格式為:

bean(idOrNameOfBean)

idOrNameOfBean可以是任何spring定義的bean. 通配符*支持有限的通配,還可以使用&&, ||, !.

請注意,bean PCD只在Spring AOP中受支持枯饿,而在AspectJ編織中不受支持.
bean PCD在實例級(基于Spring bean名稱概念)而不是僅在類型級進行操作酝锅。

組合切點表達式
切入點表達式可以使用“&&”、“||”和“!”組合奢方。還可以通過名稱引用切入點表達式搔扁。下面的例子顯示了三個切入點表達式:anyPublicOperation(如果方法執(zhí)行連接點表示任何公共方法的執(zhí)行,則匹配該表達式);inTrading(如果方法執(zhí)行在交易模塊中蟋字,它就匹配)和tradingOperation(如果方法執(zhí)行代表交易模塊中的任何公共方法稿蹲,它就匹配)。

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

@Pointcut("within(com.xyz.someapp.trading..*)")
private void inTrading() {}
// 通過名稱引入
@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}

最好的實踐是用上面所示的較小的命名組件構(gòu)建更復雜的切入點表達式鹊奖。當按名稱引用切入點時苛聘,應用普通的Java可見性規(guī)則(您可以看到相同類型的私有切入點、層次結(jié)構(gòu)中的受保護切入點、任何地方的公共切入點设哗,等等)唱捣。可見性不影響切入點匹配网梢。

定義共享的公共切入點
在處理企業(yè)應用程序時震缭,您通常希望從幾個方面引用應用程序的模塊和特定的操作集。我們建議定義一個“SystemArchitecture”方面澎粟,它捕獲用于此目的的公共切入點表達式.

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() {}

    /**
     * dao層接連點, 在com.xyz.someapp.dao包及其子包中定義
     */
    @Pointcut("within(com.xyz.someapp.dao..*)")
    public void inDataAccessLayer() {}

    /**
     *
     * 如果按功能區(qū)域?qū)ervice 接口進行分組 (如在com.xyz.someapp.abc.service and com.xyz.someapp.def.service) 
     * 那么這個切點表達式為"execution(* com.xyz.someapp..service.*.*(..))"
     * 你也可以用bean PCD來定義表達式,即"bean(*Service)".此處的前提是你的service bean的命名方式一致.
     */
    @Pointcut("execution(* com.xyz.someapp..service.*.*(..))")
    public void businessService() {}

    /**
     * 假設(shè)這個dao接口定義在 "dao" 包中, 它的實現(xiàn)類定義在其子包中
     */
    @Pointcut("execution(* com.xyz.someapp.dao.*.*(..))")
    public void dataAccessOperation() {}

}

在這樣一個方面中定義的切入點可以在任何需要切入點表達式的地方引用蛀序。例如,要使服務(wù)層具有事務(wù)性:

<aop:config>
    <aop:advisor
        pointcut="com.xyz.someapp.SystemArchitecture.businessService()"
        advice-ref="tx-advice"/>
</aop:config>

<tx:advice id="tx-advice">
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

示例
Spring AOP用戶可能最經(jīng)常使用execution 切入點標識符活烙。其格式如下:

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

除了ret-type-pattern, name-pattern, param-pattern之外, 其他都是可選的.
ret-type確定了匹配的連接點方法的返回類型.最常見的情況是使用作為返回類型模式徐裸,它匹配任何返回類型。只有當方法返回給定類型時啸盏,才會匹配完全限定類型名稱重贺。
name表示匹配的方法名稱, 可以使用
通配符作為名稱的全部或部分。
param表示參數(shù), ()表示無參, (..)匹配任何參數(shù)(0個或多個). (*)匹配一個參數(shù)(類型不限), (*, String)匹配兩個參數(shù), 第一個可以是任何類型, 第二個必須 是String.

常見切入點表達式的例子:
執(zhí)行任何公共方法:

execution(public * *(..))

執(zhí)行任何以set開頭的方法:

execution(* set*(..))

執(zhí)行在AccountService接口中定義的任何方法:

execution(* com.xyz.service.AccountService.*(..))

執(zhí)行定義在service包中的任何方法:

execution(* com.xyz.service.*.*(..))

執(zhí)行定義在service包及其子包中的任何方法:

execution(* com.xyz.service..*.*(..))

service包中的任何連接點:

within(com.xyz.service.*)

service包及其子包中的任何連接點:

within(com.xyz.service..*)

代理實現(xiàn)AccountService接口的任何連接點(僅在Spring AOP中執(zhí)行方法):

this(com.xyz.service.AccountService)

AccountService接口實現(xiàn)中定義的任何連接點(僅在Spring AOP中執(zhí)行方法):

target(com.xyz.service.AccountService)

只含有一個可序列化的參數(shù)的連接點:

args(java.io.Serializable)

這與execution(* *(java.io.Serializable))不同, args版本是如果在運行時傳遞的參數(shù)是可序列化的,則匹配. 后者是如果方法簽名聲明一個Serializable類型的參數(shù)則匹配.

目標對象上聲明了@Transactional注解的連接點:

@target(org.springframework.transaction.annotation.Transactional)

目標對象的聲明類型具有@Transactional注釋的任何連接點:

@within(org.springframework.transaction.annotation.Transactional)

任何連接點(只在Spring AOP中執(zhí)行方法)回懦,其中執(zhí)行方法具有@Transactional注釋:

@annotation(org.springframework.transaction.annotation.Transactional)

任何接受單個參數(shù)的連接點(僅在Spring AOP中執(zhí)行方法)气笙,其中傳遞的參數(shù)的運行時類型有@ classification注釋:

@args(com.xyz.security.Classified)

在名為tradeService的Spring bean中定義的任何連接點(僅在Spring AOP中執(zhí)行方法):

bean(tradeService)

在bean名稱是以Service結(jié)尾的bean中定義的連接點:

bean(*Service)

編寫好的切入點
在編譯期間,AspectJ處理切入點時會嘗試優(yōu)化匹配性能怯晕。檢查代碼并確定每個連接點是否匹配(靜態(tài)或動態(tài))給定的切入點是一個代價高昂的過程潜圃。在第一次遇到切入點聲明時,AspectJ將把它重寫為匹配過程的最佳形式舟茶√菲冢基本上切入點是用DNF(Disjunctive Normal Form)重寫的,切入點的組件被排序吧凉,以便首先檢查那些計算成本更低的組件隧出。這意味著您不必擔心理解各種切入點設(shè)計器的性能,并且可以在切入點聲明中以任意順序提供它們阀捅。
然而胀瞪,AspectJ只能處理它被告知的內(nèi)容,為了獲得最佳匹配性能饲鄙,應該考慮它們試圖實現(xiàn)什么凄诞,并在定義中盡可能縮小匹配的搜索空間。現(xiàn)有的標識符可以分為三類:kinded忍级、scoping和context:

  • kinded: 表示選擇一種特定類型的連接點.如execution, get, set, call, handler.
  • scoping: 選擇一組感興趣的連接點(可能有多種).如within, withincode.
  • context; 根據(jù)context來匹配的.如this, target, @annotation.
    一個編寫良好的切入點應該嘗試至少包含前兩種類型(kinded和scoping)帆谍,而如果希望基于連接點上下文進行匹配,或者綁定上下文以便在通知中使用颤练,則可以包含上下文指示符。提供一個kinded類型的指示符或context類的標識符都可以,但是由于所有額外的處理和分析嗦玖,可能會影響編織性能(使用的時間和內(nèi)存)患雇。scoping類的標識符匹配起來非常快宇挫,而且它們的使用意味著AspectJ可以非晨林ǎ快地取消不應該進一步處理的連接點組——這就是為什么一個好的切入點應該總是包含一個連接點(如果可能的話)。
5.2.4 聲明通知

通知與切入點表達式相關(guān)聯(lián)器瘪,并在切入點匹配的方法執(zhí)行之前翠储、之后或前后運行. 切入點表達式可以是對指定切入點的簡單引用,也可以是在適當位置聲明的切入點表達式橡疼。

Before通知
Before通知在切面中使用@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() {
        // ...
    }

}

我們可以將上面的例子重寫為:

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() {
        // ...
    }

}

后置返回通知
使用@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() {
        // ...
    }

}

有時候援所,您需要在advice主體中訪問返回的實際值。您可以在@Afterreturn中綁定這個返回值:

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屬性中使用的名稱必須與advice方法中的參數(shù)名稱對應欣除。returning子句還將匹配限制為只匹配那些返回指定類型值的方法執(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屬性來限制匹配(如果需要滔岳,使用Throwable作為異常類型),并將拋出的異常綁定到通知參數(shù)挽牢。

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) {
        // ...
    }

}

throwing屬性中使用的名稱必須與advice方法中的參數(shù)名稱相對應谱煤。throwing子句還限制只匹配那些拋出指定類型異常的方法執(zhí)行(本例中為DataAccessException)。

后置通知
使用@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)繞通知
它在連接點方法執(zhí)行之前和之后執(zhí)行禽拔,并確定何時刘离、如何、甚至是否真正執(zhí)行連接點方法奏赘。如果需要以線程安全的方式(例如啟動和停止計時器)共享方法執(zhí)行前后的狀態(tài)寥闪,通常會使用Around建議。
使用@Around聲明.advice方法的第一個參數(shù)必須是ProceedingJoinpoint類型,
在通知的方法體中磨淌,調(diào)用對ProceedingJoinpoint的proceed()會執(zhí)行連接點方法疲憋。

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 {
        // start stopwatch
        Object retVal = pjp.proceed();
        // stop stopwatch
        return retVal;
    }

}

around通知的返回值將是方法調(diào)用者得到的返回值, 請注意,proceed可以被調(diào)用一次梁只、多次缚柳,或者根本不在around建議的主體中調(diào)用.

通知參數(shù)
任何advice方法都可以聲明一個org.aspectj.lang.JoinPoint類型的參數(shù)為它的第一個參數(shù),請注意搪锣,around通知的第一個參數(shù)要求是proceedingJoinpoint類型秋忙,它是JoinPoint的子類。JoinPoint接口提供了許多有用的方法构舟,比如getArgs()(返回方法參數(shù))灰追、getThis()(返回代理對象)、getTarget()(返回目標對象)、getSignature()(返回被建議的方法的簽名)和toString()(打印被建議的方法的有用描述)弹澎。

給通知傳遞參數(shù)
要使參數(shù)值對通知主體可用朴下,可以使用args的綁定形式.

@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
public void validateAccount(Account account) {
    // ...
}

切入點表達式的args(account,..)部分有兩個目的:首先苦蒿,通知方法至少接受一個參數(shù)殴胧,并且傳遞給該參數(shù)的參數(shù)是Account類型的一個實例;其次,它通過Account參數(shù)使實際的Account對象對通知可用佩迟。
另一種編寫方法是聲明一個切入點团滥,該切入點在匹配連接點時“提供”Account對象值,然后僅引用通知中的指定切入點名稱报强。

@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
private void accountDataAccessOperation(Account account) {}

@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
    // ...
}

代理對象(this)灸姊、目標對象(target)和注釋(@within、@target躺涝、@annotation厨钻、@args)都可以以類似的方式綁定。下面的示例展示了如何匹配使用@Auditable注釋注釋的方法的執(zhí)行坚嗜。
首先定義@Auditable注釋:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {
    AuditCode value();
}

然后匹配執(zhí)行@Auditable方法的通知:

@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
public void audit(Auditable auditable) {
    AuditCode code = auditable.value();
    // ...
}

通知參數(shù)和泛型
假設(shè)您有這樣一個泛型類型:

public interface Sample<T> {
    void sampleGenericMethod(T param);
    void sampleGenericCollectionMethod(Collection<T> param);
}

您可以將方法類型的攔截限制為特定的參數(shù)類型夯膀,只需將advice參數(shù)鍵入要攔截方法的參數(shù)類型即可:

@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {
    // Advice implementation
}

但是,值得指出的是苍蔬,這不適用于泛型集合诱建。所以你不能像這樣定義切入點:

@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
public void beforeSampleMethod(Collection<MyType> param) {
    // Advice implementation
}

為了實現(xiàn)這一點,我們必須檢查集合的每個元素碟绑,這是不合理的俺猿,因為我們也不能決定如何處理空值。要實現(xiàn)類似的功能格仲,必須將參數(shù)鍵入Collection<?>并手動檢查元素的類型押袍。

確定參數(shù)名稱
Spring AOP的參數(shù)名稱是不能通過反射來獲取的,而是通過切點表達式中聲明的參數(shù)與切點方法的參數(shù)名稱來匹配的, 因此spring定義了以下規(guī)則:

  • 在切點表達式中通過argNames屬性顯式的指定.
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
        argNames="bean,auditable")
public void audit(Object bean, Auditable auditable) {
    AuditCode code = auditable.value();
    // ... use code and bean
}

如果方法的第一個參數(shù)是JoinPoint, ProceedingJoinPoint, JoinPoint.StaticPart類型, 則可省略這個參數(shù)的參數(shù)名稱(如果只有一個且是以上類型的參數(shù), 則可省略argNames屬性).

@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
        argNames="bean,auditable")
public void audit(JoinPoint jp, Object bean, Auditable auditable) {
    AuditCode code = auditable.value();
    // ... use code, bean, and jp
}
  • 不指定參數(shù)名稱, 可以用debug模式進行編譯, spring aop將根據(jù)debug模式下的本地變量來確定參數(shù).
  • 如果在編譯時沒有包含必需的調(diào)試信息, spring aop也會推斷參數(shù)的綁定信息(如只有一個參數(shù)時,那么參數(shù)的綁定是很明確的), 如果無法確定參數(shù), 則會拋出AmbiguousBindingException.
  • 如果以上策略都失敗了, 則會拋出IllegalArgumentException.
  • 通過execution()表達式.

通知的執(zhí)行順序
當在一個連接點方法上執(zhí)行多個通知時會發(fā)生什么呢? spring遵循與AspectJ相同的通知執(zhí)行優(yōu)先級規(guī)則, 對于進入通知(如before通知), 最高優(yōu)先級的通知最先執(zhí)行,對于退出通知(如after通知), 優(yōu)先級最高的最后執(zhí)行.
當兩個在不同切面定義的通知運行在同一個連接點上, 如果沒有指定, 則其運行順序是不確定的. 可以通過實現(xiàn)Ordered接口或@Order注解來定義順序,較小的值具有高的優(yōu)先級.
在同一個切面中定義的兩個通知運行在同一個連接點上,其執(zhí)行順序也是未定義的,因為無法通過反射來確定注解的編譯順序,此時考慮分開定義并指定其執(zhí)行順序.

5.4.5 引入

引入允許一個切面聲明某個通知對象實現(xiàn)一個指定的接口并代表這個通知對象實現(xiàn)這個接口.

@DeclareParents
這個注解聲明了被注解的類具有一個新的父類,例如有一個UsageTracked接口, 其有一個實現(xiàn)DefaultUsageTracked, 則下面的切面聲明了所有實現(xiàn)了service的接口也都實現(xiàn)UsageTracked接口.

@Aspect
public class UsageTracking {

    @DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class)
    public static UsageTracked mixin;

    @Before("com.xyz.myapp.SystemArchitecture.businessService() && this(usageTracked)")
    public void recordUsage(UsageTracked usageTracked) {
        usageTracked.incrementUseCount();
    }

}

@DeclareParents注解的value屬性是一個AspectJ類型的表達式, 任何與之匹配的bean都將實現(xiàn)UsageTracked接口, defaultImpl屬性指定了實現(xiàn)類.

5.4.6 切面實例化

默認情況下, 切面實例在容器中是單例的, AspectJ調(diào)用這些單例模塊. 但是也可以定義可選生命周期的切面.通過prethispertarget指定.

@Aspect("perthis(com.xyz.myapp.SystemArchitecture.businessService())")
public class MyAspect {

    private int someState;

    @Before(com.xyz.myapp.SystemArchitecture.businessService())
    public void recordServiceUsage() {
        // ...
    }

}

在上面的例子中, perthis的作用是為每個執(zhí)行businessService的唯一service對象創(chuàng)建一個切面實例.(這個唯一對象將會通過連接點上的切點表達式綁定this).切面實例將在第一次調(diào)用這個方法時被創(chuàng)建.

5.4.7 示例

在并發(fā)條件下, 有些業(yè)務(wù)操作可能會執(zhí)行失敗, 如果再次重新執(zhí)行則可能成功, 此時我們可以通過around通知來操作.

@Aspect
public class ConcurrentOperationExecutor implements Ordered {

    private static final int DEFAULT_MAX_RETRIES = 2;

    private int maxRetries = DEFAULT_MAX_RETRIES;
    private int order = 1;

    public void setMaxRetries(int maxRetries) {
        this.maxRetries = maxRetries;
    }

    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    @Around("com.xyz.myapp.SystemArchitecture.businessService()")
    public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
        int numAttempts = 0;
        PessimisticLockingFailureException lockFailureException;
        do {
            numAttempts++;
            try {
                return pjp.proceed();
            }
            catch(PessimisticLockingFailureException ex) {
                lockFailureException = ex;
            }
        } while(numAttempts <= this.maxRetries);
        throw lockFailureException;
    }

}

由于它實現(xiàn)了Ordered接口, 因此可以切面的優(yōu)先級高于事務(wù)通知的優(yōu)先級(每次重試都需要一個新的事務(wù)).
xml配置如下:

<aop:aspectj-autoproxy/>

<bean id="concurrentOperationExecutor" class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor">
    <property name="maxRetries" value="3"/>
    <property name="order" value="100"/>
</bean>

如果只是進行重試冪等操作, 我們可以定義Idempotent注解:

@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    // marker annotation
}

可以使用這個注解杰注釋service操作的實現(xiàn).

@Around("com.xyz.myapp.SystemArchitecture.businessService() && " +
        "@annotation(com.xyz.myapp.service.Idempotent)")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
    ...
}
5.5 基于XML的AOP支持

在xml配置中, 要提供aop命名空間.要引入spring-aop schema.

在xml配置中, 所有的aspectadvisor元素都必須包含在<aop:config/>元素中.

<aop:config>配置大量的使用了spring的自動代理機制, 如果你通過BeanNameAutoProxy或類似的方式顯式的指定了自動代理, 這可能會引發(fā)一些問題.因此永遠不要混合使用它們.

5.5.1聲明切面

切面是一個被聲明為bean的常規(guī)的java對象.
通過<aop:aspect>元素聲明一個切面并通過其ref屬性引用一個bean.

<aop:config>
    <aop:aspect id="myAspect" ref="aBean">
        ...
    </aop:aspect>
</aop:config>

<bean id="aBean" class="...">
    ...
</bean>
5.5.2 聲明切入點<aop:pointcut>

可以在<aop:config>元素中聲明一個切入點, 使其可以在多個切面中共享.

<aop:config>

    <aop:pointcut id="businessService"
        expression="execution(* com.xyz.myapp.service.*.*(..))"/>

</aop:config>

或者:

<aop:config>

    <aop:pointcut id="businessService"
        expression="com.xyz.myapp.SystemArchitecture.businessService()"/>

</aop:config>

在切面中聲明切入點:

<aop:config>

    <aop:aspect id="myAspect" ref="aBean">

        <aop:pointcut id="businessService"
            expression="execution(* com.xyz.myapp.service.*.*(..))"/>

        ...

    </aop:aspect>

</aop:config>

@AspectJ切面類似, 使用xml配置也可以包含切入點上下文,

<aop:config>

    <aop:aspect id="myAspect" ref="aBean">

        <aop:pointcut id="businessService"
            expression="execution(* com.xyz.myapp.service.*.*(..)) &amp;&amp; this(service)"/>

        <aop:before pointcut-ref="businessService" method="monitor"/>

        ...

    </aop:aspect>

</aop:config>
public void monitor(Object service) {
    ...
}

在xml配置中, 在切點子表達式中定義&&, ||, !可以使用and, or, not替換.

<aop:config>

    <aop:aspect id="myAspect" ref="aBean">

        <aop:pointcut id="businessService"
            expression="execution(* com.xyz.myapp.service.*.*(..)) and this(service)"/>

        <aop:before pointcut-ref="businessService" method="monitor"/>

        ...
    </aop:aspect>
</aop:config>

注意,以這種方式定義的切入點由它們的XML id引用凯肋,不能作為命名切入點來使用谊惭,以形成復合切入點。

5.5.3 聲明通知

Before

<aop:aspect id="beforeExample" ref="aBean">

    <aop:before
        pointcut-ref="dataAccessOperation"
        method="doAccessCheck"/>

    ...

</aop:aspect>

此處, dataAccessOperation是在<aop:cofig>元素中定義的一個切入點的id. 也可以通過pointcut`屬性創(chuàng)建內(nèi)聯(lián)切入點:

<aop:aspect id="beforeExample" ref="aBean">

    <aop:before
        pointcut="execution(* com.xyz.myapp.dao.*.*(..))"
        method="doAccessCheck"/>

    ...

</aop:aspect>

使用切入點名稱可以提高代碼的可讀性.
method屬性指定了通知中的一個方法.

After Returning

<aop:aspect id="afterReturningExample" ref="aBean">

    <aop:after-returning
        pointcut-ref="dataAccessOperation"
        returning="retVal" //定義返回值參數(shù)名稱
        method="doAccessCheck"/>

    ...

</aop:aspect>

After Throwing

<aop:aspect id="afterThrowingExample" ref="aBean">

    <aop:after-throwing
        pointcut-ref="dataAccessOperation"
        throwing="dataAccessEx"  //定義具體的異常類型
        method="doRecoveryActions"/>

    ...

</aop:aspect>

After Finally

<aop:aspect id="afterFinallyExample" ref="aBean">

    <aop:after
        pointcut-ref="dataAccessOperation"
        method="doReleaseLock"/>

    ...

</aop:aspect>

Around

<aop:aspect id="aroundExample" ref="aBean">

    <aop:around
        pointcut-ref="businessService"
        method="doBasicProfiling"/>

    ...

</aop:aspect>
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
    // start stopwatch
    Object retVal = pjp.proceed();
    // stop stopwatch
    return retVal;
}

確定通知參數(shù)
使用arg-names顯式指定參數(shù)名稱:

<aop:before
    pointcut="com.xyz.lib.Pointcuts.anyPublicMethod() and @annotation(auditable)"
    method="audit"
    arg-names="auditable"/>// 多個參數(shù)用逗號分隔

強類型參數(shù)示例:

public interface PersonService {

    Person getPerson(String personName, int age);
}

public class DefaultFooService implements FooService {

    public Person getPerson(String name, int age) {
        return new Person(name, age);
    }
}

通知:

import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;

public class SimpleProfiler {
    // 強類型的參數(shù)
    public Object profile(ProceedingJoinPoint call, String name, int age) throws Throwable {
        StopWatch clock = new StopWatch("Profiling for '" + name + "' and '" + age + "'");
        try {
            clock.start(call.toShortString());
            return call.proceed();
        } finally {
            clock.stop();
            System.out.println(clock.prettyPrint());
        }
    }
}

xml配置:

<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/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- this is the object that will be proxied by Spring's AOP infrastructure -->
    <bean id="personService" class="x.y.service.DefaultPersonService"/>

    <!-- this is the actual advice itself -->
    <bean id="profiler" class="x.y.SimpleProfiler"/>

    <aop:config>
        <aop:aspect ref="profiler">

            <aop:pointcut id="theExecutionOfSomePersonServiceMethod"
                expression="execution(* x.y.service.PersonService.getPerson(String,int))
                and args(name, age)"/>

            <aop:around pointcut-ref="theExecutionOfSomePersonServiceMethod"
                method="profile"/>

        </aop:aspect>
    </aop:config>

</beans>
5.5.4 引入

聲明父類
<aop:aspect>元素中使用<aop:declare-parents>元素聲明父類

<aop:aspect id="usageTrackerAspect" ref="usageTracking">

    <aop:declare-parents
        types-matching="com.xzy.myapp.service.*+"
        implement-interface="com.xyz.myapp.service.tracking.UsageTracked"
        default-impl="com.xyz.myapp.service.tracking.DefaultUsageTracked"/>

    <aop:before
        pointcut="com.xyz.myapp.SystemArchitecture.businessService()
            and this(usageTracked)"
            method="recordUsage"/>

</aop:aspect>

recordUsage方法:

public void recordUsage(UsageTracked usageTracked) {
    usageTracked.incrementUseCount();
}
5.5.5切面實例化模型

xml配置只支持單例模式.

5.5.6 Advisors

advisors的概念來自于spring中AOP的定義, 在AspectJ中沒有對等的定義. 一個advisor就相當于擁有一個通知的自包含型切面.spring通過<aop:advisor>元素來定義它, 最常見的是事務(wù)通知.

<aop:config>

    <aop:pointcut id="businessService"
        expression="execution(* com.xyz.myapp.service.*.*(..))"/>

    <aop:advisor
        pointcut-ref="businessService"
        advice-ref="tx-advice"/>

</aop:config>

<tx:advice id="tx-advice">
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

可以通過order屬性來定義advisor的順序

5.6 選擇aop的定義樣式

如果在開發(fā)中確定要使用aspect, 是使用spring aop還是AspectJ, 是使用xml形式還是注解形式呢?

5.6.1 Spring AOP 與 Full AspectJ

如果你只是在spring的bean上執(zhí)行操作, 則用Spring AOP, 如果你還要操作一些不被spring容器管理的對象, 則用AspectJ.

5.6.2 @AspectJ 與 XML

在選擇了spring aop后, 怎么選擇是使用@AspectJ還是xml.
如果使用aop作為服務(wù)工具, 則使用xml更好(即切點表達式是否可能更改的配置的一部分), 而且使用xml配置, 可以更清楚的看到系統(tǒng)中哪些地方存在切面.

但xml有兩個缺點:

  • 首先侮东,它沒有將它所處理的需求的實現(xiàn)完全封裝在一個地方圈盔。但是使用注解時, 這些都將被封裝在一個單模塊中.
  • XML樣式在它能表達的內(nèi)容上稍微有些限制:只支持“單例”方面實例化模型,并且不可能組合XML中聲明的命名切入點悄雅。
@Pointcut("execution(* get*())")
public void propertyAccess() {}

@Pointcut("execution(org.xyz.Account+ *(..))")
public void operationReturningAnAccount() {}

@Pointcut("propertyAccess() && operationReturningAnAccount()")
public void accountPropertyAccess() {}

在xml中,只能聲明前兩種切點:

<aop:pointcut id="propertyAccess"
        expression="execution(* get*())"/>

<aop:pointcut id="operationReturningAnAccount"
        expression="execution(org.xyz.Account+ *(..))"/>

xml的缺點是不能組合這兩種定義.

5.7 混合使用

兩種模式可以混合使用, 所有的這些實現(xiàn)都依賴相同的底層實現(xiàn)機制.

5.8 代理機制

Spring AOP使用JDK動態(tài)代理或CGLIB為給定的目標對象創(chuàng)建代理, 如果要代理的目標對象實現(xiàn)至少一個接口驱敲,則使用JDK動態(tài)代理。如果目標對象沒有實現(xiàn)任何接口宽闲,則創(chuàng)建一個CGLIB代理众眨。

如果想強制使用CGLIB代理, 則要考慮以下幾點:

  • 不能通知final方法. 因為它不能被重寫.
  • 從Spring 4.0開始握牧,代理對象的構(gòu)造函數(shù)不再被調(diào)用兩次, since the CGLIB proxy instance is created through Objenesis. Only if your JVM does not allow for constructor bypassing, you might see double invocations and corresponding debug log entries from Spring’s AOP support.

<aop:config>元素上配置proxy-target-class屬性為true即可.

<aop:config proxy-target-class="true">
    <!-- other beans defined here... -->
</aop:config>

要在使用@AspectJ自動代理支持時強制CGLIB代理,請將<aop:aspectj-autoproxy>元素的代理目標類屬性設(shè)置為true.

<aop:aspectj-autoproxy proxy-target-class="true"/>

如果存在多個<aop:config>, 在運行時會將其合并成為一個<aop:config>定義, 其中最強的代理配置將被使用.即在<tx:annotation-driven/><aop:aspectj-autoproxy/><aop:config/>中任何一個上使用了proxy-target-class=true, 都將強制以上三個都使用CGLIB代理.

5.8.1 理解AOP代理

spring aop是基于代理的.

首先考慮這樣一種場景:您有一個普通的娩梨、未代理的我碟、沒有任何特殊之處的、直接的對象引用姚建,如下面的代碼片段所示:

public class SimplePojo implements Pojo {

    public void foo() {
        // this next method invocation is a direct call on the 'this' reference
        this.bar();
    }

    public void bar() {
        // some logic...
    }
}

直接調(diào)用foo方法, 方法將直接在對象引用上調(diào)用:

public class Main {

    public static void main(String[] args) {
        Pojo pojo = new SimplePojo();
        // this is a direct method call on the 'pojo' reference
        pojo.foo();
    }
}
image.png

當引用是一個代理時,則會發(fā)生變化:

public class Main {

    public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory(new SimplePojo());
        factory.addInterface(Pojo.class);
        factory.addAdvice(new RetryAdvice());

        Pojo pojo = (Pojo) factory.getProxy();
        // this is a method call on the proxy!
        pojo.foo();
    }
}
image.png

此處的關(guān)鍵是在main方法中對foo方法的調(diào)用.對該對象引用的方法調(diào)用是對代理的調(diào)用。因此吱殉,代理可以委托給與特定方法調(diào)用相關(guān)的所有攔截器(通知)掸冤。然而,一旦調(diào)用最終到達目標對象(在本例中是SimplePojo引用)友雳,它對自身執(zhí)行的任何方法調(diào)用稿湿,比如this.bar()或this.foo(),都將針對這個引用而不是代理來調(diào)用押赊。這具有重要意義饺藤。這意味著自調(diào)用不會導致與方法調(diào)用關(guān)聯(lián)的通知有機會執(zhí)行。
最好的方法是重構(gòu)代碼流礁,這樣就不會發(fā)生自調(diào)用涕俗。

public class Main {

    public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory(new SimplePojo());
        factory.adddInterface(Pojo.class);
        factory.addAdvice(new RetryAdvice());
        factory.setExposeProxy(true); ////////

        Pojo pojo = (Pojo) factory.getProxy();
        // this is a method call on the proxy!
        pojo.foo();
    }
}

最后,必須注意AspectJ沒有這個自調(diào)用問題神帅,因為它不是一個基于代理的AOP框架再姑。

5.9 @AspectJ代理的編程創(chuàng)建

除了通過使用<aop:config><aop:aspectj-autoproxy>在配置中聲明方面之外,還可以通過編程創(chuàng)建通知目標對象的代理. 在這里找御,我們只關(guān)注通過使用@AspectJ方面自動創(chuàng)建代理的能力元镀。
可以使用org.springframework.aop.aspectj.annotation。AspectJProxyFactory類來為一個或多個@AspectJ方面建議的目標對象創(chuàng)建代理霎桅。這個類的基本用法很簡單栖疑,如下例所示:

// create a factory that can generate a proxy for the given target object
AspectJProxyFactory factory = new AspectJProxyFactory(targetObject);

// add an aspect, the class must be an @AspectJ aspect
// you can call this as many times as you need with different aspects
factory.addAspect(SecurityManager.class);

// you can also add existing aspect instances, the type of the object supplied must be an @AspectJ aspect
factory.addAspect(usageTracker);

// now get the proxy object...
MyInterfaceType proxy = factory.getProxy();
5.10 在spring容器中使用AspectJ

如果您的需求超出了Spring AOP單獨提供的功能,我們將研究如何使用AspectJ編譯器或編織器來代替或補充Spring AOP滔驶。
要引入spring-aspects.jar.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末遇革,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子瓜浸,更是在濱河造成了極大的恐慌澳淑,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件插佛,死亡現(xiàn)場離奇詭異杠巡,居然都是意外死亡,警方通過查閱死者的電腦和手機雇寇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門氢拥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蚌铜,“玉大人,你說我怎么就攤上這事嫩海《辏” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵叁怪,是天一觀的道長审葬。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任空繁,我火速辦了婚禮退疫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布膝宁。 她就那樣靜靜地躺著,像睡著了一般根吁。 火紅的嫁衣襯著肌膚如雪员淫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天击敌,我揣著相機與錄音满粗,去河邊找鬼。 笑死愚争,一個胖子當著我的面吹牛映皆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播轰枝,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼捅彻,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鞍陨?” 一聲冷哼從身側(cè)響起步淹,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎诚撵,沒想到半個月后缭裆,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡寿烟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年澈驼,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片筛武。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡缝其,死狀恐怖挎塌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情内边,我是刑警寧澤榴都,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站漠其,受9級特大地震影響嘴高,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜和屎,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一阳惹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧眶俩,春花似錦、人聲如沸快鱼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽抹竹。三九已至线罕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間窃判,已是汗流浹背钞楼。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留袄琳,地道東北人询件。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像唆樊,于是被迫代替她去往敵國和親宛琅。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,781評論 2 354

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

  • IoC 容器 Bean 的作用域 自定義作用域?qū)崿F(xiàn) org.springframework.beans.facto...
    Hsinwong閱讀 2,471評論 0 7
  • AOP實現(xiàn)可分為兩類(按AOP框架修改源代碼的時機): 靜態(tài)AOP實現(xiàn):AOP框架在編譯階段對程序進行修改,即實現(xiàn)...
    數(shù)獨題閱讀 2,317評論 0 22
  • 一片效、AOP 簡介 AOP(Aspect-Oriented Programming, 面向切面編程): 是一種新的方...
    leeqico閱讀 796評論 0 1
  • 本章內(nèi)容: 面向切面編程的基本原理 通過POJO創(chuàng)建切面 使用@AspectJ注解 為AspectJ切面注入依賴 ...
    謝隨安閱讀 3,146評論 0 9
  • 記憶是個有趣的孩子红伦,喜歡刻意隱藏,卻又在不經(jīng)意間出現(xiàn)淀衣,但帶來的通常只有物是人非的感慨昙读。脫離兒時的無憂,用現(xiàn)在的眼光...
    文拙閱讀 624評論 1 18