Spring AOP 官方文檔閱讀筆記

官方文檔傳送門html

本文僅涉及從 11. Aspect Oriented Programming with Spring11.2.5 Introductions 之間的內容, 11.2.5 Introductions 以後的講的是引入的內容,也與本文無關。java

本文中全部被使用引用格式的文字均來自於官方文檔,能夠打開官方文檔後在網頁內搜索定位,以更好理解上下文。同時本文的內容的順序並不徹底與官方文檔一致,對一些內容捨棄不歸入,對一些內容整理和提早了幾節。web

AOP中的術語的翻譯和文檔中詞彙的理解

詞彙理解

type : 能夠認爲此處使用 class 不夠完備,由於還有 interface 之類的也符合當前語句的說明。因此使用 typespring

AOP基本概念

Advice: action taken by an aspect at a particular join point. Different types of advice include "around", "before" and "after" advice. (Advice types are discussed below.) Many AOP frameworks, including Spring, model an advice as an interceptor, maintaining a chain of interceptors around the join point.

咱們約定將 Advice 翻譯爲加強。拋開引用內容(官方的文檔的安排是有問題的。應當先有 Advice 的概念,再有其它的概念。文檔中未將 Adive 置於第一個而且對它的解釋中使用了其它概念術語,這是不穩當的。),加強做爲動詞,能夠理解爲指針對類的方法,非侵犯地(不改動方法自己)爲其在被調用以前與被調用以後添加新的功能。做爲名詞,能夠理解爲以前說的容納/實現新的功能的方法。express

Aspect: a modularization of a concern that cuts across multiple classes. Transaction management is a good example of a crosscutting concern in enterprise Java applications. In Spring AOP, aspects are implemented using regular classes (the schema-based approach) or regular classes annotated with the @Aspectannotation (the @AspectJ style).

咱們約定將 Aspect 翻譯爲切面。切面是對用於加強的方法按照必定理解和規則進行整理分類後的模塊,其中包含了符合這一模塊的概念和理解的用於加強的方法。api

Join point: a point during the execution of a program, such as the execution of a method or the handling of an exception. In Spring AOP, a join point always represents a method execution.

咱們約定將 joint point 翻譯爲接入點。接入點是可被加強的內容,在 Spring AOP 中能夠簡單地理解爲一個類的方法。數組

Pointcut: a predicate that matches join points. Advice is associated with a pointcut expression and runs at any join point matched by the pointcut (for example, the execution of a method with a certain name). The concept of join points as matched by pointcut expressions is central to AOP, and Spring uses the AspectJ pointcut expression language by default.

咱們約定將 Pointcut 翻譯爲切點。切點是一條規則,也是根據這條規則被篩選出來的接入點的集合。安全

Introduction: declaring additional methods or fields on behalf of a type. Spring AOP allows you to introduce new interfaces (and a corresponding implementation) to any advised object. For example, you could use an introduction to make a bean implement an IsModified interface, to simplify caching. (An introduction is known as an inter-type declaration in the AspectJ community.)

咱們約定將 introduction 翻譯爲引入。引入是指爲一個 type 在改動它的源碼的基礎上爲之增長方法的行爲。app

Target object: object being advised by one or more aspects. Also referred to as the advised object. Since Spring AOP is implemented using runtime proxies, this object will always be a proxied object.

咱們約定將 target object 翻譯爲源對象。源對象指被加強的方法所屬的實例。框架

AOP proxy: an object created by the AOP framework in order to implement the aspect contracts (advise method executions and so on). In the Spring Framework, an AOP proxy will be a JDK dynamic proxy or a CGLIB proxy.

咱們約定將 AOP proxy 翻譯爲代理人。代理人指經由 Spring AOP 指派,代理源對象實現對源對象方法的加強,並將全部調用源對象的被加強的方法的請求都先由本身經手的另外一個對象。在Spring AOP中,代理人要麼使用JDK動態代理功能建立出來,要麼使用CGLIB代理功能建立。

Weaving: linking aspects with other application types or objects to create an advised object. This can be done at compile time (using the AspectJ compiler, for example), load time, or at runtime. Spring AOP, like other pure Java AOP frameworks, performs weaving at runtime.

咱們約定將 Weaving 翻譯爲織入。前面說的代理人經由 Spring AOP 指派,這個指派就是這裏的織入。

另外

咱們約定,統一使用使用調用一詞來描述接入點做爲方法被調用/被執行。

