官方文檔傳送門html
本文僅涉及從 11. Aspect Oriented Programming with Spring 到 11.2.5 Introductions 之間的內容, 11.2.5 Introductions 以後的講的是引入的內容,也與本文無關。java
本文中全部被使用引用格式的文字均來自於官方文檔,能夠打開官方文檔後在網頁內搜索定位,以更好理解上下文。同時本文的內容的順序並不徹底與官方文檔一致,對一些內容捨棄不歸入,對一些內容整理和提早了幾節。web
type : 能夠認爲此處使用 class 不夠完備,由於還有 interface 之類的也符合當前語句的說明。因此使用 type 。spring
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@Aspect
annotation (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 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 代替吧。
In either case you will also need to ensure that AspectJ’saspectjweaver.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.jar
的pom.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.
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 - 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
public
,protect
,private
。ret-type-pattern
*
表示全部 type 都行的意思。declaring-type-pattern
.
鏈接。*
能夠做爲通配符使用。要麼不寫,要麼寫全。name-pattern
*
做爲通配符,好比set*
表示以set開頭,後接零個多個其它字符的方法名的接入點。param-pattern
()
表示匹配不接收任何傳參的接入點。(..)
表示零個至多個參數均可以,即徹底不在這件事上有限制。*
表明一個參數,但不限制該參數的 type。例如(*,String)
要求切入點接受兩個參數,第一個參數的 type 沒有限制,第二個參數必須是 String
。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 - 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 - 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 typetarget - 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 - 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 toexecution(* *(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 typeSerializable
.
假設有以下的類型
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 。
只限定頭部幾個參數,後面的不關心該怎麼寫,跳轉過去後注意看加粗部分與上下文。
二者的區別留做疑問。
@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 - 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
@Classified
annotation:@args(com.xyz.security.Classified)
限定接入點參數的數量的同時,對應本身指定的順序,被傳入的實參的所屬的類有被@args限定符指定的註解。
@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
註解無效。
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). Thebean
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 abean
PCD expression to pick them out. As is the case with other pointcut designators, thebean
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"
中的s
與public 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
, orJoinPoint.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
, andJoinPoint.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. Areturning
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 the
throwing
attribute to both restrict matching (if you don't need restrict the exception type, useThrowable
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. Athrowing
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 typeProceedingJoinPoint
. Within the body of the advice, callingproceed()
on theProceedingJoinPoint
causes the underlying method to execute. Theproceed
method may also be called passing in anObject[]
- 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 theorg.springframework.core.Ordered
interface in the aspect class or annotating it with theOrder
annotation. Given two aspects, the aspect returning the lower value fromOrdered.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.Ordered
的getValue()
方法)在切面上來肯定兩個加強(實際上是切面)的優先級。@Order
的值更小的那個優先級更高。
但若是是在同一個切面,那就無法子了。並且既然是同一個切面,那麼請你好好整理一下代碼,避免這種狀況出現吧,由於這樣可能引發很糟糕的後果。
實話實說我驚呆了,到這裏竟然就結束了,那麼五種加強衝突時怎麼整?好比前置加強和環繞加強在調用前這一時機,誰先誰後,官方文檔不告知的?秀啊!
通過網上資料的彙總和本身的實驗,能夠認爲有如下結論:
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 typeProceedingJoinPoint
, which is a subclass ofJoinPoint
. TheJoinPoint
interface provides a number of useful methods such asgetArgs()
(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) andtoString()
(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
, orJoinPoint.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
, andJoinPoint.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 ofAccount
; secondly, it makes the actualAccount
object available to the advice via theaccount
parameter.
例子的@Before
註解的前半段,是在引用com.xyz.myapp.SystemArchitecture.dataAccessOperation()
上的@Pointcut
註解所定義的切點規則。後半段則是再在前半段篩出來的切點中篩出第一個參數爲Account
,以後有什麼參數,有沒有參數不限制的接入點。這裏爲何第一個參數爲Account
,不是由於args(account,..)
中寫了 account, 而後 Spring 會自動把首字母大寫,而後明白過來是 Account
類。而是由於這個account
和public 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
的類型作限制,只不過在例子中param
是T 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 toCollection
and manually check the type of the elements.
不能使用Collection
及其實現類和子接口加泛型來篩選參數的類型。