Spring Core Middleware 源码分析一:官方文档阅读笔记

Reference

http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#overview-aop-instrumentation

前言

通读官方文档中有关 Spring 中间件的章节,摘录核心论点,翻译,并做重要批注;

本文为作者的原创作品,转载需注明出处;

未完待续;

大纲

从 Spring Framework 的整体架构图上可以看到,核心的中间件主要包括 AOPAspectsInstrumentation以及Messaging

【2.2.2】AOP and Instrumentation

The spring-aop module provides an AOP Alliance-compliant aspect-oriented programming implementation allowing you to define, for example, method interceptors and pointcuts to cleanly decouple code that implements functionality that should be separated. Using source-level metadata functionality, you can also incorporate behavioral information into your code, in a manner similar to that of .NET attributes.

The separate spring-aspects module provides integration with AspectJ.

The spring-instrument module provides class instrumentation support and classloader implementations to be used in certain application servers. The spring-instrument-tomcat module contains Spring’s instrumentation agent for Tomcat.

【11】Aspect Oriented Programming with Spring

【11.1】 Introduction

Aspect-Oriented Programming (AOP) complements Object-Oriented Programming (OOP) by providing another way of thinking about program structure. The key unit of modularity in OOP is the class, whereas in AOP the unit of modularity is the aspect. Aspects enable the modularization of concerns such as transaction management that cut across multiple types and objects. (Such concerns are often termed crosscutting concerns in AOP literature.)

AOP(面向方面(切面)编程)从另一个思考程序结构的方面弥补了面向对象编程(OOP)的不足;构成 OOP 的关键模块化组件是 class,而相对于 AOP,构成其的关键模块化组件是 aspect;Aspects 通过将逻辑切入多个 types 和 objects 的方式以实现其模块化的方式,比如 transaction management 就是通过这样的方式得以实现的;(这样的相关的行为通常在 AOP 的字典里被称作 crosscutting 横切

One of the key components of Spring is the AOP framework. While the Spring IoC container does not depend on AOP, meaning you do not need to use AOP if you don’t want to, AOP complements Spring IoC to provide a very capable middleware solution.

AOP framework 是 Spring 其中一个关键的组件;当然 Spring IoC 容器并不依赖于 AOP,所以,你在开发自己的应用的时候也可以不使用它;不过 AOP 作为 Spring IoC 的一个补充,它提供了一个非常有能力的中间件解决方案;

AOP is used in the Spring Framework to…​

  • …​ provide declarative enterprise services, especially as a replacement for EJB declarative services. The most important such service is declarative transaction management.
    提供声明式的企业级服务,特别的,作为 EJG 声明式服务的替换;
  • …​ allow users to implement custom aspects, complementing their use of OOP with AOP.

【11.1.1】 AOP concepts

Let us begin by defining some central AOP concepts and terminology. These terms are not Spring-specific…​ unfortunately, AOP terminology is not particularly intuitive; however, it would be even more confusing if Spring used its own terminology.

首选,下面的这些概念并非是 Spring 专有的,AOP 的专业术语并非特别直观的;

  • 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: 与模块化相关的概念,将多个 classes 进行横向切割;Transaction management 就是一个使用横向切割概念的企业级 Java 应用的例子;在 Spring AOP 中,aspects 通过使用普通的 classes (通过使用 schema-based approach 的方式 )或者使用@Aspect style 注解普通 classes 的方式得以实现;

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

Join point: 当程序执行时候其中的一个点,比如某个方法入口或者是异常处理的地方;在 Spring AOP 中,一个 join point 通常代码的是一个方法执行的入口;

  • 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: 表示 AOP 在一个 join point 上要执行的动作;包括 “around”,”before” 以及 “after” 等不同种类的 advice 类型;在 Spring 以及大多数 AOP 框架中,将 advice 定义为 interceptor(拦截器),可以在一个 join point 上定义一系列的 around interceptors

  • 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: 一个用来匹配 joint pointspredicate (语句,这里就是后面所指的 pointcut expression);Advicepointcut expression 相关联,任何 join point 只要匹配pointcut expressionAdvice 便会执行;join points 匹配 pointcut expression 是 AOP 的核心,Spring 默认使用 AspectJ pointcut expression 语言

  • 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 声明额外的方法和属性;Spring AOP 允许你给任何的 Advised object (被切面的对象) 增加新的接口以及接口的实现。

  • 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: 一个被一个或者多个通过 asepct 技术被拦截(advised)的对象,被称作advised object;因为 Spring AOP 是通过运行时代码技术实现的,所以该advised 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: 通过 AOP 框架实现的代理对象,它的目的是去实现 aspect contracts 切面约定。Spring 框架中,AOP proxy 是通过 JDK dynamic proxy 或者 CGLIB proxy 实现的。

  • 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: _连接_由第三方应用的 types 或者 objects 所定义的 aspects,并根据该 aspects 定义在 Spring 容器中创建新的 advised object。可以在编译时期(使用 AspectJ compiler),加载使其,或者是在运行使其。Spring AOP,是在运行使其使用 weaving (织入技术);

Types of advice:

  • 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).

Before advice: 在 join point 之前执行的行为,但是它并不能终止某个方法(join point)的执行,除非它抛出异常;

  • After returning advice: Advice to be executed after a join point completes normally: for example, if a method returns without throwing an exception.

After returning advice: 当方法(join point)正常执行结束后所执行的动作;

  • After throwing advice: Advice to be executed if a method exits by throwing an exception.

    After throwing advice: 当某个方法(join point)执行过程中抛出异常后所执行的动作;

  • After (finally) advice: Advice to be executed regardless of the means by which a join point exits (normal or exceptional return).

    After (finally) advice: 不管是某个方法正常或者异常退出后都会执行该 Advice;

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

    Around advice: 一个包裹在一个方法(join point)调用上的(Advice)动作;这是一个最具威力的 advice;Around advice 可以在一个方法调用之前和调用之后执行相应的动作;它可以选择是否执行到某个 join point (方法) 或者是替换原有方法的返回对象为 advice 方法所提供的返回对象;

Around advice is the most general kind of advice. Since Spring AOP, like AspectJ, provides a full range of advice types, we recommend that you use the least powerful advice type that can implement the required behavior. For example, if you need only to update a cache with the return value of a method, you are better off implementing an after returning advice than an around advice, although an around advice can accomplish the same thing. Using the most specific advice type provides a simpler programming model with less potential for errors. For example, you do not need to invoke the proceed() method on the JoinPoint used for around advice, and hence cannot fail to invoke it.

Around advice 是最通用的 advice 类型;后续是作者建议大家适当的使用 Around advice,因为其功能太过于强大;

【11.1.2】 Spring AOP capabilities and goals

Spring AOP is implemented in pure Java. There is no need for a special compilation process. Spring AOP does not need to control the class loader hierarchy, and is thus suitable for use in a Servlet container or application server.

Spring AOP 是通过简单的 Java 对象实现的,不需要特殊的编译过程;Spring AOP 同样不需要控制 class loader 的继承关系,因此,它适合在 Servlet container 以及 application server 中使用;

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 目前仅支持方法级别的 join points;拦截 Field 的方式当前不支持,如果你需要使用 Field 级别的拦截,考虑使用 AspectJ

Spring AOP’s approach to AOP differs from that of most other AOP frameworks. The aim is not to provide the most complete AOP implementation (although Spring AOP is quite capable); it is rather to provide a close integration between AOP implementation and Spring IoC to help solve common problems in enterprise applications.

Spring AOP 的目的和大多数其他的 AOP 框架不同,它的目的不是去提供最全面的 AOP 实现,而是为了能够很好的与 Spring IoC 结合以解决大多数企业级应用当中的常见问题;

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 容器结合使用的;Aspects 是通过使用 bean definition 语法来进行配置的:这一点是与其它 AOP 实现之间的主要区别所在;因此,有些事情你不能通过 Spring AOP 简单或者有效的实现,比如,你想直接在一个 fine-grained objects 上定义切面,要这样做,直接使用 AspectJ 是更好的选择;但,Spring AOP 在解决企业级 Java 应用的问题非常的优秀;

Spring AOP will never strive to compete with AspectJ to provide a comprehensive AOP solution. We believe that both proxy-based frameworks like Spring AOP and full-blown frameworks such as AspectJ are valuable, and that they are complementary, rather than in competition. Spring seamlessly integrates Spring AOP and IoC with AspectJ, to enable all uses of AOP to be catered for within a consistent Spring-based application architecture. This integration does not affect the Spring AOP API or the AOP Alliance API: Spring AOP remains backward-compatible. See the following chapter for a discussion of the Spring AOP APIs.

Spring AOP 将不会试图像 AspectJ 那样提供无所不包的 AOP 解决方案;我们相信,Spring AOP 和 AspectJ 是互补关系的,而不是相互竞争的关系;Spring 可以无缝的集成 AspectJ 到 Spring 容器当中…..

【11.1.3】 AOP Proxies

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 默认使用标准的 JDK dynamic proxies 作为其 AOP proxies;这使得任意的接口都可以被代理;

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 to force 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.

Spring AOP 同样可以使用 CGLIB 代理的方式;CGLIB 的使用场景是,如果业务对象没有使用接口实现的时候,可以通过 CGLIB 基于 Class 对象的基础上生成相应的代理;不过,好的实践是,最好是使用 interfaces 编码而不是直接使用 classes 进行编码;当然也可以强制使用 CGLIB..

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.

【11.2】 @AspectJ support

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

@AspectJ 是通过在普通的 Java classes 对象上声明 annotations 来声明 aspects 的方式;有关 @AspectJ 风格的描述参考 AspectJ project,作为 AspectJ 5 发布版本的一部分,Spring AOP 通过使用 AspectJ 类库中所提供的 pointcunt parsing 和 matching 工具;AOP 的运行时依然是 Spring AOP,而且无需依赖 AspectJ 的编译器和织入的工具;

Using the AspectJ compiler and weaver enables use of the full AspectJ language, and is discussed in Section 11.8, “Using AspectJ with Spring applications”.

使用 AspectJ 的编译器和织入工具将会启用 full AspectJ language;

【11.2.1】 Enabling @AspectJ Support

To use @AspectJ aspects in a Spring configuration you need to enable Spring support for configuring Spring AOP based on @AspectJ aspects, and autoproxying beans based on whether or not they are advised by those aspects. By autoproxying we mean that if Spring determines that a bean is advised by one or more aspects, it will automatically generate a proxy for that bean to intercept method invocations and ensure that advice is executed as needed.

要在 Spring 容器中使用 @AspectJ asepct,你需要通过使用 @Aspect 注解的方式将 beans 声明为 aspects;是不是 autoproxying beans 是基于该 beans 是否是通过 @AspectJ 声明的 aspects;autoproxying 的意思就是,如果 Spring 发现 beans 是被一个或者多个 aspects advised,那么,Spring 将会自动的为这些 bean 生成 proxy,通过该 proxy 去拦截 bean 的方法调用以及以及确保 advice 方法( 某个 target bean 的方法被拦截后需要执行的方法 ) 能够按需执行;

The @AspectJ support can be enabled with XML or Java style configuration. In either case you will also need to ensure that AspectJ’s aspectjweaver.jarlibrary 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.

可以通过 XML 或者 Java 风格的配置的方式来激活 @AspectJ;无论使用哪种方式,你都要确保aspectjweaver.jar在你的 classpath 当中;

Enabling @AspectJ Support with Java configuration

To enable @AspectJ support with Java @Configuration add the @EnableAspectJAutoProxy annotation:

1
2
3
4
5
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}

Enabling @AspectJ Support with XML configuration

To enable @AspectJ support with XML based configuration use the aop:aspectj-autoproxy element:

1
<aop:aspectj-autoproxy/>

This assumes that you are using schema support as described in Chapter 41, XML Schema-based configuration. See Section 41.2.7, “the aop schema” for how to import the tags in the aop namespace.

【11.2.2】 Declaring an aspect

With the @AspectJ support enabled, any bean defined in your application context with a class that is an @AspectJ aspect (has the @Aspect annotation) will be automatically detected by Spring and used to configure Spring AOP. The following example shows the minimal definition required for a not-very-useful aspect:

当 @AspectJ 被激活以后,任何加载如 application context 中的 bean,只要它的 class 被声明为 @Aspect 对象,将会自动的被 Spring 检测到,被将其配置为一个 Spring AOP 对象;

A regular bean definition in the application context, pointing to a bean class that has the @Aspect annotation:

1
2
3
<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
<!-- configure properties of aspect here as normal -->
</bean>

And the NotVeryUsefulAspect class definition, annotated with org.aspectj.lang.annotation.Aspect annotation;

1
2
3
4
5
6
7
package org.xyz;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class NotVeryUsefulAspect {

}

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.

进一步,@Aspect class 可以包含 pointcut,advice 以及 introduction 等声明;

[Note]
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).

你可以通过 Spring XML 配置的方式或者是通过 classpath scanning 的方式对 @AspectJ bean 进行注册;但是要注意的是,单独配置 @ApsectJ 注解是不会被 classpath scanning 所识别的,你必须首先配置 @Component 注解,使其能够被 classpath scanning 所识别;

[Note]
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 AOP 中,不能讲一个 aspects 对象再作为其他切面的 target;被 @Aspect 所注解的 class,将不会被 auto-proxing;

【11.2.3】 Declaring a pointcut

Recall that pointcuts determine join points of interest, and thus enable us to control when advice executes. Spring AOP only supports method execution join points for Spring beans, so you can think of a pointcut as matching the execution of methods on Spring beans. A pointcut declaration has _two part_s: a signature comprising a name and any parameters, and a pointcut expression that determines exactly which method executions we are interested in. In the @AspectJ annotation-style of AOP, a pointcut signature is provided by a regular method definition, and the pointcut expression is indicated using the @Pointcut annotation (the method serving as the pointcut signature must have a void return type).

Spring AOP 只能够支持在方法级别的 join points 的配置;pointcut 的声明包含两个部分:由一个名字和参数所构成的一个签名,另外一个 pointcut expression 用来决定哪个方法是我们所关心的;在 @AspectJ 风格下定义的 AOP 中,pointcut 签名是通过一个普通的 method definition 提供的,而pointcut expression是通过Pointcut注解定义的;

An example will help make this distinction between a pointcut signature and a pointcut expression clear. The following example defines a pointcut named ‘anyOldTransfer’ that will match the execution of any method named ‘transfer’:

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

如上述例子所述,组成一个 pointcut 的声明的两个部分如下所述,

  1. pointcut signature 就是该方法名,既是 anyOldTransfer
  2. pointcut expression 既是 @Pointcut(“execution(* transfer(..))”)

Supported Pointcut Designators

Spring AOP supports the following AspectJ pointcut designators (PCD) for use in pointcut expressions:

Spring AOP 所使用的pointcut expression支持如下的 AspectJ pointcut 指示符,

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

    execution - 匹配方法级别的 join points,它是最首要被使用到的指示符;

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

    within - 匹配某些类型的 join points (根据方法声明的类型来限制)

  • 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

    this - 限制条件是,bean reference (Spring AOP proxy,指通过 bean 所生成的代理对象) 是指定 type 的实例;this 指代的是代理过后的 AOP proxy 对象;

  • 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

    target - 限制条件是当前的 target object (原始的 object)是指定 type 的实例;与 this 不同,target 表示被 AOP Proxy 所代理的原始对象;

  • args - limits matching to join points (the execution of methods when using Spring AOP) where the arguments are instances of the given types
    args - 限制条件是当前的参数是指定类型的实例;

  • @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
    @target - 限制条件是当前正在执行的 object 需要由指定类型的 annotation;

  • @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)

    @args - 限制条件是运行时刻的实际参数类型需要由指定类型的 annotations;

  • @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)

    @within- 限制条件是 join points 方法本身的声明需要由指定类型的 annotation

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

    @annotation- 限制条件是 join point 拦截的方法的对象需要有指定类型的 annotation;

In addition, AspectJ itself has type-based semantics and at an execution join point both this and target refer to the same object - the object executing the method. Spring AOP is a proxy-based system and differentiates between the proxy object itself (bound to this) and the target object behind the proxy (bound to target).

额外的,因为 AspectJ 是基于类型的语法类型,因此在 join point 上指定的标识符thistarget都是指向的同一个对象 - 执行该方法的对象(我的补充,这里没有说明白到底是指的是 proxy 还是原始对象,但是我猜想应该指的是原始对象);而 Spring AOP 是基于代理实现的系统,所以thistarget是分别表示的不同的对象,this表示代理自身,而target表示的是被其所代理的原始对象;

[Note]
Due to the proxy-based nature of Spring’s AOP framework, protected methods are by definition not intercepted, neither for JDK proxies (where this isn’t applicable) nor for CGLIB proxies (where this is technically possible but not recommendable for AOP purposes). As a consequence, any given pointcut will be matched against public methods only!

因为基于代理的天然属性,Spring AOP 不能再 protected 方法级别上实现拦截,JDK proxies 做不到同样 CGLIB 也做不到;所以,Spring AOP 只能去拦截 public 方法;

If your interception needs include protected/private methods or even constructors, consider the use of Spring-driven native AspectJ weaving instead of Spring’s proxy-based AOP framework. This constitutes a different mode of AOP usage with different characteristics, so be sure to make yourself familiar with weaving first before making a decision.

如果你真的需要对 protected、privated 以及 constructor 方法进行拦截,可以考虑使用 native AspectJ weaving

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:

Spring AOP 允许你使用一个额外的 PCD,叫做bean;该标识符允许你匹配一个特定名字的 Spring bean 或者是一系列的名字,如下所述,

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

idOrNameOfBean可以使用通配符*以及 &&’ed, ||’ed, and ! (negated) too

Combining pointcut expressions

Pointcut expressions can be combined using ‘&&’, ‘||’ and ‘!’. It is also possible to refer to pointcut expressions by name. The following example shows three pointcut expressions: anyPublicOperation (which matches if a method execution join point represents the execution of any public method); inTrading (which matches if a method execution is in the trading module), and tradingOperation (which matches if a method execution represents any public method in the trading module).

Pointcut expression 可以通过使用 ‘&&’, ‘||’ and ‘!’ 符号进行连接,同样通过使用名字也可以引用一个 pointcut expression;下面的用例显示了三种形式的 pointcut expression,

1
2
3
4
5
6
7
8
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {}

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

@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}
  • anyPublicOperation
    which matches if a method execution join point represents the execution of any public method;任何 public 方法将会被拦截;

  • inTrading
    which matches if a method execution is in the trading module;匹配指定包下面的类 com.xyz.someapp.trading..*

  • tradingOperation
    which matches if a method execution represents any public method in the trading module,这里有意思了,是anyPublicOperationinTrading条件的组合

Sharing common pointcut definitions

When working with enterprise applications, you often want to refer to modules of the application and particular sets of operations from within several aspects.

当在开发企业级应用的时候,你经常需要引用一些模块以及一些由 several aspects 所定义的特定的操作;

We recommend defining a “SystemArchitecture” aspect that captures common pointcut expressions for this purpose. A typical such aspect would look as follows:

我们建议定义一个”SystemArchitecture”的切面去捕获公共的 pointcut 调用;比方说,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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() {}

}

Ok,上述的定义非常清晰的将不同的模块通过不同的 Aspect 进行了区分和定义;

The pointcuts defined in such an aspect can be referred to anywhere that you need a pointcut expression. For example, to make the service layer transactional, you could write:

由上述方式定义的 aspect 中的 pointcut expression 可以在任何地方被引用;比如,你可以在你的 service layer 上定义事务,你可以这样写:

1
2
3
4
5
6
7
8
9
10
11
<aop:config>
<aop:advisor
pointcut="com.xyz.someapp.SystemArchitecture.businessService()"
advice-ref="tx-advice"/>
</aop:config>

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

The <aop:config> and <aop:advisor> elements are discussed in Section 11.3, “Schema-based AOP support”. The transaction elements are discussed in Chapter 17, Transaction Management.

Examples

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

定义execution expression的格式如下,

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

ret-type-pattern( returning type pattern ),name pattern 以及 parameters pattern三个部分在配置的时候是必须的;

🚩🚩 等等;格式,上面的格式到底指什么?反正我第一次没看懂;后来仔细思索,大概知道作者这样写所表达的意思了,总结如下,

  1. 凡是名字后面跟?符号的 pattern 定义,都是可选的,不是必须的,这里包含modifiers-pattern?declaring-type-pattern?以及throws-pattern?三个这样的 pattern 定义,表示这三个 pattern 在定义的时候,都不是必须定义的;

  2. 空格,两个 pattern 之间如果有空格,表示在定义规则的时候,是需要分开定义的;否则,在定义两个 pattern 的时候,就需要将他们定义在一起;

为了更好的理解上述针对该格式的分析,我们来看看下面这个典型的用例,

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

该用例就是用来匹配在 com.xyz.service 包下的所有类的所有方法,且无论方法名、参数和返回类型是什么,那么它是如何做到的呢?我们来依次分析上述的 pointcut expression 表达式语句 * com.xyz.service.*.*(..) 到底表示什么意思?

首先,该语句由空格符分割后,包含两个部分,_*_ 和 com.xyz.service.*.*(..)

然后,我们分别来分析这两个部分,

  • 第一个部分 _*_
    该部分表示的是ret-type-pattern的定义;因为整个表达式的第一个空格之后一定是name-pattern的相关定义,所以该 _*_ 必须是ret-type-pattern

  • 第二个部分 com.xyz.service.*.*(..)
    首先,该部分包含了三个 pattern,下面依次分析

    • com.xyz.service.* 表示的是declaring-type-pattern,表示 com.xyz.service 包下的任意类;
    • 余下的 .*(..) 表示,第一个 . 表示declaring-type-patternname-pattern之间的连接符号,通常表示类和方法之间的连接,可以理解为匹配该类下面的所有方法;后面有针对该连接方式更详细的描述;
    • 余下的 (..) 表示的就是param-pattern了;后面有更详细的描述;

好的,当搞懂了上述表达式的格式以后,再看后续的部分就简单了;继续读官文;

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.

returning type pattern

决定了方法的返回类型是否与 join point 匹配;大部分时候你将会使用通配符 * 来匹配任何的返回类型;一个全限定名将只会匹配当某个方法返回了指定的类型;

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.

name pattern

用来匹配方法名;可以使用通配符 * 来匹配任何的方法名;可以使用一个后缀符号.来链接declaring type patternname pattern;这段重要了,我建议读至少三遍;备注,declaring type pattern是可选的,

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.

parameter pattern

() 表示匹配没有参数的情况;(..) 表示匹配任意多个参数包含 0 个;(*) 表示值匹配一个参数的情况;(*,String) 表示匹配两个参数,第一个参数可以是任何类型的参数,第二参数的类型必须是 String;

Consult the Language Semantics section of the AspectJ Programming Guide for more information.

最后,官网上遗漏了对剩余两个可选的 pattern 的介绍,这里将其补上,

declaring type pattern

其实在name pattern中顺带的将它提了一下,它的定义方式是通过使用declaring type pattern.name pattern,通过中间的连接符号.定义了前面的declaring type pattern;该 pattern 定义的是与包名相关的规则,比如 com.xyz.service.AccountService,个人认为,非常重要,在实际的使用频率上也是比较高的;默认情况下,既是在不配置的情况下,匹配所有的包;

throws-pattern

匹配异常返回的类型,默认情况下,不匹配任何类型;


Some examples of common pointcut expressions are given below.

  • the execution of any public method:

    1
    execution(public * *(..))

    该表达式显示的定义了四个 pattern,

    第一个 pattern modifiers-patternpublic 表示,表示该方法的声明类型为 public;备注,该 pattern 是可选的;
    第二个 pattern ret-type-pattern由第一个 _*_ 表示,表示任意的返回类型;备注,该 pattern 是必须的;
    第三个 pattern name-pattern由第二个 _*_ 表示,表示任意的方法名;备注,该 pattern 是必须的;
    第四个 pattern parameter-pattern(..) 表示,表示任意多个任何类型的参数;备注,该 pattern 是必须的;

    最后,该表达式隐式的配置了declaring-type-patternthrows-pattern?declaring-type-pattern默认表示匹配任何类型,throws-pattern?默认是不匹配任何异常类型;

  • the execution of any method with a name beginning with “set”:

    1
    execution(* set*(..))

    第一个 * 表示的是ret-type-pattern,随后的 set* 表示方法名开头为 set 的方法,最后的 (..) 表示匹配任何个数任何类型的参数;

  • the execution of any method defined by the AccountService interface:

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

    这里通过使用declaring type pattern.name pattern的方式,通过连接符号.定义了前面的declaring type pattern,也就是包名;

  • the execution of any method defined in the service package:

    1
    execution(* com.xyz.service.*.*(..))
  • the execution of any method defined in the service package or a sub-package:

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

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

    1
    within(com.xyz.service..*)
  • any join point (method execution only in Spring AOP) where the proxy implements the AccountService interface:

    1
    this(com.xyz.service.AccountService)

    使用的 proxy 本身需要实现了 AccountService;

  • any join point (method execution only in Spring AOP) where the target object implements the AccountService interface:

    1
    target(com.xyz.service.AccountService)

    匹配规则是被代理的对象实现了 AccountService

  • any join point (method execution only in Spring AOP) which takes a single parameter, and where the argument passed at runtime is Serializable:

    1
    args(java.io.Serializable)

    匹配规则是,join point 既方法的参数必须只有一个且参数类型必须是 java.io.Serializable

    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.

    需要注意的是,args(java.io.Serializable)execution( (java.io.Serializable)) 两者是不同的,args 是表示,在 runtime 的时候,传递给方法的参数类型是 Serializable,而 execution 的版本是去判断方法本身的参数类型是否是 Serializable;言外之意,也就是说,该方法的参数可以为不是 Serializable,但是在 Runtime 执行的时候,如果传递给该方法的参数的类型是 Serializable,那么,该方法的调用就会被拦截;wow.. 我像我这一生都不会用到这样的情况….

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

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

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

    这里要特别特别注意与前面使用 @target 的区别,@within 的过滤条件是基于 target 对象的 declared type,而 @target 的条件是针对 target object 本身;作者没有继续分析这两者的区别,我猜想,

    1. @target 表示是 target object 包含了该 annotation 的定义,包括其接口或者其父类,只要其中一个定义了该 annoation,均表示定义了该 annotation;
    2. @within 表示的是,target object 本身声明定义了 annoation 接口,不包含其接口和父类中的定义;
  • any join point (method execution only in Spring AOP) where the executing method has an @Transactional annotation:

    1
    @annotation(org.springframework.transaction.annotation.Transactional)
  • 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:

    1
    @args(com.xyz.security.Classified)
  • any join point (method execution only in Spring AOP) on a Spring bean named tradeService:

    1
    bean(tradeService)
  • any join point (method execution only in Spring AOP) on Spring beans having names that match the wildcard expression *Service:

    1
    bean(*Service)

writing good pointcuts

During compilation, AspectJ processes pointcuts in order to try and optimize matching performance. Examining code and determining if each join point matches (statically or dynamically) a given pointcut is a costly process. (A dynamic match means the match cannot be fully determined from static analysis and a test will be placed in the code to determine if there is an actual match when the code is running). On first encountering a pointcut declaration, AspectJ will rewrite it into an optimal form for the matching process. What does this mean? Basically pointcuts are rewritten in DNF (Disjunctive Normal Form) and the components of the pointcut are sorted such that those components that are cheaper to evaluate are checked first. This means you do not have to worry about understanding the performance of various pointcut designators and may supply them in any order in a pointcut declaration.

在编译期,挡在处理 pointcuts 的时候 AspectJ 会试图提升匹配的性能;无论是静态的或者是动态的去检查代码并依次判断每一个 join pint 是否与 pointcut expression 匹配,都是非常耗费性能的;当首次碰到一个 pointcut 的声明后,AspectJ 将会对它进行重写成一种优化的格式然后来进行匹配操作;什么意思呢?pointcuts 被重写成 DNF (Disjunctive Normal Form) 格式,然后 pointcuts 会被排序,这样的话,挡在进行匹配的时候,性能开销就会小很多;这意味着,你并不用去担心多种类型的 pointcut 带来的性能开销;

【11.2.4】 Declaring advice

Advice is associated with a pointcut expression, and runs before, after, or around method executions matched by the pointcut. The pointcut expression may be either a simple reference to a named pointcut, or a pointcut expression declared in place.

Advice是与pointcut expression关联在一起的,在匹配的方法执行之前之后以及前后执行Advice所声明的方法的逻辑;pointcut expression可以既是一个对其它 named pointcut 的引用,也可以是一个在当前位置声明的pointcut expression

Before advice

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

1
2
3
4
5
6
7
8
9
10
11
12
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() {
// ...
}

}

If using an in-place pointcut expression we could rewrite the above example as:

1
2
3
4
5
6
7
8
9
10
11
12
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() {
// ...
}

}

注意,可以不同单独定义 @Pointcut 注解对象,而是直接在 Advice 的注解上直接定义;

很遗憾,官网上并没有说这么做有什么作用,这里是什么意思呢?很简单,意思是,凡是匹配 com.xyz.myapp.SystemArchitecture.dataAccessOperation() 或者 execution( com.xyz.myapp.dao..*(..)) 中所定义的 pointcut points,将会执行这里由 @Before 所声明的方法 doAccessCheck()

After returning advice

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

1
2
3
4
5
6
7
8
9
10
11
12
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

@AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck() {
// ...
}

}

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:

有时候,你需要访问被拦截方法的返回值,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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).

@AfterReturning 中所定义的 returning 属性的值必须与 advice 方法名( 这里为 doAccessCheck )中的某一个参数的名字相同;当某个被拦截的方法执行返回,它的返回值将会被传入当前由 returning 属性所注明的方法参数上;

After throwing advice

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

1
2
3
4
5
6
7
8
9
10
11
12
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

@AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doRecoveryActions() {
// ...
}

}

上述例子,当被监控的方法抛出任意的异常,都会被该 advice 方法所拦截执行;

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 desired, use Throwable as the exception type otherwise) and bind the thrown exception to an advice parameter.

通常你希望在某些特定的异常抛出以后,才会执行 advice 的相关方法,可以使用throwing属性,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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).

同样的,throwing 属性中的值必须与 doRecoveryActions 中的某个参数的名字相匹配;这里,当且仅当被监视的方法抛出的异常为DataAccessException类型的时候,才会执行该方法;

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.

After (finally) advice 总是会在被拦截的方式执行完以后执行,所以,你需要同时处理正常或者异常的情况;

1
2
3
4
5
6
7
8
9
10
11
12
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;

@Aspect
public class AfterFinallyExample {

@After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doReleaseLock() {
// ...
}

}

Around advice

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 将会在被拦截方法执行的前后执行,通常用来决定什么时候,怎样执行该方法;上文也强调到了,执行过程中需要考虑线程安全的问题;

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.

上面注意一点,就是Object[],可以作为被拦截方法调用的参数;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;

@Aspect
public class AroundExample {

@Around("com.xyz.myapp.SystemArchitecture.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}

}

The value returned by the around advice will be the return value seen by the caller of the method. A simple caching aspect for example could return a value from a cache if it has one, and invoke proceed() if it does not. Note that proceed may be invoked once, many times, or not at all within the body of the around advice, all of these are quite legal.

retVal 是被拦截方法调用以后的返回值;

Advice parameters

Spring offers fully typed advice - meaning that you declare the parameters you need in the advice signature (as we saw for the returning and throwing examples above) rather than work with Object[] arrays all the time. We’ll see how to make argument and other contextual values available to the advice body in a moment. First let’s take a look at how to write generic advice that can find out about the method the advice is currently advising.

上文提到,最好是通过 advice 的签名去声明参数而不是使用Object[]

Access to the current 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.

任何的 advice 方法都可以将它的第一个参数声明为org.aspectj.lang.JoinPoint( ProceedingJoinPoint 是 JoinPoint 的子类); ProceedingJoinPoint 提供了一系列有用的方法

  • getArgs() 返回方法的参数
  • getThis() 返回当前 proxy 对象;
  • getTarget() 返回被 proxy 的原始对象,既是 target object
  • getSignature() 返回被拦截方法的方法名的描述
  • toString() 返回被拦截方法的描述;

Passing parameters to 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:

你可以使用 args 绑定使得 argument value 在 advice body 中可用;下面的例子很清晰的说明了如何使用;

1
2
3
4
@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.

上述例子中,当参数从被拦截的方法传入 advice 方法体的时候,需要遵循两个约定

  • 被拦截方法至少需要有一个参数
  • 被拦截方法中的参数至少有一个为 Account 对象;这时通过 args 中的 account 匹配 validateAccount 方法中的 account 参数的参数的类型 Account 所约定的;

Another way of writing this is to declare a pointcut that “provides” the Account object value when it matches a join point, and then just refer to the named pointcut from the advice. This would look as follows:

另外一种方式是当在定义 pointcut 声明的时候通过 args 属性和方法参数来定义需要传递的 Account 参数;然后在 Advice 方法的声明中,只需要引用该 named pointcut 即可;

1
2
3
4
5
6
7
@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.

1
2
3
4
5
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {
AuditCode value();
}

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

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

上面的例子定义了这样一个 pointcut,首先通过 named pointcut com.xyz.lib.Pointcuts.anyPublicMethod() 拦截所有的的 public 方法,然后通过 pointcut expression @annotation(auditable) 限定 public 方法声明了 @Auditable 的方法,注意这里的限定所使用的 Annotation 类型是由与参数 auditable 匹配的方法的参数类型 Auditable 决定的;

Advice parameters and generics

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

Spring AOP 可以处理类声明和方法参数上所使用的泛型;比如

1
2
3
4
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:

你可以通过使用 args 属性(对了别忘了该属性的学名叫做 pointcut designators (PCD))值匹配 advice 方法签名中的参数类型 MyType 去限定那些被拦截的方法需要执行 advice 方法 beforeSampleMethod 的;

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

🚩 等等,在经过上述比较别扭的解释后,意思上是通顺了,但是,背后的逻辑是什么呢?这里也该给出答案了;args 背后的实现机制是什么?我试图来解释一下吧,Generic Type 泛型类型在编译期间,是不能被确定的,所以只能在执行期 Runtime 时期来确定被拦截方法的参数类型(并与 args 所指定的参数类型 MyType 进行比对);这就决定了,一开始,在编译器,只能通过 execution( ..Sample+.sampleGenericMethod()) 的静态类型检查来设置 pointcuts (既设置拦截点),而最终哪些被拦截的方法最终会被 Advice 方法执行,是需要在执行期通过判断传递给被拦截方法的(真实)参数类型是否为 MyType?如果是,那么将会执行这里的 advice 方法既 beforeSampleMethod;yeah,clear;
🚩 上面还有一种情况是非常重要的,因为 java 不能通过反射获得 parameter names 既参数的名称,也就是说,我们无法获得 beforeSampleMethod(MyType param) 中的参数名称 param,所以 pointcut expression 中定义的参数名( args(param) 中的 param )又是如何与 beforeSampleMethod 方法中的参数名 param 匹配的呢?要解答这个疑惑,参考 Determining argument names 插旗旗的地方( worthing reading,这里一旦搞懂了,也就对整个 Spring AOP 参数匹配的机制彻底搞懂了~);

That this works is pretty obvious as we already discussed above. However, it’s worth pointing out that this won’t work for generic collections. So you cannot define a pointcut like this:

不过要注意的一点是,它不能匹配 generic collections 类型;很显然,因为 Generic Type 自身也不支持这样比较,是的,这时因为 Java 本身泛型上的特点或者叫做缺陷所造成的;

1
2
3
4
@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<?>,该类型会匹配任何类型的 Collection,并调用该 advice 方法 beforeSampleMethod,而你需要做的就是你自己在 beforeSampleMethod 方法中哪些情况需要被过滤掉…

Determining argument names

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:

advice 方法中调用参数的绑定机制依赖于 pointcut expressions 中所使用到的参数名字与 advice 或 pointcut 方法签名中的参数的名字如何匹配;Parameter names 不能够通过 Java 反射获得,所以 Spring AOP 使用了如下这种方式来决定 parameter name;

