1. 程式人生 > >Spring framework(Version 5.0.8)摘要 5,Aspect Oriented Programming With Spring

Spring framework(Version 5.0.8)摘要 5,Aspect Oriented Programming With Spring

 


                                                        喜歡的話,還可以打賞哦,以資鼓勵

                                                                    

  

                                                                          (支付寶)             (微信)


5.1 Introduction

AOP框架是Spring的核心元件。但是Spring IoC容器並不依賴於AOP。AOP是Spirng IoC的補充,如果你不需要的話,完全可以不用。

Spring 2.0開始提供schema-base(XML schema)和@AspectJ註解兩種自定義aspects的方式。

AOP在Spring Framework中用於:

  •            提供宣告式的企業級服務,特別是用於替代EJB服務。最重要的應用場景是事務管理。
  •            允許使用者自定義aspects,作為OOP的補充。

5.1.1 AOP 概念

  • Aspect:是對一個橫跨了多個Classes的問題的模組化,比如事物處理(事物處理的操作以切面的形式插入到業務處理的流程中,這些切面的集合可以抽象成一個模組,代表一個完整的事務處理邏輯)。
  • Join point:程式執行過程中的一個切入點,它可以是一個方法的執行或一個異常的處理(我們找到一個切入點,並在此處加入我們需要的處理邏輯)。
  • Advice:即我們在切入點所加入的處理邏輯(比如在一個方法執行後列印一段日誌)。它有幾種型別“before”,“after”(分別代表在切入點的前面,後面)。很多AOP框架把advice抽象成interceptor(攔截器),圍繞在切入點前後,形成一個攔截器鏈。
  • Pointcut:一個申明,用來匹配哪些才是你要找的切入點。(比如,我們宣告“所有已Service結尾的類它的do方法”都需要執行日誌列印,引號內的申明就是Pointcut)。Spring預設使用的是AspectJ pointcut expression。
  • Introduction:Introduction允許你為adviced物件引入新的interface,併為其提供該interface的一個實現(相當於為其添加了一個父類,使它具有父類的功能介面)。這樣依賴這個adviced物件就具有了新的功能。
  • Target object:被aspect植入操作的物件。由於Spring AOP是使用執行時代理實現的,所以也可以認為是被代理物件。
  • AOP proxy:被AOP框架建立的用於植入程式碼的物件。在Spring中,AOP代理指JDK動態代理或CGLIB代理。
  • Weaving:程式碼織入。把aspect與類或物件聯絡起來,得到一個植入了新操作的物件。程式碼織入可以在編譯,載入或執行時進行。Spring AOP和其他純Java實現的AOP框架一樣,都是在執行時織入程式碼的。

advice的型別

  • Before advice:在join point之前執行。不過這種方式無法制止程式碼繼續執行,除非丟擲異常。
  • After returning advice:在join point不拋錯,並且正常執行完成之後執行。
  • After throwing advice:在join point執行拋錯時執行。
  • After (finally) advice:無論正常執行亦或拋錯,都執行。
  • Around advice:與前面的advice型別不同。在Around advice中,join point的執行由使用者自己控制。你可以在呼叫join point之前,之後加入自己的程式碼邏輯。也可以選擇呼叫或不呼叫join point。Around advice相當於對join point進行了封裝,為使用者提供了很大的自主權。

在advice的選擇上,應該遵循最小化影響原則(夠用就行)。

 

5.1.2 Spring AOP的能力和目標

Spring AOP是純Java實現的。

Spring AOP當前只支援方法執行這種join points,不支援屬性攔截。如果你需要的話,可以使用AspectJ。

Spring AOP的目標不是提供完整的AOP功能,它更傾向於通過與Spring IoC的緊密整合,來提供企業級應用中的問題。

5.1.3 AOP 代理

Spring AOP預設使用JDK動態代理機制,動態代理需要業務程式碼繼承於某個介面。Spring AOP也支援CGLIB代理,如果業務程式碼不繼承任何介面的情況下可以選擇CGLIB。

 

5.2 @AspectJ支援

@AspectJ指的是一種使用註解宣告aspects的方式。@AspectJ是由AspectJ專案引入的。Spring使用AspectJ提供的jar包解析@AspectJ註解。不過儘管使用了相同的註解,AOP仍然是純Spring AOP,並不依賴於AspectJ編譯器或程式碼織入。

5.2.1 啟用@AspectJ支援

使用Java Configuration啟用@AspectJ註解

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}

使用XML Configuration啟用@AspectJ註解

<?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/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here -->

    <aop:aspectj-autoproxy/>

