对于最新稳定版本,请使用Spring Framework 7.0.1spring-doc.cadn.net.cn

基于模式的AOP支持

如果你更喜欢基于XML的格式,Spring还支持定义切面 使用AOP命名空间标签。同样的切分表达式和建议类型 就像使用支持的 @AspectJ 风格一样。因此,本节我们重点关注 该语法并引导读者回到上一节的讨论 (@AspectJ支持)理解点切割表达式的写作和绑定 关于建议参数。spring-doc.cadn.net.cn

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

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

<aop:config>这种配置风格大量使用了Spring的自动代理机制。这可能会引发问题(比如建议 如果你已经通过 使用 了显式自动代理,则不编织)豆名自动代理创作者或者类似的。推荐的使用模式是 仅使用以下两者<aop:config>风格或仅是自动代理创建器风格和 绝不要混在一起。

宣告一个方面

当你使用模式支持时,切面是定义为 bean 的普通 Java 对象 你的 Spring 应用上下文。状态和行为被捕捉在场中, 对象的方法以及点切和建议信息都被捕捉在XML中。spring-doc.cadn.net.cn

你可以通过以下方式声明一个方面<aop:aspect>元素,并引用背衬豆 通过使用裁判属性,如下例所示:spring-doc.cadn.net.cn

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

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

支撑这方面(aBean在这种情况下,当然可以配置和 依赖就像其他春季豆一样注入。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支持中描述的。如果你使用基于模式的声明 样式,你也可以参考定义在@Aspect类型在 尖锐的表情。因此,上述点割的另一种定义方式如下:spring-doc.cadn.net.cn

<aop:config>

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

</aop:config>
1 参考文献商务服务命名点切口定义于“共享命名点切定义”

一个方面内声明点割与声明顶层点割非常相似, 如下示例所示: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方面,点割通过基于模式的宣告 定义风格可以收集连接点上下文。例如,以下点切割 收集对象作为连接点上下文,并将其传递给建议: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) {
	// ...
}

当结合点切割子表达式时,&&在 XML 中 是尴尬的 文档,所以你可以使用,关键词&&,||!分别。例如,之前的点割可以更好地写为 遵循: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来指代身份证并且不可能 用作命名的点切以形成复合切点。命名的点切支撑在 基于模式的定义风格因此比@AspectJ所提供的更为有限 风格。spring-doc.cadn.net.cn

声明建议

基于模式的AOP支持使用与@AspectJ样式相同的五种建议,并且有 语义完全相同。spring-doc.cadn.net.cn

咨询前

Before a Advisor 在匹配方法执行前运行。它被声明为<aop:aspect>通过使用<aop:之前>元素,如下例所示:spring-doc.cadn.net.cn

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

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

	...

</aop:aspect>

在上述示例中,dataAccess作身份证定义在 的命名点切面 顶部(<aop:config>)水平(参见“宣告切点”)。spring-doc.cadn.net.cn

正如我们在讨论@AspectJ风格时提到的,使用命名的点割可以 显著提升代码的可读性。参见分享命名点切割定义 详。

要定义内联的点割,则替换为点切-参考属性点切属性,具体如下:spring-doc.cadn.net.cn

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

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

	...

</aop:aspect>

方法属性标识一个方法(doAccessCheck) 提供 的主体 建议。该方法必须为体元所引用的豆子定义 里面有建议。在执行数据访问作(方法执行)之前 连接点由点切割表达式匹配),该doAccessCheck在该方面的方法 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风格一样,你可以在建议文中获得回报价值。 为此,可以使用返回属性指定参数名称,使其 返回值应传递,如下示例所示: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>

doAccessCheck方法必须声明一个参数,名为retVal.这种类型 参数以描述的方式约束匹配@AfterReturning.为 例如,你可以声明方法签名如下:spring-doc.cadn.net.cn

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

投掷后建议

在抛出建议后,当匹配的方法执行通过抛出 例外。它被声明为<aop:aspect>通过使用抛球后元素 如下示例所示:spring-doc.cadn.net.cn

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

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

	...
