声明切入点

切入点决定了感兴趣的连接点,从而使我们能够控制通知何时运行。Spring AOP 只支持 Spring bean 的方法执行连接点,因此您可以将切入点视为匹配 Spring bean 上方法的执行。切入点声明由两部分组成:包含名称和任何参数的签名,以及准确确定我们感兴趣的方法执行的切入点表达式。在 @AspectJ 注解风格的 AOP 中,切入点签名由常规方法定义提供,切入点表达式通过使用 @Pointcut 注解指示(作为切入点签名的方法必须具有 void 返回类型)。

一个例子可能有助于明确切入点签名和切入点表达式之间的区别。以下示例定义了一个名为 anyOldTransfer 的切入点,它匹配任何名为 transfer 的方法的执行。

  • Java

  • Kotlin

@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 in Action)。

支持的切入点指示符

Spring AOP 支持以下 AspectJ 切入点指示符(PCD)用于切入点表达式:

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

  • within: 将匹配限制在特定类型内的连接点(使用 Spring AOP 时,在匹配类型中声明的方法的执行)。

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

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

  • args: 将匹配限制在连接点(使用 Spring AOP 时,方法的执行),其中参数是给定类型的实例。

  • @target: 将匹配限制在连接点(使用 Spring AOP 时,执行对象的类具有给定类型的注解)。

  • @args: 将匹配限制在连接点(使用 Spring AOP 时,实际传递的参数的运行时类型具有给定类型的注解)。

  • @within: 将匹配限制在具有给定注解的类型内的连接点(使用 Spring AOP 时,在具有给定注解的类型中声明的方法的执行)。

  • @annotation: 将匹配限制在连接点的主体(在 Spring AOP 中运行的方法)具有给定注解的情况。

其他切入点类型

完整的 AspectJ 切入点语言支持 Spring 中不支持的其他切入点指示符:callgetsetpreinitializationstaticinitializationinitializationhandleradviceexecutionwithincodecflowcflowbelowif@this@withincode。在 Spring AOP 解释的切入点表达式中使用这些切入点指示符会导致抛出 IllegalArgumentException

Spring AOP 支持的切入点指示符集可能会在未来的版本中扩展,以支持更多的 AspectJ 切入点指示符。

由于 Spring AOP 将匹配限制为仅方法执行连接点,因此前面关于切入点指示符的讨论给出了比 AspectJ 编程指南中更窄的定义。此外,AspectJ 本身具有基于类型的语义,并且在执行连接点,thistarget 都引用同一个对象:执行方法的对象。Spring AOP 是一个基于代理的系统,它区分代理对象本身(绑定到 this)和代理后面的目标对象(绑定到 target)。

由于 Spring AOP 框架基于代理的性质,目标对象内的调用,根据定义,不会被拦截。对于 JDK 代理,只能拦截代理上的公共接口方法调用。对于 CGLIB,可以拦截代理上的公共和受保护方法调用(如果需要,甚至可以拦截包可见方法)。但是,通过代理进行的常见交互应始终通过公共签名进行设计。

请注意,切入点定义通常与任何被拦截的方法匹配。如果切入点严格只用于公共方法,即使在 CGLIB 代理场景中可能存在非公共交互,也需要相应地进行定义。

如果您的拦截需求包括目标类中的方法调用甚至构造函数,请考虑使用 Spring 驱动的 原生 AspectJ 织入,而不是 Spring 基于代理的 AOP 框架。这构成了一种不同模式的 AOP 使用,具有不同的特性,因此在做出决定之前请务必熟悉织入。

Spring AOP 还支持一个名为 bean 的附加 PCD。此 PCD 允许您将连接点的匹配限制为特定的命名 Spring bean 或一组命名 Spring bean(当使用通配符时)。bean PCD 具有以下形式:

bean(idOrNameOfBean)

