Declaring a Pointcut
切入点确定感兴趣的连接点,从而让我们能够控制何时运行建议。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 切入点语言的完整讨论,请参见https://www.eclipse.org/aspectj/doc/released/progguide/index.html[AspectJ 编程指南](并且,有关扩展信息,请参见https://www.eclipse.org/aspectj/doc/released/adk15notebook/index.html[AspectJ 5 开发人员手册])或有关 AspectJ 的书籍之一(例如 Eclipse AspectJ,Colyer 等著,或 AspectJ in Action,Ramnivas Laddad 著)。
Supported Pointcut Designators
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中不支持的其他切入点指示符:call
、get
、set
、preinitialization
、staticinitialization
、initialization
、handler
、adviceexecution
、withincode
、cflow
、cflowbelow
、if
、@this`和
@withincode`。在Spring AOP解释的切入点表达式中使用这些切入点指示符将导致抛出`IllegalArgumentException`。
Spring AOP支持的切入点指示符集可能会在未来版本中扩展,以支持更多的AspectJ切入点指示符。
由于Spring AOP只将匹配限制为方法执行连接点,因此前面对切入点指示符的讨论给出了比您在AspectJ编程指南中找到的更窄的定义。此外,AspectJ本身具有基于类型的语义,并且在执行连接点处,this`和`target`都指向同一个对象:执行该方法的对象。Spring AOP是一个基于代理的系统,并且区分代理对象本身(绑定到`this
)和代理后面的目标对象(绑定到`target`)。
由于Spring AOP框架基于代理,因此,目标对象内的调用在定义上不会被拦截。对于JDK代理,只能拦截代理上的公共接口方法调用。使用CGLIB,代理上的公共和受保护的方法调用会被拦截(如果有必要,甚至是包可见的方法)。但是,常见的代理交互应始终通过公共签名设计。 请注意,切入点定义通常与任何被拦截的方法匹配。如果切入点严格上仅为公共的,即使在与CGLIB代理中通过代理进行潜在的非公开交互的情况下,也需要对其进行相应定义。 如果您的拦截需要包括目标类中的方法调用,甚至其构造函数,请考虑使用 Spring 驱动的 native AspectJ weaving,而不是基于 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 也可以与 &&
(与)、||
(或)和 !
(否定)运算符一起使用。
|
Combining Pointcut Expressions
你可以使用 &&,
||
和 !
组合切入点表达式。你还可以按名称引用切入点表达式。以下示例显示了三个切入点表达式:
- Java
-
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`匹配交易模块中的任何公有方法的方法执行。
|
4 | `publicMethod`匹配方法执行连接点是否表示任何公有方法的执行。 |
5 | `inTrading`匹配交易模块中的方法执行。 |
6 | `tradingOperation`匹配交易模块中的任何公有方法的方法执行。 |
最好像上面所示那样,用较小的 命名切入点 构建更复杂的切入点表达式。在按名称引用切入点时,会应用正常的 Java 可见性规则(你可以看到同类型中的 private
切入点、层次结构中的 protected
切入点、任何地方的 public
切入点,依此类推)。可见性不会影响切入点匹配。
Sharing Named Pointcut Definitions
在使用企业应用程序时,开发人员经常需要从多个方面引用应用程序的模块和特定操作集。我们建议为此目的定义一个专用的类,以捕获常用的 命名字入点 表达式。此类通常类似于以下 CommonPointcuts
示例(尽管你如何命名该类由你决定):
-
Java
-
Kotlin
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() {}
}
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-based AOP Support 中进行讨论。事务元素在 Transaction Management 中进行讨论。
Examples
Spring AOP 用户最有可能最常使用 execution
切入点设计符。执行表达式的格式如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
除了返回类型模式(前面代码段中的 ret-type-pattern
)、名称模式和参数模式之外,所有部分都是可选的。返回类型模式确定连接点必须具有的方法返回类型才能匹配。 is most frequently used as the returning type pattern. It matches any return
type. A fully-qualified type name matches only when the method returns the given
type. The name pattern matches the method name. You can use the
通配符作为名称模式的一部分或全部。如果你指定了一个声明类型模式,请包含一个尾随
.
将其连接到名称模式分量。参数模式略微复杂一些:()
匹配不包含任何参数的方法,而 (..)
匹配任意数量(零个或更多)的参数。()
pattern matches a method that takes one parameter of any type.
(,String)
匹配接受两个参数的方法。第一个参数可以是任何类型,而第二个参数必须是 String
。有关详细信息,请参阅 AspectJ 编程指南的https://www.eclipse.org/aspectj/doc/released/progguide/semantics-pointcuts.html[语言语义]部分。
以下示例显示了一些常见的切入点表达式:
-
任何公有方法的执行:[indent="0",subs="verbatim"] execution(public * *(..))
-
以 `set`开头的名称执行的任何方法:[indent="0",subs="verbatim"] execution(* set*(..))
-
`AccountService`接口定义的任何方法的执行:[indent="0",subs="verbatim"] execution(* com.xyz.service.AccountService.*(..))
-
`service`包中定义的任何方法的执行:[indent="0",subs="verbatim"] execution(* com.xyz.service..(..))
-
在服务包或其子包之一中定义的任何方法的执行:[indent="0",subs="verbatim"] execution(* com.xyz.service...(..))
-
服务包中的任何连接点(仅在 Spring AOP 中执行方法):[indent="0",subs="verbatim"] within(com.xyz.service.*)
-
服务包或其子包之一中的任何连接点(仅在 Spring AOP 中执行方法):[indent="0",subs="verbatim"] within(com.xyz.service..*)
-
任何连接点(仅在 Spring AOP 中执行方法),其中代理实现 `AccountService`接口:[indent="0",subs="verbatim"] this(com.xyz.service.AccountService)
|
-
任何连接点(仅在 Spring AOP 中执行方法),其中目标对象实现 `AccountService`接口:[indent="0",subs="verbatim"] target(com.xyz.service.AccountService)
|
-
接收单个参数的任何连接点(仅在 Spring AOP 中执行方法),并且运行时传递的参数为
Serializable
:[indent="0",subs="verbatim"] args(java.io.Serializable)
|
请注意,本示例中给出的切入点与 execution(**(java.io.Serializable))
不同。如果在运行时传递的参数是 Serializable
,则 args 版本匹配,如果方法签名声明了一个类型为 Serializable
的单个参数,则 execution 版本匹配。
* 任何连接点(仅在 Spring AOP 中执行方法),其中目标对象具有 `@Transactional`注解:[indent="0",subs="verbatim"]
@target(org.springframework.transaction.annotation.Transactional)
您还可以在绑定形式中使用 |
-
任何连接点(仅在 Spring AOP 中执行方法),其中目标对象的声明类型具有 `@Transactional`注解:[indent="0",subs="verbatim"] @within(org.springframework.transaction.annotation.Transactional)
您还可以在绑定形式中使用 |
-
任何连接点(仅在 Spring AOP 中执行方法),其中执行的方法具有 `@Transactional`注解:[indent="0",subs="verbatim"] @annotation(org.springframework.transaction.annotation.Transactional)
您还可以在绑定形式中使用 |
-
任何连接点(仅在 Spring AOP 中执行方法),该连接点接收单个参数,并且传递参数的运行时类型具有 `@Classified`注解:[indent="0",subs="verbatim"] @args(com.xyz.security.Classified)
您还可以在绑定形式中使用 |
-
名为 `tradeService`的 Spring bean 上的任何连接点(仅在 Spring AOP 中执行方法):[indent="0",subs="verbatim"] bean(tradeService)
-
任何连接点(在 Spring AOP 中仅针对方法执行)对名称与通配符表达式 `*Service`相符的 Spring Bean:[indent="0",subs="verbatim"] bean(*Service)
Writing Good Pointcuts
在编译期间,AspectJ 处理切入点以优化匹配性能。检查代码并确定每个连接点是否(静态地或动态地)匹配给定切入点是一个昂贵的过程。(动态匹配意味着无法从静态分析中完全确定匹配,并且在代码运行时将测试放入代码中以确定是否有实际匹配)。首次遇到切入点声明时,AspectJ 会将其重写为匹配过程的最佳形式。这意味着什么?基本上,切入点被重写为 DNF(析取范式),并且切入点的组件被排序,以便首先检查成本较低的组件。这意味着你不必担心了解各种切入点设计符的性能,并且可以在切入点声明中以任何顺序提供它们。
但是,AspectJ 只能够处理它被告知的内容。为了获得最佳匹配性能,你应该考虑要实现的目标,并在定义中尽可能缩小匹配的搜索空间。现有的设计符自然分为三种类型之一:种类、范围和上下文:
-
种类设计器选择特定种类的连接点:
execution
、get
、set
、call`和 `handler
。 -
范围设计器选择一组关注的连接点(可能是多类):
within`和 `withincode
。 -
上下文设计器基于上下文进行匹配(和可选绑定):
this
、target`和 `@annotation
。
一个写得好的切入点应至少包括前两种类型(种类和范围)。你可以包括上下文设计符来根据连接点上下文进行匹配或绑定该上下文以在建议中使用。仅提供一个种类设计符或仅提供一个上下文设计符可以工作,但由于需要额外的处理和分析,可能会影响编织性能(时间和内存的使用)。范围设计符的匹配速度非常快,使用它们意味着 AspectJ 可以非常快速地忽略不应该进一步处理的连接点组。如果可能,一个好的切入点应该始终包括一个。