加強的類型的術語

  • Before advice: Advice that executes before a join point, but which does not have the ability to prevent execution flow proceeding to the join point (unless it throws an exception).
  • After returning advice: Advice to be executed after a join point completes normally: for example, if a method returns without throwing an exception.
  • After throwing advice: Advice to be executed if a method exits by throwing an exception.
  • After (finally) advice: Advice to be executed regardless of the means by which a join point exits (normal or exceptional return).
  • Around advice: Advice that surrounds a join point such as a method invocation. This is the most powerful kind of advice. Around advice can perform custom behavior before and after the method invocation. It is also responsible for choosing whether to proceed to the join point or to shortcut the advised method execution by returning its own return value or throwing an exception.

從上到下咱們分別約定翻譯爲:前置加強,返回後加強,拋出異常後加強,後置加強,環繞加強。

Spring AOP 被期待實現的能力和目標

Spring AOP currently supports only method execution join points (advising the execution of methods on Spring beans). Field interception is not implemented, although support for field interception could be added without breaking the core Spring AOP APIs. If you need to advise field access and update join points, consider a language such as AspectJ.

....

Spring AOP defaults to using standard JDK dynamic proxies for AOP proxies. This enables any interface (or set of interfaces) to be proxied.

Spring AOP can also use CGLIB proxies. This is necessary to proxy classes rather than interfaces. CGLIB is used by default if a business object does not implement an interface. As it is good practice to program to interfaces rather than classes; business classes normally will implement one or more business interfaces. It is possible toforce the use of CGLIB, in those (hopefully rare) cases where you need to advise a method that is not declared on an interface, or where you need to pass a proxied object to a method as a concrete type.

It is important to grasp the fact that Spring AOP is proxy-based. See Section 11.6.1, 「Understanding AOP proxies」 for a thorough examination of exactly what this implementation detail actually means.

Spring AOP 並不實現對成員對象的賦值,更新(也就是咱們說的構造器,this.filed = something )的攔截。僅支持對註冊爲 Spring bean 的實體類的方法進行加強。

Spring AOP 默認使用 JDK動態代理 來爲實現了接口並具備切點(即便具備的切點並非實現的接口的方法)的類進行加強。而對不實現任何接口的類則默認使用 CGLIB代理。

Thus, for example, the Spring Framework’s AOP functionality is normally used in conjunction with the Spring IoC container. Aspects are configured using normal bean definition syntax (although this allows powerful "autoproxying" capabilities): this is a crucial difference from other AOP implementations. There are some things you cannot do easily or efficiently with Spring AOP, such as advise very fine-grained objects (such as domain objects typically): AspectJ is the best choice in such cases. However, our experience is that Spring AOP provides an excellent solution to most problems in enterprise Java applications that are amenable to AOP.

這是由於 Spring AOP 設計之初,目標就是和 Spring IoC container 一塊兒提供常見的優秀的java商用領域的解決方案。所以對於很是細節處進行加強,不是 Spring AOP 的目標,請使用 AspectJ 代替吧。

Spring AOP 使用的必需條件

In either case you will also need to ensure that AspectJ’s aspectjweaver.jar library is on the classpath of your application (version 1.6.8 or later). This library is available in the 'lib' directory of an AspectJ distribution or via the Maven Central repository.

必需準備好aspectjweaver.jar

關於使用 maven 引入aspectjweaver.jarpom.xml的代碼

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>本身寫</version>
</dependency>
@AspectJ refers to a style of declaring aspects as regular Java classes annotated with annotations. The @AspectJ style was introduced by the AspectJ project as part of the AspectJ 5 release. Spring interprets the same annotations as AspectJ 5, using a library supplied by AspectJ for pointcut parsing and matching. The AOP runtime is still pure Spring AOP though, and there is no dependency on the AspectJ compiler or weaver.

這一段說明了引入aspectjwearver不表明將底層實現從 JDK動態代理或CGLIB代理 改成了ASPECTJ。

必需通知 Spring 你要使用它。

顯然先要有 Spring 框架。咱們的官方文檔直接定位到了 Spring AOP 這塊,其實回到頂部看目錄,第一節的標題的就是 Getting Started with Spring ,若是 Spring 環境尚未配好,先按照文檔配好。

而後在一個配置類上增長@EnableAspectJAutoProxy註解

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}

XML寫法文檔有,此處略。

聲明一個類含有切面

請注意以前咱們對切面的理解:切面是對用於加強的方法按照必定理解和規則進行整理分類後的模塊,其中包含了符合這一模塊的概念和理解的用於加強的方法。而一個類含有切面,不表明它的全部方法均是切面,簡單來講,並未用於加強的方法便不包含在該類的切面中。