idOrNameOfBean 令牌可以是任何 Spring bean 的名称。提供了使用 * 字符的有限通配符支持,因此,如果您为 Spring bean 建立一些命名约定,您可以编写一个 bean PCD 表达式来选择它们。与其他切入点指示符一样,bean PCD 也可以与 &&(与)、||(或)和 !(非)运算符一起使用。

bean PCD 仅在 Spring AOP 中支持,而在原生 AspectJ 织入中不支持。它是对 AspectJ 定义的标准 PCD 的 Spring 特定扩展,因此不适用于在 @Aspect 模型中声明的方面。

bean PCD 在实例级别(基于 Spring bean 名称概念)而不是仅在类型级别(织入 AOP 受此限制)操作。基于实例的切入点指示符是 Spring 基于代理的 AOP 框架及其与 Spring bean 工厂紧密集成的一项特殊功能,在 Spring bean 工厂中,通过名称识别特定 bean 是自然而直接的。

组合切入点表达式

您可以使用 &&||! 组合切入点表达式。您还可以按名称引用切入点表达式。以下示例显示了三个切入点表达式:

  • Java

  • Kotlin

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 匹配。

最佳实践是如上所示,从较小的命名切入点构建更复杂的切入点表达式。按名称引用切入点时,适用正常的 Java 可见性规则(您可以在同一类型中看到 private 切入点,在层次结构中看到 protected 切入点,在任何地方看到 public 切入点,等等)。可见性不影响切入点匹配。

共享命名切入点定义

在处理企业应用程序时,开发人员经常需要从多个切面中引用应用程序的模块和特定的操作集。为此,我们建议定义一个专用类,用于捕获常用的命名切入点表达式。此类通常类似于以下 CommonPointcuts 示例(尽管类的命名由您决定):

  • Java

  • Kotlin

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() 命名切入点

<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 AOP 用户最有可能经常使用 execution 切入点指示符。执行表达式的格式如下:

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

除了返回类型模式(上述片段中的 ret-type-pattern)、名称模式和参数模式之外,所有部分都是可选的。返回类型模式决定了方法的返回类型必须是什么才能匹配连接点。* 最常被用作返回类型模式。它匹配任何返回类型。完全限定的类型名称仅在方法返回给定类型时匹配。名称模式匹配方法名。您可以使用 * 通配符作为名称模式的全部或一部分。如果指定声明类型模式,请包含一个尾随的 . 以将其与名称模式组件连接。参数模式稍微复杂一些:() 匹配不带参数的方法,而 (..) 匹配任意数量(零个或多个)的参数。(*) 模式匹配带有一个任何类型参数的方法。(*,String) 匹配带两个参数的方法。第一个可以是任何类型,而第二个必须是 String。有关更多信息,请查阅 AspectJ 编程指南的 语言语义 部分。

