对于最新稳定版本,请使用 Spring Framework 7.0.6spring-doc.cadn.net.cn

声明切入点

切入点(Pointcuts)用于确定我们感兴趣的连接点(join points),从而让我们能够控制通知(advice)的执行时机。Spring AOP 仅支持针对 Spring Bean 的方法执行连接点,因此你可以将切入点理解为匹配 Spring Bean 上方法的执行。切入点声明包含两个部分:一个由名称和任意参数组成的签名,以及一个切入点表达式,该表达式精确地指定了我们感兴趣的方法执行。在基于 @AspectJ 注解风格的 AOP 中,切入点签名通过一个常规的方法定义来提供,而切入点表达式则通过使用 @Pointcut 注解来指定(作为切入点签名的方法必须具有 void 返回类型)。spring-doc.cadn.net.cn

一个示例或许有助于阐明切入点签名(pointcut signature)与切入点表达式(pointcut expression)之间的区别。以下示例定义了一个名为 anyOldTransfer 的切入点,它匹配任何名为 transfer 的方法的执行:spring-doc.cadn.net.cn

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

构成 @Pointcut 注解值的切点表达式是一个标准的 AspectJ 切点表达式。有关 AspectJ 切点语言的完整讨论,请参阅 AspectJ 编程指南(以及关于扩展内容的 AspectJ 5 开发者笔记),或阅读有关 AspectJ 的书籍(例如 Colyer 等人著的《Eclipse AspectJ》,或 Ramnivas Laddad 著的《AspectJ 实战》)。spring-doc.cadn.net.cn

支持的切点设计器

Spring AOP 支持在切入点表达式中使用以下 AspectJ 切入点指示符(PCD):spring-doc.cadn.net.cn

  • execution:用于匹配方法执行连接点。这是在使用 Spring AOP 时主要使用的切入点指示符。spring-doc.cadn.net.cn

  • within:将匹配限制在特定类型内的连接点(使用 Spring AOP 时,指在匹配类型内声明的方法的执行)。spring-doc.cadn.net.cn

  • this:将匹配限制在连接点(使用 Spring AOP 时指方法的执行)上,其中 bean 引用(Spring AOP 代理)是给定类型的实例。spring-doc.cadn.net.cn

  • target:将匹配限制在连接点(使用 Spring AOP 时指方法的执行)上,其中目标对象(被代理的应用程序对象)是给定类型的实例。spring-doc.cadn.net.cn

  • args:限制匹配到连接点(使用 Spring AOP 时指方法的执行),其参数是给定类型的实例。spring-doc.cadn.net.cn

  • @target:将匹配限制在连接点(使用 Spring AOP 时指方法的执行)上,且执行对象的类具有指定类型的注解。spring-doc.cadn.net.cn

  • @args:将匹配限制在连接点(使用 Spring AOP 时指方法的执行)上,这些连接点所传递的实际参数的运行时类型具有指定类型的注解。spring-doc.cadn.net.cn

  • @within:将匹配限制在具有指定注解的类型内的连接点(当使用 Spring AOP 时,指的是在具有该注解的类型中声明的方法的执行)。spring-doc.cadn.net.cn

  • @annotation:将匹配限制在连接点的目标(在 Spring AOP 中即正在执行的方法)具有指定注解的连接点上。spring-doc.cadn.net.cn

其他切入点类型

完整的 AspectJ 切点语言支持其他一些 Spring 不支持的切点指示符:callgetsetpreinitializationstaticinitializationinitializationhandleradviceexecutionwithincodecflowcflowbelowif@this@withincode。在由 Spring AOP 解析的切点表达式中使用这些切点指示符会导致抛出 IllegalArgumentExceptionspring-doc.cadn.net.cn

Spring AOP 所支持的切入点指示符(pointcut designator)集合在未来版本中可能会扩展,以支持更多 AspectJ 切入点指示符。spring-doc.cadn.net.cn

由于 Spring AOP 将匹配限制在方法执行连接点(join points)上,因此上述关于切入点(pointcut)指示符的讨论比 AspectJ 编程指南中的定义更为狭窄。此外,AspectJ 本身具有基于类型的语义,在方法执行连接点处,thistarget 都指向同一个对象:即正在执行该方法的对象。而 Spring AOP 是一个基于代理的系统,它区分代理对象本身(绑定到 this)和代理背后的目标对象(绑定到 target)。spring-doc.cadn.net.cn