形如

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

@Aspect
public class NotVeryUsefulAspect {

}

XML寫法文檔有,此處略。

切面所在的類也可做爲源對象

Aspects (classes annotated with @Aspect) may have methods and fields just like any other class. They may also contain pointcut, advice, and introduction (inter-type) declarations.

如開頭所說,類是類,切面是切面,不過是這個切面(模塊)都在這個類中,不表明這個類就不正常了。

切面不能再被加強

In Spring AOP, it is not possible to have aspects themselves be the target of advice from other aspects. The @Aspect annotation on a class marks it as an aspect, and hence excludes it from auto-proxying.

切面聲明不足以讓切面被 Spring beans 容器發現

You may register aspect classes as regular beans in your Spring XML configuration, or autodetect them through classpath scanning - just like any other Spring-managed bean. However, note that the @Aspect annotation is not sufficient for autodetection in the classpath: For that purpose, you need to add a separate @Component annotation (or alternatively a custom stereotype annotation that qualifies, as per the rules of Spring’s component scanner).

公用切點規則表達式 @Pointcut 一次定義,到處引用。

When working with enterprise applications, you often want to refer to modules of the application and particular sets of operations from within several aspects. We recommend defining a "SystemArchitecture" aspect that captures common pointcut expressions for this purpose. A typical such aspect would look as follows:

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註解內,而這個註解則註解在一個方法上。以後向要應用該切點規則,只需在加強的註解的內容中寫被該@Pointcut註解的方法的全限定名,而沒必要反覆寫切點規則。

例子(本例中com.xyz.myapp.SystemArchitecture.dataAccessOperation()即是上面那段代碼裏的(最後那個)的切點規則):

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

}

切點規則表達式(value屬性的值)

接下來的內容通常是寫在@Pointcut這個定義切點規則和@Before`@After等定義加強時機的註解的括號中的。由於只有它(切點規則表達式),因此就被默認賦給了註解中的value屬性。實際上,還有argNames屬性可用。若是說這兩個都使用,那就必須明確指定value = "起點規則表達式"`。

這裏先談切點規則表達式。

切點規則中輔助用的限定符

如下限定符的格式省略了它們是被包在@Pointcut()切點聲明註解或者@Before@Around等標識加強時機的註解的括號之中的。

execution 限定符:接入點(方法)的全路徑,返回類型和權限符限定

execution - for matching method execution join points, this is the primary pointcut designator you will use when working with Spring AOP

Spring AOP users are likely to use the execution pointcut designator the most often. The format of an execution expression is:

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

All parts except the returning type pattern (ret-type-pattern in the snippet above), name pattern, and parameters pattern are optional. The returning type pattern determines what the return type of the method must be in order for a join point to be matched. Most frequently you will use * as the returning type pattern, which matches any return type. A fully-qualified type name will match only when the method returns the given type. The name pattern matches the method name. You can use the * wildcard as all or part of a name pattern. If specifying a declaring type pattern then include a trailing . to join it to the name pattern component. The parameters pattern is slightly more complex: () matches a method that takes no parameters, whereas (..) matches any number of parameters (zero or more). The pattern (*)matches a method taking one parameter of any type, (*,String) matches a method taking two parameters, the first can be of any type, the second must be a String. Consult the Language Semantics section of the AspectJ Programming Guide for more information.

  • modifiers-pattern

    • 權限修飾符,如publicprotectprivate
    • 能夠省略,表明沒有限制。
  • ret-type-pattern

    • 方法(接入點/切點是一個個方法)返回值的 type
    • 不可省略 但能夠用*表示全部 type 都行的意思。
    • 一旦指定必須使用全限定名,且接入點返回的值的 type 爲要求 type 的子類或實現類時亦不算符合。
  • declaring-type-pattern

    • 方法所屬的 type 的全限定名
    • 能夠省略,表明沒有限制。
    • 若是不省略,則與 name-pattern 間用一個.鏈接。
    • 沒有*能夠做爲通配符使用。要麼不寫,要麼寫全
  • name-pattern

    • 方法本身的名字
    • 不能夠省略。
    • 也能夠用*做爲通配符,好比set*表示以set開頭,後接零個多個其它字符的方法名的接入點。
  • param-pattern

    • 方法的參數表
    • 不能夠省略。
    • ()表示匹配不接收任何傳參的接入點。
    • (..)表示零個至多個參數均可以,即徹底不在這件事上有限制。
    • 能夠用*表明一個參數,但不限制該參數的 type。例如(*,String) 要求切入點接受兩個參數,第一個參數的 type 沒有限制,第二個參數必須是 String
    • 在指定一個 type 的狀況下,應當使用 type 的全限定名。匹配時只會認準這個 type ,對於該 type 的子類或實現類,不認爲匹配。
  • throws-pattern

    • 能夠省略,表明沒有限制。
    • 方法可能拋出的異常。