</beans>

不過,無論使用哪種引入方式,都需要依賴於aspectjweaver.jar包。

 

5.2.2 宣告一個aspect

<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 {

}

也可以不配置xml,而使用註解自動掃描的方式,不過因為@Aspect無法被自動掃描到,所以需要加上@Component註解。

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

@Aspect
@Component
public class NotVeryUsefulAspect {

}

注意:被@Aspect標註的類,不能再作為AOP的target。

5.2.3 宣告一個pointcut

一個pointcut包含兩部分:一個由name和引數構成的簽名(由於Spring AOP只支援基於方法執行的pointcut,所以這裡的簽名可以理解為方法簽名),一個pointcut表示式用來標識作用於哪些方法。

@Pointcut("execution(* transfer(..))")// the pointcut expression 表示式 
private void anyOldTransfer() {}// the pointcut signature 方法簽名

支援的pointcut指示符(AspectJ pointcut designators ,PCD)

PCD就是pointcut expression中的關鍵字,Spring支援如下關鍵字。

  • execution - 匹配方法執行join points
  • within - 限定匹配的類型範圍
  • this - 限定AOP代理是一個指定型別的例項
  • target - 限定AOP被代理物件是一個指定型別的例項
  • args - 限定引數是指定型別
  • @target - 限定執行物件的類有一個指定型別的註解
  • @args - 限定傳入的引數類有指定型別的註解
  • @within - 限定匹配的類有指定型別的註解
  • @annotation - 限定join point 方法有指定型別註解

另外Spring添加了一個bean關鍵字,用於限定join points為指定名稱的spring bean。針對例項的PCD是Spring特有的。


pointcut表示式的結合使用

pointcut表示式可以使用“&&”,“||”,“!”。

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

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

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

上面這個例子中我們先定義了anyPublicOperation和inTrading兩個pointcut,然後把anyPublicOperation和inTrading作為元件,定義了新的pointcut。


共享通用的pointcut定義

上面介紹瞭如何把pointcut作為元件來使用,spring推薦預定義通用的ponitcut表示式,作為共享元件。

package com.xyz.someapp;

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

@Aspect
public class SystemArchitecture {

    /**
     * A join point is in the web layer if the method is defined
     * in a type in the com.xyz.someapp.web package or any sub-package
     * under that.
     */
    @Pointcut("within(com.xyz.someapp.web..*)")
    public void inWebLayer() {}

    /**
     * A join point is in the service layer if the method is defined
     * in a type in the com.xyz.someapp.service package or any sub-package
     * under that.
     */
    @Pointcut("within(com.xyz.someapp.service..*)")
    public void inServiceLayer() {}

    /**
     * A join point is in the data access layer if the method is defined
     * in a type in the com.xyz.someapp.dao package or any sub-package
     * under that.
     */
    @Pointcut("within(com.xyz.someapp.dao..*)")
    public void inDataAccessLayer() {}

    /**
     * A business service is the execution of any method defined on a service
     * interface. This definition assumes that interfaces are placed in the
     * "service" package, and that implementation types are in sub-packages.
     *
     * If you group service interfaces by functional area (for example,
     * in packages com.xyz.someapp.abc.service and com.xyz.someapp.def.service) then
     * the pointcut expression "execution(* com.xyz.someapp..service.*.*(..))"
     * could be used instead.
     *
     * Alternatively, you can write the expression using the 'bean'
     * PCD, like so "bean(*Service)". (This assumes that you have
     * named your Spring service beans in a consistent fashion.)
     */
    @Pointcut("execution(* com.xyz.someapp..service.*.*(..))")
    public void businessService() {}

    /**
     * A data access operation is the execution of any method defined on a
     * dao interface. This definition assumes that interfaces are placed in the
     * "dao" package, and that implementation types are in sub-packages.
     */
    @Pointcut("execution(* com.xyz.someapp.dao.*.*(..))")
    public void dataAccessOperation() {}

}

完整的pointcut表示式包含以下幾個部分(就像正真的方法簽名),除了returning type pattern,name pattern,parameters-pattern其他都是可選的。

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

表示式中可以使用萬用字元表示。比較複雜的部分是方法的引數,()代表沒有引數,(..)代表任意數量的引數,(*)代表一個不限定型別的引數,(*,String)代表兩個引數一個不限定型別,另一個是字串型別。

pointcut表示式例子

表示任何public方法的執行

execution(public * *(..))

表示任何名字以set開頭的方法的執行

execution(* set*(..))

表示任何AccountService介面中定義的方法的執行

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

表示包中定義的任何方法的執行

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