  • If the parameter names have been specified by the user explicitly, then the specified parameter names are used: both the advice and the pointcut annotations have an optional “argNames” attribute which can be used to specify the argument names of the annotated method - these argument names are available at runtime. For example:
    如果参数名被用户通过使用"argNames"属性显示指定,那么将会使用用户所定义的参数名来弥补 Parameter names 不能够通过 Java 反射获得的不足;不过,我想说的是,这里作者没有将所有意思表达全,如果想要准确的通过参数名去匹配 pointcut expressions 中的参数(这里是通过 @annotation(auditable) 指定的) 与 advice 方法签名中的参数,那么不仅仅 advice 方法中的参数名是由 argNames 来指定,其参数顺序也必须和 argNames 中指定的顺序一致,因为,既然不能通过 Java 反射得到参数名,那么自然也就无法知道 advice 方法中的参数的名字到底是什么,就只能全部依赖于 argNames,所以 argNames 的顺序也决定了 advice 方法中的参数名的匹配顺序;这一点很重要,在配置的时候,不仅仅名称要对应,顺序更要对应;
    1
    2
    3
    4
    5
    6
    @Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
    argNames="bean,auditable")
    public void audit(Object bean, Auditable auditable) {
    AuditCode code = auditable.value();
    // ... use code and bean
    }

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:
如果你的第一参数使用的是JoinPoint, ProceedingJoinPoint, or JoinPoint.StaticPart类型,在定义”argNames”的时候,直接忽略掉即可,比如

1
2
3
4
5
6
@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:

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

  • Using the 'argNames' attribute is a little clumsy, so if the 'argNames' attribute has not been specified, then Spring AOP will look at the debug information for the class and try to determine the parameter names from the local variable table. This information will be present as long as the classes have been compiled with debug information ( '-g:vars' at a minimum). The consequences of compiling with this flag on are: (1) your code will be slightly easier to understand (reverse engineer), (2) the class file sizes will be very slightly bigger (typically inconsequential), (3) the optimization to remove unused local variables will not be applied by your compiler. In other words, you should encounter no difficulties building with this flag on.

使用argNames实际上会显得有些笨拙,所以,如果'argNames'没有被指定,那么 Spring AOP 将会从 class 的debug information中的 local variable table 去找 parameter names 并用来进行匹配;只有当 classes 是通过启动了debug information的方式进行编译的时候,才行 (编译的时候,至少要指定 '-g:vars' 参数)

If an @AspectJ aspect has been compiled by the AspectJ compiler (ajc) even without the debug information then there is no need to add the argNames attribute as the compiler will retain the needed information.

如果 @Asepct aspect 类是通过 AspectJ 编译器编译的,即便是不使用'argNames'同样也无需使用debug information进行编译;

  • If the code has been compiled without the necessary debug information, then Spring AOP will attempt to deduce the pairing of binding variables to parameters (for example, if only one variable is bound in the pointcut expression, and the advice method only takes one parameter, the pairing is obvious!). If the binding of variables is ambiguous given the available information, then an AmbiguousBindingException will be thrown.
    如果没有使用debug information进行编译,Spring AOP 将试图去根据成对儿的方式去推导可能与之匹配的参数;比如当 pointcut expression 只定义了一个参数,而 advice 方法名中也只定义了一个参数,那么它们两个当然就是一对儿;如果被绑定的参数类型是彼此 ambiguous 的,那么 AmbiguousBindingException 将会抛出;具体一点说,如果是下面这种情况
    1
    2
    3
    4
    5
    @Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable1)")
    public void audit(Auditable auditable2) {
    AuditCode code = auditable.value();
    // ... use code and bean
    }

🚩 这里,不单单解释了上述情况,并且描绘了 parameter names matching 的内部的执行机制;
因为无法通过 Java 反射准确的获取得到 Advice 方法 audit 中的参数的名字,那么这个时候,Spring AOP 将会使用 pairing (成对儿) 的方式来推导可能与之匹配的参数名,这里呢,Spring AOP 发现 Advice 方法有一个参数,pointcut expressions 也使用了一个参数,那么它会认为两者是匹配的,也就是说,会让 auditable1 去匹配 auditable2,那么这个时候,Spring AOP 就会认为 pointcut expressions 中使用到的 @annotation(auditable1) 中的参数 auditable1 的类型为 @Auditable,那么在使用该 pointcut expressions 定义拦截的时候,就会去判断方法是否声明了 @Auditable 注解;就这个例子而言,如果 com.xyz.lib 包下面的某个类的方法为 public 且 声明了 @Auditable 注解,那么该方法将会被此 pointcut 拦截,并将会执行这里的 Advice 方法 audit

Proceeding with arguments

We remarked earlier that we would describe how to write a proceed call with arguments that works consistently across Spring AOP and AspectJ. The solution is simply to ensure that the advice signature binds each of the method parameters in order. For example:

1
2
3
4
5
6
7
8
@Around("execution(List<Account> find*(..)) && " +
"com.xyz.myapp.SystemArchitecture.inDataAccessLayer() && " +
"args(accountHolderNamePattern)")
public Object preProcessQueryPattern(ProceedingJoinPoint pjp,
String accountHolderNamePattern) throws Throwable {
String newPattern = preProcess(accountHolderNamePattern);
return pjp.proceed(new Object[] {newPattern});
}

In many cases you will be doing this binding anyway (as in the example above).

有关这个例子,作者只是寥寥几句带过,其实这个例子非常有意思,包含的好几层意思在里面,值得深入推敲推敲

  1. 通过 pairing 机制(既是成对儿机制) 判断出 pointcut expression 中的 args(accountHolderNamePattern) 参数 accountHolderNamePattern 的类型为 String
  2. 所以,在通过使用例子中的 pointcut expression 去寻找相应的 pointcut 方法的时候,要求方法的第一个参数为 String 类型,只有这样的方法能够被匹配然后被拦截;
  3. 在 Advice 方法 preProcessQueryPattern 中,得到被拦截方法的入参 accountHolderNamePattern,通过方法 preProcess 进行转换,然后将转换后的入参再次传入原方法实现了对原方法参数的织入操作;

Advice ordering

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

如果有多个 advice 在同一个 join point 中运行,那么它的执行顺序是什么?Spring AOP 遵循与 AspectJ 一样的优先级原则;如果是 “on the way in”,既是多个 before advice 的情况下,那么优先级最高的最先执行;但是,如果是 “on the way out”,既多个 after advice 的情况,那么就是优先级最高的最后被执行;

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.Ordered interface 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.

除非你显示指定了执行的优先级顺序,否则会认为没有定义;你可以通过实现org.springframework.core.Ordered接口或者是通过Order注解来实现优先级定义;两个 aspects,Ordered.getValue()值越小的,优先级越高;

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.

上面提到了一种比较特殊的情况,就是两个 advice 方法在同一个 aspect 类中定义,而且同时需要在同一个 join point 上执行,这样的话 ordering 就不能被确定了,给出了解释: (since there is no way to retrieve the declaration order via reflection for javac-compiled classes);那么如果遇到这种情况的话,作者建议重构代码,将两个 advice 放置在两个不同的 aspect 中,这样就可以来确定执行的顺序了;

有关 Ordering 例子参考【11.2.7】 Example 中的第一个例子。

【11.2.5】 Introductions

Introductions (known as inter-type declarations in AspectJ) enable an aspect to declare that advised objects implement a given interface, and to provide an implementation of that interface on behalf of those objects.

Introductions 使得一个 aspect 能够让一个 advised objects 声明并一个指定的 interface,并且能够让其实现该 interface;在 AspectJ 中,这个被称作 inter-type declarations;言外之意,可以给一个 advised object 声明一个新的接口并实现该接口;

An introduction is made using the @DeclareParents annotation. This annotation is used to declare that matching types have a new parent (hence the name).

Introduction 是通过@DeclareParents实现的;该注解用来使得所有被匹配的 types 能够有一个新的父类;

For example, given an interface UsageTracked, and an implementation of that interface DefaultUsageTracked, the following aspect declares that all implementors of service interfaces also implement the UsageTracked interface. (In order to expose statistics via JMX for example.)

看例子,(注意 @DeclareParents 的部分) 提供了一个接口 UsageTracked,然后实现了该接口的实现 DefaultUsageTracked,下面的这个 aspect 通过 @DeclareParents 注解使得所有的 com.xzy.myapp.service.*+ 中的 service interfaces 同样实现了 UsageTracked 接口( 通过默认实现类 DefaultUsageTracked 实现的);这个例子的目的就是通过 JMX 提供监控统计数据;(我的备注:的确,满灵活的,在一个已有的接口实现类上,让它再实现一个新的接口和实现类;其实这一切如果从 JDK Dynamic Proxy 的原理上看,都很简单,因为你的代理类只要实现了原生类的接口以后,其它的东西都可以任意的实现)

1
2
3
4
5
6
7
8
9
10
11
12
@Aspect
public class UsageTracking {

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

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

}

The interface to be implemented is determined by the type of the annotated field. The value attribute of the @DeclareParents annotation is an AspectJ type pattern :- any bean of a matching type will implement the UsageTracked interface.

哪些 interface 实例将会被 UsageTracked 接口注入是通过@DeclareParents注解中的value属性来确定的,这里表示凡是 com.xzy.myapp.service.*+ 下的接口实例都会实现 UsageTracked 接口;

Note that in the before advice of the above example, service beans can be directly used as implementations of the UsageTracked interface.

这个 before advice 要注意了,它通过使用 this PCD 表示去拦截代理对象本身,只要这个代理对象实现了 UsageTracked type,并且满足 com.xyz.myapp.SystemArchitecture.businessService(),那么该 proxy 对象就会被拦截;重要的事情再说一次,这里是拦截的代理对象,而不是 target;

If accessing a bean programmatically you would write the following:

1
UsageTracked usageTracked = (UsageTracked) context.getBean("myService");

这里其实描述了一个很重要的情况,ApplicatonContext 中的 beans,最终存放的是 proxy 而不是 target;

【11.2.6】 Aspect instantiation models

By default there will be a single instance of each aspect within the application context. AspectJ calls this the singleton instantiation model. It is possible to define aspects with alternate lifecycles :- Spring supports AspectJ’s perthis and pertarget instantiation models ( percflow, percflowbelow, and pertypewithin are not currently supported).

默认的,每一个 aspect 对象在 application context 中都是 singleton 的;但是可以通过其它的方式为 aspect 定义不同的生命周期(言外之意,就是不再是 singleton 的方式了),Spring supports AspectJ’s perthis and pertarget instantiation models ( percflow, percflowbelow, and pertypewithin are not currently supported).

A “perthis” aspect is declared by specifying a perthis clause in the @Aspect annotation. Let’s look at an example, and then we’ll explain how it works.

1
2
3
4
5
6
7
8
9
10
11
@Aspect("perthis(com.xyz.myapp.SystemArchitecture.businessService())")
public class MyAspect {

private int someState;

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

}

The effect of the 'perthis' clause is that one aspect instance will be created for each unique service object executing a business service (each unique object bound to ‘this’ at join points matched by the pointcut expression). The aspect instance is created the first time that a method is invoked on the service object. The aspect goes out of scope when the service object goes out of scope. Before the aspect instance is created, none of the advice within it executes. As soon as the aspect instance has been created, the advice declared within it will execute at matched join points, but only when the service object is the one this aspect is associated with. See the AspectJ programming guide for more information on per-clauses.

perthis语句的意思就是说,针对每一个 com.xyz.myapp.SystemArchitecture.businessService() 中指定的所有的 business service 都会新建一个新的 aspect instance;随后的一大段讲述的是 aspect 对象以及内部的 advice 方法的关系,很显然,只有当 aspect 对象创建以后,才能拦截方法并执行 advice 方法;

The 'pertarget' instantiation model works in exactly the same way as perthis, but creates one aspect instance for each unique target object at matched join points.

'perthis'非常的类似,区别是只会在 matched join points 以后才会新建;

【11.2.7】 Example

The execution of business services can sometimes fail due to concurrency issues (for example, deadlock loser). If the operation is retried, it is quite likely to succeed next time round. For business services where it is appropriate to retry in such conditions (idempotent operations that don’t need to go back to the user for conflict resolution), we’d like to transparently retry the operation to avoid the client seeing a PessimisticLockingFailureException. This is a requirement that clearly cuts across multiple services in the service layer, and hence is ideal for implementing via an aspect.

Because we want to retry the operation, we will need to use around advice so that we can call proceed multiple times. Here’s how the basic aspect implementation looks:

很好,第一个例子就是我期盼已久的,很多时候,会因为 concurrency 的问题而导致PessimisticLockingFailureException异常而需要重试;这里,作者通过使用一个 around advice 就搞定了,让我眼前一亮;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Aspect
public class ConcurrentOperationExecutor implements Ordered {

private static final int DEFAULT_MAX_RETRIES = 2;

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

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

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

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

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

}

Note that the aspect implements the Ordered interface so we can set the precedence of the aspect higher than the transaction advice (we want a fresh transaction each time we retry). The maxRetries and order properties will both be configured by Spring. The main action happens in the doConcurrentOperation around advice. Notice that for the moment we’re applying the retry logic to all businessService()s. We try to proceed, and if we fail with an PessimisticLockingFailureException we simply try again unless we have exhausted all of our retry attempts.

注意,该 aspect 通过实现了Ordered接口设置其值比transaction advice更大,也就是说优先级比transaction advice越低,值越大,优先级越低;这样就保证了,每次 retry 都会有一个新的事务产生;

The corresponding Spring configuration is:

1
2
3
4
5
6
<aop:aspectj-autoproxy/>

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

To refine the aspect so that it only retries idempotent operations, we might define an Idempotent annotation:

1
2
3
4
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
// marker annotation
}

and use the annotation to annotate the implementation of service operations. The change to the aspect to only retry idempotent operations simply involves refining the pointcut expression so that only @Idempotent operations match:

上面的默认做法也可以通过用注解@Idempotent的方式来进行匹配;更灵活;

【11.3】 Schema-based AOP support

If you prefer an XML-based format, then Spring also offers support for defining aspects using the new “aop” namespace tags. The exact same pointcut expressions and advice kinds are supported as when using the @AspectJ style, hence in this section we will focus on the new syntax and refer the reader to the discussion in the previous section (Section 11.2, “@AspectJ support”) for an understanding of writing pointcut expressions and the binding of advice parameters.

Spring AOP 提供了 “aop” namespace 元素来执行 XML-based format;

To use the aop namespace tags described in this section, you need to import the spring-aop schema as described in Chapter 41, XML Schema-based configuration. See Section 41.2.7, “the aop schema” for how to import the tags in the aop namespace.

上面描述了如何将 aop namespace 导入到 xml 配置中;