一些例子

  • the execution of any public method:
execution(public * *(..))
  • the execution of any method with a name beginning with "set":
execution(* set*(..))
  • the execution of any method defined by the AccountService interface:
execution(* com.xyz.service.AccountService.*(..))
  • the execution of any method defined in the service package:
execution(* com.xyz.service.*.*(..))
  • the execution of any method defined in the service package or a sub-package:
execution(* com.xyz.service..*.*(..))

within 限定符:限定到某個 type

within - limits matching to join points within certain types (simply the execution of a method declared within a matching type when using Spring AOP)

在某一 type 內部的全部接入點。以前在詞彙理解時就已經探討過, type 用於寫 class 感受並不完善的狀況下。這裏就是一個例子,若是要完善的話,顯然還要說接口,說包(package)才完善(固然我也不肯定這樣是否完善了)。

一些例子

  • any join point (method execution only in Spring AOP) within the service package:
within(com.xyz.service.*)
  • any join point (method execution only in Spring AOP) within the service package or a sub-package:
within(com.xyz.service..*)

this限定符和target限定符:限定調用接入點的實例是指定 type 的實例

this - limits matching to join points (the execution of methods when using Spring AOP) where the bean reference (Spring AOP proxy) is an instance of the given type

target - limits matching to join points (the execution of methods when using Spring AOP) where the target object (application object being proxied) is an instance of the given type

這兩個限定符我網上也找了很多資料,可是都沒有舉具體的實例,讓人十分困惑。因此對於二者的區別做爲疑問留存,沒法給出。二者的共性效果就是限制調用接入點的實例是被指定的 type (類或者接口)的實例

舉例說明;

在一個基礎的引入了AOP的 Spring Boot 應用中

@SpringBootApplication
@EnableAspectJAutoProxy
public class LogbackandaopApplication {

    public static void main(String[] args) {
        SpringApplication.run(LogbackandaopApplication.class, args);
        
        //其中DemoA實現了IDemo接口,而DemoB未實現任何接口
        DemoA demoA = (DemoA) ApplicationContextProvider.getBean("demoA");
        DemoB demoB = (DemoB) ApplicationContextProvider.getBean("demoB");
        demoA.IDemoTest();
        demoB.IDemoTest();
    }
}

其中:

package xyz.d613.logbackandaop.demo;

public interface IDemo {
    void IDemoTest();
}
@Component
public class DemoA implements IDemo {
    @Override
    public void IDemoTest() {
        System.out.println("A");
    }
}
/*
注意!DemoB類未實現任何接口!
*/
@Component
public class DemoB {

    public void IDemoTest() {
        System.out.println("B");
    }
}

定義切面爲

@Component
@Aspect
public class MyAspect {
    @Before("execution(* *.IDemoTest(..)) && this(...logbackandaop.demo.IDemo)")
    public void advice() throws Throwable {
        System.out.println("MyAspect advice");
    }
}

運行此應用,獲得控制檯上的輸出:

MyAspect advice
A
B

能夠發現A被前置加強了,而B沒有。這就是this(...logbackandaop.demo.IDemo)在起做用。儘管B擁有知足execution(* *.IDemoTest(..))的方法,但它不是IDemo接口的實例。故被排除。

args 限定符:限定接入點中的參數表(按給出順序)的各個參數是指定 type 或它的子/實現類

args - limits matching to join points (the execution of methods when using Spring AOP) where the arguments are instances of the given types

限定接入點爲接入點被調用時傳入的實參(按照順序地)都是args限定符中聲明的 type 的實例。

Note that the pointcut given in this example is different to execution(* *(java.io.Serializable)): the args version matches if the argument passed at runtime is Serializable, the execution version matches if the method signature declares a single parameter of type Serializable.

假設有以下的類型

public interface A;
public class B implements A;