表示包中的任何join point

within(com.xyz.service.*)

表示此包或其子包中的任何join point

within(com.xyz.service..*)

表示任何join point其代理實現了AccountService介面。this只適用於在Spring AOP中執行的情況。

this(com.xyz.service.AccountService)

表示任何join point其target物件實現了AccountService介面。target只適用於在Spring AOP中執行的情況。

target(com.xyz.service.AccountService)

表示任何join point其在執行時,傳入的引數只有一個,且是Serializble。

注意:這個例子與execution(* *(java.io.Serializable))是不同的,execution關注的是方法簽名,args關注的是執行時傳入的實際引數。

args(java.io.Serializable)

表示任何join point其target物件帶有@Transactional註解

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

表示任何join point其target的類宣告上帶有@Transactional註解

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

表示任何join point其執行的方法上帶有@Transactional註解

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

表示任何join point其只有一個引數,且執行時傳入的引數型別帶有@Classified註解

@args(com.xyz.security.Classified)

表示任何join point其spring bean name是tradeService

bean(tradeService)

也可以帶有萬用字元

bean(*Service)

PCD的三種類型

PCD可以總結為三種類型:Kinded,Scoping,Contextual。

Kinded(指向特定型別的join point):execution

Scoping:within

Contextual:this,target,@annotation

 

5.2.4 宣告advice

advice由一個pointcut表示式,和before、after、around等組成。

Before advice

這裡的com.xyz.myapp.SystemArchitecture.dataAccessOperation()指的是宣告好的pointcut(這種預先宣告的方式在前面已經講到過),不是實際的切入點。

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

}

當然也可以直接使用pointcut表示式

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

}

After ruterning advice

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

}

如果要對返回值進行處理,可以加上returning屬性

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

}

After throwing advice

當匹配到的方法,丟擲異常時執行。

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

}

可以傳入ex

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

}

After(finally)advice

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

}

Around advice

我們可以看到這裡傳入的引數就是原本要執行的業務邏輯,這樣一來我們就有了更大的自由發揮的空間,可以選擇是否執行它,或者在其周圍加上自定義的程式碼。

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;
    }

}

advice parameters

獲取當前的JointPoint

任何advice方法,都可以宣告其第一個引數為org.aspectj.lang.JoinPoint型別的。在around advice中必須是ProceedingJoinPoint型別,它是JoinPoint的子類。JoinPoint提供了很多方法,getArgs、getThis、getTarget、getSignature、toString等等。

傳參到advice方法中

我們可以使用args PCD來傳入引數,下例中傳入了一個名為account的引數,這樣在advice方法中就可以接受到了。在這裡args(account,..)有兩個作用,一個是限定執行的方法必須至少有一個引數,且型別是Account;另一個是為advice傳參。

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

以上的例子,也可以用如下方式表示。

事先宣告好一個帶引數的pointcut,然後直接在advice中使用。

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

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

更多有趣的用法可以參考AspectJ的文件。

一個使用@annotation PCD傳參的例子

先定義一個Auditable標籤。

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

然後使用@annotation去匹配

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

advice引數和泛型

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

假設有如上泛型介面,那麼對應的advice可以這樣寫

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

不過需要指出的是,advice不支援泛型集合Collection<T>。

 

決定引數名

我們可以使用argNames屬性來指定pointcut表示式和advice方法之間引數的對應關係。argNames屬性中的引數順序與advice方法的引數順序一致,argNames屬性中的引數名稱需要與pointcut表示式中的引數名稱一致。

@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
}

如果advice的第一個引數,其型別是JoinPoint,ProceedingJoinPoint,JoinPoint.StaticPart,則不需要在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
}

如果不指定argNames屬性,Spring AOP會查詢class的debug information,根據本地變量表決定引數名。如果是在AspectJ編輯器中編譯則不需要debug information。

 

advice執行順序

當多個advice都作用於同一個join point時,如果不指定順序,那麼執行順序是不確定的。你可以通過優先順序來控制執行順序,advice根據優先順序決定執行順序。在進入join point時,優先順序最高的先執行;在離開join point時,優先順序最高的後執行。控制優先順序可以通過讓aspect class實現org.springframwork.core.Ordered介面或使用@Order註解,order值越小,優先順序越高。

注意:如果同一個aspect下面的兩個advice都作用於同一個join point,這種情況下是沒辦法控制優先順序的。所以最好考慮分拆到不同的aspect中。

 

5.2.5 Introductions

Introduction,允許aspect為advised物件新增一個父interface,併為advised物件提供了該interface的一個實現。