Within your Spring configurations, all aspect and advisor elements must be placed within an <aop:config>`element (you can have more than one <aop:config> element in an application context configuration). An <aop:config> element can contain pointcut, advisor, and aspect elements (note these must be declared in that order).

【11.3.1】 Declaring an aspect

An aspect is declared using the aop:aspect element, and the backing bean is referenced using the ref attribute:

1
2
3
4
5
6
7
8
9
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
...
</aop:aspect>
</aop:config>

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

The bean backing the aspect (“ aBean“ in this case) can of course be configured and dependency injected just like any other Spring bean.

什么是backing bean?其实上面作者的用意是,aspect 本身就是一个 bean,所以可以注入其它的 bean;

【11.3.2】 Declaring a pointcut

A named pointcut can be declared inside an <aop:config> element, enabling the pointcut definition to be shared across several aspects and advisors.

A pointcut representing the execution of any business service in the service layer could be defined as follows:

1
2
3
4
5
6
<aop:config>

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

</aop:config>

Another way of defining the above pointcut would be:

1
2
3
4
5
6
<aop:config>

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

</aop:config>

Assuming you have a SystemArchitecture aspect as described in the section called “Sharing common pointcut definitions”.

Declaring a pointcut inside an aspect is very similar to declaring a top-level pointcut:

1
2
3
4
5
6
7
8
9
10
11
12
<aop:config>

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

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

...

</aop:aspect>

</aop:config>

Much the same way in an @AspectJ aspect, pointcuts declared using the schema based definition style may collect join point context.

For example, the following pointcut collects the 'this' object as the join point context and passes it to advice:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<aop:config>

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

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

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

...

</aop:aspect>

</aop:config>

The advice must be declared to receive the collected join point context by including parameters of the matching names:

1
2
3
public void monitor(Object service) {
...
}

上面这个例子通过 XML 的方式进行显得非常非常的笨拙;其实它相干的就是下面这样一件事情,

1
2
3
4
5
6
7
8
@Aspect
public class UsageTracking {

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

  1. 首先通过 parameter names 的匹配,定义了 pointcut epxression 中的 this PCD 的参数类型为 UsageTracked
  2. 在选定被拦截方法的时候,两个规则:一、必须符合 com.xyz.myapp.SystemArchitecture.businessService();二、该拦截方法的对象必须是 UsageTracked 接口实例;
  3. 最后,将被拦截的对象 usageTracked 传递给方法这里的 advised 方法 recordUsage

所以,类比于上面作者所给的例子,根据 Object 类型去匹配被拦截方法的对象,然后将其通过 this PCD 将被拦截的对象赋值给 advice 方法的 usageTracked 参数;

不过,XML 的方式的确显得笨拙,且非常的不直观;所以,我决定在项目开发过程中不会使用它;

When combining pointcut sub-expressions, ‘&&’ is awkward within an XML document, and so the keywords ‘and’, ‘or’ and ‘not’ can be used in place of ‘&&’, ‘||’ and ‘!’ respectively. For example, the previous pointcut may be better written as:

1
2
3
4
5
6
7
8
9
10
11
12
<aop:config>

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

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

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

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

Note that pointcuts defined in this way are referred to by their XML id and cannot be used as named pointcuts to form composite pointcuts. The named pointcut support in the schema based definition style is thus more limited than that offered by the @AspectJ style.

注意,这里通过嵌套在 aspect 中定义的 pointcut 不能被其它 aspect 通过 named pointcut 所引用;限制比使用 @AspectJ 风格多;

【11.3.3】 Declaring advice

由于作者不决定使用 XML 风格的 Spring AOP,所以余下部分就放在以后来阅读、翻译并领悟;

Before advice

After returning advice

After throwing advice

After (finally) advice

Around advice

Advice parameters

Advice ordering

【11.3.4】 Introductions

由于作者不决定使用 XML 风格的 Spring AOP,所以余下部分就放在以后来阅读、翻译并领悟;

【11.3.5】 Aspect instantiation models

【11.3.6】 Advisors

The concept of "advisors" is brought forward from the AOP support defined in Spring 1.2 and does not have a direct equivalent in AspectJ. An advisor is like a small self-contained aspect that has a single piece of advice. The advice itself is represented by a bean, and must implement one of the advice interfaces described in [Section 12.3.2, “Advice types in Spring”]. Advisors can take advantage of AspectJ pointcut expressions though.

“advisors” 这个概念是从 Spring 1.2 开始引入的,在 ApsectJ 中没有相对应的概念;advisor 类似于一个小的自我管理的 aspect 对象,且实现了一个简单的 advice,注意,这里所指的 advice 与 前面 AspetctJ 所描述的 advice 不同,它本身是一个 bean,并且需要实现一个 advice 接口;看来 advisor 是一个纯粹的 Spring AOP 自家的东西;

Spring supports the advisor concept with the <aop:advisor> element. You will most commonly see it used in conjunction with transactional advice, which also has its own namespace support in Spring. Here’s how it looks:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<aop:config>

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

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

</aop:config>

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

As well as the pointcut-ref attribute used in the above example, you can also use the pointcut attribute to define a pointcut expression inline.

To define the precedence of an advisor so that the advice can participate in ordering, use the order attribute to define the Ordered value of the advisor.

【11.3.7】 Example

【11.4】 Choosing which AOP declaration style to use

Once you have decided that an aspect is the best approach for implementing a given requirement, how do you decide between using Spring AOP or AspectJ, and between the Aspect language (code) style, @AspectJ annotation style, or the Spring XML style? These decisions are influenced by a number of factors including application requirements, development tools, and team familiarity with AOP.

如何在 Spring AOP 和 ApsectJ 中做出抉择,以及 Aspect language (code) style, @AspectJ annotation style, or the Spring XML style 中做出抉择?

【11.4.1】 Spring AOP or full AspectJ?

Use the simplest thing that can work. Spring AOP is simpler than using full AspectJ as there is no requirement to introduce the AspectJ compiler / weaver into your development and build processes. If you only need to advise the execution of operations on Spring beans, then Spring AOP is the right choice. If you need to advise objects not managed by the Spring container (such as domain objects typically), then you will need to use AspectJ. You will also need to use AspectJ if you wish to advise join points other than simple method executions (for example, field get or set join points, and so on).

如果你只是希望去拦截 Spring beans 上的方法执行,Spring AOP 就是一个正确的选择;如果你需要考虑被拦截的对象不仅仅是 Spring 容器中的对象,那么你就需要使用 AspectJ;另外,如果你不单单是相对方法进行拦截,那么你也需要使用 AspectJ;

【11.4.2】 @AspectJ or XML for Spring AOP?

If you have chosen to use Spring AOP, then you have a choice of @AspectJ or XML style. There are various tradeoffs to consider.

其实我已经做好了抉择;

说说 XML 的缺陷吧,不能像 @AspectJ 那样使用 combining 的方式,比如,

1
2
3
4
5
6
7
8
@Pointcut(execution(* get*()))
public void propertyAccess() {}

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

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

可以看到 accountPropertyAccess 是 propertyAccess 和 operationReturningAnAccount 的组合;

1
2
3
4
5
<aop:pointcut id="propertyAccess"
expression="execution(* get*())"/>

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

但是 XML 的方式不能支持这种组合方式;

【11.5】 Mixing aspect types

It is perfectly possible to mix @AspectJ style aspects using the autoproxying support, schema-defined <aop:aspect> aspects, <aop:advisor> declared advisors and even proxies and interceptors defined using the Spring 1.2 style in the same configuration. All of these are implemented using the same underlying support mechanism and will co-exist without any difficulty.

【11.6】 Proxying mechanisms

Spring AOP uses either JDK dynamic proxies or CGLIB to create the proxy for a given target object. (JDK dynamic proxies are preferred whenever you have a choice).

If the target object to be proxied implements at least one interface then a JDK dynamic proxy will be used. All of the interfaces implemented by the target type will be proxied. If the target object does not implement any interfaces then a CGLIB proxy will be created.

If you want to force the use of CGLIB proxying (for example, to proxy every method defined for the target object, not just those implemented by its interfaces) you can do so. However, there are some issues to consider:

使用 CGLIB 需要注意的事项,

  • final methods cannot be advised, as they cannot be overridden.
    final 方法是不能被拦截的;
  • As of Spring 3.2, it is no longer necessary to add CGLIB to your project classpath, as CGLIB classes are repackaged under org.springframework and included directly in the spring-core JAR. This means that CGLIB-based proxy support ‘just works’ in the same way that JDK dynamic proxies always have.
    Spring 3.2 以后,不再需要额外的将 CGLIB 包导入,CGLIB 已经被添加到了 spring-core 当中;
  • As of Spring 4.0, the constructor of your proxied object will NOT be called twice anymore since the CGLIB proxy instance will be created via Objenesis. Only if your JVM does not allow for constructor bypassing, you might see double invocations and corresponding debug log entries from Spring’s AOP support.
    从 Spring 4.0 以后,在构建 CGLIB proxy 对象的时候不用再调用两次被代理类的构造方法了;

To force the use of CGLIB proxies set the value of the proxy-target-class attribute of the <aop:config> element to true:

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

To force CGLIB proxying when using the @AspectJ autoproxy support, set the 'proxy-target-class' attribute of the <aop:aspectj-autoproxy> element to true:

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

Multiple <aop:config/> sections are collapsed into a single unified auto-proxy creator at runtime, which applies the strongest proxy settings that any of the <aop:config/> sections (typically from different XML bean definition files) specified. This also applies to the <tx:annotation-driven/> and <aop:aspectj-autoproxy/> elements.
To be clear: using proxy-target-class="true" on <tx:annotation-driven/>, <aop:aspectj-autoproxy/> or <aop:config/> elements will force the use of CGLIB proxies for all three of them.

上面的一大段其实就是在说,如果在这是哪个 <tx:annotation-driven/>, <aop:aspectj-autoproxy/> or <aop:config/> 元素中任意一个上面设置了proxy-target-class="true",那么将都会使用 CGLIB 的方式;

【11.6.1】 Understanding AOP proxies

Spring AOP is proxy-based. It is vitally important that you grasp the semantics of what that last statement actually means before you write your own aspects or use any of the Spring AOP-based aspects supplied with the Spring Framework.

Consider first the scenario where you have a plain-vanilla, un-proxied, nothing-special-about-it, straight object reference, as illustrated by the following code snippet.

1
2
3
4
5
6
7
8
9
10
11
public class SimplePojo implements Pojo {

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

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

If you invoke a method on an object reference, the method is invoked directly on that object reference, as can be seen below.

1
2
3
4
5
6
7
8
9
10
public class Main {

public static void main(String[] args) {

Pojo pojo = new SimplePojo();

// this is a direct method call on the 'pojo' reference
pojo.foo();
}
}

Things change slightly when the reference that client code has is a proxy. Consider the following diagram and code snippet.

很好,作者非常清晰的通过这张图描绘了如何通过 proxy 对象去调用的 Plain 对象中的方法;调用过程是通过 proxy 对象中的 foo() 方法去调用 plain object 中的 foo() 方法,也就实现了对 plain object 的代理;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Main {

public static void main(String[] args) {

ProxyFactory factory = new ProxyFactory(new SimplePojo());
factory.addInterface(Pojo.class);
factory.addAdvice(new RetryAdvice());

Pojo pojo = (Pojo) factory.getProxy();

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

The key thing to understand here is that the client code inside the main(..) of the Main class has a reference to the proxy. This means that method calls on that object reference will be calls on the proxy, and as such the proxy will be able to delegate to all of the interceptors (advice) that are relevant to that particular method call. However, once the call has finally reached the target object, the SimplePojo reference in this case, any method calls that it may make on itself, such as this.bar() or this.foo(), are going to be invoked against the this reference, and not the proxy. This has important implications. It means that self-invocation is not going to result in the advice associated with a method invocation getting a chance to execute.

好了,我相信 90% 的读者读到上面这段话以后,即便是逐字逐句翻译以后的内容,也会迷糊,最后的那段,作者到底想要表达什么意思;作者究竟想要表达什么意思呢?前面部分还算比较通俗易懂,也就是说,我们得到一个 SimplePojo 的代理类 pojo,然后是在 pojo.foo() 的方法内部去调用原生 SimplePojo.foo() 方法;从一个 However 单词转折开始,相信后面的部分,大部分人就会看得云里雾里了,这里呢,我不试图去逐字逐句的去翻译它,其实作者这里是给了一个警示,什么意思呢,举个例子,你通常在写一个将要被代理的对象的时候,嗯,比如说是一个将要被事务处理的方法,比如我们有一个 MyService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
public class MyService(){

@Transactional
private void save(){
...
}

public void tryToSave(){

this.save();

}
}

其中的 save() 是需要被事务所支持的,tryToSave 方法是不需要事务的,但是 save() 方法只能所以 tryToSave() 方法中被调用;这样调用,你会发现,在 this.save() 方法调用上,并没有事务;Ok,这就是作者想要表达的意思了,这里的 this 表示的是 plain object 本身,如果想要让 save() 方法上有事务,那么必须是被 MyService 的代理对象 proxy 执行,而非 plain object 对象本身;So,明白了吧;所以,以后在开发类型的 Service 的时候,记得使用 Proxy 对象来替换这里的this;后续作者给出了解决方案,

Okay, so what is to be done about this? The best approach (the term best is used loosely here) is to refactor your code such that the self-invocation does not happen. For sure, this does entail some work on your part, but it is the best, least-invasive approach. The next approach is absolutely horrendous, and I am almost reticent to point it out precisely because it is so horrendous. You can (choke!) totally tie the logic within your class to Spring AOP by doing this:

作者上面写了一大段,就是在赞赏下面的这种非常直观的解决方案,horrendous 吃惊的,吃惊得让人窒息;

1
2
3
4
5
6
7
8
9
10
11
public class SimplePojo implements Pojo {

public void foo() {
// this works, but... gah!
((Pojo) AopContext.currentProxy()).bar();
}

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

看到了没,直接通过 AopContext.currentProxy() 便可以得到 SimplePojo 当前的代理对象;这要求当通过 ProxyFactory 生成 proxy 的时候,将 ExposeProxy 属性设置为 true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main {

public static void main(String[] args) {

ProxyFactory factory = new ProxyFactory(new SimplePojo());
factory.adddInterface(Pojo.class);
factory.addAdvice(new RetryAdvice());
factory.setExposeProxy(true);

Pojo pojo = (Pojo) factory.getProxy();

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

具体参考【12.5.2】 JavaBean properties 插旗旗的地方;

那么如果,ExposeProxy 为 false 呢,没有将 proxy 设置到当前的 ThreadLocal 中呢?如果我要获取该 Proxy 对象该怎么做呢?我能想到的是,调用 ApplicationContext.getBean 方法,或者在当前的 bean 当中定义 @Autowired 引用自身;

【11.7】 Programmatic creation of @AspectJ Proxies

In addition to declaring aspects in your configuration using either <aop:config> or <aop:aspectj-autoproxy>, it is also possible programmatically to create proxies that advise target objects. For the full details of Spring’s AOP API, see the next chapter. Here we want to focus on the ability to automatically create proxies using @AspectJ aspects.

除了使用<aop:config>或者<aop:aspectj-autoproxy>来声明你的 aspects 以外,你同样可以使用编程的方式来创建 proxies;

The class org.springframework.aop.aspectj.annotation.AspectJProxyFactory can be used to create a proxy for a target object that is advised by one or more @AspectJ aspects. Basic usage for this class is very simple, as illustrated below. See the javadocs for full information.

可以使用org.springframework.aop.aspectj.annotation.AspectJProxyFactory来为一个 target object 创建代理对象;

1
2
3
4
5
6
7
8
9
10
11
12
// create a factory that can generate a proxy for the given target object
AspectJProxyFactory factory = new AspectJProxyFactory(targetObject);

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

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

// now get the proxy object...
MyInterfaceType proxy = factory.getProxy();

上面通过AspectJProxyFactory工厂封装 target object,并且通过设置 Aspect 切面,为代理对象提供代理行为;

【11.8】 Using AspectJ with Spring applications

Everything we’ve covered so far in this chapter is pure Spring AOP. In this section, we’re going to look at how you can use the AspectJ compiler/weaver instead of, or in addition to, Spring AOP if your needs go beyond the facilities offered by Spring AOP alone.

从这章节开始,将会提到如何在 Spring 容器中使用全功能的 AspectJ,包括 AspectJ compiler 和 weaver 等;

【11.9】 Further Resources

More information on AspectJ can be found on the AspectJ website.

【12】 Spring AOP APIs

The previous chapter described the Spring’s support for AOP using @AspectJ and schema-based aspect definitions. In this chapter we discuss the lower-level Spring AOP APIs and the AOP support used in Spring 1.2 applications. For new applications, we recommend the use of the Spring 2.0 and later AOP support described in the previous chapter, but when working with existing applications, or when reading books and articles, you may come across Spring 1.2 style examples. Spring 4.0 is backwards compatible with Spring 1.2 and everything described in this chapter is fully supported in Spring 4.0.

前面的章节提到了通过 @AspectJ 或者 schema-based aspect 使用 Spring AOP 的基本概念;这个章节将会开始提到底层的相关实现细节;

【12.2】 Pointcut API in Spring

Let’s look at how Spring handles the crucial pointcut concept.

【12.2.1】 Concepts

Spring’s pointcut model enables pointcut reuse independent of advice types. It’s possible to target different advice using the same pointcut.

Spring 允许其 pointcut 被不同的 advice 重用;

The org.springframework.aop.Pointcut interface is the central interface, used to target advices to particular classes and methods. The complete interface is shown below:

Pointcut 是核心的接口,用来将 advice 指向特定的类和方法;

1
2
3
4
5
6
7
public interface Pointcut {

ClassFilter getClassFilter();

MethodMatcher getMethodMatcher();

}

Splitting the Pointcut interface into two parts allows reuse of class and method matching parts, and fine-grained composition operations (such as performing a “union” with another method matcher).

Pointcut接口分成两个部分允许重用通过 class 和 方法所匹配的部分,同样可以使得两个不同的 method matcher 能够进行组合;

The ClassFilter interface is used to restrict the pointcut to a given set of target classes. If the matches() method always returns true, all target classes will be matched:

ClassFilter用来限制 pointcut 为执行类型的类;如果 matches() 方法总是返回 true,那么所有的 target classes 都将会被匹配;

1
2
3
4
public interface ClassFilter {

boolean matches(Class clazz);
}

The MethodMatcher interface is normally more important. The complete interface is shown below:

1
2
3
4
5
6
7
8
public interface MethodMatcher {

boolean matches(Method m, Class targetClass);

boolean isRuntime();

boolean matches(Method m, Class targetClass, Object[] args);
}

The matches(Method, Class) method is used to test whether this pointcut will ever match a given method on a target class. This evaluation can be performed when an AOP proxy is created, to avoid the need for a test on every method invocation.

matches(Method, Class),该方法可以在 AOP proxy 创建的时候就执行,以避免在执行期去检测每个方法的调用;

If the 2-argument matches method returns true for a given method, and the isRuntime() method for the MethodMatcher returns true, the 3-argument matches method will be invoked on every method invocation. This enables a pointcut to look at the arguments passed to the method invocation immediately before the target advice is to execute.

如果 matches(Method, Class)isRuntime() 方法都返回 true,那么matches(Method m, Class targetClass, Object[] args);将会在每一个方法的执行期间被调用;这使得 pointcut 有能力在参数传递给方法执行之前并且在 advice 方法执行之前,检查该传入的参数

🚩 好的,上面短短几句话,其实里面隐含了一个非常关键的信息,就是 pointcut 能够在方法的执行期间去检测,主要是检测传入给方法的参数,判断,以便实施拦截;

Most MethodMatchers are static, meaning that their isRuntime() method returns false. In this case, the 3-argument matches method will never be invoked.

大多数 MethodMatchers 是静态的,也意味着isRuntime() 方法返回 false,这样的话,matches(Method m, Class targetClass, Object[] args) 将永远不会被执行;

【12.2.2】 Operations on pointcuts

Spring supports operations on pointcuts: notably, union and intersection.

  • Union means the methods that either pointcut matches.
  • Intersection means the methods that both pointcuts match.
    Union is usually more useful.
  • Pointcuts can be composed using the static methods in the org.springframework.aop.support.Pointcuts class, or using the ComposablePointcut class in the same package. However, using AspectJ pointcut expressions is usually a simpler approach.
    Pointcuts 可以通过静态的方法实现组合;

【12.2.3】 AspectJ expression pointcuts

Since 2.0, the most important type of pointcut used by Spring is org.springframework.aop.aspectj.AspectJExpressionPointcut. This is a pointcut that uses an AspectJ supplied library to parse an AspectJ pointcut expression string.

最最重要的 pointcut 的实现类是org.springframework.aop.aspectj.AspectJExpressionPointcut

See the previous chapter for a discussion of supported AspectJ pointcut primitives.

【12.2.4】 Convenience pointcut implementations

Spring provides several convenient pointcut implementations. Some can be used out of the box; others are intended to be subclassed in application-specific pointcuts.

Spring 提供了一系列比较实用的 pointcut 的实现;

Static pointcuts

Static pointcuts are based on method and target class, and cannot take into account the method’s arguments.

Static pointcuts 是基于目标类的方法的,同时不能考虑到方法的参数;

Static pointcuts are sufficient - and best - for most usages. It’s possible for Spring to evaluate a static pointcut only once, when a method is first invoked: after that, there is no need to evaluate the pointcut again with each method invocation.

Let’s consider some static pointcut implementations included with Spring.

Regular expression pointcuts

One obvious way to specify static pointcuts is regular expressions. Several AOP frameworks besides Spring make this possible.

org.springframework.aop.support.JdkRegexpMethodPointcut is a generic regular expression pointcut, using the regular expression support in JDK 1.4+.

Using the JdkRegexpMethodPointcut class, you can provide a list of pattern Strings. If any of these is a match, the pointcut will evaluate to true. (So the result is effectively the union of these pointcuts.)

The usage is shown below:

1
2
3
4
5
6
7
8
9
<bean id="settersAndAbsquatulatePointcut"
class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="patterns">
<list>
<value>.*set.*</value>
<value>.*absquatulate</value>
</list>
</property>
</bean>

Spring provides a convenience class, RegexpMethodPointcutAdvisor, that allows us to also reference an Advice (remember that an Advice can be an interceptor, before advice, throws advice etc.). Behind the scenes, Spring will use a JdkRegexpMethodPointcut. Using RegexpMethodPointcutAdvisor simplifies wiring, as the one bean encapsulates both pointcut and advice, as shown below:

RegexpMethodPointcutAdvisor允许我们引用一个 Advice,背后的实现逻辑依然使用的是JdkRegexpMethodPointcut

1
2
3
4
5
6
7
8
9
10
11
12
<bean id="settersAndAbsquatulateAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref bean="beanNameOfAopAllianceInterceptor"/>
</property>
<property name="patterns">
<list>
<value>.*set.*</value>
<value>.*absquatulate</value>
</list>
</property>
</bean>

RegexpMethodPointcutAdvisor can be used with any Advice type.

Attribute-driven pointcuts

An important type of static pointcut is a metadata-driven pointcut. This uses the values of metadata attributes: typically, source-level metadata.

Dynamic pointcuts

Dynamic pointcuts are costlier to evaluate than static pointcuts. They take into account method arguments, as well as static information. This means that they must be evaluated with every method invocation; the result cannot be cached, as arguments will vary.

🚩 Dynamic pointcuts 比 Static pointcuts 开销更大;他们不仅考虑静态的信息,同时也考虑方法的参数;也就是说,它们必须考虑每次方法的调用,拦截传入的参数;

The main example is the control flow pointcut.

Control flow pointcuts

Spring control flow pointcuts are conceptually similar to AspectJ cflow pointcuts, although less powerful. (There is currently no way to specify that a pointcut executes below a join point matched by another pointcut.) A control flow pointcut matches the current call stack. For example, it might fire if the join point was invoked by a method in the com.mycompany.web package, or by the SomeCaller class. Control flow pointcuts are specified using the org.springframework.aop.support.ControlFlowPointcut class.

A control flow pointcut matches the current call stack. 比如,它可能在某个 join point 被某个方法调用或者其它类的调用过程中而被启用;Control flow pointcuts 是通过org.springframework.aop.support.ControlFlowPointcut class 实现的;

[Note]
Control flow pointcuts are significantly more expensive to evaluate at runtime than even other dynamic pointcuts. In Java 1.4, the cost is about 5 times that of other dynamic pointcuts.

注意的是,Control flow pointcuts 显著的比其它 dynamic pointcuts 更消耗新能;

【12.2.5】 Pointcut superclasses

Spring provides useful pointcut superclasses to help you to implement your own pointcuts.

Because static pointcuts are most useful, you’ll probably subclass StaticMethodMatcherPointcut, as shown below. This requires implementing just one abstract method (although it’s possible to override other methods to customize behavior):

因为静态的 pointcuts 最有用,所以,你通常只需要继承StaticMethodMatcherPointcut实现自己的 pointcuts 即可;

1
2
3
4
5
6
class TestStaticPointcut extends StaticMethodMatcherPointcut {

public boolean matches(Method m, Class targetClass) {
// return true if custom criteria match
}
}

There are also superclasses for dynamic pointcuts.

You can use custom pointcuts with any advice type in Spring 1.0 RC2 and above.

【12.2.6】 Custom pointcuts

Because pointcuts in Spring AOP are Java classes, rather than language features (as in AspectJ) it’s possible to declare custom pointcuts, whether static or dynamic. Custom pointcuts in Spring can be arbitrarily complex. However, using the AspectJ pointcut expression language is recommended if possible.

因为 Spring AOP 中的 pointcuts 是通过 Java classes 实现的,而不是像 AspectJ 那样使用的 language features,language features 可以使用声明式的方式自定义 pointcuts,无论是 static 或者是 dynamic 的;随意的自定义 pointcuts 在 Spring 中会比较的复杂,但是在 AspectJ 的 pointcut expression language 中体检这样做;

【12.3】 Advice API in Spring

【12.3.1】 Advice lifecycles

Each advice is a Spring bean. An advice instance can be shared across all advised objects, or unique to each advised object. This corresponds to per-class or per-instance advice.

每一个 advice 都是一个 Spring bean;advice 可以是单例的也可以是不是,这主要取决于采用的是 per-class 还是 per-instance 的 advice;

Per-class advice is used most often. It is appropriate for generic advice such as transaction advisors. These do not depend on the state of the proxied object or add new state; they merely act on the method and arguments.

per-class 的 advice 是最常用的方式;它比较适合用于 transaction advisors;它不依赖于代理对象的当前状态或者新的状态属性;

Per-instance advice is appropriate for introductions, to support mixins. In this case, the advice adds state to the proxied object. It’s possible to use a mix of shared and per-instance advice in the same AOP proxy.

Per-instance advice 适合 introductions 以支持 mixins;这种 advice 将会将状态加入 proxied object;可以在同一个 Spring AOP proxy 中混合使用多个 per-instance 的 advices;

【12.3.2】 Advice types in Spring

Interception around advice

The most fundamental advice type in Spring is interception around advice.

Spring 中最基础的 advice type 是 interception around advice.

Spring is compliant with the AOP Alliance interface for around advice using method interception. MethodInterceptors implementing around advice should implement the following interface:

Spring AOP 中的 around advice 是通过实现MethodInterceptor接口实现的;

1
2
3
4
public interface MethodInterceptor extends Interceptor {

Object invoke(MethodInvocation invocation) throws Throwable;
}

The MethodInvocation argument to the invoke() method exposes the method being invoked; the target join point; the AOP proxy; and the arguments to the method. The invoke() method should return the invocation’s result: the return value of the join point.

这个接口方法太重要了,首先该参数 invocation (MethodInvocation) 包含了

  1. 被调用的方法 Method
  2. the target join point,
  3. AOP proxy,代理对象
  4. 被调用方法的参数 arguments

A simple MethodInterceptor implementation looks as follows:

1
2
3
4
5
6
7
8
9
public class DebugInterceptor implements MethodInterceptor {

public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("Before: invocation=[" + invocation + "]");
Object rval = invocation.proceed();
System.out.println("Invocation returned");
return rval;
}
}

Note the call to the MethodInvocation’s proceed() method. This proceeds down the interceptor chain towards the join point.

注意,invocation.proceed() 方法的调用,该方法会沿着 interceptor 链依次调用直到 join point 为止;

Most interceptors will invoke this method, and return its return value. However, a MethodInterceptor, like any around advice, can return a different value or throw an exception rather than invoke the proceed method. However, you don’t want to do this without good reason!

最后,贴一个 Around 的 Aspect 对象来看看,

1
2
3
4
5
6
7
8
9
10
11
12
13
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;
@Aspect
public class AroundExample {
@Around("com.xyz.myapp.SystemArchitecture.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
}

Before advice

A simpler advice type is a before advice. This does not need a MethodInvocation object, since it will only be called before entering the method.

The main advantage of a before advice is that there is no need to invoke the proceed() method, and therefore no possibility of inadvertently failing to proceed down the interceptor chain.

The MethodBeforeAdvice interface is shown below. (Spring’s API design would allow for field before advice, although the usual objects apply to field interception and it’s unlikely that Spring will ever implement it).

1
2
3
4
public interface MethodBeforeAdvice extends BeforeAdvice {

void before(Method m, Object[] args, Object target) throws Throwable;
}

Note the return type is void. Before advice can insert custom behavior before the join point executes, but cannot change the return value. If a before advice throws an exception, this will abort further execution of the interceptor chain. The exception will propagate back up the interceptor chain. If it is unchecked, or on the signature of the invoked method, it will be passed directly to the client; otherwise it will be wrapped in an unchecked exception by the AOP proxy.

注意它的返回类型是 void,它是在 join point 方法之前执行,并且不能修改方法的返回值;如果 before advice 抛出了异常,将会终止后续的 interceptor chain 的执行;异常将会 propagate back up the interceptor chain;

An example of a before advice in Spring, which counts all method invocations:

1
2
3
4
5
6
7
8
9
10
11
12
public class CountingBeforeAdvice implements MethodBeforeAdvice {

private int count;

public void before(Method m, Object[] args, Object target) throws Throwable {
++count;
}

public int getCount() {
return count;
}
}

源码分析的时候可以深入的去了解下 MethodBeforeAdvice 等的 Advice 接口是在什么地方实现的;

Throws advice

Throws advice is invoked after the return of the join point if the join point threw an exception.

Spring offers typed throws advice. Note that this means that the org.springframework.aop.ThrowsAdvice interface does not contain any methods: It is a tag interface identifying that the given object implements one or more typed throws advice methods. These should be in the form of:

Spring 提供了 typed throws advice;也就意味着 org.springframework.aop.ThrowsAdvice 不包含任何的方法;它作为一个标签类型的 interface 表明指定的对象实现了一个或者多个 throws advice 方法;

1
afterThrowing([Method, args, target], subclassOfThrowable)

Only the last argument is required. The method signatures may have either one or four arguments, depending on whether the advice method is interested in the method and arguments. The following classes are examples of throws advice.

只有最后一个参数是必须的;

The advice below is invoked if a RemoteException is thrown (including subclasses):

1
2
3
4
5
6
public class RemoteThrowsAdvice implements ThrowsAdvice {

public void afterThrowing(RemoteException ex) throws Throwable {
// Do something with remote exception
}
}

The following advice is invoked if a ServletException is thrown. Unlike the above advice, it declares 4 arguments, so that it has access to the invoked method, method arguments and target object:

1
2
3
4
5
6
public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {

public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
// Do something with all arguments
}
}

The final example illustrates how these two methods could be used in a single class, which handles both RemoteException and ServletException. Any number of throws advice methods can be combined in a single class.

1
2
3
4
5
6
7
8
9
10
public static class CombinedThrowsAdvice implements ThrowsAdvice {

public void afterThrowing(RemoteException ex) throws Throwable {
// Do something with remote exception
}

public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
// Do something with all arguments
}
}

[Note]
If a throws-advice method throws an exception itself, it will override the original exception (i.e. change the exception thrown to the user). The overriding exception will typically be a RuntimeException; this is compatible with any method signature. However, if a throws-advice method throws a checked exception, it will have to match the declared exceptions of the target method and is hence to some degree coupled to specific target method signatures. Do not throw an undeclared checked exception that is incompatible with the target method’s signature!

throws-advice 方法往往需要抛出自定义的异常,该异常会覆盖原有的异常,这里需要注意的是,通过 throws-advice 抛出的异常通常需要是 RuntimeException,而最好不要抛出 checked 类型的异常,因为如果抛出 checked 类型的异常需要与目标方法的异常类型匹配;

After Returning advice

An after returning advice in Spring must implement the org.springframework.aop.AfterReturningAdvice interface, shown below:

1
2
3
4
5
public interface AfterReturningAdvice extends Advice {

void afterReturning(Object returnValue, Method m, Object[] args, Object target)
throws Throwable;
}

An after returning advice has access to the return value (which it cannot modify), invoked method, methods arguments and target.

The following after returning advice counts all successful method invocations that have not thrown exceptions:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class CountingAfterReturningAdvice implements AfterReturningAdvice {

private int count;

public void afterReturning(Object returnValue, Method m, Object[] args, Object target)
throws Throwable {
++count;
}

public int getCount() {
return count;
}
}

This advice doesn’t change the execution path. If it throws an exception, this will be thrown up the interceptor chain instead of the return value.

该方法将不会改变执行的路径,如果它抛出异常,它将不会继续执行后续的 interceptor chain,但并不影响当前的 return value;

Introduction advice

Spring treats introduction advice as a special kind of interception advice.

Introduction requires an IntroductionAdvisor, and an IntroductionInterceptor, implementing the following interface:

1
2
3
4
public interface IntroductionInterceptor extends MethodInterceptor {

boolean implementsInterface(Class intf);
}

The invoke() method inherited from the AOP Alliance MethodInterceptor interface must implement the introduction: that is, if the invoked method is on an introduced interface, the introduction interceptor is responsible for handling the method call - it cannot invoke proceed().

…..
…..

【12.4】 Advisor API in Spring

In Spring, an Advisor is an aspect that contains just a single adviceobject associated with a pointcut expression.

在 Spring 中,一个 Advisor 是一个 aspect 对象,只包含一个 advice 对象;

Apart from the special case of introductions, any advisor can be used with any advice. org.springframework.aop.support.DefaultPointcutAdvisor is the most commonly used advisor class. For example, it can be used with a MethodInterceptor, BeforeAdvice or ThrowsAdvice.

It is possible to mix advisor and advice types in Spring in the same AOP proxy. For example, you could use a interception around advice, throws advice and before advice in one proxy configuration: Spring will automatically create the necessary interceptor chain.

【12.5】 Using the ProxyFactoryBean to create AOP proxies

If you’re using the Spring IoC container (an ApplicationContext or BeanFactory) for your business objects - and you should be! - you will want to use one of Spring’s AOP FactoryBeans. (Remember that a factory bean introduces a layer of indirection, enabling it to create objects of a different type.)

Note The Spring AOP support also uses factory beans under the covers.

The basic way to create an AOP proxy in Spring is to use the org.springframework.aop.framework.ProxyFactoryBean. This gives complete control over the pointcuts and advice that will apply, and their ordering. However, there are simpler options that are preferable if you don’t need such control.

【12.5.1】 Basics

The ProxyFactoryBean, like other Spring FactoryBean implementations, introduces a level of indirection. If you define a ProxyFactoryBean with name foo, what objects referencing foo see is not the ProxyFactoryBean instance itself, but an object created by the ProxyFactoryBean’s implementation of the `getObject() method. This method will create an AOP proxy wrapping a target object.