</aop:aspect>

和 @AspectJ 一样,你可以在建议文中获得投掷例外。 为此,可以使用属性指定参数名称 该例外应通过,如下示例所示: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>

doRecoveryActions(恢复行动)方法必须声明一个参数,名为dataAccessEx. 该参数的类型以与描述的匹配方式约束匹配@AfterThrowing.例如,方法签名可以声明如下:spring-doc.cadn.net.cn

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

在(终于)得到建议之后

在(终于)无论匹配方法如何执行后,建议都会运行。 你可以用元素,如下例所示:spring-doc.cadn.net.cn

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

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

	...
</aop:aspect>

周边建议

最后一种建议是关于建议的。建议会“绕着”匹配跑 方法的执行。它有机会在方法前后都进行工作 运行,以及确定该方法何时、如何,甚至是否能运行。 如果你需要在方法前后分享状态,通常会用到Around Tips(约有建议) 以线程安全的方式执行——例如,启动和停止计时器。spring-doc.cadn.net.cn

始终使用最不有效的建议,但符合你的需求。spring-doc.cadn.net.cn

例如,如果之前的建议足够满足你的需求,不要使用其他建议spring-doc.cadn.net.cn

你可以通过以下方式来申报相关建议aop:关于元素。建议方法应该是 宣对象作为其返回类型,且方法的第一个参数必须为 类型推进加入点.在建议体系正文中,你必须引用继续()推进加入点为了让底层方法能够运行。 调用继续()没有参数则会得到呼叫者的原始参数 当底层方法被调用时,会被提供给该方法。高级用例,有 是 的过载变体继续()接受数组参数的方法 (对象[]).数组中的值将作为底层参数的参数 方法在被调用时。关于来电的注意点,请参见Around Advice进行带有对象[].spring-doc.cadn.net.cn

以下示例展示了如何在XML中声明关于建议:spring-doc.cadn.net.cn

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

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

	...
</aop:aspect>

实施doBasicProfiling建议可以完全相同于 @AspectJ示例(当然不包括注释),如下示例所示: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()
}

建议参数

基于模式的声明样式支持完全类型的建议,方式与以下相同 为@AspectJ支持描述——通过按名称匹配点割参数 建议方法参数。详情请参见建议参数。如果你愿意 明确指定建议方法的参数名称(不依赖于 检测策略,你可以通过使用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-names属性接受逗号分隔的参数列表。spring-doc.cadn.net.cn

以下稍为复杂的XSD方法示例显示 关于建议的一些结合多个强类型参数: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)
	}
}

接下来是相位。注意个人资料(..)方法接受若干 强类型参数,其中第一个恰好是用于 继续执行方法调用。该参数的存在表明个人资料(..)将用于周围建议,正如下面的例子所示: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())
		}
	}
}

最后,以下示例XML配置影响执行 针对特定连接点的前置建议: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>

请考虑以下驱动程序脚本: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)
}

用这样的靴子类,我们会得到类似于标准输出的以下结果:spring-doc.cadn.net.cn

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

建议订购

当多个建议需要在同一连接点运行(执行方法)时 排序规则如建议排序中所述。优先级 在方面之间,通过次序属性<aop:aspect>元素或 通过添加以下@Order对支持该方面或的豆子的注释,通过具有 豆子实现了命令接口。spring-doc.cadn.net.cn

与同一中定义的建议方法优先规则不同@Aspect当两个建议在同一个领域定义时<aop:aspect>元素 双方都必须 在同一连接点运行时,优先级由建议的顺序决定 元素在包围内声明<aop:aspect>元素,从高到低 优先。spring-doc.cadn.net.cn

例如,给定周围建议和以前建议也在同书中定义<aop:aspect>该元素适用于同一连接点,以确保周围建议的优先级高于以前建议,<aop:四处都是>元素必须是 在<aop:之前>元素。spring-doc.cadn.net.cn