例子:

先定義一個Student類,等一下我們會為該類的例項加入新邏輯。

package com.zjc.spring.person;

import org.springframework.stereotype.Component;

@Component
public class Student {
}

準備加入的新邏輯介面

​
package com.zjc.spring;

public interface Driver {

    void drive();
}

​

介面的實現

package com.zjc.spring;

public class CarDriver implements Driver{
    @Override
    public void drive() {
        System.out.println("driving car");
    }
}

一個aspect,用於給Student加入Dirver功能

package com.zjc.spring;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MyAspect {

    @DeclareParents(value = "com.zjc.spring.person.*", defaultImpl = com.zjc.spring.CarDriver.class)
    public Driver driver;
}

執行

package com.zjc.spring;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;

@SpringBootApplication
public class Test {

    public static void main(String args[]){
        ApplicationContext context = SpringApplication.run(Test.class, args);
        Driver driver = (Driver)context.getBean("student");
        driver.drive();
    }
}

我們可以看到student可以轉換成Driver型別了,具有了Driver介面的功能。

 

5.2.6 Aspect 例項化模型

預設情況下,aspect都是單例模式的,即在application context中每個aspect只會存在一個例項。不過Spring也支援AspectJ的perthis,pertarget模式。

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

    private int someState;

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

}

perthis表示spring會為每個符合條件的代理物件建立一個aspect例項。

 

5.3 Schema-based AOP支援

要使用aop namespace標籤,你需要引入spring-aop schema,就像下面這樣。

<?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/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here -->

</beans>

注意:在使用<aop:config>配置模式的情況下,不要啟用<aop:aspectj-autoproxy/>,否則會出問題。

5.3.1 宣告一個aspect

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

<bean id="aBean" class="...">
    ...
</bean>

 

5.3.2 宣告一個pointcut

<aop:config>

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

</aop:config>

也可以把宣告指向一個預先宣告好的pointcut

<aop:config>

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

</aop:config>

也可以在aspect中宣告pointcut

<aop:config>

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

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

        ...

    </aop:aspect>

</aop:config>

結合before advice使用

<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>

結合多個pointcut表示式,在這裡&&、||、!是不適用的,需要用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>

5.3.3 宣告advice

Before advice

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

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

    ...

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

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

    ...

</aop:aspect>

After returning advice

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

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

    ...

</aop:aspect>

可以帶上返回值

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

    <aop:after-returning
        pointcut-ref="dataAccessOperation"
        returning="retVal"
        method="doAccessCheck"/>

    ...

</aop:aspect>
public void doAccessCheck(Object retVal) {...

After throw advice

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

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

    ...

</aop:aspect>

也可以把異常作為引數傳入advice方法

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

    <aop:after-throwing
        pointcut-ref="dataAccessOperation"
        throwing="dataAccessEx"
        method="doRecoveryActions"/>

    ...

</aop:aspect>
public void doRecoveryActions(DataAccessException dataAccessEx) {...

 

After (finally) advice

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

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

    ...

</aop:aspect>

Around advice

<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;
}

Advice parameters

業務類

package x.y.service;

public interface FooService {

    Foo getFoo(String fooName, int age);
}

public class DefaultFooService implements FooService {

    public Foo getFoo(String name, int age) {
        return new Foo(name, age);
    }
}

aspect類

package x.y;

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

public class SimpleProfiler {

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

配置

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

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

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

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

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

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

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

</beans>

啟動類

import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import x.y.service.FooService;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        BeanFactory ctx = new ClassPathXmlApplicationContext("x/y/plain.xml");
        FooService foo = (FooService) ctx.getBean("fooService");
        foo.getFoo("Pengo", 12);
    }
}

執行結果

StopWatch 'Profiling for 'Pengo' and '12'': running time (millis) = 0
-----------------------------------------
ms     %     Task name
-----------------------------------------
00000  ?  execution(getFoo)

5.3.4 Introductions

<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>

5.3.5 Aspect例項化模型

在schema定義的aspects中唯一支援的例項化模式是單例模型,其他的展示不支援。

 

5.3.6 Advisors

 

<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>

5.8 在Spring應用中使用AspectJ

在Spring中使用aspects,你需要引入spring-aspects.jar。

5.8.1 在Spring中使用AspectJ做領域物件的依賴注入

Spring容器可以例項化和配置那些定義在application context中的beans。也可以配置一個已經存在的物件。

Spring提供了對那些在容器之外建立的物件的支援,比如領域物件(Domain objects),他們通常由ORM工具等通過new操作建立。