那麼切點@Pointcut(execution(* *(A))不會對public anytype method(B);生效。但@Pointcut(args(A))則會對public anytype method(B);攔截並進行加強。這就是所謂 the argument passed at runtime

利用args限定符使加強得到切點的參數

只限定頭部幾個參數,後面的不關心該怎麼寫,跳轉過去後注意看加粗部分與上下文。

@target限定符 @within限定符:限定接入點爲調用者必須指定的註解的接入點

二者的區別留做疑問。

@target - limits matching to join points (the execution of methods when using Spring AOP) where the class of the executing object has an annotation of the given type

  • any join point (method execution only in Spring AOP) where the target object has an @Transactional annotation:
@target(org.springframework.transaction.annotation.Transactional)

@within - limits matching to join points within types that have the given annotation (the execution of methods declared in types with the given annotation when using Spring AOP)

  • any join point (method execution only in Spring AOP) where the declared type of the target object has an @Transactional annotation:
@within(org.springframework.transaction.annotation.Transactional)

限制接入點被調用時,調用它的實例所屬的類必須帶有指定的註解。在上述例子中爲@Transactional

@args限定符:限定被傳入的實參所屬的類(按指定的順序地)有被要求的註解

@args - limits matching to join points (the execution of methods when using Spring AOP) where the runtime type of the actual arguments passed have annotations of the given type(s)

  • any join point (method execution only in Spring AOP) which takes a single parameter, and where the runtime type of the argument passed has the @Classifiedannotation:
@args(com.xyz.security.Classified)

限定接入點參數的數量的同時,對應本身指定的順序,被傳入的實參的所屬的類有被@args限定符指定的註解。

@annotation限定符:限定接入點自身有被限定符指定的註解

@annotation - limits matching to join points where the subject of the join point (method being executed in Spring AOP) has the given annotation

  • any join point (method execution only in Spring AOP) where the executing method has an @Transactional annotation:
@annotation(org.springframework.transaction.annotation.Transactional)

接入點(指方法自己而不是其所在的類)要帶有@annotation限定符指定的註解。

不知爲什麼對java.lang.Override註解無效。

bean限定符:限定接入點是被限定符指定的 bean 中的接入點

Spring AOP also supports an additional PCD named bean. This PCD allows you to limit the matching of join points to a particular named Spring bean, or to a set of named Spring beans (when using wildcards). The bean PCD has the following form:

bean(idOrNameOfBean)

The idOrNameOfBean token can be the name of any Spring bean: limited wildcard support using the * character is provided, so if you establish some naming conventions for your Spring beans you can quite easily write a bean PCD expression to pick them out. As is the case with other pointcut designators, the bean PCD can be &&'ed, ||'ed, and ! (negated) too.

可使用通配符以一次性指定多個 bean

使用 Bean 這個術語,就意味着其必須是被 Spring IOC 所管理的類,即帶有@Component@Controller之類的註解或經過配置交由Spring的Bean容器管理。

argNames屬性

上一節開篇已經說過

接下來的內容通常是寫在 @Pointcut這個定義切點規則和 @Before`@After 等定義加強時機的註解的括號中的。由於只有它(切點規則表達式),因此就被默認賦給了註解中的value 屬性。實際上,還有argNames 屬性可用。若是說這兩個都使用,那就必須明確指定value = "起點規則表達式"`。

這一節關於另外一個屬性:argNames

The parameter binding in advice invocations relies on matching names used in pointcut expressions to declared parameter names in (advice and pointcut) method signatures. Parameter names are not available through Java reflection, so Spring AOP uses the following strategies to determine parameter names:

這裏用個人例子。

假設有這麼一個類有這麼一個方法想要被加強:

package root.demo
    
public class Demo {
    public void printf(String info){
        System.out.println(info);
    }
}

肯定切點切到這個接入點身上並不麻煩,如下兩種寫法都行:

package root.aspects;

@Aspect
public class MyAspect {

    @Before("execution(* root.demo.Demo.printf(java.lang.String))")
    public void advice(){
    }
    
    //另外一種寫法
    @Before("execution(* root.demo.Demo.printf(..) && args(java.lang.String)")
    public void advice(){
    }
}

然而問題在於,我想要得到切點中的那個被傳入的String類型的參數,怎麼辦?這樣寫麼?

@Before("execution(* root.demo.Demo.printf(java.lang.String))")
    public void advice(String s){
    }

這裏的s是不會獲得任何值的。

那麼要怎麼作呢,官方文檔的意思,這麼寫:

@Before(value = "execution(* xyz.d613.logbackandaop.demo.Demo.printf(..)) && args(s))",
    argNames = "s")
    public void advice(String s){
        System.out.println(s);
    }

請注意,此處args(s)中,雖然args還是限定符,但其內部的值顯然不多是一個 type 了。在使用argNames的使用,原來限定符的用法發生了改變。寫在value中的各個限定符(排除execution)中的值老是要和argNames屬性中的值存在一一對應關係,而argNames中的值又於加強的函數簽名的參數表中的參數一一對應,如這裏的argNames="s"中的spublic void advice(String s)中的String s中的s對應。

這樣創建起聯繫後,傳入切點中的參數就能被傳入加強中並交給指定加強的方法簽名中的指定的參數。同時 Spring AOP 也會反過來(因爲String s的這個String)意識到至關於有一個args(java.lang.String)的限定符。

If the first parameter is of the JoinPoint, ProceedingJoinPoint, or JoinPoint.StaticPart type, you may leave out the name of the parameter from the value of the "argNames" attribute. For example, if you modify the preceding advice to receive the join point object, the "argNames" attribute need not include it:

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

The special treatment given to the first parameter of the JoinPoint, ProceedingJoinPoint, and JoinPoint.StaticPart types is particularly convenient for advice that do not collect any other join point context. In such situations, you may simply omit the "argNames" attribute. For example, the following advice need not declare the "argNames" attribute:

@Before("com.xyz.lib.Pointcuts.anyPublicMethod()")
public void audit(JoinPoint jp) {
    // ... use jp
}

這部分在說JointPoint接口以及它的子接口,實現類,在做爲加強的參數表中的一員時,不須要額外準備,只要寫好(JointPoint jp...)(其中JointPoint可用特定實現類或子接口換掉,具體看其它部分)便可。

關於這部分知識更多細節,見獲知被加強的切點的相關信息

加強的執行順序

@Before 前置加強

Before advice

Before advice is declared in an aspect using the @Before annotation:、

@AfterReturnning 返回後加強

After returning advice

After returning advice runs when a matched method execution returns normally. It is declared using the @AfterReturning annotation:

獲取返回後加強的返回值該怎麼寫

Sometimes you need access in the advice body to the actual value that was returned. You can use the form of @AfterReturning that binds the return value for this:

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

}