由于 Spring AOP 框架基于代理的特性,目标对象内部的方法调用按照定义是不会被拦截的。对于 JDK 代理,只有通过代理对公共接口方法的调用才能被拦截。而使用 CGLIB 时,通过代理对公共(public)和受保护(protected)方法的调用会被拦截(如有必要,包可见(package-visible)的方法也会被拦截)。然而,通过代理进行的常规交互应始终通过公共方法签名来设计。spring-doc.cadn.net.cn

请注意,切入点(pointcut)定义通常会与任何被拦截的方法进行匹配。 如果某个切入点严格限定仅适用于公共(public)方法,即使在 CGLIB 代理场景下可能存在通过代理进行的非公共(non-public)交互,也需要相应地进行定义。spring-doc.cadn.net.cn

如果你的拦截需求包括目标类中的方法调用甚至构造函数,请考虑使用 Spring 驱动的原生 AspectJ 编织(weaving),而不是 Spring 基于代理的 AOP 框架。这构成了一种具有不同特性的 AOP 使用模式,因此在做出决定之前,请务必先熟悉编织(weaving)的相关知识。spring-doc.cadn.net.cn

Spring AOP 还支持一个名为 bean 的额外切入点指示符(PCD)。该 PCD 允许你将连接点的匹配限制为特定名称的 Spring Bean,或一组使用通配符指定的 Spring Bean。bean PCD 的形式如下:spring-doc.cadn.net.cn

bean(idOrNameOfBean)

idOrNameOfBean 标记可以是任意 Spring Bean 的名称。系统提供了有限的通配符支持,使用 * 字符,因此,如果你为 Spring Bean 建立了一些命名约定,就可以编写一个 bean 切入点表达式(PCD)来选择它们。与其他切入点指示符(pointcut designator)一样,bean PCD 也可以与 &&(与)、||(或)和 !(非)操作符一起使用。spring-doc.cadn.net.cn

bean 切入点指示符(PCD)仅在 Spring AOP 中受支持,而在原生的 AspectJ 编织中不受支持。它是 Spring 对 AspectJ 所定义的标准切入点指示符的一种特定扩展,因此对于使用 @Aspect 模型声明的切面不可用。spring-doc.cadn.net.cn

bean 切入点指示符(PCD)作用于实例级别(基于 Spring Bean 名称的概念),而不仅仅局限于类型级别(这是基于织入的 AOP 所受限的范围)。 基于实例的切入点指示符是 Spring 基于代理的 AOP 框架及其与 Spring Bean 工厂紧密集成所具备的一项特殊能力,在这种集成中,通过名称来识别特定的 Bean 是自然而直接的。spring-doc.cadn.net.cn

组合切点表达式

你可以使用 &&, ||! 来组合切入点(pointcut)表达式。你也可以通过名称引用切入点表达式。以下示例展示了三个切入点表达式:spring-doc.cadn.net.cn

package com.xyz;

public class Pointcuts {

	@Pointcut("execution(public * *(..))")
	public void publicMethod() {} (1)

	@Pointcut("within(com.xyz.trading..*)")
	public void inTrading() {} (2)

	@Pointcut("publicMethod() && inTrading()")
	public void tradingOperation() {} (3)
}
1 publicMethod 在方法执行连接点表示任意公共方法的执行时匹配。
2 inTrading 在方法执行位于交易模块中时匹配。
3 tradingOperation 在方法执行代表交易模块中的任意公共方法时匹配。
package com.xyz

class Pointcuts {

	@Pointcut("execution(public * *(..))")
	fun publicMethod() {} (1)

	@Pointcut("within(com.xyz.trading..*)")
	fun inTrading() {} (2)

	@Pointcut("publicMethod() && inTrading()")
	fun tradingOperation() {} (3)
}
1 publicMethod 在方法执行连接点表示任意公共方法的执行时匹配。
2 inTrading 在方法执行位于交易模块中时匹配。
3 tradingOperation 在方法执行代表交易模块中的任意公共方法时匹配。

如上所示,将较小的命名切入点(named pointcuts)组合起来构建更复杂的切入点表达式是一种最佳实践。在通过名称引用切入点时,适用常规的 Java 可见性规则(例如,可以在同一类型中访问private切入点,在继承层次结构中访问protected切入点,以及在任何地方访问public切入点等)。可见性不会影响切入点的匹配。spring-doc.cadn.net.cn

共享命名切点定义

在开发企业级应用程序时,开发者经常需要在多个切面(aspect)中引用应用程序的某些模块以及特定的操作集合。 为此,我们建议定义一个专用的类,用于封装常用的命名切入点(named pointcut)表达式。 此类通常类似于下面的CommonPointcuts示例(当然,类的具体名称可由您自行决定):spring-doc.cadn.net.cn

