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

基于 Schema 的 AOP 支持

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

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

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

<aop:config>样式大量使用了 Spring 的自动代理机制。这可能会导致问题(例如建议 not being woven)如果您已经通过使用BeanNameAutoProxyCreator或类似的东西。建议的使用模式是 仅使用<aop:config>样式或仅AutoProxyCreatorstyle 和 切勿混合使用它们。

声明一个 Aspect

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

您可以使用<aop:aspect>元素,并引用支持 Bean 通过使用ref属性,如下例所示:spring-doc.cadn.net.cn

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

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

支持 aspect (aBean在这种情况下)当然可以配置,并且 依赖项注入,就像任何其他 Spring bean 一样。spring-doc.cadn.net.cn

声明切入点

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

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

<aop:config>

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

</aop:config>

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

<aop:config>

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

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

在 aspect 声明一个切入点与声明一个顶级切入点非常相似。 如下例所示:spring-doc.cadn.net.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 声明的切入点 定义样式可以收集连接点上下文。例如,以下切入点 收集thisobject 作为连接点上下文,并将其传递给通知:spring-doc.cadn.net.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.cadn.net.cn

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

组合切入点子表达式时,&amp;&amp;在 XML 中很尴尬 文档,因此您可以使用and,ornot关键字代替&amp;&amp;,||!分别。例如,前面的切入点可以更好地写成 遵循:spring-doc.cadn.net.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 引用id,并且不能是 用作命名切入点以形成复合切入点。命名切入点支持 因此,基于架构的定义样式比 @AspectJ 提供的定义样式更受限制 风格。spring-doc.cadn.net.cn

申报通知书

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

建议前

Before 通知在匹配的方法执行之前运行。它是在<aop:aspect>通过使用<aop:before>元素,如下例所示:spring-doc.cadn.net.cn

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

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

	...

</aop:aspect>

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

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

要定义内联切入点,请将pointcut-ref属性替换为pointcut属性,如下所示:spring-doc.cadn.net.cn

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

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

	...

</aop:aspect>

method属性标识方法 (doAccessCheck),它提供 建议。必须为 aspect 元素引用的 bean 定义此方法 其中包含建议。在执行数据访问作之前(方法执行 连接点匹配),则doAccessCheckaspect 上的 method bean 被调用。spring-doc.cadn.net.cn

退货后通知

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

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

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

	...
</aop:aspect>

与 @AspectJ 样式一样,您可以在 Advice Body 中获取返回值。 为此,请使用returning属性来指定 应传递 return 值,如下例所示:spring-doc.cadn.net.cn

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

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

	...
</aop:aspect>

doAccessCheckmethod 必须声明一个名为retVal.此 type of this parameter 约束匹配,其方式与@AfterReturning.为 example,您可以按如下方式声明 Method Signature:spring-doc.cadn.net.cn

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

抛出后的建议

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

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

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

	...
</aop:aspect>

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

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

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

	...
</aop:aspect>

doRecoveryActionsmethod 必须声明一个名为dataAccessEx. 此参数的类型以与@AfterThrowing.例如,方法签名可以按如下方式声明:spring-doc.cadn.net.cn

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

之后(最后)建议

After (finally) 通知运行,无论匹配的方法执行如何退出。 您可以使用after元素,如下例所示:spring-doc.cadn.net.cn

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

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

	...
</aop:aspect>

周边建议

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

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

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

你可以使用aop:around元素。通知方法应该 宣Object作为其返回类型,并且该方法的第一个参数必须为 类型ProceedingJoinPoint.在 advice 方法的主体中,您必须调用proceed()ProceedingJoinPoint以便底层方法运行。 调用proceed()不带参数将导致调用者的原始参数 在调用它时提供给底层方法。对于高级用例,有 是proceed()method 接受一个参数数组 (Object[]).数组中的值将用作底层 方法。参见 Around Advice 了解打电话的注意事项proceed替换为Object[].spring-doc.cadn.net.cn

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

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

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

	...
</aop:aspect>

doBasicProfilingadvice 可以与 @AspectJ示例(当然不包括 Annotation),如下例所示:spring-doc.cadn.net.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 。如果您愿意 要显式指定通知方法的参数名称(不依赖于 检测策略),您可以使用arg-names属性,其处理方式与argNames属性(如确定参数名称中所述)。 以下示例演示如何在 XML 中指定参数名称:spring-doc.cadn.net.cn

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

arg-namesattribute 接受以逗号分隔的参数名称列表。spring-doc.cadn.net.cn

以下基于 XSD 的方法的稍微复杂一些的示例显示了 一些 around 建议与许多强类型参数结合使用:spring-doc.cadn.net.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)
	}
}

接下来是方面。请注意,profile(..)method 接受多个 强类型参数,其中第一个参数恰好是用于 继续执行 method 调用。此参数的存在表明profile(..)将用作around建议,如下例所示:spring-doc.cadn.net.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())
		}
	}
}

Finally, the following example XML configuration effects the execution of the preceding advice for a particular join point:spring-doc.cadn.net.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>

Consider the following driver script:spring-doc.cadn.net.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)
}

With such a Boot class, we would get output similar to the following on standard output:spring-doc.cadn.net.cn

