此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Framework 6.2.0spring-doc.cn

基于 Schema 的 AOP 支持

如果你更喜欢基于 XML 的格式,Spring 还提供了对定义方面的支持 使用 namespace 标签。完全相同的切入点表达式和通知类型 与使用 @AspectJ 样式时一样。因此,在本节中,我们重点关注 该语法,并让读者参考上一节中的讨论 (@AspectJ支持)用于理解编写切入点表达式和绑定 的建议参数。aopspring-doc.cn

要使用本节中描述的 aop 命名空间标记,您需要导入模式,如 XML 基于模式的配置 中所述。请参阅 AOP 架构,了解如何在命名空间中导入标签。spring-aopaopspring-doc.cn

在你的 Spring 配置中,所有 aspect 和 advisor 元素都必须放在 元素(您可以在 应用程序上下文配置)。元素可以包含切入点、 advisor 和 aspect 元素(请注意,这些元素必须按此顺序声明)。<aop:config><aop:config><aop:config>spring-doc.cn

配置风格大量使用了 Spring 的自动代理机制。这可能会导致问题(例如建议 not being wovening),如果你已经通过使用或类似的东西使用了显式的自动代理。建议的使用模式是 仅使用 style 或 only the style 和 切勿混合使用它们。<aop:config>BeanNameAutoProxyCreator<aop:config>AutoProxyCreator

声明一个 Aspect

当您使用 schema 支持时,aspect 是定义为 您的 Spring 应用程序上下文。状态和行为在字段中捕获,并且 方法,以及 XML 中的切入点和通知信息。spring-doc.cn

你可以使用元素声明一个 face,并引用支持 bean 通过使用 Attribute,如下例所示:<aop:aspect>refspring-doc.cn

<aop:config>
	<aop:aspect id="myAspect" ref="aBean">
		...
	</aop:aspect>
</aop:config>

<bean id="aBean" class="...">
	...
</bean>

支持切面的 bean(在本例中)当然可以配置并且 依赖项注入,就像任何其他 Spring bean 一样。aBeanspring-doc.cn

声明切入点

您可以在元素中声明命名切入点,让切入点 定义在多个方面和顾问之间共享。<aop:config>spring-doc.cn

表示服务层中任何业务服务的执行的切入点可以 定义如下:spring-doc.cn

<aop:config>

	<aop:pointcut id="businessService"
		expression="execution(* com.xyz.service.*.*(..))" />

</aop:config>

请注意,切入点表达式本身使用相同的 AspectJ 切入点表达式 语言,如 @AspectJ 支持中所述。如果使用基于架构的声明 样式,您还可以引用 切入点表达式。因此,定义上述切入点的另一种方法如下:@Aspectspring-doc.cn

<aop:config>

	<aop:pointcut id="businessService"
		expression="com.xyz.CommonPointcuts.businessService()" /> (1)

</aop:config>
1 引用在共享命名切入点定义中定义的命名切入点。businessService

在 aspect 声明一个切入点与声明一个顶级切入点非常相似。 如下例所示:spring-doc.cn

<aop:config>

	<aop:aspect id="myAspect" ref="aBean">

		<aop:pointcut id="businessService"
			expression="execution(* com.xyz.service.*.*(..))"/>

		...
	</aop:aspect>

</aop:config>

与 @AspectJ 方面大致相同,使用基于 schema 的 schema 声明的切入点 定义样式可以收集连接点上下文。例如,以下切入点 收集对象作为连接点上下文并将其传递给通知:thisspring-doc.cn

<aop:config>

	<aop:aspect id="myAspect" ref="aBean">

		<aop:pointcut id="businessService"
			expression="execution(* com.xyz.service.*.*(..)) &amp;&amp; this(service)"/>

		<aop:before pointcut-ref="businessService" method="monitor"/>

		...
	</aop:aspect>

</aop:config>

必须声明通知以接收收集的加入点上下文,方法是包含 参数,如下所示:spring-doc.cn

public void monitor(Object service) {
	// ...
}
fun monitor(service: Any) {
	// ...
}

组合切入点子表达式时,在 XML 中很尴尬 document,因此您可以分别使用 、 、 和 关键字来代替 、 和 。例如,前面的切入点可以更好地写成 遵循:&amp;&amp;andornot&amp;&amp;||!spring-doc.cn

<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 提供的定义样式更受限制 风格。idspring-doc.cn

申报通知书

基于 schema 的 AOP 支持使用与 @AspectJ 样式相同的五种通知,并且它们具有 完全相同的语义。spring-doc.cn

建议前

Before 通知在匹配的方法执行之前运行。它是使用 element 在 an 中声明的,如下例所示:<aop:aspect><aop:before>spring-doc.cn

<aop:aspect id="beforeExample" ref="aBean">

	<aop:before
		pointcut-ref="dataAccessOperation"
		method="doAccessCheck"/>

	...

</aop:aspect>

在上面的示例中, 是定义在 top () 级别(请参阅声明切入点)。dataAccessOperationid<aop:config>spring-doc.cn

