|
对于最新的稳定版本,请使用 Spring Framework 7.0.6! |
声明切入点
切点确定了感兴趣的连接点,从而允许我们控制建议何时运行。Spring AOP仅支持Spring Bean的方法执行连接点,因此您可以将切点视为匹配Spring Bean上的方法执行。切点声明有两个部分:一个由名称和任何参数组成的签名,以及一个切点表达式,该表达式确定我们感兴趣的具体方法执行。在@AspectJ注解样式中,切点签名由常规方法定义提供,而切点表达式通过使用@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中运行的方法)具有给定的注解。
由于Spring AOP仅限于方法执行连接点的匹配,前面关于切入点指示器的讨论的定义比你在AspectJ编程指南中找到的定义要狭窄。此外,AspectJ本身具有基于类型的语义,并且在执行连接点处,this和target指的是同一个对象:正在执行方法的对象。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 Tokens可以是任何 Spring Bean 的名称。提供有限的通配符支持,使用 * 字符,因此,如果您为 Spring Bean 建立了一些命名约定,就可以编写一个 bean PCD 表达式来选择它们。与其他切入点指示器一样,bean PCD 也可以与 &&(且)、||(或)和 !(否定)运算符一起使用。
|
The |
组合切入点表达式
您可以组合切入点表达式,使用 &&, || 和 !。您也可以通过名称引用切入点表达式。以下示例展示了三个切入点表达式:
-
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>
The <aop:config> 和 <aop:advisor> 元素在基于模式的AOP支持中讨论。The
事务元素在事务管理中讨论。
示例
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..*)
-
任何连接点(仅限Spring AOP中的方法执行)实现了
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,则 args 版本匹配;如果方法签名声明了一个类型为Serializable的单个参数,则 execution 版本匹配。 -
任何连接点(仅限Spring AOP中的方法执行)中,目标对象具有
@Transactional注解:@target(org.springframework.transaction.annotation.Transactional)
你也可以在绑定形式中使用 @target。有关如何在建议体中使注解对象可用的信息,请参阅 声明通知 部分。 -
任何连接点(仅限Spring AOP中的方法执行)中,目标对象的声明类型具有
@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 AOP中的方法执行)在一个名为
tradeService的Spring bean上:bean(tradeService)
-
任何连接点(仅限Spring AOP中的方法执行)在Spring bean上,这些bean的名称与通配符表达式
*Service匹配:bean(*Service)
编写良好的切入点
在编译期间,AspectJ 处理切入点以优化匹配性能。检查代码并确定每个连接点是否与给定的切入点匹配(静态或动态)是一个代价高昂的过程。(动态匹配意味着无法通过静态分析完全确定匹配,并且在代码中放置了一个测试来确定代码运行时是否存在实际匹配)。在首次遇到切入点声明时,AspectJ 将其重写为匹配过程的最佳形式。这意味着什么?基本上,切入点被重写为 DNF(析取范式),并且切入点的组件按评估成本较低的顺序进行排序,以便首先检查那些评估成本较低的组件。这意味着你不必担心理解各种切入点指定符的性能,并且可以在切入点声明中以任意顺序提供它们。
然而,AspectJ 只能处理被告知的内容。为了获得最佳的匹配操作性能,您应当明确目标并尽可能在定义中缩小匹配的搜索范围。现有的指示器天然分为三类:类型指示器、范围指示器和上下文指示器:
-
种类选择符选择一种特定的连接点类型:
execution,get,set,call, 和handler. -
作用域设计符选择一组感兴趣的连接点 (可能是多种类型的):
within和withincode -
上下文标识符基于上下文匹配(并可选地绑定):
this,target, 和@annotation
一个编写良好的切点应至少包含前两种类型(种类和作用域)。您可以包含上下文设计符,以根据连接点上下文进行匹配,或者绑定该上下文以便在通知中使用。仅提供种类设计符或仅提供上下文设计符是可以的,但可能会影响编织性能(所用时间和内存),因为需要额外的处理和分析。作用域设计符的匹配速度非常快,使用它们意味着AspectJ可以快速排除不需要进一步处理的连接点组。一个好的切点应尽可能包含一个。