StopWatch 'Profiling for 'Pengo' and '12': running time (millis) = 0
-----------------------------------------
ms     %     Task name
-----------------------------------------
00000  ?  execution(getFoo)

Advice Ordering

When multiple pieces of advice need to run at the same join point (executing method) the ordering rules are as described in Advice Ordering. The precedence between aspects is determined via the order attribute in the <aop:aspect> element or by either adding the @Order annotation to the bean that backs the aspect or by having the bean implement the Ordered interface.spring-doc.cadn.net.cn

In contrast to the precedence rules for advice methods defined in the same @Aspect class, when two pieces of advice defined in the same <aop:aspect> element both need to run at the same join point, the precedence is determined by the order in which the advice elements are declared within the enclosing <aop:aspect> element, from highest to lowest precedence.spring-doc.cadn.net.cn

For example, given an around advice and a before advice defined in the same <aop:aspect> element that apply to the same join point, to ensure that the around advice has higher precedence than the before advice, the <aop:around> element must be declared before the <aop:before> element.spring-doc.cadn.net.cn

As a general rule of thumb, if you find that you have multiple pieces of advice defined in the same <aop:aspect> element that apply to the same join point, consider collapsing such advice methods into one advice method per join point in each <aop:aspect> element or refactor the pieces of advice into separate <aop:aspect> elements that you can order at the aspect level.spring-doc.cadn.net.cn

Introductions

Introductions (known as inter-type declarations in AspectJ) let an aspect declare that advised objects implement a given interface and provide an implementation of that interface on behalf of those objects.spring-doc.cadn.net.cn

You can make an introduction by using the aop:declare-parents element inside an aop:aspect. You can use the aop:declare-parents element to declare that matching types have a new parent (hence the name). For example, given an interface named UsageTracked and an implementation of that interface named DefaultUsageTracked, the following aspect declares that all implementors of service interfaces also implement the UsageTracked interface. (In order to expose statistics through JMX for example.)spring-doc.cadn.net.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>

The class that backs the usageTracking bean would then contain the following method:spring-doc.cadn.net.cn

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

The interface to be implemented is determined by the implement-interface attribute. The value of the types-matching attribute is an AspectJ type pattern. Any bean of a matching type implements the UsageTracked interface. Note that, in the before advice of the preceding example, service beans can be directly used as implementations of the UsageTracked interface. To access a bean programmatically, you could write the following:spring-doc.cadn.net.cn

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

Aspect Instantiation Models

The only supported instantiation model for schema-defined aspects is the singleton model. Other instantiation models may be supported in future releases.spring-doc.cadn.net.cn

Advisors

The concept of "advisors" comes from the AOP support defined in Spring and does not have a direct equivalent in AspectJ. An advisor is like a small self-contained aspect that has a single piece of advice. The advice itself is represented by a bean and must implement one of the advice interfaces described in Advice Types in Spring. Advisors can take advantage of AspectJ pointcut expressions.spring-doc.cadn.net.cn

Spring supports the advisor concept with the <aop:advisor> element. You most commonly see it used in conjunction with transactional advice, which also has its own namespace support in Spring. The following example shows an advisor:spring-doc.cadn.net.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>

As well as the pointcut-ref attribute used in the preceding example, you can also use the pointcut attribute to define a pointcut expression inline.spring-doc.cadn.net.cn

To define the precedence of an advisor so that the advice can participate in ordering, use the order attribute to define the Ordered value of the advisor.spring-doc.cadn.net.cn

An AOP Schema Example

This section shows how the concurrent locking failure retry example from An AOP Example looks when rewritten with the schema support.spring-doc.cadn.net.cn

The execution of business services can sometimes fail due to concurrency issues (for example, a deadlock loser). If the operation is retried, it is likely to succeed on the next try. For business services where it is appropriate to retry in such conditions (idempotent operations that do not need to go back to the user for conflict resolution), we want to transparently retry the operation to avoid the client seeing a PessimisticLockingFailureException. This is a requirement that clearly cuts across multiple services in the service layer and, hence, is ideal for implementing through an aspect.spring-doc.cadn.net.cn

Because we want to retry the operation, we need to use around advice so that we can call proceed multiple times. The following listing shows the basic aspect implementation (which is a regular Java class that uses the schema support):spring-doc.cadn.net.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
	}
}

Note that the aspect implements the Ordered interface so that we can set the precedence of the aspect higher than the transaction advice (we want a fresh transaction each time we retry). The maxRetries and order properties are both configured by Spring. The main action happens in the doConcurrentOperation around advice method. We try to proceed. If we fail with a PessimisticLockingFailureException, we try again, unless we have exhausted all of our retry attempts.spring-doc.cadn.net.cn

This class is identical to the one used in the @AspectJ example, but with the annotations removed.

The corresponding Spring configuration is as follows:spring-doc.cadn.net.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>

Notice that, for the time being, we assume that all business services are idempotent. If this is not the case, we can refine the aspect so that it retries only genuinely idempotent operations, by introducing an Idempotent annotation and using the annotation to annotate the implementation of service operations, as the following example shows:spring-doc.cadn.net.cn

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

The change to the aspect to retry only idempotent operations involves refining the pointcut expression so that only @Idempotent operations match, as follows:spring-doc.cadn.net.cn

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