一般来说,如果你发现自己有多个建议 同一时期<aop:aspect>适用于同一连接点的元素,请考虑折叠 这些建议方法会在每个连接点中合并成一个建议方法<aop:aspect>元素 或者将这些建议片段重构成单独的<aop:aspect>你可以订购的元素 从方面层面来说。spring-doc.cadn.net.cn

介绍

引言(在 AspectJ 中称为类型间声明)允许一个体声明 该建议对象实现给定接口,并提供 代表这些对象的接口。spring-doc.cadn.net.cn

你可以通过以下方式进行介绍aop:声明-父母元素在AOP:方面. 你可以使用aop:声明-父母用来声明匹配类型有新的父(因此得名)。 例如,给定一个名为使用追踪以及该接口的实现默认使用追踪,以下方面声明所有服务的实现者 接口还实现了使用追踪接口。(为了揭示统计学 例如通过JMX。)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>

支持该类的使用情况追踪豆子则包含以下方法:spring-doc.cadn.net.cn

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

待实现的接口由以下条件确定实现接口属性。这 的值类型匹配attribute 是 AspectJ 类型的模式。任何一颗小子 匹配类型实现了使用追踪接口。注意,在之前 参考前述示例的建议,服务豆可以直接作为 这使用追踪接口。要程序化访问豆子,你可以写 以后:spring-doc.cadn.net.cn

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

切面实例化模型

唯一支持的模式定义切面实例化模型是单例 型。未来版本可能会支持其他实例化模型。spring-doc.cadn.net.cn

顾问

“顾问”的概念源自春季中定义的AOP支持系统 并且在AspectJ中没有直接对应的。导师就像个小人物 这是一个自成一体的部分,只有一条建议。建议本身是 以豆子表示,必须实现《Spring 的建议类型》中描述的其中一种建议接口。顾问可以利用 AspectJ 的点切割表达式。spring-doc.cadn.net.cn

Spring 支持 Advisor 概念,包括<aop: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>

以及点切-参考在前面示例中使用的属性,你也可以使用点切属性以定义内联的点切割表达式。spring-doc.cadn.net.cn

为了定义顾问的优先级,使该顾问能够参与订购, 使用以下次序属性以定义命令顾问的价值。spring-doc.cadn.net.cn

AOP模式示例

本节展示了AOP示例中并发锁定失败重试示例在采用模式支持后呈现的样貌。spring-doc.cadn.net.cn

业务服务的执行有时可能因并发问题而失败(对于 例如,僵局失败者)。如果重试该行动,成功的可能性很大 下次再试。对于适合重新尝试的商业服务 条件(幂冪级作,无需返回用户进行冲突 我们希望透明地重试作,以避免客户端看到PessimisticLockingFailureException.这是一项明确跨越界限的要求 服务层中的多个服务,因此非常适合通过 方面。spring-doc.cadn.net.cn

因为我们想重试作,需要参考相关建议以便 叫进行多次。以下列表展示了基本的方面实现 (这是一个常规的 Java 类,使用了模式支持):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
	}
}

注意,该切面实现了命令接口,这样我们就可以设置 的优先级 比交易建议更高的方面(我们希望每次都获得新的交易) 重试)。这maxRetries次序这两个属性都由 Spring 配置。这 主要作用发生在doConcurrentOperation关于建议的方法。我们尽力了 进行。如果我们用 a 失败PessimisticLockingFailureException,我们再试一次, 除非我们已经用尽所有重试尝试。spring-doc.cadn.net.cn

该类与@AspectJ示例中使用的相同,但其 注释已移除。

对应的Spring配置如下: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>

请注意,目前我们假设所有商业服务都是幂等的。如果 事实并非如此,我们可以细化该方面,使其只真正重试 幂冪等作,通过引入一个幂等注释以及使用 用于注释服务作的实现,如下示例所示:spring-doc.cadn.net.cn

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

这 将重试的面向变更为仅限幂零运算,涉及对 点切割表达式,仅有@Idempotent作匹配如下:spring-doc.cadn.net.cn

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