The name used in the returning attribute must correspond to the name of a parameter in the advice method. When a method execution returns, the return value will be passed to the advice method as the corresponding argument value. A returning clause also restricts matching to only those method executions that return a value of the specified type ( Object in this case, which will match any return value).

Please note that it is not possible to return a totally different reference when using after-returning advice.

@AfterThrowing 拋出異常後加強

After throwing advice

After throwing advice runs when a matched method execution exits by throwing an exception. It is declared using the @AfterThrowing annotation:

限制加強對應的異常類型/獲取拋出的異常

Often you want the advice to run only when exceptions of a given type are thrown, and you also often need access to the thrown exception in the advice body. Use thethrowing attribute to both restrict matching (if you don't need restrict the exception type, use Throwable as the exception type otherwise) and bind the thrown exception to an advice parameter.

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

}

The name used in the throwing attribute must correspond to the name of a parameter in the advice method. When a method execution exits by throwing an exception, the exception will be passed to the advice method as the corresponding argument value. A throwing clause also restricts matching to only those method executions that throw an exception of the specified type ( DataAccessException in this case).

@After 後置加強

After (finally) advice

After (finally) advice runs however a matched method execution exits. It is declared using the @After annotation. After advice must be prepared to handle both normal and exception return conditions. It is typically used for releasing resources, etc.

從文檔中的括號也能夠看出來,後置加強至關於對應try{}catch(){}finally{}中的finally加強。對於拋出異常致使的調用結束或正常返回的調用結束,它均能處理。它常常用來釋放資源。

@Around 環繞加強

Around advice

The final kind of advice is around advice. Around advice runs "around" a matched method execution. It has the opportunity to do work both before and after the method executes, and to determine when, how, and even if, the method actually gets to execute at all. Around advice is often used if you need to share state before and after a method execution in a thread-safe manner (starting and stopping a timer for example). Always use the least powerful form of advice that meets your requirements (i.e. don’t use around advice if simple before advice would do).

環繞加強能在調用前加強,也能在調用後加強,更能同時實現二者,還能通過它決定被調用的方法到底會不會真的被調用。

若是須要以線程安全的方式(例如,啓動和中止計時器)在方法執行以前和以後共享狀態,則一般使用環繞加強。

在能夠不用環繞加強的情景下,儘可能不要使用環繞加強,不然會帶來額外的開支。

使用環繞加強決定切點是否被真的調用

Around advice is declared using the @Around annotation. The first parameter of the advice method must be of type ProceedingJoinPoint. Within the body of the advice, calling proceed() on the ProceedingJoinPoint causes the underlying method to execute. The proceed method may also be called passing in an Object[] - the values in the array will be used as the arguments to the method execution when it proceeds.

