此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Framework 6.2.0! |
基于 Schema 的 AOP 支持
如果你更喜欢基于 XML 的格式,Spring 还提供了对定义方面的支持
使用aop
namespace 标签。完全相同的切入点表达式和通知类型
与使用 @AspectJ 样式时一样。因此,在本节中,我们重点关注
该语法,并让读者参考上一节中的讨论
(@AspectJ支持)用于理解编写切入点表达式和绑定
的建议参数。
要使用本节中描述的 aop 命名空间标签,您需要导入spring-aop
模式,如 XML 基于模式的配置 中所述。请参阅 AOP 架构,了解如何在aop
Namespace。
在你的 Spring 配置中,所有 aspect 和 advisor 元素都必须放在
一<aop:config>
元素(您可以有多个<aop:config>
元素中
应用程序上下文配置)。一<aop:config>
元素可以包含 pointcut、
advisor 和 aspect 元素(请注意,这些元素必须按此顺序声明)。
这<aop:config> 样式大量使用了 Spring 的自动代理机制。这可能会导致问题(例如建议
not being woven)如果您已经通过使用BeanNameAutoProxyCreator 或类似的东西。建议的使用模式是
仅使用<aop:config> 样式或仅AutoProxyCreator style 和
切勿混合使用它们。 |
声明一个 Aspect
当您使用 schema 支持时,aspect 是定义为 您的 Spring 应用程序上下文。状态和行为在字段中捕获,并且 方法,以及 XML 中的切入点和通知信息。
您可以使用<aop:aspect>
元素,并引用支持 Bean
通过使用ref
属性,如下例所示:
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
...
</aop:aspect>
</aop:config>
<bean id="aBean" class="...">
...
</bean>
支持 aspect (aBean
在这种情况下)当然可以配置,并且
依赖项注入,就像任何其他 Spring bean 一样。
声明切入点
您可以在<aop:config>
元素,让切入点
定义在多个方面和顾问之间共享。
表示服务层中任何业务服务的执行的切入点可以 定义如下:
<aop:config>
<aop:pointcut id="businessService"
expression="execution(* com.xyz.service.*.*(..))" />
</aop:config>
请注意,切入点表达式本身使用相同的 AspectJ 切入点表达式
语言,如 @AspectJ 支持中所述。如果使用基于架构的声明
style 中,您还可以引用@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
object 作为连接点上下文,并将其传递给通知:
<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 中很尴尬
文档,因此您可以使用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 引用id
,并且不能是
用作命名切入点以形成复合切入点。命名切入点支持
因此,基于架构的定义样式比 @AspectJ 提供的定义样式更受限制
风格。
申报通知书
基于 schema 的 AOP 支持使用与 @AspectJ 样式相同的五种通知,并且它们具有 完全相同的语义。
建议前
Before 通知在匹配的方法执行之前运行。它是在<aop:aspect>
通过使用<aop:before>
元素,如下例所示:
<aop:aspect id="beforeExample" ref="aBean">
<aop:before
pointcut-ref="dataAccessOperation"
method="doAccessCheck"/>
...
</aop:aspect>
在上面的示例中,dataAccessOperation
是id
定义于
顶部 (<aop:config>
) 级别(请参阅声明切入点)。
正如我们在讨论 @AspectJ 样式时所指出的,使用命名切入点可以 显著提高代码的可读性。请参阅共享命名切入点定义 详。 |
要定义内联切入点,请将pointcut-ref
属性替换为pointcut
属性,如下所示:
<aop:aspect id="beforeExample" ref="aBean">
<aop:before
pointcut="execution(* com.xyz.dao.*.*(..))"
method="doAccessCheck"/>
...
</aop:aspect>
这method
属性标识方法 (doAccessCheck
),它提供
建议。必须为 aspect 元素引用的 bean 定义此方法
其中包含建议。在执行数据访问作之前(方法执行
连接点匹配),则doAccessCheck
aspect 上的 method
bean 被调用。
退货后通知
返回后,通知在匹配的方法执行正常完成时运行。是的
在<aop:aspect>
和之前的建议一样。以下示例
演示如何声明它:
<aop:aspect id="afterReturningExample" ref="aBean">
<aop:after-returning
pointcut="execution(* com.xyz.dao.*.*(..))"
method="doAccessCheck"/>
...
</aop:aspect>
与 @AspectJ 样式一样,您可以在 Advice Body 中获取返回值。
为此,请使用returning
属性来指定
应传递 return 值,如下例所示:
<aop:aspect id="afterReturningExample" ref="aBean">
<aop:after-returning
pointcut="execution(* com.xyz.dao.*.*(..))"
returning="retVal"
method="doAccessCheck"/>
...
</aop:aspect>
这doAccessCheck
method 必须声明一个名为retVal
.此 type of this
parameter 约束匹配,其方式与@AfterReturning
.为
example,您可以按如下方式声明 Method Signature:
-
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 异常。
为此,请使用throwing
attribute 来指定参数的名称
应传递异常,如下例所示:
<aop:aspect id="afterThrowingExample" ref="aBean">
<aop:after-throwing
pointcut="execution(* com.xyz.dao.*.*(..))"
throwing="dataAccessEx"
method="doRecoveryActions"/>
...
</aop:aspect>
这doRecoveryActions
method 必须声明一个名为dataAccessEx
.
此参数的类型以与@AfterThrowing
.例如,方法签名可以按如下方式声明:
-
Java
-
Kotlin
public void doRecoveryActions(DataAccessException dataAccessEx) {...
fun doRecoveryActions(dataAccessEx: DataAccessException) {...
之后(最后)建议
After (finally) 通知运行,无论匹配的方法执行如何退出。
您可以使用after
元素,如下例所示:
<aop:aspect id="afterFinallyExample" ref="aBean">
<aop:after
pointcut="execution(* com.xyz.dao.*.*(..))"
method="doReleaseLock"/>
...
</aop:aspect>
周边建议
最后一种建议是围绕建议。环绕建议 “绕过” 匹配的 方法的执行。它有机会在方法之前和之后都做工作 运行,并确定该方法何时、如何运行,甚至是否真的开始运行。 如果你需要在方法之前和之后共享状态,通常会使用 Around 通知 以线程安全的方式执行 – 例如,启动和停止计时器。
始终使用满足您要求的最弱的建议形式。 例如,如果 before advice 足以满足您的需求,请不要使用 around advice。 |
你可以使用aop:around
元素。通知方法应该
宣Object
作为其返回类型,并且该方法的第一个参数必须为
类型ProceedingJoinPoint
.在 advice 方法的主体中,您必须调用proceed()
在ProceedingJoinPoint
以便底层方法运行。
调用proceed()
不带参数将导致调用者的原始参数
在调用它时提供给底层方法。对于高级用例,有
是proceed()
method 接受一个参数数组
(Object[]
).数组中的值将用作底层
方法。参见 Around Advice 了解打电话的注意事项proceed
替换为Object[]
.
下面的示例展示了如何在 XML 中声明 around advice:
<aop:aspect id="aroundExample" ref="aBean">
<aop:around
pointcut="execution(* com.xyz.service.*.*(..))"
method="doBasicProfiling"/>
...
</aop:aspect>
的doBasicProfiling
advice 可以与
@AspectJ示例(当然不包括 Annotation),如下例所示:
-
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 。如果您愿意
要显式指定通知方法的参数名称(不依赖于
检测策略),您可以使用arg-names
属性,其处理方式与argNames
属性(如确定参数名称中所述)。
以下示例演示如何在 XML 中指定参数名称:
<aop:before
pointcut="com.xyz.Pointcuts.publicMethod() and @annotation(auditable)" (1)
method="audit"
arg-names="auditable" />
1 | 引用publicMethod 命名切入点在 组合切入点表达式 中定义。 |
这arg-names
attribute 接受以逗号分隔的参数名称列表。
以下基于 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)
}
}
接下来是方面。请注意,profile(..)
method 接受多个
强类型参数,其中第一个参数恰好是用于
继续执行 method 调用。此参数的存在表明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
类,我们将在 standard output 上得到类似于以下内容的输出:
StopWatch 'Profiling for 'Pengo' and '12': running time (millis) = 0 ----------------------------------------- ms % Task name ----------------------------------------- 00000 ? execution(getFoo)
建议订购
当多个通知需要在同一个连接点运行时(执行方法)
排序规则如 Advice Ordering 中所述。优先权
aspect 之间的order
属性中的<aop:aspect>
元素或
通过添加@Order
注解添加到支持该 aspect 的 bean 中,或者通过使用
Bean 实现Ordered
接口。
与同一中定义的通知方法的优先规则相反 例如,给定一个 作为一般的经验法则,如果您发现您定义了多条建议
在同一 |
介绍
介绍(在 AspectJ 中称为类型间声明)让一个 aspect 声明 that advised objects 实现给定的接口并提供 该接口代表这些对象。
您可以使用aop:declare-parents
元素中aop:aspect
.
您可以使用aop:declare-parents
元素来声明匹配的类型具有新的父级(因此得名)。
例如,给定一个名为UsageTracked
以及名为DefaultUsageTracked
,以下方面声明 service 的所有 implementationrs
接口还实现了UsageTracked
接口。(为了公开统计数据
例如,通过 JMX。
<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>
支持usageTracking
然后 bean 将包含以下方法:
-
Java
-
Kotlin
public void recordUsage(UsageTracked usageTracked) {
usageTracked.incrementUseCount();
}
fun recordUsage(usageTracked: UsageTracked) {
usageTracked.incrementUseCount()
}
要实现的接口由implement-interface
属性。这
的值types-matching
attribute 是一个 AspectJ 类型的模式。任何 bean 的
matching 类型实现UsageTracked
接口。请注意,在之前的
建议,服务 Bean 可以直接用作
这UsageTracked
接口。要以编程方式访问 bean,您可以编写
以后:
-
Java
-
Kotlin
UsageTracked usageTracked = context.getBean("myService", UsageTracked.class);
val usageTracked = context.getBean("myService", UsageTracked.class)
顾问
“advisors” 的概念来自 Spring 中定义的 AOP 支持 并且在 AspectJ 中没有直接的等价物。顾问就像一个小 具有单个建议的自包含方面。建议本身是 由 bean 表示,并且必须实现 Spring 中的通知类型中描述的建议接口之一。顾问可以利用 AspectJ 切入点表达式。
Spring 通过<aop:advisor>
元素。你最
通常看到它与 transactional advice 结合使用,后者也有自己的
namespace 支持。以下示例显示了一个 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 可以参与排序,
使用order
属性来定义Ordered
顾问的价值。
AOP 架构示例
本节显示 AOP 示例中的并发锁定失败重试示例在使用架构支持重写时的外观。
业务服务的执行有时会由于并发问题(对于
例如,一个死锁失败者)。如果重试作,则可能会成功
下次尝试时。对于适合在此类
条件(无需因冲突而返回给用户的幂等作
resolution)中,我们希望透明地重试该作,以避免客户端看到PessimisticLockingFailureException
.这是一个明确贯穿的要求
服务层中的多个服务,因此非常适合通过
方面。
因为我们想要重试作,所以我们需要使用 around advice,以便我们可以
叫proceed
多次。下面的清单显示了基本的 aspect 实现
(这是一个使用 schema 支持的常规 Java 类):
-
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 实现了Ordered
接口,以便我们可以设置
方面高于 Transaction Advice(我们希望每次
retry)。这maxRetries
和order
properties 都是由 Spring 配置的。这
main作发生在doConcurrentOperation
围绕 Advice 方法。我们尝试
进行。如果我们失败时出现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 以便它仅真正重试
幂等作,通过引入Idempotent
annotation 和使用注释
对 Service作的实现进行注释,如下例所示:
-
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)"/>