package com.xyz;

import org.aspectj.lang.annotation.Pointcut;

public class CommonPointcuts {

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

	/**
	 * A join point is in the service layer if the method is defined
	 * in a type in the com.xyz.service package or any sub-package
	 * under that.
	 */
	@Pointcut("within(com.xyz.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.dao package or any sub-package
	 * under that.
	 */
	@Pointcut("within(com.xyz.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.abc.service and com.xyz.def.service) then
	 * the pointcut expression "execution(* com.xyz..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..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.dao.*.*(..))")
	public void dataAccessOperation() {}

}
package com.xyz

import org.aspectj.lang.annotation.Pointcut

class CommonPointcuts {

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

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

	/**
	 * A join point is in the data access layer if the method is defined
	 * in a type in the com.xyz.dao package or any sub-package
	 * under that.
	 */
	@Pointcut("within(com.xyz.dao..*)")
	fun 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.abc.service and com.xyz.def.service) then
	 * the pointcut expression "execution(* com.xyz..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..service.*.*(..))")
	fun 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.dao.*.*(..))")
	fun dataAccessOperation() {}

}

你可以在任何需要切入点表达式的地方,通过引用该类的全限定名加上@Pointcut方法的名称,来引用在此类中定义的切入点。例如,要使服务层具有事务性,你可以编写如下代码,它引用了名为com.xyz.CommonPointcuts.businessService()命名切入点spring-doc.cadn.net.cn

<aop:config>
	<aop:advisor
		pointcut="com.xyz.CommonPointcuts.businessService()"
		advice-ref="tx-advice"/>
</aop:config>

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

<aop:config><aop:advisor> 元素在基于 Schema 的 AOP 支持中讨论。事务相关元素在事务管理中讨论。spring-doc.cadn.net.cn

<h1>示例</h1>

Spring AOP 用户最常使用的是 execution 切入点指示符。 execution 表达式的格式如下:spring-doc.cadn.net.cn

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

除了返回类型模式(前文代码片段中的 ret-type-pattern)、名称模式和参数模式之外,其余部分都是可选的。返回类型模式决定了方法的返回类型必须是什么,才能匹配一个连接点。* 是最常用的返回类型模式。它匹配任意返回类型。只有当方法返回指定类型时,完全限定的类型名称才会匹配。名称模式匹配方法名。您可以使用通配符*作为名称模式的全部或部分。如果指定了声明类型模式,请在名称模式组件后添加一个尾随的.。参数模式稍微复杂一些:() 匹配不带参数的方法,而(..) 匹配任意数量(零个或多个)的参数。spring-doc.cadn.net.cn

The (*) pattern 匹配一个接受任意类型参数的方法。spring-doc.cadn.net.cn

(*,String) 匹配一个接受两个参数的方法。第一个可以是任何类型,而第二个必须是 String。咨询 语言语义 部分的 AspectJ 编程指南 以获取更多详细信息。

spring-doc.cadn.net.cn