以下示例显示了一些常见的切入点表达式:

  • 任何公共方法的执行

    execution(public * *(..))
  • 任何名称以 set 开头的方法的执行

    execution(* set*(..))
  • AccountService 接口定义的任何方法的执行

    execution(* com.xyz.service.AccountService.*(..))
  • service 包中定义的任何方法的执行

    execution(* com.xyz.service.*.*(..))
  • 服务包或其子包中定义的任何方法的执行

    execution(* com.xyz.service..*.*(..))
  • 服务包内的任何连接点(在 Spring AOP 中仅限于方法执行)

    within(com.xyz.service.*)
  • 服务包或其子包内的任何连接点(在 Spring AOP 中仅限于方法执行)

    within(com.xyz.service..*)
  • 代理实现 AccountService 接口的任何连接点(在 Spring AOP 中仅限于方法执行)

    this(com.xyz.service.AccountService)
    this 更常用于绑定形式。有关如何在通知正文中使代理对象可用的信息,请参阅 声明通知 部分。
  • 目标对象实现 AccountService 接口的任何连接点(在 Spring AOP 中仅限于方法执行)

    target(com.xyz.service.AccountService)
    target 更常用于绑定形式。有关如何在通知正文中使目标对象可用的信息,请参阅 声明通知 部分。
  • 接受单个参数且运行时传递的参数是 Serializable 的任何连接点(在 Spring AOP 中仅限于方法执行)

    args(java.io.Serializable)
    args 更常用于绑定形式。有关如何在通知正文中使方法参数可用的信息,请参阅 声明通知 部分。

    请注意,此示例中给出的切入点与 execution(* *(java.io.Serializable)) 不同。args 版本匹配运行时传递的参数是 Serializable,而 execution 版本匹配方法签名声明单个 Serializable 类型的参数。

  • 目标对象具有 @Transactional 注解的任何连接点(在 Spring AOP 中仅限于方法执行)

    @target(org.springframework.transaction.annotation.Transactional)
    您也可以以绑定形式使用 @target。有关如何在通知正文中使注解对象可用的信息,请参阅 声明通知 部分。
  • 目标对象的声明类型具有 @Transactional 注解的任何连接点(在 Spring AOP 中仅限于方法执行)

    @within(org.springframework.transaction.annotation.Transactional)
    您也可以以绑定形式使用 @within。有关如何在通知正文中使注解对象可用的信息,请参阅 声明通知 部分。
  • 执行方法具有 @Transactional 注解的任何连接点(在 Spring AOP 中仅限于方法执行)

    @annotation(org.springframework.transaction.annotation.Transactional)
    您也可以以绑定形式使用 @annotation。有关如何在通知正文中使注解对象可用的信息,请参阅 声明通知 部分。
  • 接受单个参数且传递的参数的运行时类型具有 @Classified 注解的任何连接点(在 Spring AOP 中仅限于方法执行)

    @args(com.xyz.security.Classified)
    您也可以以绑定形式使用 @args。有关如何在通知正文中使注解对象可用的信息,请参阅 声明通知 部分。
  • 在名为 tradeService 的 Spring bean 上的任何连接点(在 Spring AOP 中仅限于方法执行)

    bean(tradeService)
  • 在名称匹配通配符表达式 *Service 的 Spring bean 上的任何连接点(在 Spring AOP 中仅限于方法执行)

    bean(*Service)

编写良好的切入点

在编译期间,AspectJ 会处理切入点以优化匹配性能。检查代码并确定每个连接点是否匹配(静态或动态)给定切入点是一个耗时的过程。(动态匹配意味着匹配无法完全通过静态分析确定,并且在代码中放置了一个测试以确定代码运行时是否存在实际匹配)。在首次遇到切入点声明时,AspectJ 会将其重写为匹配过程的最佳形式。这意味着什么?基本上,切入点被重写为 DNF(析取范式),并且切入点的组件经过排序,以便首先检查那些评估成本较低的组件。这意味着您不必担心理解各种切入点指示符的性能,并且可以在切入点声明中以任何顺序提供它们。

然而,AspectJ 只能根据被告知的信息工作。为了获得最佳的匹配性能,您应该考虑您想要实现的目标,并在定义中尽可能缩小匹配的搜索空间。现有的指示符自然分为三类:类型、作用域和上下文。

  • 类型指示符选择特定类型的连接点:executiongetsetcallhandler

  • 作用域指示符选择一组感兴趣的连接点(可能属于多种类型):withinwithincode

  • 上下文指示符根据上下文进行匹配(并可选地绑定):thistarget@annotation

一个编写良好的切入点应至少包含前两种类型(类型和作用域)。您可以包含上下文指示符以根据连接点上下文进行匹配或绑定该上下文以在通知中使用。仅提供类型指示符或仅提供上下文指示符也可以工作,但由于额外的处理和分析,可能会影响织入性能(时间和内存使用)。作用域指示符匹配速度非常快,使用它们意味着 AspectJ 可以非常快速地排除不应进一步处理的连接点组。一个好的切入点如果可能的话应该始终包含一个。

© . This site is unofficial and not affiliated with VMware.