加強的參數聲明的第一個必須是ProceddingJointPoint的實例或其子類的實例(不過它本身實際上是org.aspectj.lang.JoinPoint的實現類)。在加強的方法體中使用ProceddingJointPoint#proceed(Object ...)方法,纔會使切點被真的調用。若是不使用此方法,則切點不會被真正調用。

ProceddingJointPoint#proceed(Object ...)的返回值就是切點的返回值,實際傳入的參數(對應Object ...則應該是切點所須要的參數。

同一時機多個加強衝突時的執行策略

What happens when multiple pieces of advice all want to run at the same join point? Spring AOP follows the same precedence rules as AspectJ to determine the order of advice execution. The highest precedence advice runs first "on the way in" (so given two pieces of before advice, the one with highest precedence runs first). "On the way out" from a join point, the highest precedence advice runs last (so given two pieces of after advice, the one with the highest precedence will run second).

高優先級的加強在切點被調用前的時機的加強執行順序中更靠前,在切點被調用後的時機的加強執行順序中更靠後。

When two pieces of advice defined in different aspects both need to run at the same join point, unless you specify otherwise the order of execution is undefined. You can control the order of execution by specifying precedence. This is done in the normal Spring way by either implementing the org.springframework.core.Orderedinterface in the aspect class or annotating it with the Order annotation. Given two aspects, the aspect returning the lower value from Ordered.getValue() (or the annotation value) has the higher precedence.

When two pieces of advice defined in the same aspect both need to run at the same join point, the ordering is undefined (since there is no way to retrieve the declaration order via reflection for javac-compiled classes). Consider collapsing such advice methods into one advice method per join point in each aspect class, or refactor the pieces of advice into separate aspect classes - which can be ordered at the aspect level.

當兩個相同優先級的加強在同一個切點的同一個時機時,它們的執行順序順序沒法肯定。若是這兩個加強所在的切面不一樣,但你可使用@Order註解(或實現org.springframework.core.OrderedgetValue()方法)在切面上來肯定兩個加強(實際上是切面)的優先級。@Order的值更小的那個優先級更高。

但若是是在同一個切面,那就無法子了。並且既然是同一個切面,那麼請你好好整理一下代碼,避免這種狀況出現吧,由於這樣可能引發很糟糕的後果。


實話實說我驚呆了,到這裏竟然就結束了,那麼五種加強衝突時怎麼整?好比前置加強和環繞加強在調用前這一時機,誰先誰後,官方文檔不告知的?秀啊!

通過網上資料的彙總和本身的實驗,能夠認爲有如下結論:

  1. 前面所謂的優先級是同種類加強之間才用得上的,不一樣種類間的加強的執行順序不能被調控。
  2. 在調用前這個時機上,環繞加強先於前置加強。能夠這麼理解——環繞加強有阻止切點被真實調用的能力,而且是默認阻止的。因此環繞加強必需要先於一切拿到切點。
  3. 後置加強後於環繞加強在調用後的加強。在調用後這個時機上,返回後加強和拋出異常後加強屬於兩個徹底不一樣的時機(一個函數要麼拋出異常結束,要麼正常返回結束,不可能二者同時發生),通常不會衝突。而二者都後於後置加強。

獲知被加強的切點的相關信息

獲取切點:接口org.aspectj.lang.JoinPoint