ProxyFactoryBean顾名思义,就是用来制造 Proxy 的工厂实例;

One of the most important benefits of using a ProxyFactoryBean or another IoC-aware class to create AOP proxies, is that it means that advices and pointcuts can also be managed by IoC. This is a powerful feature, enabling certain approaches that are hard to achieve with other AOP frameworks. For example, an advice may itself reference application objects (besides the target, which should be available in any AOP framework), benefiting from all the pluggability provided by Dependency Injection.

使用ProxyFactoryBean来构造 proxies 有一个重要的优势,就是 advicespointcuts 可以被 Spring 容器管理,这时一个非常强大的特性,是其他的 AOP 框架不能做到的;举个例子,一个 advice 自己可能也需要引用一个应用中的对象,该对象可能是一个 proxy,也可能是一个 target,反正是容器中任意的一个对象;

【12.5.2】 JavaBean properties

In common with most FactoryBean implementations provided with Spring, the ProxyFactoryBean class is itself a JavaBean. Its properties are used to:

  • Specify the target you want to proxy.
  • Specify whether to use CGLIB (see below and also Section 12.5.3, “JDK- and CGLIB-based proxies”).
    可以设定是否使用 CGLIB 或者是 JDK dynamic proxy 的方式;

Some key properties are inherited from org.springframework.aop.framework.ProxyConfig (the superclass for all AOP proxy factories in Spring). These key properties include:

  • proxyTargetClass: true if the target class is to be proxied, rather than the target class’ interfaces. If this property value is set to true, then CGLIB proxies will be created (but see also Section 12.5.3, “JDK- and CGLIB-based proxies”).
    如果设置为true,那么表示 target class 将要被代理,而不是使用其接口进行代理;true,则表示使用 CGLIB proxies 的方式创建代理;
  • optimize: controls whether or not aggressive optimizations are applied to proxies created via CGLIB. One should not blithely use this setting unless one fully understands how the relevant AOP proxy handles optimization. This is currently used only for CGLIB proxies; it has no effect with JDK dynamic proxies.
    对 CGLIB proxies 进行优化的;
  • frozen: if a proxy configuration is frozen, then changes to the configuration are no longer allowed. This is useful both as a slight optimization and for those cases when you don’t want callers to be able to manipulate the proxy (via the Advised interface) after the proxy has been created. The default value of this property is false, so changes such as adding additional advice are allowed.
    既是让 proxy 的配置被冻结;you don’t want callers to be able to manipulate the proxy (via the Advised interface) after the proxy has been created,你不期望当 proxy 被创建以后,可以被其它的 caller 动态的修改 proxy configuration;
  • exposeProxy: determines whether or not the current proxy should be exposed in a ThreadLocal so that it can be accessed by the target. If a target needs to obtain the proxy and the exposeProxy property is set to true, the target can use the AopContext.currentProxy() method.

