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及其实现类和子接口加泛型来筛选参数的类型。

相关文章
相关标签/搜索