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