以下示例展示了一些常见的切入点表达式:spring-doc.cadn.net.cn

  • 任何公共方法的执行:spring-doc.cadn.net.cn

    execution(public * *(..))
  • 任何方法名以 set 开头的方法的执行:spring-doc.cadn.net.cn

    execution(* set*(..))
  • AccountService 接口定义的任何方法的执行:spring-doc.cadn.net.cn

    execution(* com.xyz.service.AccountService.*(..))
  • service 包中定义的任何方法的执行:spring-doc.cadn.net.cn

    execution(* com.xyz.service.*.*(..))
  • 服务包或其任意子包中定义的任何方法的执行:spring-doc.cadn.net.cn

    execution(* com.xyz.service..*.*(..))
  • 服务包内的任意连接点(在 Spring AOP 中仅限方法执行):spring-doc.cadn.net.cn

    within(com.xyz.service.*)
  • 服务包或其任意子包内的任何连接点(在 Spring AOP 中仅限方法执行):spring-doc.cadn.net.cn

    within(com.xyz.service..*)
  • 任何连接点(在 Spring AOP 中仅限方法执行),其中代理实现了 AccountService 接口:spring-doc.cadn.net.cn

    this(com.xyz.service.AccountService)
    this 更常用于绑定形式。有关如何在通知(advice)体中使代理对象可用,请参见声明通知一节。
  • 任何连接点(在 Spring AOP 中仅限方法执行),其中目标对象实现了 AccountService 接口:spring-doc.cadn.net.cn

    target(com.xyz.service.AccountService)
    target 更常用于绑定形式。有关如何在通知体中使目标对象可用,请参见声明通知部分。
  • 任何连接点(在 Spring AOP 中仅限方法执行),该连接点接受单个参数,并且在运行时传入的参数是 Serializable 类型:spring-doc.cadn.net.cn

    args(java.io.Serializable)
    args 更常用于绑定形式。有关如何在通知(advice)体中使用方法参数,请参见声明通知一节。

    请注意,本示例中给出的切入点与 execution(* *(java.io.Serializable)) 不同。args 版本在运行时传入的参数是 Serializable 类型时匹配,而 execution 版本则在方法签名声明了一个类型为 Serializable 的参数时匹配。spring-doc.cadn.net.cn

  • 任何连接点(在 Spring AOP 中仅限方法执行),其中目标对象具有 @Transactional 注解:spring-doc.cadn.net.cn

    @target(org.springframework.transaction.annotation.Transactional)
    你也可以在绑定形式中使用 @target。有关如何在通知体中使注解对象可用,请参见声明通知部分。
  • 任何连接点(在 Spring AOP 中仅限方法执行),其目标对象的声明类型具有 @Transactional 注解:spring-doc.cadn.net.cn

    @within(org.springframework.transaction.annotation.Transactional)
    你也可以在绑定形式中使用 @within。有关如何在通知体中使注解对象可用,请参见声明通知部分。
  • 任何连接点(在 Spring AOP 中仅限方法执行),其执行的方法带有 @Transactional 注解:spring-doc.cadn.net.cn

    @annotation(org.springframework.transaction.annotation.Transactional)
    你也可以在绑定形式中使用 @annotation。有关如何在通知体中使注解对象可用,请参见声明通知部分。
  • 任何连接点(在 Spring AOP 中仅限方法执行),该连接点接受单个参数,并且所传递参数的运行时类型具有 @Classified 注解:spring-doc.cadn.net.cn

    @args(com.xyz.security.Classified)
    你也可以在绑定形式中使用 @args。请参阅声明通知一节, 了解如何在通知体中使注解对象可用。
  • Spring Bean 名为 tradeService 的任意连接点(在 Spring AOP 中仅限方法执行):spring-doc.cadn.net.cn

    bean(tradeService)
  • 任何连接点(在 Spring AOP 中仅限方法执行)在名称匹配通配符表达式 *Service 的 Spring Bean 上:spring-doc.cadn.net.cn

    bean(*Service)

编写良好的切点

在编译期间,AspectJ 会处理切入点(pointcut),以优化匹配性能。检查代码并确定每个连接点(join point)是否匹配(静态或动态地)给定的切入点是一个代价高昂的过程。(动态匹配是指无法仅通过静态分析完全确定匹配结果,而需要在代码中插入测试逻辑,在运行时判断是否存在实际匹配。)当 AspectJ 首次遇到一个切入点声明时,会将其重写为匹配过程的最优形式。这意味着什么?基本上,切入点会被重写为析取范式(DNF, Disjunctive Normal Form),并且切入点的各个组成部分会被排序,使得计算成本较低的组件优先被检查。因此,您无需担心理解各种切入点指示符(pointcut designator)的性能差异,可以在切入点声明中以任意顺序提供它们。spring-doc.cadn.net.cn

然而,AspectJ 只能根据所告知的内容进行工作。为了实现最佳的匹配性能,你应该仔细考虑自己的目标,并在定义中尽可能缩小匹配的搜索范围。现有的指示符自然可以分为以下三类:类型(kinded)、作用域(scoping)和上下文(contextual):spring-doc.cadn.net.cn

  • 种类化指示符用于选择特定类型的连接点: executiongetsetcallhandlerspring-doc.cadn.net.cn

  • 作用域指示符用于选择一组感兴趣的连接点(可能包含多种类型):withinwithincodespring-doc.cadn.net.cn

  • 上下文指示符根据上下文进行匹配(并可选择性地绑定): thistarget@annotationspring-doc.cadn.net.cn

一个编写良好的切入点(pointcut)应至少包含前两种类型(kinded 和 scoping)。你可以加入上下文指示符(contextual designators),以便根据连接点(join point)的上下文进行匹配,或将该上下文绑定以在通知(advice)中使用。仅提供 kinded 指示符或仅提供 contextual 指示符虽然也能工作,但由于需要额外的处理和分析,可能会影响织入(weaving)性能(时间和内存消耗)。Scoping 指示符的匹配速度非常快,使用它们可以让 AspectJ 快速排除那些无需进一步处理的连接点组。因此,如果可能的话,一个好的切入点应始终包含一个 scoping 指示符。spring-doc.cadn.net.cn