对于最新的稳定版本,请使用 Spring Framework 6.2.0! |
基于 Schema 的 AOP 支持
如果你更喜欢基于 XML 的格式,Spring 还提供了对定义方面的支持
使用 namespace 标签。完全相同的切入点表达式和通知类型
与使用 @AspectJ 样式时一样。因此,在本节中,我们重点关注
该语法,并让读者参考上一节中的讨论
(@AspectJ支持)用于理解编写切入点表达式和绑定
的建议参数。aop
要使用本节中描述的 aop 命名空间标记,您需要导入模式,如 XML 基于模式的配置 中所述。请参阅 AOP 架构,了解如何在命名空间中导入标签。spring-aop
aop
在你的 Spring 配置中,所有 aspect 和 advisor 元素都必须放在
元素(您可以在
应用程序上下文配置)。元素可以包含切入点、
advisor 和 aspect 元素(请注意,这些元素必须按此顺序声明)。<aop:config>
<aop:config>
<aop:config>
配置风格大量使用了 Spring 的自动代理机制。这可能会导致问题(例如建议
not being wovening),如果你已经通过使用或类似的东西使用了显式的自动代理。建议的使用模式是
仅使用 style 或 only the style 和
切勿混合使用它们。<aop:config> BeanNameAutoProxyCreator <aop:config> AutoProxyCreator |
声明一个 Aspect
当您使用 schema 支持时,aspect 是定义为 您的 Spring 应用程序上下文。状态和行为在字段中捕获,并且 方法,以及 XML 中的切入点和通知信息。
你可以使用元素声明一个 face,并引用支持 bean
通过使用 Attribute,如下例所示:<aop:aspect>
ref
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
...
</aop:aspect>
</aop:config>
<bean id="aBean" class="...">
...
</bean>
支持切面的 bean(在本例中)当然可以配置并且
依赖项注入,就像任何其他 Spring bean 一样。aBean
声明切入点
您可以在元素中声明命名切入点,让切入点
定义在多个方面和顾问之间共享。<aop:config>
表示服务层中任何业务服务的执行的切入点可以 定义如下:
<aop:config>
<aop:pointcut id="businessService"
expression="execution(* com.xyz.service.*.*(..))" />
</aop:config>
请注意,切入点表达式本身使用相同的 AspectJ 切入点表达式
语言,如 @AspectJ 支持中所述。如果使用基于架构的声明
样式,您还可以引用
切入点表达式。因此,定义上述切入点的另一种方法如下:@Aspect
<aop:config>
<aop:pointcut id="businessService"
expression="com.xyz.CommonPointcuts.businessService()" /> (1)
</aop:config>
1 | 引用在共享命名切入点定义中定义的命名切入点。businessService |
在 aspect 中声明一个切入点与声明一个顶级切入点非常相似。 如下例所示:
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.service.*.*(..))"/>
...
</aop:aspect>
</aop:config>
与 @AspectJ 方面大致相同,使用基于 schema 的 schema 声明的切入点
定义样式可以收集连接点上下文。例如,以下切入点
收集对象作为连接点上下文并将其传递给通知:this
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.service.*.*(..)) && this(service)"/>
<aop:before pointcut-ref="businessService" method="monitor"/>
...
</aop:aspect>
</aop:config>
必须声明通知以接收收集的加入点上下文,方法是包含 参数,如下所示:
-
Java
-
Kotlin
public void monitor(Object service) {
// ...
}
fun monitor(service: Any) {
// ...
}
组合切入点子表达式时,在 XML 中很尴尬
document,因此您可以分别使用 、 、 和 关键字来代替 、 和 。例如,前面的切入点可以更好地写成
遵循:&&
and
or
not
&&
||
!
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.service.*.*(..)) and this(service)"/>
<aop:before pointcut-ref="businessService" method="monitor"/>
...
</aop:aspect>
</aop:config>
请注意,以这种方式定义的切入点由其 XML 引用,并且不能
用作命名切入点以形成复合切入点。命名切入点支持
因此,基于架构的定义样式比 @AspectJ 提供的定义样式更受限制
风格。id
申报通知书
基于 schema 的 AOP 支持使用与 @AspectJ 样式相同的五种通知,并且它们具有 完全相同的语义。
建议前
Before 通知在匹配的方法执行之前运行。它是使用 element 在 an 中声明的,如下例所示:<aop:aspect>
<aop:before>
<aop:aspect id="beforeExample" ref="aBean">
<aop:before
pointcut-ref="dataAccessOperation"
method="doAccessCheck"/>
...
</aop:aspect>
在上面的示例中, 是定义在
top () 级别(请参阅声明切入点)。dataAccessOperation
id
<aop:config>
正如我们在讨论 @AspectJ 样式时所指出的,使用命名切入点可以 显著提高代码的可读性。请参阅共享命名切入点定义 详。 |
要改为定义内联切入点,请将 attribute 替换为 attribute,如下所示:pointcut-ref
pointcut
<aop:aspect id="beforeExample" ref="aBean">
<aop:before
pointcut="execution(* com.xyz.dao.*.*(..))"
method="doAccessCheck"/>
...
</aop:aspect>
该属性标识一个方法 (),该方法提供
建议。必须为 aspect 元素引用的 bean 定义此方法
其中包含建议。在执行数据访问操作之前(方法执行
join point matched by the pointcut 表达式),则 aspect 上的
bean 被调用。method
doAccessCheck
doAccessCheck
退货后通知
返回后,通知在匹配的方法执行正常完成时运行。是的
声明的方式与之前的建议相同。以下示例
演示如何声明它:<aop:aspect>
<aop:aspect id="afterReturningExample" ref="aBean">
<aop:after-returning
pointcut="execution(* com.xyz.dao.*.*(..))"
method="doAccessCheck"/>
...
</aop:aspect>
与 @AspectJ 样式一样,您可以在 Advice Body 中获取返回值。
为此,请使用 attribute 指定参数的名称,该参数
应传递 return 值,如下例所示:returning
<aop:aspect id="afterReturningExample" ref="aBean">
<aop:after-returning
pointcut="execution(* com.xyz.dao.*.*(..))"
returning="retVal"
method="doAccessCheck"/>
...
</aop:aspect>
该方法必须声明一个名为 .此 type of this
parameter 约束匹配的方式与 中所述的相同。为
example,您可以按如下方式声明 Method Signature:doAccessCheck
retVal
@AfterReturning
-
Java
-
Kotlin
public void doAccessCheck(Object retVal) {...
fun doAccessCheck(retVal: Any) {...
抛出后的建议
抛出后,当匹配的方法执行退出时,通过抛出一个
例外。它是通过使用元素
如下例所示:<aop:aspect>
after-throwing
<aop:aspect id="afterThrowingExample" ref="aBean">
<aop:after-throwing
pointcut="execution(* com.xyz.dao.*.*(..))"
method="doRecoveryActions"/>
...
</aop:aspect>
与 @AspectJ 样式一样,您可以在通知正文中获取 thrown 异常。
为此,请使用 attribute 指定参数的名称
应传递该异常,如下例所示:throwing
<aop:aspect id="afterThrowingExample" ref="aBean">
<aop:after-throwing
pointcut="execution(* com.xyz.dao.*.*(..))"
throwing="dataAccessEx"
method="doRecoveryActions"/>
...
</aop:aspect>
该方法必须声明一个名为 .
此参数的类型以与 中所述相同的方式约束匹配。例如,方法签名可以按如下方式声明:doRecoveryActions
dataAccessEx
@AfterThrowing
-
Java
-
Kotlin
public void doRecoveryActions(DataAccessException dataAccessEx) {...
fun doRecoveryActions(dataAccessEx: DataAccessException) {...
之后(最后)建议
After (finally) 通知运行,无论匹配的方法执行如何退出。
您可以使用 element 声明它,如下例所示:after
<aop:aspect id="afterFinallyExample" ref="aBean">
<aop:after
pointcut="execution(* com.xyz.dao.*.*(..))"
method="doReleaseLock"/>
...
</aop:aspect>
周边建议
最后一种建议是围绕建议。环绕建议 “绕过” 匹配的 方法的执行。它有机会在方法之前和之后都做工作 运行,并确定该方法何时、如何运行,甚至是否真的开始运行。 如果你需要在方法之前和之后共享状态,通常会使用 Around 通知 以线程安全的方式执行 – 例如,启动和停止计时器。
始终使用满足您要求的最弱的建议形式。 例如,如果 before advice 足以满足您的需求,请不要使用 around advice。 |
你可以使用 element 来声明 around advice。通知方法应该
declare 作为其返回类型,并且该方法的第一个参数必须是
类型。在 通知 方法的主体中,您必须在 上调用 ,以便底层方法运行。
不带参数的调用将导致调用者的原始参数
在调用它时提供给底层方法。对于高级用例,有
是接受参数数组的方法的重载变体
().数组中的值将用作底层
方法。有关使用 .aop:around
Object
ProceedingJoinPoint
proceed()
ProceedingJoinPoint
proceed()
proceed()
Object[]
proceed
Object[]
下面的示例展示了如何在 XML 中声明 around advice:
<aop:aspect id="aroundExample" ref="aBean">
<aop:around
pointcut="execution(* com.xyz.service.*.*(..))"
method="doBasicProfiling"/>
...
</aop:aspect>
建议的实现可以与
@AspectJ示例(当然不包括 Annotation),如下例所示:doBasicProfiling
-
Java
-
Kotlin
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
fun doBasicProfiling(pjp: ProceedingJoinPoint): Any? {
// start stopwatch
val retVal = pjp.proceed()
// stop stopwatch
return pjp.proceed()
}
Advice 参数
基于 schema 的声明样式支持完全类型化的通知,其方式与
针对 @AspectJ 支持进行描述 — 通过按名称将切入点参数与
通知方法参数。有关详细信息,请参阅 Advice Parameters 。如果您愿意
要显式指定通知方法的参数名称(不依赖于
检测策略),您可以通过使用 Advice 元素的属性来实现此目的,该属性的处理方式与通知注释中的属性相同(如确定参数名称中所述)。
以下示例演示如何在 XML 中指定参数名称:arg-names
argNames
<aop:before
pointcut="com.xyz.Pointcuts.publicMethod() and @annotation(auditable)" (1)
method="audit"
arg-names="auditable" />
1 | 引用组合切入点表达式中定义的命名切入点。publicMethod |
该属性接受以逗号分隔的参数名称列表。arg-names
以下基于 XSD 的方法的稍微复杂一些的示例显示了 一些 around 建议与许多强类型参数结合使用:
-
Java
-
Kotlin
package com.xyz.service;
public interface PersonService {
Person getPerson(String personName, int age);
}
public class DefaultPersonService implements PersonService {
public Person getPerson(String name, int age) {
return new Person(name, age);
}
}
package com.xyz.service
interface PersonService {
fun getPerson(personName: String, age: Int): Person
}
class DefaultPersonService : PersonService {
fun getPerson(name: String, age: Int): Person {
return Person(name, age)
}
}
接下来是方面。请注意,该方法接受多个
强类型参数,其中第一个参数恰好是用于
继续执行 method 调用。此参数的存在表明 将用作建议,如下例所示:profile(..)
profile(..)
around
-
Java
-
Kotlin
package com.xyz;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;
public class SimpleProfiler {
public Object profile(ProceedingJoinPoint call, String name, int age) throws Throwable {
StopWatch clock = new StopWatch("Profiling for '" + name + "' and '" + age + "'");
try {
clock.start(call.toShortString());
return call.proceed();
} finally {
clock.stop();
System.out.println(clock.prettyPrint());
}
}
}
package com.xyz
import org.aspectj.lang.ProceedingJoinPoint
import org.springframework.util.StopWatch
class SimpleProfiler {
fun profile(call: ProceedingJoinPoint, name: String, age: Int): Any? {
val clock = StopWatch("Profiling for '$name' and '$age'")
try {
clock.start(call.toShortString())
return call.proceed()
} finally {
clock.stop()
println(clock.prettyPrint())
}
}
}
最后,以下示例 XML 配置会影响 针对特定连接点的上述建议:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- this is the object that will be proxied by Spring's AOP infrastructure -->
<bean id="personService" class="com.xyz.service.DefaultPersonService"/>
<!-- this is the actual advice itself -->
<bean id="profiler" class="com.xyz.SimpleProfiler"/>
<aop:config>
<aop:aspect ref="profiler">
<aop:pointcut id="theExecutionOfSomePersonServiceMethod"
expression="execution(* com.xyz.service.PersonService.getPerson(String,int))
and args(name, age)"/>
<aop:around pointcut-ref="theExecutionOfSomePersonServiceMethod"
method="profile"/>
</aop:aspect>
</aop:config>
</beans>
请考虑以下驱动程序脚本:
-
Java
-
Kotlin
public class Boot {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
PersonService person = ctx.getBean(PersonService.class);
person.getPerson("Pengo", 12);
}
}
fun main() {
val ctx = ClassPathXmlApplicationContext("beans.xml")
val person = ctx.getBean(PersonService.class)
person.getPerson("Pengo", 12)
}
使用这样的类,我们将在标准输出上获得类似于以下内容的输出:Boot
StopWatch 'Profiling for 'Pengo' and '12': running time (millis) = 0 ----------------------------------------- ms % Task name ----------------------------------------- 00000 ? execution(getFoo)
建议订购
当多个通知需要在同一个连接点运行时(执行方法)
排序规则如 Advice Ordering 中所述。优先权
between aspects 是通过元素中的属性确定的,或者
通过将注释添加到支持该方面的 bean 中,或者通过使用
bean 实现接口。order
<aop:aspect>
@Order
Ordered
与同一类中定义的通知方法的优先规则相反,当同一元素中定义的两条通知都需要
run 相同的 join point 时,优先级由通知的顺序决定
元素在封闭元素中按从最高到最低的顺序声明
优先。 例如,给定一个 advice 和一个在同一个元素中定义的 advice,它们适用于同一个连接点,为了确保 advice 的优先级高于 advice,该元素必须是
在 element 之前声明。 作为一般的经验法则,如果您发现您定义了多条建议
在应用于同一连接点的同一元素中,考虑折叠
此类通知方法转换为每个元素中每个连接点的一个通知方法
或者将建议片段重构为您可以订购的单独元素
在 aspect 级别。 |
介绍
介绍(在 AspectJ 中称为类型间声明)让一个 aspect 声明 that advised objects 实现给定的接口并提供 该接口代表这些对象。
您可以通过在 .
你可以使用 element 来声明匹配的类型有一个新的父级(因此得名)。
例如,给定一个名为 的接口和该接口的实现,以下方面声明 service 的所有实现者
interfaces 还实现 interface。(为了公开统计数据
例如,通过 JMX。aop:declare-parents
aop:aspect
aop:declare-parents
UsageTracked
DefaultUsageTracked
UsageTracked
<aop:aspect id="usageTrackerAspect" ref="usageTracking">
<aop:declare-parents
types-matching="com.xyz.service.*+"
implement-interface="com.xyz.service.tracking.UsageTracked"
default-impl="com.xyz.service.tracking.DefaultUsageTracked"/>
<aop:before
pointcut="execution(* com.xyz..service.*.*(..))
and this(usageTracked)"
method="recordUsage"/>
</aop:aspect>
然后,支持 bean 的类将包含以下方法:usageTracking
-
Java
-
Kotlin
public void recordUsage(UsageTracked usageTracked) {
usageTracked.incrementUseCount();
}
fun recordUsage(usageTracked: UsageTracked) {
usageTracked.incrementUseCount()
}
要实现的接口由 属性 确定。这
value 是 AspectJ 类型的模式。任何 bean 的
匹配类型实现接口。请注意,在之前的
建议,服务 Bean 可以直接用作
界面。要以编程方式访问 bean,您可以编写
以后:implement-interface
types-matching
UsageTracked
UsageTracked
-
Java
-
Kotlin
UsageTracked usageTracked = context.getBean("myService", UsageTracked.class);
val usageTracked = context.getBean("myService", UsageTracked.class)
顾问
“advisors” 的概念来自 Spring 中定义的 AOP 支持 并且在 AspectJ 中没有直接的等价物。顾问就像一个小 具有单个建议的自包含方面。建议本身是 由 bean 表示,并且必须实现 Spring 中的通知类型中描述的建议接口之一。顾问可以利用 AspectJ 切入点表达式。
Spring 通过元素支持 advisor 概念。你最
通常看到它与 transactional advice 结合使用,后者也有自己的
namespace 支持。以下示例显示了一个 advisor:<aop:advisor>
<aop:config>
<aop:pointcut id="businessService"
expression="execution(* com.xyz.service.*.*(..))"/>
<aop:advisor
pointcut-ref="businessService"
advice-ref="tx-advice" />
</aop:config>
<tx:advice id="tx-advice">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
除了前面示例中使用的属性外,您还可以使用该属性来内联定义切入点表达式。pointcut-ref
pointcut
要定义 advisor 的优先级,以便 advice 可以参与排序,
使用 attribute 来定义 advisor 的值。order
Ordered
AOP 架构示例
本节显示 AOP 示例中的并发锁定失败重试示例在使用架构支持重写时的外观。
业务服务的执行有时会由于并发问题(对于
例如,一个死锁失败者)。如果重试操作,则可能会成功
下次尝试时。对于适合在此类
条件(无需因冲突而返回给用户的幂等操作
resolution),我们希望以透明方式重试该操作,以避免客户端看到 .这是一个明确贯穿的要求
服务层中的多个服务,因此非常适合通过
方面。PessimisticLockingFailureException
因为我们想要重试操作,所以我们需要使用 around advice,以便我们可以
多次调用。下面的清单显示了基本的 aspect 实现
(这是一个使用 schema 支持的常规 Java 类):proceed
-
Java
-
Kotlin
public class ConcurrentOperationExecutor implements Ordered {
private static final int DEFAULT_MAX_RETRIES = 2;
private int maxRetries = DEFAULT_MAX_RETRIES;
private int order = 1;
public void setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
}
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
int numAttempts = 0;
PessimisticLockingFailureException lockFailureException;
do {
numAttempts++;
try {
return pjp.proceed();
}
catch(PessimisticLockingFailureException ex) {
lockFailureException = ex;
}
} while(numAttempts <= this.maxRetries);
throw lockFailureException;
}
}
class ConcurrentOperationExecutor : Ordered {
private val DEFAULT_MAX_RETRIES = 2
private var maxRetries = DEFAULT_MAX_RETRIES
private var order = 1
fun setMaxRetries(maxRetries: Int) {
this.maxRetries = maxRetries
}
override fun getOrder(): Int {
return this.order
}
fun setOrder(order: Int) {
this.order = order
}
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any? {
var numAttempts = 0
var lockFailureException: PessimisticLockingFailureException
do {
numAttempts++
try {
return pjp.proceed()
} catch (ex: PessimisticLockingFailureException) {
lockFailureException = ex
}
} while (numAttempts <= this.maxRetries)
throw lockFailureException
}
}
请注意,该 aspect 实现了接口,以便我们可以设置
方面高于 Transaction Advice(我们希望每次
retry)。和 属性都是由 Spring 配置的。这
主要操作发生在 Around Advice 方法中。我们尝试
进行。如果我们失败了 ,我们再试一次,
除非我们已经用尽了所有的重试尝试。Ordered
maxRetries
order
doConcurrentOperation
PessimisticLockingFailureException
这个类与@AspectJ示例中使用的类相同,但使用 已删除注释。 |
对应的 Spring 配置如下:
<aop:config>
<aop:aspect id="concurrentOperationRetry" ref="concurrentOperationExecutor">
<aop:pointcut id="idempotentOperation"
expression="execution(* com.xyz.service.*.*(..))"/>
<aop:around
pointcut-ref="idempotentOperation"
method="doConcurrentOperation"/>
</aop:aspect>
</aop:config>
<bean id="concurrentOperationExecutor"
class="com.xyz.service.impl.ConcurrentOperationExecutor">
<property name="maxRetries" value="3"/>
<property name="order" value="100"/>
</bean>
请注意,目前,我们假设所有业务服务都是幂等的。如果
事实并非如此,我们可以优化 Aspect 以便它只真正重试
幂等操作,通过引入注解并使用注解
对 Service 操作的实现进行注释,如下例所示:Idempotent
-
Java
-
Kotlin
@Retention(RetentionPolicy.RUNTIME)
// marker annotation
public @interface Idempotent {
}
@Retention(AnnotationRetention.RUNTIME)
// marker annotation
annotation class Idempotent
这
将 aspect 更改为仅重试幂等操作涉及优化
pointcut 表达式,以便仅匹配操作,如下所示:@Idempotent
<aop:pointcut id="idempotentOperation"
expression="execution(* com.xyz.service.*.*(..)) and
@annotation(com.xyz.service.Idempotent)"/>