Any advice method may declare as its first parameter, a parameter of type org.aspectj.lang.JoinPoint (please note that around advice is required to declare a first parameter of type ProceedingJoinPoint, which is a subclass of JoinPoint. The JoinPoint interface provides a number of useful methods such as getArgs()(returns the method arguments), getThis() (returns the proxy object), getTarget() (returns the target object), getSignature() (returns a description of the method that is being advised) and toString() (prints a useful description of the method being advised). Please do consult the javadocs for full details.

If the first parameter is of the JoinPoint, ProceedingJoinPoint, or JoinPoint.StaticPart type, you may leave out the name of the parameter from the value of the "argNames" attribute. For example, if you modify the preceding advice to receive the join point object, the "argNames" attribute need not include it:

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

The special treatment given to the first parameter of the JoinPoint, ProceedingJoinPoint, and JoinPoint.StaticPart types is particularly convenient for advice that do not collect any other join point context. In such situations, you may simply omit the "argNames" attribute. For example, the following advice need not declare the "argNames" attribute:

@Before("com.xyz.lib.Pointcuts.anyPublicMethod()")
public void audit(JoinPoint jp) {
    // ... use jp
}

以上兩段引用在官方文檔中亦不在同一個地方,但整理認爲它們所表達的意思只有一個:

任何加強的參數聲明均可以不作額外準備地將第一個參數聲明爲(JointPoint jp),好比

@Aspect
public class MyAspect {
    @Before("execution(* *.IDemoTest(..))")
    public void advice(org.aspectj.lang.JoinPoint jp) throws Throwable {
        System.out.println("MyAspect advice");
        System.out.println(jp);
        System.out.println(jp.getSignature());
    }
}

Spring AOP 會自動地將切點,加強的相關信息封裝爲接口JointPoint的實例(如前面的環繞加強所用的,一般是一個ProceedingJointPoint,因此通常寫參數的時候也是寫ProceedingJointPoint而不是直接寫這個接口)。同時接口JointPoint也提供了一些用於調取出被封裝的信息的方法,這個實例必然會將之實現。那麼具體有哪些信息以及怎麼使用,請見官方文檔

不過較爲經常使用的方法其實文檔(上面摘抄過來的引用)中已經給出了。

獲取切點的參數

第一種方法就是使用上面一節,先獲取切點相關信息,即JoinPoint得實例,而後經過JoinPoint#getArgs()方法得到切點的參數們,一個Object[]數組。

第二種方法,文檔介紹的使用args限定符。

We’ve already seen how to bind the returned value or exception value (using after returning and after throwing advice). To make argument values available to the advice body, you can use the binding form of args. If a parameter name is used in place of a type name in an args expression, then the value of the corresponding argument will be passed as the parameter value when the advice is invoked. An example should make this clearer. Suppose you want to advise the execution of dao operations that take an Account object as the first parameter, and you need access to the account in the advice body. You could write the following:

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

The args(account,..) part of the pointcut expression serves two purposes: firstly, it restricts matching to only those method executions where the method takes at least one parameter, and the argument passed to that parameter is an instance of Account; secondly, it makes the actual Account object available to the advice via the account parameter.

例子的@Before註解的前半段,是在引用com.xyz.myapp.SystemArchitecture.dataAccessOperation()上的@Pointcut註解所定義的切點規則。後半段則是再在前半段篩出來的切點中篩出第一個參數爲Account以後有什麼參數,有沒有參數不限制的接入點。這裏爲何第一個參數爲Account,不是由於args(account,..)中寫了 account, 而後 Spring 會自動把首字母大寫,而後明白過來是 Account 類。而是由於這個accountpublic void validateAccount(Account account)中的account對應,因而 Spring 從 Account account 中得知args(account,..)中的account要求的是一個Account的實例。實際上將account改成dfaljlkdj,只要加強的參數表也跟着改成public void validateAccount(Account dfaljlkdj),那麼一切就能正常工做。

上文說到,@Before註解的前半段是引用一個切點規則。那麼可否直接讓那個切點把後半段的事情也作了,我就一個引用完事呢?答案是能夠,寫法以下:

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

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

仿照獲取切點的參數,獲取切點的註解,代理對象,源對象...

The proxy object ( this), target object ( target), and annotations ( @within, @target, @annotation, @args) can all be bound in a similar fashion. The following example shows how you could match the execution of methods annotated with an @Auditable annotation, and extract the audit code.

First the definition of the @Auditable annotation:

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

And then the advice that matches the execution of @Auditable methods:

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

切點中有泛型

Spring AOP can handle generics used in class declarations and method parameters. Suppose you have a generic type like this:

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

You can restrict interception of method types to certain parameter types by simply typing the advice parameter to the parameter type you want to intercept the method for:

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

首先,一個切點有泛型,那麼它(切點是一個個方法)所屬的 type 必然能接收泛型(如引用中的第一段代碼),因此在 type 與方法之間的點前添加一個加號:Sample+.sampleGenericMethod(*),代表這個 type 要接收泛型。

使人遺憾的是,這個MyType並無什麼值得稱道的功能——我還覺得這個對應泛型裏的T,因此我能夠經過param.getClass()來得到被加強的切點的泛型的具體類型。但實際上這個MyType就是表示這個地方由你來填,沒有什麼功能。若是相不對泛型的類型作限制——實際上只不過是不對切點的第一個參數,即param的類型作限制,只不過在例子中paramT param,看起來就像是對泛型有限制同樣——就填入Object

So you cannot define a pointcut like this:

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

To make this work we would have to inspect every element of the collection, which is not reasonable as we also cannot decide how to treat null values in general. To achieve something similar to this you have to type the parameter to Collection and manually check the type of the elements.

不能使用Collection及其實現類和子接口加泛型來篩選參數的類型。

相關文章
相關標籤/搜索