@Configurable註解使一個類適用於Spring的配置,被標註的類的新例項會被Spring配置,注入依賴。

package com.xyz.myapp.domain;

import org.springframework.beans.factory.annotation.Configurable;

@Configurable
public class Account {
    // ...
}

當用@Configurable標識類時,Spring會以全限定名來命名配置這個類的新例項。

package com.xyz.myapp.domain;

import org.springframework.beans.factory.annotation.Configurable;

@Configurable("account")
public class Account {
    // ...
}

你也可以通過以上方式指定例項的名稱。

另外,你可以使用@Configurable(autowire=Autowire.BY_TYPE)或@Configurable(autowire=Autowire.BY_NAME)來指定依賴注入到這個類的新例項的方式。使用@Configurable(dependencyCheck=true)來對新例項中的依賴進行檢查(當所有屬性配置完成後,spring會進行校驗)。

註解本身並沒有做什麼,真正起作用的是spring-aspects.jar中的AnnotationBeanConfigurerAspect。本質上,他做了這麼一件事——“當被@Configurable註解標註的類的新例項被初始化後,根據註解的屬性使用Spirng來配置這個新生成的例項”。

注意:以上的表述暗示了依賴注入發生在建構函式執行完成後。如果你希望在建構函式方法體執行前注入依賴,你可以做如下配置。

@Configurable(preConstruction=true)

以上這些必須使用AspectJ的程式碼織入(可以通過Ant或Maven任務,也可以使用類載入期織入)。另外,你需要通過spring配置來啟用AnnotationBeanConfigurerAspect,如果你使用Java based配置,那麼可以通過在任一@Configuration class上新增@EnableSpringConfigured註解來啟用AnnotationBeanConfigurerAspect。

@Configuration
@EnableSpringConfigured
public class AppConfig {

}

如果是xml based配置,則可以如下配置

<context:spring-configured/>

如果一個類確定要用到@Configurable的功能,為了防止它在AnnotationBeanConfigurerAspect之前建立。可以使用depends-on

<bean id="myService"
        class="com.xzy.myapp.service.MyService"
        depends-on="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect">

    <!-- ... -->

</bean>

注意:如果一個類是在Spring的控制下,那麼前往不要在上面加@Configurable註解,否則會有兩次初始化,一次是容器發起的,一次是aspect發起的。

@Configurable物件的單元測試

@Configurable的一個目標就是使對領域物件的單元測試更簡單。

多個application contexts的情況

AnnotationBeanConfigurerAspect是個AspectJ單例aspect,這意味一個classloader上只能有一個這樣的aspect例項。如果你有多個applictaion context在同一個classloader上,你就需要考慮在哪裡定義@EnableSpringConfigured bean,哪裡放置spring-aspects.jar。

在擁有共享的parent application context的情況下,建議把@EnableSpringConfigured bean定義在parent application context上。

當servlet容器上部署了多個web-apps時,要確保每個web-app都使用自己的classloader載入spring-aspects.jar(比如,把spring-aspect.jar放到WEB-INF/lib目錄下),如果放置在servlet容器的classpath下,則所有的web-app都會共用同一個aspect例項。

 

5.8.2 其它的AspectJ aspects

除了@Configurable aspect,spring-aspects.jar包含一個AspectJ aspect,對那些使用@Transactional標註的類和方法執行Spring的事務管理。它主要用於在Spring容器控制之外使用Spring的事務支援。

@Transactional註解由AnnotationTransactionAspect解釋。(注意,你必須把註解放置在具體的實現類上,而不是父介面上。因為介面上的註解是不會被繼承的)

類上的@Transactional註解為該類的所有public方法提供了事務支援。

你可以在方法上使用@Transactional註解,即使該方法是private的。

注意:Spring Framework4.2以來,spring-aspects提供了一個小的aspect支援同樣的javax.transaction.Transactional註解特性。詳見JtaAnnotationTransactionAspect。

如果你希望使用Spring configuration和事務管理支援,但不想用註解,spring-aspects.jar提供了抽象的aspects來支援你定義自己的pointcut定義。詳見AbstractBeanConfigurerAspect 和AbstractTransactionAspect 。

public aspect DomainObjectConfiguration extends AbstractBeanConfigurerAspect {

    public DomainObjectConfiguration() {
        setBeanWiringInfoResolver(new ClassNameBeanWiringInfoResolver());
    }

    // the creation of a new bean (any object in the domain model)
    protected pointcut beanCreation(Object beanInstance) :
        initialization(new(..)) &&
        SystemArchitecture.inDomainModel() &&
        this(beanInstance);

}