🚩 用以决定当前的 proxy 是否能够被暴露至 ThreadLocal 中,以至于它可以被 target 访问;如果 target 需要获取 proxy,需要将 exposeProxy 属性设置为 true,然后 target 就可以通过使用AopContext.currentProxy()获得 proxy 对象了,也就是从当前的 ThreadLocal 中去获取;

Other properties specific to ProxyFactoryBean include:

  • proxyInterfaces: array of String interface names. If this isn’t supplied, a CGLIB proxy for the target class will be used (but see also Section 12.5.3, “JDK- and CGLIB-based proxies”).

    用来生成 target 代理的接口,如果该接口没有指定,将会使用 CGLIB proxy 的方式;备注,为什么非要指定?难道不能通过 Java 反射获取 target class 所实现的接口吗?

  • interceptorNames: String array of Advisor, interceptor or other advice names to apply. Ordering is significant, on a first come-first served basis. That is to say that the first interceptor in the list will be the first to be able to intercept the invocation.

指定 Adviosr,interceptor 的名字;注意,这里的赋值顺序很重要,String[] 中排在前面 interceptor 的将会被先执行;

The names are bean names in the current factory, including bean names from ancestor factories. You can’t mention bean references here since doing so would result in the ProxyFactoryBean ignoring the singleton setting of the advice.

names 表示的是当前 factory 中的 bean names;

You can append an interceptor name with an asterisk ( *). This will result in the application of all advisor beans with names starting with the part before the asterisk to be applied. An example of using this feature can be found in Section 12.5.6, “Using ‘global’ advisors”.

你可以在 interceptor 名字的后面使用通配符 *;

  • singleton: whether or not the factory should return a single object, no matter how often the getObject() method is called. Several FactoryBean implementations offer such a method. The default value is true. If you want to use stateful advice - for example, for stateful mixins - use prototype advices along with a singleton value of false.

factory 是否应该返回一个单例对象;默认的值是 true;

【12.5.3】 JDK- and CGLIB-based proxies

This section serves as the definitive documentation on how the ProxyFactoryBean chooses to create one of either a JDK- and CGLIB-based proxy for a particular target object (that is to be proxied).

[Note]
The behavior of the ProxyFactoryBean with regard to creating JDK- or CGLIB-based proxies changed between versions 1.2.x and 2.0 of Spring. The ProxyFactoryBean now exhibits similar semantics with regard to auto-detecting interfaces as those of the TransactionProxyFactoryBean class.

注意用来通过 JDK 或者 CGLIB 创建 proxy 的ProxyFactoryBean,在 Sprig 1.2.x 和 2.0 版本之间有了变化;现在的ProxyFactoryBean更倾向于使用 auto-detecting 接口的方式,比如TransactionProxyFactoryBean

If the class of a target object that is to be proxied (hereafter simply referred to as the target class) doesn’t implement any interfaces, then a CGLIB-based proxy will be created. This is the easiest scenario, because JDK proxies are interface based, and no interfaces means JDK proxying isn’t even possible. One simply plugs in the target bean, and specifies the list of interceptors via the interceptorNames property. Note that a CGLIB-based proxy will be created even if the proxyTargetClass property of the ProxyFactoryBean has been set to false. (Obviously this makes no sense, and is best removed from the bean definition because it is at best redundant, and at worst confusing.)

如果目标类没有接口,那么会使用 CGLIB 来生成代理类,注意,这种情况下,即便是 ProxyFactoryBean 的属性 proxyTargetClass 被设置为 false,同样会使用 CGLIB;

If the target class implements one (or more) interfaces, then the type of proxy that is created depends on the configuration of the ProxyFactoryBean.

如果目标类实现了多个接口,目标被代理后的 proxy 使用哪些接口由ProxyFactoryBean的配置来决定;

If the proxyTargetClass property of the ProxyFactoryBean has been set to true, then a CGLIB-based proxy will be created. This makes sense, and is in keeping with the principle of least surprise. Even if the proxyInterfaces property of the ProxyFactoryBean has been set to one or more fully qualified interface names, the fact that the proxyTargetClass property is set to true will cause CGLIB-based proxying to be in effect.

只要ProxyFactoryBean的属性proxyTargetClass被设置为 true,无论是否proxyInterfaces设置了接口,都会采用 CGLIB 的方式;

If the proxyInterfaces property of the ProxyFactoryBean has been set to one or more fully qualified interface names, then a JDK-based proxy will be created. The created proxy will implement all of the interfaces that were specified in the proxyInterfaces property; if the target class happens to implement a whole lot more interfaces than those specified in the proxyInterfaces property, that is all well and good but those additional interfaces will not be implemented by the returned proxy.

ProxyFactoryBean通过proxyInterfaces所指定的接口类型为 target 生成相应的代理;如果 target class 实现了比proxyInterfaces参数所指定的接口类型多,那么,ProxyFactoryBean还是只会通过proxyInterfaces所指定的接口类型实现响应的代理对象;

If the proxyInterfaces property of the ProxyFactoryBean has not been set, but the target class does implement one (or more) interfaces, then the ProxyFactoryBean will auto-detect the fact that the target class does actually implement at least one interface, and a JDK-based proxy will be created. The interfaces that are actually proxied will be all of the interfaces that the target class implements; in effect, this is the same as simply supplying a list of each and every interface that the target class implements to the proxyInterfaces property. However, it is significantly less work, and less prone to typos.

重点来了,如果proxyInterfaces没有设置,但是如果ProxyFactoryBean检测到当前的 target class 的确实现了一个或者多个 interfaces,那么ProxyFactoryBean会自动检测 target class 上的接口,并且根据这些接口生成相关的 JDK-based proxy 代理对象;注意,这里的 proxy 对象会实现所有的接口;

【12.5.4】 Proxying interfaces

Let’s look at a simple example of ProxyFactoryBean in action. This example involves:

  • A target bean that will be proxied. This is the “personTarget” bean definition in the example below.
  • An Advisor and an Interceptor used to provide advice.
    advice 指的就是提供 before, after, around 等回调方法;
  • An AOP proxy bean definition specifying the target object (the personTarget bean) and the interfaces to proxy, along with the advices to apply.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<bean id="personTarget" class="com.mycompany.PersonImpl">
<property name="name" value="Tony"/>
<property name="age" value="51"/>
</bean>

<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
<property name="someProperty" value="Custom string property value"/>
</bean>

<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor">
</bean>

<bean id="person"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="com.mycompany.Person"/>

<property name="target" ref="personTarget"/>
<property name="interceptorNames">
<list>
<value>myAdvisor</value>
<value>debugInterceptor</value>
</list>
</property>
</bean>

Note that the interceptorNames property takes a list of String: the bean names of the interceptor or advisors in the current factory. Advisors, interceptors, before, after returning and throws advice objects can be used. The ordering of advisors is significant.

[Note]
You might be wondering why the list doesn’t hold bean references(这里指的是 interceptorNames 为什么不直接使用 ref-bean 的方式). The reason for this is that if the ProxyFactoryBean’s singleton property is set to false, it must be able to return independent proxy instances. If any of the advisors is itself a prototype, an independent instance would need to be returned, so it’s necessary to be able to obtain an instance of the prototype from the factory; holding a reference isn’t sufficient.

为什么ProxyFactoryBean参数interceptorNames不适用 ref-bean 的方式,而是使用 string name 的方式;因为ProxyFactoryBean有可能将其singleton property参数设置为false,那么返回的对象就不能是单例模式的,但是虽然不是单例模式的,但是自身仍然独立的,这就要求它的 interceptors / adviosrs 同样不能是单例模式,因为如果 interceptors / adviosrs 是单例的,那么必然导致所生成 proxy 就不是 independent;所以,这里不能直接使用 ref-bean,因为通过 ref-bean 的方式很有可能引用到了单例模式的 advisors;同样,如果ProxyFactoryBeansingleton property参数设置为true表示返回的 proxy 是单例的,如果通过 ref-bean 引导的方式,很有可能引用到不是单例模式的 advisors;所以,综上,这里只能传入名字,而且根据ProxyFactoryBean的内部情况相机生成对应的 interceptors 或者 advisors,这里的相机就是指,生成单例的 interceptors 或者不是单例的 interceptors;

The “person” bean definition above can be used in place of a Person implementation, as follows:

可以通过如下的方式获得 Person 的代理对象;

1
Person person = (Person) factory.getBean("person");

Other beans in the same IoC context can express a strongly typed dependency on it, as with an ordinary Java object:

1
2
3
<bean id="personUser" class="com.mycompany.PersonUser">
<property name="person"><ref bean="person"/></property>
</bean>

The PersonUser class in this example would expose a property of type Person. As far as it’s concerned, the AOP proxy can be used transparently in place of a “real” person implementation. However, its class would be a dynamic proxy class. It would be possible to cast it to the Advised interface (discussed below).

可以通过上述的方式使得PersonUser的属性 person 通过 ref bean 的方式引用 person 代理对象;

It’s possible to conceal the distinction between target and proxy using an anonymous inner bean, as follows. Only the ProxyFactoryBean definition is different; the advice is included only for completeness:

可以使用一个匿名的内部 bean 来隐藏 target 和 proxy 之间的区别;ok,这个匿名的 bean 就是指的是 ProxyFactoryBean 中的 target property;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
<property name="someProperty" value="Custom string property value"/>
</bean>

<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"/>

<bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="com.mycompany.Person"/>
<!-- Use inner bean, not local reference to target -->
<property name="target">
<bean class="com.mycompany.PersonImpl">
<property name="name" value="Tony"/>
<property name="age" value="51"/>
</bean>
</property>
<property name="interceptorNames">
<list>
<value>myAdvisor</value>
<value>debugInterceptor</value>
</list>
</property>
</bean>

This has the advantage that there’s only one object of type Person: useful if we want to prevent users of the application context from obtaining a reference to the un-advised object, or need to avoid any ambiguity with Spring IoC autowiring. There’s also arguably an advantage in that the ProxyFactoryBean definition is self-contained. However, there are times when being able to obtain the un-advised target from the factory might actually be an advantage: for example, in certain test scenarios.

【12.5.5】 Proxying classes

What if you need to proxy a class, rather than one or more interfaces?

如果你想要去代理一个 class,而不是去代理一个或者多个 interfaces?

Imagine that in our example above, there was no Person interface: we needed to advise a class called Person that didn’t implement any business interface. In this case, you can configure Spring to use CGLIB proxying, rather than dynamic proxies. Simply set the proxyTargetClass property on the ProxyFactoryBean above to true. While it’s best to program to interfaces, rather than classes, the ability to advise classes that don’t implement interfaces can be useful when working with legacy code. (In general, Spring isn’t prescriptive. While it makes it easy to apply good practices, it avoids forcing a particular approach.)

【12.5.6】 Using ‘global’ advisors

By appending an asterisk to an interceptor name, all advisors with bean names matching the part before the asterisk, will be added to the advisor chain. This can come in handy if you need to add a standard set of ‘global’ advisors:

1
2
3
4
5
6
7
8
9
10
11
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="service"/>
<property name="interceptorNames">
<list>
<value>global*</value>
</list>
</property>
</bean>

<bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/>
<bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>

其实我对本小节使用 global 的描述是持有异议的,这里的目的ProxyFactoryBeaninterceptorNames属性可以使用通配符 * 的方式去引用多个 Interceptors,当然前缀可以不适用 global 而是任意的字符名称;

【12.6】 Concise proxy definitions

Especially when defining transactional proxies, you may end up with many similar proxy definitions. The use of parent and child bean definitions, along with inner bean definitions, can result in much cleaner and more concise proxy definitions.

特别的,当你在定义一个 transactional proxies 的时候,你会遇到非常多相似的 proxy definitions,比如 parent、child bean 定义,包括使用 inner bean 的定义,而这些定义可以通过更简洁的方式来定义 proxy;

First a parent, template, bean definition is created for the proxy:

首先定义一个由 parent, template 和 bean definition 构成的 proxy,

1
2
3
4
5
6
7
8
9
<bean id="txProxyTemplate" abstract="true"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>

上述定义了一个 parent and template bean

This will never be instantiated itself, so may actually be incomplete. Then each proxy which needs to be created is just a child bean definition, which wraps the target of the proxy as an inner bean definition, since the target will never be used on its own anyway.

1
2
3
4
5
6
<bean id="myService" parent="txProxyTemplate">
<property name="target">
<bean class="org.springframework.samples.MyServiceImpl">
</bean>
</property>
</bean>

继承自父类,实现了 myService,并通过 target 注入需要被 transactional proxy 代理的 target;

It is of course possible to override properties from the parent template, such as in this case, the transaction propagation settings:

当然,你可以通过覆盖父类模板中的 properties;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<bean id="mySpecialService" parent="txProxyTemplate">
<property name="target">
<bean class="org.springframework.samples.MySpecialServiceImpl">
</bean>
</property>
<property name="transactionAttributes">
<props>
<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="store*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>

Note that in the example above, we have explicitly marked the parent bean definition as abstract by using the abstract attribute, as described previously, so that it may not actually ever be instantiated. Application contexts (but not simple bean factories) will by default pre-instantiate all singletons. It is therefore important (at least for singleton beans) that if you have a (parent) bean definition which you intend to use only as a template, and this definition specifies a class, you must make sure to set the abstract attribute to true, otherwise the application context will actually try to pre-instantiate it.

上面的这段作者主要是在提醒,如果你只是想定义一个模板,记得使用 abstract = true 属性,这样,Spring 容器启动的时候,就不会将其作为一个单例进行实例化了;

【12.7】 Creating AOP proxies programmatically with the ProxyFactory

It’s easy to create AOP proxies programmatically using Spring. This enables you to use Spring AOP without dependency on Spring IoC.

The following listing shows creation of a proxy for a target object, with one interceptor and one advisor. The interfaces implemented by the target object will automatically be proxied:

interceptoradvisor到底有什么区别?

1
2
3
4
ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl);
factory.addAdvice(myMethodInterceptor);
factory.addAdvisor(myAdvisor);
MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy();

【12.8】 Manipulating advised objects

However you create AOP proxies, you can manipulate them using the org.springframework.aop.framework.Advised interface. Any AOP proxy can be cast to this interface, whichever other interfaces it implements. This interface includes the following methods:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Advisor[] getAdvisors();

void addAdvice(Advice advice) throws AopConfigException;

void addAdvice(int pos, Advice advice) throws AopConfigException;

void addAdvisor(Advisor advisor) throws AopConfigException;

void addAdvisor(int pos, Advisor advisor) throws AopConfigException;

int indexOf(Advisor advisor);

boolean removeAdvisor(Advisor advisor) throws AopConfigException;

void removeAdvisor(int index) throws AopConfigException;

boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException;

boolean isFrozen();

The getAdvisors() method will return an Advisor for every advisor, interceptor or other advice type that has been added to the factory.

getAdvisors()将会返回注入 ProxyFactory 的,由 adviosrs,interceptors 或者其它 advice type 所构成的 Advisor 对象;

  • If you added an Advisor, the returned advisor at this index will be the object that you added.

