此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Framework 6.2.0! |
声明切入点
切入点确定感兴趣的连接点,从而使我们能够控制
当 Advice 运行时。Spring AOP 仅支持 Spring 的方法执行连接点
beans,因此你可以将切入点视为与 Spring 上方法的执行相匹配
豆。切入点声明有两个部分:一个由 name 组成的签名和任何
参数和精确确定方法的切入点表达式
我们感兴趣的执行。在 AOP 的 @AspectJ 注解样式中,一个切入点
signature 由常规方法定义提供,切入点表达式为
通过使用@Pointcut
注释(用作切入点签名的方法
必须具有void
return 类型)。
一个示例可能有助于区分切入点签名和切入点
表达式 clear。以下示例定义了一个名为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
annotation 是常规的
AspectJ 切入点表达式。有关 AspectJ 的切入点语言的完整讨论,请参阅
AspectJ
编程指南(对于扩展,还有 AspectJ 5
Developer's Notebook)或关于 AspectJ 的书籍之一(例如 Colyer 编写的 Eclipse AspectJ
et al.,或 AspectJ in Action,作者:Ramnivas Laddad)。
支持的切入点标号
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),其中执行对象的类具有给定类型的 Comments。 -
@args
:限制与连接点的匹配(使用 Spring 时方法的执行 AOP),其中传递的实际参数的运行时类型具有 给定类型。 -
@within
:限制匹配到具有给定 annotation(执行在具有给定注解的类型中声明的方法,当 使用 Spring AOP)。 -
@annotation
:将匹配限制为连接点的主题 (在 Spring AOP 中运行的方法)具有给定的注解。
由于 Spring AOP 将匹配限制为仅方法执行连接点,因此前面的讨论
的切入点指示符给出的定义比您在
AspectJ 编程指南。此外,AspectJ 本身具有基于类型的语义,并且在
执行连接点,则this
和target
引用同一对象:
对象执行该方法。Spring AOP 是一个基于代理的系统,它与众不同
在代理对象本身(绑定到this
) 和
proxy(绑定到target
).
由于 Spring 的 AOP 框架基于代理的性质,目标对象内的调用 根据定义,不会被拦截。对于 JDK 代理,只有公共接口方法 可以拦截对代理的调用。使用 CGLIB 时,对 public 和 protected 方法调用 代理被拦截(如有必要,甚至是包可见的方法)。然而 通过代理的常见交互应始终通过公共签名进行设计。 请注意,切入点定义通常与任何截获的方法匹配。 如果切入点严格来说是公开的,即使在 CGLIB 代理场景中 通过代理进行潜在的非公开交互,则需要相应地定义。 如果您的拦截需要包括目标中的方法调用甚至构造函数 类中,请考虑使用 Spring 驱动的原生 AspectJ 编织 Spring 的基于代理的 AOP 框架。这构成了 AOP 使用的不同模式 具有不同的特性,所以一定要让自己熟悉编织 在做出决定之前。 |
Spring AOP 还支持一个名为bean
.此 PCD 允许您限制
连接点与特定命名 Spring bean 或一组命名
Spring bean(使用通配符时)。这bean
PCD 具有以下形式:
bean(idOrNameOfBean)
这idOrNameOfBean
token 可以是任何 Spring bean 的名称。有限通配符
提供了使用该字符的支持,因此,如果您建立一些命名
约定,您可以编写一个*
bean
PCD 表达
以选择它们。与其他切入点指示符一样,bean
PCD 罐
与 (and) 一起使用,&&
||
(或)和!
(否定)运算符。
这 这 |
组合切入点表达式
您可以使用&&,
||
和!
.您还可以参考
按名称切入表达式。下面的示例显示了三个切入点表达式:
-
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 如果 Method Execution 连接点表示执行,则匹配
任何公共方法。 |
2 | inTrading 如果方法执行在 Trading 模块中,则匹配。 |
3 | tradingOperation 如果方法执行表示
trading 模块。 |
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 如果 Method Execution 连接点表示执行,则匹配
任何公共方法。 |
2 | inTrading 如果方法执行在 Trading 模块中,则匹配。 |
3 | tradingOperation 如果方法执行表示
trading 模块。 |
最佳实践是从较小的命名
切入点,如上所示。当按名称引用切入点时,正常的 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
method 的名称。例如,要使服务层具有事务性,您需要
可以编写以下内容,该代码引用了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 支持中进行了讨论。这
事务 Management 中讨论了事务元素。
例子
Spring AOP 用户可能会使用execution
切入点指示符。
执行表达式的格式如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
除返回类型模式 (ret-type-pattern
在前面的代码段中),
Name pattern 和 parameters pattern 是可选的。返回的类型模式确定
方法的返回类型必须是什么才能匹配 Join Point。 最常用作返回类型模式。它与任何 return 匹配
类型。仅当方法返回给定的
类型。名称模式与方法名称匹配。您可以将通配符用作 all 或
名称模式的一部分。如果指定声明类型 pattern,
包括尾随*
*
.
将其连接到 Name pattern 组件。
parameters 模式稍微复杂一些:匹配
方法,而()
(..)
匹配任意数量的 (零个或多个) 参数。
该模式与采用任意类型一个参数的方法匹配。(*)
(*,String)
匹配采用两个参数的方法。第一个可以是任何类型,而
second 必须是String
.查阅语言
Semantics 部分。
以下示例显示了一些常见的切入点表达式:
-
任何公共方法的执行:
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
接口:this(com.xyz.service.AccountService)
this
更常以装订形式使用。有关如何在通知正文中使 proxy 对象可用的信息,请参见 Declaring Advice 部分。 -
目标对象 实现
AccountService
接口:target(com.xyz.service.AccountService)
target
更常以装订形式使用。参见 Declaring Advice 部分 了解如何使目标对象在通知正文中可用。 -
任何采用单个参数的连接点(仅在 Spring AOP 中执行方法) 其中,在运行时传递的参数为
Serializable
:args(java.io.Serializable)
args
更常以装订形式使用。参见 Declaring Advice 部分 了解如何使方法参数在 Advice Body 中可用。请注意,此示例中给出的切入点与
execution(* *(java.io.Serializable))
.如果在运行时传递的参数为Serializable
,并且如果方法签名声明了单个 type 为Serializable
. -
目标对象具有
@Transactional
注解:@target(org.springframework.transaction.annotation.Transactional)
您还可以使用 @target
以装订形式。参见 Declaring Advice 部分 如何使 Annotation 对象在通知正文中可用。 -
任何连接点(仅在 Spring AOP 中执行方法),其中 target 对象具有
@Transactional
注解:@within(org.springframework.transaction.annotation.Transactional)
您还可以使用 @within
以装订形式。参见 Declaring Advice 部分 如何使 Annotation 对象在通知正文中可用。 -
任何连接点(仅在 Spring AOP 中执行方法),其中执行方法具有
@Transactional
注解:@annotation(org.springframework.transaction.annotation.Transactional)
您还可以使用 @annotation
以装订形式。参见 Declaring Advice 部分 了解如何使 Annotation 对象在通知正文中可用。 -
任何连接点(仅在 Spring AOP 中执行方法)采用单个参数 其中,传递的参数的运行时类型具有
@Classified
注解:@args(com.xyz.security.Classified)
您还可以使用 @args
以装订形式。参见 Declaring Advice 部分 如何使 Annotation 对象在 Advice Body 中可用。 -
名为
tradeService
:bean(tradeService)
-
Spring bean 上任何名称为 匹配通配符表达式
*Service
:bean(*Service)
编写好的切入点
在编译期间,AspectJ 处理切入点以优化匹配 性能。检查代码并确定每个连接点是否匹配(静态或 动态地)给定的切入点是一个昂贵的过程。(动态匹配是指匹配 无法从静态分析中完全确定,并且测试放置在代码中以 确定代码运行时是否存在实际匹配项)。首次遇到 pointcut 声明时,AspectJ 会将其重写为匹配的最佳形式 过程。这是什么意思?基本上,切入点是用 DNF 重写的(析取 Normal Form) 和切入点的组件进行排序,以便这些组件 首先检查评估成本较低的 URL。这意味着您不必担心 关于了解各种切入点指示符的性能并可能提供它们 在切入点声明中以任何顺序。
然而,AspectJ 只能与它被告知的内容一起工作。为了获得最佳性能 匹配,您应该考虑您要实现的目标并缩小搜索范围 space for 在定义中尽可能匹配。现有标号 自然属于以下三组之一:kinded、scope 和 contextual:
-
Kinded 标号选择特定类型的连接点:
execution
,get
,set
,call
和handler
. -
范围界定号选择一组感兴趣的连接点 (可能有很多种):
within
和withincode
-
上下文指示符根据上下文进行匹配(并选择性地绑定):
this
,target
和@annotation
一个写得好的切入点应该至少包括前两种类型(kinded 和 范围界定)。您可以包含上下文指示符以根据 join point context 或 bind 该上下文以在通知中使用。仅提供 kinded 指示符或仅上下文指示符有效,但可能会影响编织 性能(使用的时间和内存),由于额外的处理和分析。范围 标号的匹配速度非常快,使用它们意味着 AspectJ 可以非常快地 关闭不应进一步处理的联接点组。一个好的 如果可能,切入点应始终包含一个。