正如我们在讨论 @AspectJ 样式时所指出的,使用命名切入点可以 显著提高代码的可读性。请参阅共享命名切入点定义 详。

要改为定义内联切入点,请将 attribute 替换为 attribute,如下所示:pointcut-refpointcutspring-doc.cn

<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 被调用。methoddoAccessCheckdoAccessCheckspring-doc.cn

退货后通知

返回后,通知在匹配的方法执行正常完成时运行。是的 声明的方式与之前的建议相同。以下示例 演示如何声明它:<aop:aspect>spring-doc.cn

<aop:aspect id="afterReturningExample" ref="aBean">

	<aop:after-returning
		pointcut="execution(* com.xyz.dao.*.*(..))"
		method="doAccessCheck"/>

	...
</aop:aspect>

与 @AspectJ 样式一样,您可以在 Advice Body 中获取返回值。 为此,请使用 attribute 指定参数的名称,该参数 应传递 return 值,如下例所示:returningspring-doc.cn

<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:doAccessCheckretVal@AfterReturningspring-doc.cn

public void doAccessCheck(Object retVal) {...
fun doAccessCheck(retVal: Any) {...

抛出后的建议

抛出后,当匹配的方法执行退出时,通过抛出一个 例外。它是通过使用元素 如下例所示:<aop:aspect>after-throwingspring-doc.cn

<aop:aspect id="afterThrowingExample" ref="aBean">

	<aop:after-throwing
		pointcut="execution(* com.xyz.dao.*.*(..))"
		method="doRecoveryActions"/>

	...
</aop:aspect>

与 @AspectJ 样式一样,您可以在通知正文中获取 thrown 异常。 为此,请使用 attribute 指定参数的名称 应传递该异常,如下例所示:throwingspring-doc.cn

<aop:aspect id="afterThrowingExample" ref="aBean">

	<aop:after-throwing
		pointcut="execution(* com.xyz.dao.*.*(..))"
		throwing="dataAccessEx"
		method="doRecoveryActions"/>

	...
</aop:aspect>

该方法必须声明一个名为 . 此参数的类型以与 中所述相同的方式约束匹配。例如,方法签名可以按如下方式声明:doRecoveryActionsdataAccessEx@AfterThrowingspring-doc.cn

public void doRecoveryActions(DataAccessException dataAccessEx) {...
fun doRecoveryActions(dataAccessEx: DataAccessException) {...

之后(最后)建议

After (finally) 通知运行,无论匹配的方法执行如何退出。 您可以使用 element 声明它,如下例所示:afterspring-doc.cn

<aop:aspect id="afterFinallyExample" ref="aBean">

	<aop:after
		pointcut="execution(* com.xyz.dao.*.*(..))"
		method="doReleaseLock"/>

	...
</aop:aspect>

周边建议

最后一种建议是围绕建议。环绕建议 “绕过” 匹配的 方法的执行。它有机会在方法之前和之后都做工作 运行,并确定该方法何时、如何运行,甚至是否真的开始运行。 如果你需要在方法之前和之后共享状态,通常会使用 Around 通知 以线程安全的方式执行 – 例如,启动和停止计时器。spring-doc.cn

始终使用满足您要求的最弱的建议形式。spring-doc.cn

例如,如果 before advice 足以满足您的需求,请不要使用 around advice。spring-doc.cn

你可以使用 element 来声明 around advice。通知方法应该 declare 作为其返回类型,并且该方法的第一个参数必须是 类型。在 通知 方法的主体中,您必须在 上调用 ,以便底层方法运行。 不带参数的调用将导致调用者的原始参数 在调用它时提供给底层方法。对于高级用例,有 是接受参数数组的方法的重载变体 ().数组中的值将用作底层 方法。有关使用 .aop:aroundObjectProceedingJoinPointproceed()ProceedingJoinPointproceed()proceed()Object[]proceedObject[]spring-doc.cn

下面的示例展示了如何在 XML 中声明 around advice:spring-doc.cn

<aop:aspect id="aroundExample" ref="aBean">

	<aop:around
		pointcut="execution(* com.xyz.service.*.*(..))"
		method="doBasicProfiling"/>

	...
</aop:aspect>

建议的实现可以与 @AspectJ示例(当然不包括 Annotation),如下例所示:doBasicProfilingspring-doc.cn

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-namesargNamesspring-doc.cn

<aop:before
	pointcut="com.xyz.Pointcuts.publicMethod() and @annotation(auditable)" (1)
	method="audit"
	arg-names="auditable" />
1 引用组合切入点表达式中定义的命名切入点。publicMethod

该属性接受以逗号分隔的参数名称列表。arg-namesspring-doc.cn

以下基于 XSD 的方法的稍微复杂一些的示例显示了 一些 around 建议与许多强类型参数结合使用:spring-doc.cn

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(..)aroundspring-doc.cn

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 配置会影响 针对特定连接点的上述建议:spring-doc.cn

<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>

请考虑以下驱动程序脚本:spring-doc.cn

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)
}

使用这样的类,我们将在标准输出上获得类似于以下内容的输出:Bootspring-doc.cn

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>@OrderOrderedspring-doc.cn

与同一类中定义的通知方法的优先规则相反,当同一元素中定义的两条通知都需要 run 相同的 join point 时,优先级由通知的顺序决定 元素在封闭元素中按从最高到最低的顺序声明 优先。@Aspect<aop:aspect><aop:aspect>spring-doc.cn

例如,给定一个 advice 和一个在同一个元素中定义的 advice,它们适用于同一个连接点,为了确保 advice 的优先级高于 advice,该元素必须是 在 element 之前声明。aroundbefore<aop:aspect>aroundbefore<aop:around><aop:before>spring-doc.cn

作为一般的经验法则,如果您发现您定义了多条建议 在应用于同一连接点的同一元素中,考虑折叠 此类通知方法转换为每个元素中每个连接点的一个通知方法 或者将建议片段重构为您可以订购的单独元素 在 aspect 级别。<aop:aspect><aop:aspect><aop:aspect>spring-doc.cn

介绍

介绍(在 AspectJ 中称为类型间声明)让一个 aspect 声明 that advised objects 实现给定的接口并提供 该接口代表这些对象。spring-doc.cn

您可以通过在 . 你可以使用 element 来声明匹配的类型有一个新的父级(因此得名)。 例如,给定一个名为 的接口和该接口的实现,以下方面声明 service 的所有实现者 interfaces 还实现 interface。(为了公开统计数据 例如,通过 JMX。aop:declare-parentsaop:aspectaop:declare-parentsUsageTrackedDefaultUsageTrackedUsageTrackedspring-doc.cn

<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 的类将包含以下方法:usageTrackingspring-doc.cn

public void recordUsage(UsageTracked usageTracked) {
	usageTracked.incrementUseCount();
}
fun recordUsage(usageTracked: UsageTracked) {
	usageTracked.incrementUseCount()
}

要实现的接口由 属性 确定。这 value 是 AspectJ 类型的模式。任何 bean 的 匹配类型实现接口。请注意,在之前的 建议,服务 Bean 可以直接用作 界面。要以编程方式访问 bean,您可以编写 以后:implement-interfacetypes-matchingUsageTrackedUsageTrackedspring-doc.cn

UsageTracked usageTracked = context.getBean("myService", UsageTracked.class);
val usageTracked = context.getBean("myService", UsageTracked.class)

Aspect 实例化模型

模式定义的方面唯一支持的实例化模型是 singleton 型。将来的版本可能支持其他实例化模型。spring-doc.cn

顾问

“advisors” 的概念来自 Spring 中定义的 AOP 支持 并且在 AspectJ 中没有直接的等价物。顾问就像一个小 具有单个建议的自包含方面。建议本身是 由 bean 表示,并且必须实现 Spring 中的通知类型中描述的建议接口之一。顾问可以利用 AspectJ 切入点表达式。spring-doc.cn

Spring 通过元素支持 advisor 概念。你最 通常看到它与 transactional advice 结合使用,后者也有自己的 namespace 支持。以下示例显示了一个 advisor:<aop:advisor>spring-doc.cn

<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-refpointcutspring-doc.cn

要定义 advisor 的优先级,以便 advice 可以参与排序, 使用 attribute 来定义 advisor 的值。orderOrderedspring-doc.cn

AOP 架构示例

本节显示 AOP 示例中的并发锁定失败重试示例在使用架构支持重写时的外观。spring-doc.cn

业务服务的执行有时会由于并发问题(对于 例如,一个死锁失败者)。如果重试操作,则可能会成功 下次尝试时。对于适合在此类 条件(无需因冲突而返回给用户的幂等操作 resolution),我们希望以透明方式重试该操作,以避免客户端看到 .这是一个明确贯穿的要求 服务层中的多个服务,因此非常适合通过 方面。PessimisticLockingFailureExceptionspring-doc.cn

因为我们想要重试操作,所以我们需要使用 around advice,以便我们可以 多次调用。下面的清单显示了基本的 aspect 实现 (这是一个使用 schema 支持的常规 Java 类):proceedspring-doc.cn

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 方法中。我们尝试 进行。如果我们失败了 ,我们再试一次, 除非我们已经用尽了所有的重试尝试。OrderedmaxRetriesorderdoConcurrentOperationPessimisticLockingFailureExceptionspring-doc.cn

这个类与@AspectJ示例中使用的类相同,但使用 已删除注释。

对应的 Spring 配置如下:spring-doc.cn

<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 操作的实现进行注释,如下例所示:Idempotentspring-doc.cn

@Retention(RetentionPolicy.RUNTIME)
// marker annotation
public @interface Idempotent {
}
@Retention(AnnotationRetention.RUNTIME)
// marker annotation
annotation class Idempotent

这 将 aspect 更改为仅重试幂等操作涉及优化 pointcut 表达式,以便仅匹配操作,如下所示:@Idempotentspring-doc.cn

<aop:pointcut id="idempotentOperation"
		expression="execution(* com.xyz.service.*.*(..)) and
		@annotation(com.xyz.service.Idempotent)"/>