如果你添加了一个Advisor,那么对应返回数组的下标的对象就是 Advisor 对象;

  • If you added an interceptor or other advice type, Spring will have wrapped this in an advisor with a pointcut that always returns true.

如果你添加了一个interceptor或者是other advice type,Spring 将会返回一个 Advisor 对象,只是该 Advisor 对象封装了该interceptor并且对应的 pointcut 将总是会返回为 true;

  • Thus if you added a MethodInterceptor, the advisor returned for this index will be an DefaultPointcutAdvisor returning your MethodInterceptor and a pointcut that matches all classes and methods.

如果你添加的是一个MethodInterceptor,将会返回一个其 pointcut 匹配所有类和方法的DefaultPointcutAdvisor

By default, it’s possible to add or remove advisors or interceptors even once a proxy has been created. The only restriction is that it’s impossible to add or remove an introduction advisor, as existing proxies from the factory will not show the interface change. (You can obtain a new proxy from the factory to avoid this problem.)

🚩 默认的,即便是 proxy 被创建了你也可以删除或者添加 advisors 或者 interceptors;只是有一个限制,当 proxy 创建以后,就是你不能删除或者添加一个 introdution advisor 了;

A simple example of casting an AOP proxy to the Advised interface and examining and manipulating its advice:

下面演示了一个被代理的对象 advised 被继续添加 Advice 的例子,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Advised advised = (Advised) myObject;
Advisor[] advisors = advised.getAdvisors();
int oldAdvisorCount = advisors.length;
System.out.println(oldAdvisorCount + " advisors");

// Add an advice like an interceptor without a pointcut
// Will match all proxied methods
// Can use for interceptors, before, after returning or throws advice
advised.addAdvice(new DebugInterceptor());

// Add selective advice using a pointcut
advised.addAdvisor(new DefaultPointcutAdvisor(mySpecialPointcut, myAdvice));

assertEquals("Added two advisors", oldAdvisorCount + 2, advised.getAdvisors().length);

[Note]
It’s questionable whether it’s advisable (no pun intended) to modify advice on a business object in production, although there are no doubt legitimate usage cases. However, it can be very useful in development: for example, in tests. I have sometimes found it very useful to be able to add test code in the form of an interceptor or other advice, getting inside a method invocation I want to test. (For example, the advice can get inside a transaction created for that method: for example, to run SQL to check that a database was correctly updated, before marking the transaction for roll back.)

经常会被问到的情况是,是否可以修改一个产品环境中的 business object 的 advice,无疑是可以的;但是,其实在开发阶段会更有用,因为我可以通过 interceptor 或者 advice 添加额外的测试代码;比如,可以通过 advice 添加事务检查,比如,在数据库进行回滚以前,去执行一个 SQL 去检查数据库是否已经正确的更新了;

Depending on how you created the proxy, you can usually set a frozen flag, in which case the Advised isFrozen() method will return true, and any attempts to modify advice through addition or removal will result in an AopConfigException. The ability to freeze the state of an advised object is useful in some cases, for example, to prevent calling code removing a security interceptor. It may also be used in Spring 1.1 to allow aggressive optimization if runtime advice modification is known not to be required.

你可以通过isForzen()方法去设置frozen标志进而禁止修改 advised 对象的 advice 的行为;这样做的用处有,避免我的 advice 被删除,比如 security interceptor;

笔者补充内容,

🚩 最后,是时候对 Spring 中的 Advice、Advisor 以及 Advised 做一个诠释了;

  • 首先,看下 Advice 类在 Spring 中的定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package org.aopalliance.aop;

    /**
    * Tag interface for Advice. Implementations can be any type
    * of advice, such as Interceptors.
    *
    * @author Rod Johnson
    * @version $Id: Advice.java,v 1.1 2004/03/19 17:02:16 johnsonr Exp $
    */
    public interface Advice {

    }

    可以看到,Advice 是一个 tag interface,意思就是通过该接口为某特类的对象打上标签;这类型的对象可以是 advice,比如 Interceptors;

    光看 Advice 接口,会觉得有些莫名其妙的感觉,我们看看它的子接口就一目了然了;以 before 为例,

    BeforeAdvice.jav

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /**
    * Common marker interface for before advice, such as {@link MethodBeforeAdvice}.
    *
    * <p>Spring supports only method before advice. Although this is unlikely to change,
    * this API is designed to allow field advice in future if desired.
    *
    * @author Rod Johnson
    * @see AfterAdvice
    */
    public interface BeforeAdvice extends Advice {

    }

    同样看到,是一个 tag interface;但是我们继续看它的一个子接口,

    MethodBeforeAdvice

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    /**
    * Advice invoked before a method is invoked. Such advices cannot
    * prevent the method call proceeding, unless they throw a Throwable.
    *
    * @see AfterReturningAdvice
    * @see ThrowsAdvice
    *
    * @author Rod Johnson
    */
    public interface MethodBeforeAdvice extends BeforeAdvice {

    /**
    * Callback before a given method is invoked.
    * @param method method being invoked
    * @param args arguments to the method
    * @param target target of the method invocation. May be {@code null}.
    * @throws Throwable if this object wishes to abort the call.
    * Any exception thrown will be returned to the caller if it's
    * allowed by the method signature. Otherwise the exception
    * will be wrapped as a runtime exception.
    */
    void before(Method method, Object[] args, Object target) throws Throwable;

    }

    同样,Spring 提供了AfterAdviceMethodRoundAdvice;来看看 Advice 接口的全景图

    总体而言,Advice 提供了对 join point 的 before、after 和 around 等的方法调用实现;Advice 做为 tag interface 兼容了所有的实现方案;

  • 再次,看先 Advisor 的实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
      /**
    - Base interface holding AOP <b>advice</b> (action to take at a joinpoint)
    - and a filter determining the applicability of the advice (such as
    - a pointcut). <i>This interface is not for use by Spring users, but to
    - allow for commonality in support for different types of advice.</i>
    *
    - <p>Spring AOP is based around <b>around advice</b> delivered via method
    - <b>interception</b>, compliant with the AOP Alliance interception API.
    - The Advisor interface allows support for different types of advice,
    - such as <b>before</b> and <b>after</b> advice, which need not be
    - implemented using interception.
    *
    - @author Rod Johnson
    */
    public interface Advisor {

    /**
    - Return the advice part of this aspect. An advice may be an
    - interceptor, a before advice, a throws advice, etc.
    - @return the advice that should apply if the pointcut matches
    - @see org.aopalliance.intercept.MethodInterceptor
    - @see BeforeAdvice
    - @see ThrowsAdvice
    - @see AfterReturningAdvice
    */
    Advice getAdvice();

    /**
    - Return whether this advice is associated with a particular instance
    - (for example, creating a mixin) or shared with all instances of
    - the advised class obtained from the same Spring bean factory.
    - <p><b>Note that this method is not currently used by the framework.</b>
    - Typical Advisor implementations always return {@code true}.
    - Use singleton/prototype bean definitions or appropriate programmatic
    - proxy creation to ensure that Advisors have the correct lifecycle model.
    - @return whether this advice is associated with a particular target instance
    */
    boolean isPerInstance();

    }

    从类的注解上可以清晰的看到 Advisor 的作用,封装了 Advice,并且提供了一个额外的东西就是 Pointcut,也就是说,在 Advice 上更进一步,Advice 在 join point 上定义了行为,而 Advisor 在此基础上通过 pointcut 定义了该行为怎么发生,什么时候发生;看一个它的子类接口便可以一目了然了,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package org.springframework.aop;

    /**
    - Superinterface for all Advisors that are driven by a pointcut.
    - This covers nearly all advisors except introduction advisors,
    - for which method-level matching doesn't apply.
    *
    - @author Rod Johnson
    */
    public interface PointcutAdvisor extends Advisor {

    /**
    - Get the Pointcut that drives this advisor.
    */
    Pointcut getPointcut();

    }
  • 最后,看看 Advised 接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    /**
    - Interface to be implemented by classes that hold the configuration
    - of a factory of AOP proxies. This configuration includes the
    - Interceptors and other advice, Advisors, and the proxied interfaces.
    *
    - <p>Any AOP proxy obtained from Spring can be cast to this interface to
    - allow manipulation of its AOP advice.
    *
    - @author Rod Johnson
    - @author Juergen Hoeller
    - @since 13.03.2003
    - @see org.springframework.aop.framework.AdvisedSupport
    */
    public interface Advised extends TargetClassAware {

    /**
    - Return whether the Advised configuration is frozen,
    - in which case no advice changes can be made.
    */
    boolean isFrozen();

    /**
    - Are we proxying the full target class instead of specified interfaces?
    */
    boolean isProxyTargetClass();

    /**
    - Return the interfaces proxied by the AOP proxy.
    - <p>Will not include the target class, which may also be proxied.
    */
    Class<?>[] getProxiedInterfaces();

    .....

    }

    Advised 接口,从命名上就非常清晰的体现了它的作用,已经被 Advice 之后的对象,叫做 Advised;从类的注解上可以清晰的看到,该接口对象保存了被代理对象(proxied object)的相关配置属性等;

【12.9】 Using the “auto-proxy” facility

So far we’ve considered explicit creation of AOP proxies using a ProxyFactoryBean or similar factory bean.

目前,我们描述如何通过ProxyFactoryBean或者类型的 factory bean 来显示创建 AOP proxies 的方法;

Spring also allows us to use "auto-proxy" bean definitions, which can automatically proxy selected bean definitions. This is built on Spring "bean post processor" infrastructure, which enables modification of any bean definition as the container loads.

Spring 同样允许我们使用"auto-proxy" bean definitions,它可以让我们自动的将 bean definitions 进行代理;这个是通过使用bean-post-processor的方式实现的,具体逻辑是通过在容器加载的时候去修改 bean definitions 而实现的;

In this model, you set up some special bean definitions in your XML bean definition file to configure the auto proxy infrastructure. This allows you just to declare the targets eligible for auto-proxying: you don’t need to use ProxyFactoryBean.

使用这种模式,你只需要在 XML bean definition 中配置相应的 auto proxy infrastructure 内容,从而表明 targets 使用 auto-proxing 策略,之后,你就不必再使用 ProxyFactoryBean 了;

There are two ways to do this:

有两种方式可以做到这一点,

  • Using an auto-proxy creator that refers to specific beans in the current context.
    在当前的上下文环境中为特定的 beans 使用 auto-proxy creator
  • A special case of auto-proxy creation that deserves to be considered separately; auto-proxy creation driven by source-level metadata attributes.
    可以使用 source-level 的 metadata 属性;

【12.9.1】 Autoproxy bean definitions

The org.springframework.aop.framework.autoproxy package provides the following standard auto-proxy creators.

BeanNameAutoProxyCreator

The BeanNameAutoProxyCreator class is a BeanPostProcessor that automatically creates AOP proxies for beans with names matching literal values or wildcards.

1
2
3
4
5
6
7
8
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames" value="jdk*,onlyJdk"/>
<property name="interceptorNames">
<list>
<value>myInterceptor</value>
</list>
</property>
</bean>

As with ProxyFactoryBean, there is an interceptorNames property rather than a list of interceptors, to allow correct behavior for prototype advisors. Named “interceptors” can be advisors or any advice type.

As with auto proxying in general, the main point of using BeanNameAutoProxyCreator is to apply the same configuration consistently to multiple objects, with minimal volume of configuration. It is a popular choice for applying declarative transactions to multiple objects.

Bean definitions whose names match, such as “jdkMyBean” and “onlyJdk” in the above example, are plain old bean definitions with the target class. An AOP proxy will be created automatically by the BeanNameAutoProxyCreator. The same advice will be applied to all matching beans. Note that if advisors are used (rather than the interceptor in the above example), the pointcuts may apply differently to different beans.

只要是名字匹配了,就会自动的通过BeanNameAutoProxyCreator创建其代理对象;如果使用了 Advisor,那么将会通过 Pointcut 去筛选出需要被代理的 target 对象;

DefaultAdvisorAutoProxyCreator

A more general and extremely powerful auto proxy creator is DefaultAdvisorAutoProxyCreator. This will automagically apply eligible advisors in the current context, without the need to include specific bean names in the auto-proxy advisor’s bean definition. It offers the same merit of consistent configuration and avoidance of duplication as BeanNameAutoProxyCreator.

更强大的是使用DefaultAdvisorAutoProxyCreator;它不需要包含 bean names 的配置而自动的将核实的 bean 进行代理;

Using this mechanism involves:

  • Specifying a DefaultAdvisorAutoProxyCreator bean definition.
    在 bean definition 中指明使用DefaultAdvisorAutoProxyCreator
  • Specifying any number of Advisors in the same or related contexts. Note that these must be Advisors, not just interceptors or other advices. This is necessary because there must be a pointcut to evaluate, to check the eligibility of each advice to candidate bean definitions.
    必须使用 Advisors 而不能使用 interceptors 或者其它的 advices,因为 pointcut 必须使用;

The DefaultAdvisorAutoProxyCreator will automatically evaluate the pointcut contained in each advisor, to see what (if any) advice it should apply to each business object (such as “businessObject1” and “businessObject2” in the example).

DefaultAdvisorAutoProxyCreator将会根据每一个 advisor 对象中的 pointcut 属性去匹配哪些业务对象是将会被代理;

This means that any number of advisors can be applied automatically to each business object. If no pointcut in any of the advisors matches any method in a business object, the object will not be proxied. As bean definitions are added for new business objects, they will automatically be proxied if necessary.

也就意味着,多个 advisors 可以作用于每一个业务对象;如果某个业务对象没有被任何的 pointcut 所匹配,那么该对象将不会被代理;

Autoproxying in general has the advantage of making it impossible for callers or dependencies to obtain an un-advised object. Calling getBean(“businessObject1”) on this ApplicationContext will return an AOP proxy, not the target business object. (The “inner bean” idiom shown earlier also offers this benefit.)

🚩 Autoproxing 的优势是,通过 caller 或者 dependencies 将无法获得没有被代理的对象,也就是说,当使用 ApplicationContext.getBean(“businessObject1”)只会得到其被代理的对象,而不会得到其原生的 target 对象;如下面的例子所述,

1
2
3
4
5
6
7
8
9
10
11
12
13
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
<property name="transactionInterceptor" ref="transactionInterceptor"/>
</bean>

<bean id="customAdvisor" class="com.mycompany.MyAdvisor"/>

<bean id="businessObject1" class="com.mycompany.BusinessObject1">
<!-- Properties omitted -->
</bean>

<bean id="businessObject2" class="com.mycompany.BusinessObject2"/>

The DefaultAdvisorAutoProxyCreator is very useful if you want to apply the same advice consistently to many business objects. Once the infrastructure definitions are in place, you can simply add new business objects without including specific proxy configuration. You can also drop in additional aspects very easily - for example, tracing or performance monitoring aspects - with minimal change to configuration.

使用DefaultAdvisorAutoProxyCreator便利的地方是,你可以很方便的将相同的 advice 应用到多个业务对象上;当你的代理规则配置好了以后,你可以添加符合规则的业务对象,而自动的适配当前的代理规则;比如当配置性能监控的 aspects 等;

The DefaultAdvisorAutoProxyCreator offers support for filtering (using a naming convention so that only certain advisors are evaluated, allowing use of multiple, differently configured, AdvisorAutoProxyCreators in the same factory) and ordering. Advisors can implement the org.springframework.core.Ordered interface to ensure correct ordering if this is an issue. The TransactionAttributeSourceAdvisor used in the above example has a configurable order value; the default setting is unordered.

DefaultAdvisorAutoProxyCreator同样提供了过滤和排序;通过实现org.springframework.core.Ordered interface 确保 advice 被执行的顺序;

AbstractAdvisorAutoProxyCreator

This is the superclass of DefaultAdvisorAutoProxyCreator. You can create your own auto-proxy creators by subclassing this class, in the unlikely event that advisor definitions offer insufficient customization to the behavior of the framework DefaultAdvisorAutoProxyCreator.

【12.9.2】 Using metadata-driven auto-proxying

A particularly important type of auto-proxying is driven by metadata. This produces a similar programming model to .NET ServicedComponents. Instead of defining metadata in XML descriptors, configuration for transaction management and other enterprise services is held in source-level attributes.

一个特殊的且重要的 auto-proxing 的实现方式是通过metadata实现的;该方式与 .NET ServicedComponents 类似;取而代之,使用 source-level 属性的配置方式来取代 XML 中配置属性;

In this case, you use the DefaultAdvisorAutoProxyCreator, in combination with Advisors that understand metadata attributes. The metadata specifics are held in the pointcut part of the candidate advisors, rather than in the auto-proxy creation class itself.

通过联合 Advisors 使用DefaultAdvisorAutoProxyCreator,Advisors 能够识别 metadata 属性;通过 pointcut 来定义 metadata,而不再通过 target class 来指定;

This is really a special case of the DefaultAdvisorAutoProxyCreator, but deserves consideration on its own. (The metadata-aware code is in the pointcuts contained in the advisors, not the AOP framework itself.)

在 advisors 中 通过 pointcuts 定义 metadata-aware code,这被作者当做是DefaultAdvisorAutoProxyCreator的一种特殊的使用情况;

The /attributes directory of the JPetStore sample application shows the use of attribute-driven auto-proxying. In this case, there’s no need to use the TransactionProxyFactoryBean. Simply defining transactional attributes on business objects is sufficient, because of the use of metadata-aware pointcuts. The bean definitions include the following code, in /WEB-INF/declarativeServices.xml. Note that this is generic, and can be used outside the JPetStore:

在目录/attributes中的 JPetStore 的例子使用到了 attribute-driven auto-proxing 的技术;在这个用例中,不再需要使用TransactionProxyFactoryBean;只需要在业务对象中定义 transactional attributes 即可,因为在业务对象中使用了 metadata-aware 的 pointcuts;见如下配置的 bean definitions,/WEB-INF/declarativeServices.xml,包含了这些 metadata-aware pointcuts,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
<property name="transactionInterceptor" ref="transactionInterceptor"/>
</bean>

<bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributeSource">
<bean class="org.springframework.transaction.interceptor.AttributesTransactionAttributeSource">
<property name="attributes" ref="attributes"/>
</bean>
</property>
</bean>

<bean id="attributes" class="org.springframework.metadata.commons.CommonsAttributes"/>

The DefaultAdvisorAutoProxyCreator bean definition (the name is not significant, hence it can even be omitted) will pick up all eligible pointcuts in the current application context.

DefaultAdvisorAutoProxyCreator bean definition 将会从当前的 application context 中获取所有符合规则的 pointcuts;

In this case, the “transactionAdvisor” bean definition, of type TransactionAttributeSourceAdvisor, will apply to classes or methods carrying a transaction attribute.

这个用例中,使用到了TransactionAttributeSourceAdvisor的 transactionAdvisor bean definition,使得标注了 transaction 属性的 classes 或者 methods 被代理;

The TransactionAttributeSourceAdvisor depends on a TransactionInterceptor, via constructor dependency. The example resolves this via autowiring.

构建TransactionAttributeSourceAdvisor实例依赖于TransactionInterceptor,通过构造函数依赖注入;这个用例是使用的autowiring的方式;

The AttributesTransactionAttributeSource depends on an implementation of the org.springframework.metadata.Attributes interface. In this fragment, the “attributes” bean satisfies this, using the Jakarta Commons Attributes API to obtain attribute information. (The application code must have been compiled using the Commons Attributes compilation task.)

实例化AttributesTransactionAttributeSource依赖于org.springframework.metadata.Attributes接口的实现;当前用例中,”attributes” bean 适合this,使用 Jakarta Commons Attributes API 来获得 attribute information;也就意味着,应用代码必须是通过 Commons Attributes compilation 任务进行编译的;

The /annotation directory of the JPetStore sample application contains an analogous example for auto-proxying driven by JDK 1.5+ annotations. The following configuration enables automatic detection of Spring’s Transactional annotation, leading to implicit proxies for beans containing that annotation:

🚩 “/annotation”目录下的 JPetStore 例子还包含了一个相似的,使用了 JDK 1.5+ 注解方式的一个例子;下面的配置将会自动的去检测Transactional注解,并为其实现代理;

1
2
3
4
5
6
7
8
9
10
11
12
13
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
<property name="transactionInterceptor" ref="transactionInterceptor"/>
</bean>

<bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributeSource">
<bean class="org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"/>
</property>
</bean>

注意,这里的属性transactionAttributeSource是用的是 org.springframework.transaction.annotation.AnnotationTransactionAttributeSource,所以才能够支持@Transactional注解;

The TransactionInterceptor defined here depends on a PlatformTransactionManager definition, which is not included in this generic file (although it could be) because it will be specific to the application’s transaction requirements (typically JTA, as in this example, or Hibernate, JDO or JDBC):

另外TransactionInterceptor需要依赖一个PlatformTransactionManager,这里给出其定义,

1
2
<bean id="transactionManager"
class="org.springframework.transaction.jta.JtaTransactionManager"/>

This mechanism is extensible. It’s possible to do auto-proxying based on custom attributes. You need to:

这个机制是可以被扩展的;可以通过自定义的 attributes 实现 auto-proxing;

  • Define your custom attribute.
    定义你自己的属性;

  • Specify an Advisor with the necessary advice, including a pointcut that is triggered by the presence of the custom attribute on a class or method. You may be able to use an existing advice, merely implementing a static pointcut that picks up the custom attribute.
    实现一个包含必要 advice 的Advisor,并且包含一个 pointcut 来执行实现了你自定义属性的 class 或者 method;你可以使用线程的 advice,仅仅是通过一个静态的 pointcut 获取你的自定义属性即可;

It’s possible for such advisors to be unique to each advised class (for example, mixins): they simply need to be defined as prototype, rather than singleton, bean definitions. For example, the LockMixin introduction interceptor from the Spring test suite, shown above, could be used in conjunction with a generic DefaultIntroductionAdvisor:

也可以使得 advisor 对每一个 advised class 来说是唯一的;只需要将他们简单的定义为prototype而不是singleton的接口;

1
2
3
4
5
6
<bean id="lockMixin" class="test.mixin.LockMixin" scope="prototype"/>

<bean id="lockableAdvisor" class="org.springframework.aop.support.DefaultIntroductionAdvisor"
scope="prototype">
<constructor-arg ref="lockMixin"/>
</bean>

Note that both lockMixin and lockableAdvisor are defined as prototypes.

【12.10】 Using TargetSources

Spring offers the concept of a TargetSource, expressed in the org.springframework.aop.TargetSource interface. This interface is responsible for returning the "target object" implementing the join point. The TargetSource implementation is asked for a target instance each time the AOP proxy handles a method invocation.

TargetSource可以返回 join point 所对应的 target object;只要每次 AOP proxy 对象调用噶 join point 的时候,都会去获取该 target instance,言外之意,每次调用,获取最新状态的 target instance;

Developers using Spring AOP don’t normally need to work directly with TargetSources, but this provides a powerful means of supporting pooling, hot swappable and other sophisticated targets. For example, a pooling TargetSource can return a different target instance for each invocation, using a pool to manage instances.

If you do not specify a TargetSource, a default implementation is used that wraps a local object. The same target is returned for each invocation (as you would expect).

如果不指明使用TargetSource,也会通过一个默认的实现去封装当前的与原始对象;同样的,每次调用,target 将会返回;

Let’s look at the standard target sources provided with Spring, and how you can use them.

[Tip]
When using a custom target source, your target will usually need to be a prototype rather than a singleton bean definition. This allows Spring to create a new target instance when required.

当需要使用你自己定义的 target source 的时候,最好是将其定义为 prototype 而不是 singleton,这样,Spring 可以在需要的时候将其初始化;

【12.10.1】 Hot swappable target sources

The org.springframework.aop.target.HotSwappableTargetSource exists to allow the target of an AOP proxy to be switched while allowing callers to keep their references to it.

在不改变调用方对 target 的引用的前提下,HotSwappableTargetSource允许 AOP proxy 的 target 被替换;-> Hot swappable 热替换技术;

Changing the target source’s target takes effect immediately. The HotSwappableTargetSource is threadsafe.

改变 target source 将会立即生效;并且HotSwappableTargetSource是线程安全的;

You can change the target via the swap() method on HotSwappableTargetSource as follows:

你可以在HotSwappableTargetSource对象上通过调用swap()方法来改变 target 对象

1
2
HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper");
Object oldTarget = swapper.swap(newTarget);
1
2
3
4
5
6
7
8
9
<bean id="initialTarget" class="mycompany.OldTarget"/>

<bean id="swapper" class="org.springframework.aop.target.HotSwappableTargetSource">
<constructor-arg ref="initialTarget"/>
</bean>

<bean id="swappable" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="targetSource" ref="swapper"/>
</bean>

上面给出了一个非常好的例子,通过HotSwappableTargetSource实例 swapper 封装原有的对象OldTarget,并将 swapper 对象作为 target 提供给ProxyFactoryBean生成其相应的代理对象;Ok,好玩的事情发生了,看上面的 Java 代码,用新的对象实例 newTarget 来替换原有的 OldTarget 实现;诠释了热替换在 Spring 中是如何实现的;

The above swap() call changes the target of the swappable bean. Clients who hold a reference to that bean will be unaware of the change, but will immediately start hitting the new target.

Although this example doesn’t add any advice - and it’s not necessary to add advice to use a TargetSource - of course any TargetSource can be used in conjunction with arbitrary advice.

TODO, 需要验证的是,如何不通过 XML 的方式类实现热替换?

【12.10.2】 Pooling target sources

Using a pooling target source provides a similar programming model to stateless session EJBs, in which a pool of identical instances is maintained, with method invocations going to free objects in the pool.

使用 pooling target source 提供了类似于stateless session EJBs的编程模型,一个包含同一个对象的多个 instances 在 pool 中被管理和维护,在该对象上的方法调用可以作用到该 pool 中的该对象的任何一个 instances 上;这样做的一个很重要的前提就是 stateless,好比对 database connection 的池化技术;

A crucial difference between Spring pooling and SLSB pooling is that Spring pooling can be applied to any POJO. As with Spring in general, this service can be applied in a non-invasive way.

与 SLSB(stateless session EJBs) pooling 的一个重要区别是,Spring pooling 允许使用 POJO;

Spring provides out-of-the-box support for Commons Pool 2.2, which provides a fairly efficient pooling implementation. You’ll need the commons-pool Jar on your application’s classpath to use this feature. It’s also possible to subclass org.springframework.aop.target.AbstractPoolingTargetSource to support any other pooling API.

Spring 提供了兼容容器之外的 Commons Pool 2.2 的支持;你需要将 Commons Pool 的 jar 文件放置在你的 classpath 中;同样可以通过继承AbstractPoolingTargetSource以提供对其他 pooling 技术的支持;

[Note]
Commons Pool 1.5+ is also supported but deprecated as of Spring Framework 4.2.

这里的意思是,不推荐在 Spring Framework 4.2 以后继续使用 Commons Pool 了;

Sample configuration is shown below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject"
scope="prototype">
... properties omitted
</bean>

<bean id="poolTargetSource" class="org.springframework.aop.target.CommonsPool2TargetSource">
<property name="targetBeanName" value="businessObjectTarget"/>
<property name="maxSize" value="25"/>
</bean>

<bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="targetSource" ref="poolTargetSource"/>
<property name="interceptorNames" value="myInterceptor"/>
</bean>

Note that the target object - “businessObjectTarget” in the example - must be a prototype. This allows the PoolingTargetSource implementation to create new instances of the target to grow the pool as necessary. See the javadocs of AbstractPoolingTargetSource and the concrete subclass you wish to use for information about its properties: "maxSize" is the most basic, and always guaranteed to be present.

注意,必须对将要被 pool 的对象 businessObjectTarget 使用prototype,这样PoolingTargetSource才能够将该 target 进行多次实例化得到多个实例放入 pool;

In this case, "myInterceptor" is the name of an interceptor that would need to be defined in the same IoC context. However, it isn’t necessary to specify interceptors to use pooling. If you want only pooling, and no other advice, don’t set the interceptorNames property at all.

可以使用myInterceptor根据 interface 的名字引用 Spring 容器中的 interceptor…

上面这个例子让我映像深刻的是,可以把放置在 pool 中的实例,使用 FactoryBean 生成其代理,这样的话,pool 中的对象就不在是原生对象了,而是被代理过后的 proxies 了;

It’s possible to configure Spring so as to be able to cast any pooled object to the org.springframework.aop.target.PoolingConfig interface, which exposes information about the configuration and current size of the pool through an introduction. You’ll need to define an advisor like this:

通过 Spring 的配置可以使得任何 pooled object 通过类型转换为org.springframework.aop.target.PoolingConfig的对象,该对象可以输出当前pool的配置和大小,该技术是通过introduction实现的;如下,

1
2
3
4
<bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetObject" ref="poolTargetSource"/>
<property name="targetMethod" value="getPoolingConfigMixin"/>
</bean>

This advisor is obtained by calling a convenience method on the AbstractPoolingTargetSource class, hence the use of MethodInvokingFactoryBean. This advisor’s name (“poolConfigAdvisor“ here) must be in the list of interceptors names in the ProxyFactoryBean exposing the pooled object.

poolConfigAdvisor必须出现在ProxyFactoryBeaninterceptors names 中;上述配置通过targetMethod属性告诉ProxyFactoryBean需要额外为targetObject(既 poolTargetSource)所实现的代理对象businessObject额外提供一个接口方法的实现既是getPoolingConfigMixin( 既是继承 AbstractPoolingTargetSource#getPoolingConfigMixin 方法 );这样,就可以直接通过该代理对象 businessObject 获得 pool 的当前信息;

The cast will look as follows:

1
2
PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
System.out.println("Max pool size is " + conf.getMaxSize());

补充,给出上述的一个完整用例,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject"
scope="prototype">
... properties omitted
</bean>

<bean id="poolTargetSource" class="org.springframework.aop.target.CommonsPool2TargetSource">
<property name="targetBeanName" value="businessObjectTarget"/>
<property name="maxSize" value="25"/>
</bean>

<bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="targetSource" ref="poolTargetSource"/>
<property name="interceptorNames" value="poolConfigAdvisor"/>
</bean>

<bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetObject" ref="poolTargetSource"/>
<property name="targetMethod" value="getPoolingConfigMixin"/>
</bean>

[Note]
Pooling stateless service objects is not usually necessary. We don’t believe it should be the default choice, as most stateless objects are naturally thread safe, and instance pooling is problematic if resources are cached.

注意,pooling 技术只适合于 stateless 的 objects,如果缓存的是 prototype 的对象,将会造成一些问题;

Simpler pooling is available using auto-proxying. It’s possible to set the TargetSources used by any auto-proxy creator.

简单的 pooling 可以用于 auto-proxing;

写在最后,我在想,什么样的场景会使用到这样的东西,除了 database connection 的情况;我觉得场景就是当初始化 Target 的性能开销比较大的前提下;

【12.10.3】 Prototype target sources

Setting up a “prototype” target source is similar to a pooling TargetSource. In this case, a new instance of the target will be created on every method invocation. Although the cost of creating a new object isn’t high in a modern JVM, the cost of wiring up the new object (satisfying its IoC dependencies) may be more expensive. Thus you shouldn’t use this approach without very good reason.

将 target source 设置为 prototype 类似于 pooling TargetSource;这种情况情况之下,每次方法调用的时候一个新的 target 实例将会被创建;有相应的性能开销,没有特殊原因,不能这么去做;

To do this, you could modify the poolTargetSource definition shown above as follows. (I’ve also changed the name, for clarity.)

要达到上述目标,你需要对你的 TargetSource 的配置做如下的变更,换成使用PrototypeTargetSource

1
2
3
<bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource">
<property name="targetBeanName" ref="businessObjectTarget"/>
</bean>

There’s only one property: the name of the target bean. Inheritance is used in the TargetSource implementations to ensure consistent naming. As with the pooling target source, the target bean must be a prototype bean definition.

这有一个限制,就是 targetBeanName 引用的必须是prototype

写在最后,我在想,什么样的场景会使用到这个东西?Ok,一种可以很快想到的情况,就是该 bean 不能是一个 stateless bean 的而是一个 stateful bean 状态 bean 的情况;

【12.10.4】 ThreadLocal target sources

ThreadLocal target sources are useful if you need an object to be created for each incoming request (per thread that is). The concept of a ThreadLocal provide a JDK-wide facility to transparently store resource alongside a thread. Setting up a ThreadLocalTargetSource is pretty much the same as was explained for the other types of target source:

ThreadLocal target sources 可以为每一个 incoming request (既 per thread ) 创建一个新的对象;使用 ThreadLocalTargetSource,

1
2
3
<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource">
<property name="targetBeanName" value="businessObjectTarget"/>
</bean>

[Note]
ThreadLocals come with serious issues (potentially resulting in memory leaks) when incorrectly using them in a multi-threaded and multi-classloader environments. One should always consider wrapping a threadlocal in some other class and never directly use the ThreadLocal itself (except of course in the wrapper class). Also, one should always remember to correctly set and unset (where the latter simply involved a call to ThreadLocal.set(null)) the resource local to the thread. Unsetting should be done in any case since not unsetting it might result in problematic behavior. Spring’s ThreadLocal support does this for you and should always be considered in favor of using ThreadLocals without other proper handling code.

作者提醒了使用 TheadLocal 的一些注意事项,那就是如果当你不再使用 ThreadLocal 中的对象以后,记得手动的 unset it,通过ThreadLocal.set(null))进行 unset;

【12.11】 Defining new Advice types

Spring AOP is designed to be extensible. While the interception implementation strategy is presently used internally, it is possible to support arbitrary advice types in addition to the out-of-the-box interception around advice, before, throws advice and after returning advice.

The org.springframework.aop.framework.adapter package is an SPI package allowing support for new custom advice types to be added without changing the core framework. The only constraint on a custom Advice type is that it must implement the org.aopalliance.aop.Advice marker interface.

可以通过 SPI 的方式添加新的自定义的 advice types 而无需修改 Spring Core Framework;唯一的限制就是必须实现org.aopalliance.aop.Advice接口;

Please refer to the org.springframework.aop.framework.adapter javadocs for further information.

【12.12】 Further resources

Please refer to the Spring sample applications for further examples of Spring AOP:

  • The JPetStore’s default configuration illustrates the use of the TransactionProxyFactoryBean for declarative transaction management.
  • The /attributes directory of the JPetStore illustrates the use of attribute-driven declarative transaction management.

Wow ~~~~,总算完成了 Spring AOP 的部分.. 总共耗时 21.7 hrs,三天,平均每天差不多 8 小时…. 挺累的.. 不过挺好,整理完成以后,让我对 Spring AOP 的整体特性有了一个比较全面的认识了… Good~~~