如果您更喜欢基于 XML 的格式,Spring 还支持定义方面
使用命名空间标记。完全相同的切入点表达式和建议类型
与使用时一样,支持@AspectJ样式。因此,在本节中,我们将重点关注
该语法,并让读者参考上一节中的讨论
(@AspectJ支持)用于理解写入点切表达式和绑定
的建议参数。aop
要使用本节中描述的 aop 命名空间标记,您需要导入架构,如基于 XML 架构的配置中所述。请参阅 AOP 架构,了解如何导入命名空间中的标签。spring-aop
aop
在 Spring 配置中,所有方面和顾问元素都必须放置在
一个元素(一个
应用程序上下文配置)。元素可以包含 pointcut,
advisor 和 aspect 元素(请注意,这些元素必须按该顺序声明)。<aop:config>
<aop:config>
<aop:config>
配置风格大量使用了 Spring 的自动代理机制。这可能会导致问题(例如建议
not being woven),如果您已经通过使用 或类似的东西使用显式自动代理。推荐的使用模式是
仅使用样式或仅使用样式和
永远不要把它们混在一起。<aop:config> BeanNameAutoProxyCreator <aop:config> AutoProxyCreator |
声明一个方面
使用模式支持时,一个方面是一个常规的 Java 对象,定义为 Bean 您的 Spring 应用程序上下文。状态和行为被捕获在字段中,并且 对象的方法,以及 XML 中的切入点和建议信息。
您可以使用元素声明一个方面,并引用背景 Bean
通过使用该属性,如以下示例所示:<aop:aspect>
ref
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
...
</aop:aspect>
</aop:config>
<bean id="aBean" class="...">
...
</bean>
当然,可以配置支持方面的 bean(在本例中)和
像任何其他 Spring Bean 一样注入依赖关系。aBean
声明切点
您可以在元素中声明一个命名的 pointcut,让 pointcut
定义在多个方面和顾问之间共享。<aop:config>
表示服务层中任何业务服务执行的切口可以 定义如下:
<aop:config>
<aop:pointcut id="businessService"
expression="execution(* com.xyz.service.*.*(..))" />
</aop:config>
请注意,pointcut 表达式本身使用相同的 AspectJ pointcut 表达式
@AspectJ支持中所述的语言。如果使用基于架构的声明
样式,您还可以引用在
Pointcut 表达式。因此,定义上述切入点的另一种方法如下:@Aspect
<aop:config>
<aop:pointcut id="businessService"
expression="com.xyz.CommonPointcuts.businessService()" /> (1)
</aop:config>
1 | 引用在共享命名切口定义中定义的命名切口。businessService |
在方面内部声明切点与声明顶级切点非常相似, 如以下示例所示:
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.service.*.*(..))"/>
...
</aop:aspect>
</aop:config>
与@AspectJ方面大致相同,使用基于架构声明的切入点
定义样式可以收集连接点上下文。例如,以下切入点
收集对象作为连接点上下文,并将其传递给建议: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) {
// ...
}
组合 pointcut 子表达式时,在 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
声明建议
基于模式的 AOP 支持使用与 @AspectJ 样式相同的五种建议,并且它们具有 完全相同的语义。
建议前
在建议运行之前,在匹配的方法执行之前。它是使用 element 在 an 中声明的,如以下示例所示:<aop:aspect>
<aop:before>
<aop:aspect id="beforeExample" ref="aBean">
<aop:before
pointcut-ref="dataAccessOperation"
method="doAccessCheck"/>
...
</aop:aspect>
在上面的示例中,是定义在
顶层 () 级别(请参阅声明 Pointcut)。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>
该属性标识一个方法 (),该方法提供
建议。必须为 aspect 元素引用的 Bean 定义此方法
其中包含建议。在执行数据访问操作之前(方法执行
与切入表达式匹配的连接点),该方法在方面
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样式一样,您可以在建议正文中获取返回值。
为此,请使用该属性指定其参数的名称
应传递返回值,如以下示例所示:returning
<aop:aspect id="afterReturningExample" ref="aBean">
<aop:after-returning
pointcut="execution(* com.xyz.dao.*.*(..))"
returning="retVal"
method="doAccessCheck"/>
...
</aop:aspect>
该方法必须声明一个名为 的参数。这个的类型
参数约束匹配的方式与 .为
例如,您可以按如下方式声明方法签名: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样式一样,您可以在建议正文中获得抛出的异常。
为此,请使用该属性将参数的名称指定为
应传递异常,如以下示例所示: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
<aop:aspect id="afterFinallyExample" ref="aBean">
<aop:after
pointcut="execution(* com.xyz.dao.*.*(..))"
method="doReleaseLock"/>
...
</aop:aspect>
周边建议
最后一种建议是关于建议的。围绕建议运行“围绕”匹配 方法的执行。它有机会在方法之前和之后做工作 运行并确定该方法何时、如何运行,甚至是否实际运行。 如果您需要共享方法前后的状态,通常会使用 Around 建议 以线程安全的方式执行 - 例如,启动和停止计时器。
始终使用满足您要求的最弱的建议形式。 例如,如果在建议足以满足您的需求之前,请不要使用周围的建议。 |
您可以使用该元素声明 around advice。建议方法应
声明作为其返回类型,并且该方法的第一个参数必须为
类型。在建议方法的正文中,必须调用 才能运行基础方法。
不带参数的调用将导致调用方的原始参数
在调用基础方法时提供给基础方法。对于高级用例,有
是接受参数数组的方法的重载变体
().数组中的值将用作基础的参数
方法。有关使用 .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示例(当然,减去注释),如以下示例所示: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()
}
建议参数
基于架构的声明样式支持完全类型的建议,其方式与以下方式相同
针对 @AspectJ 支持进行描述 — 通过按名称匹配 Pointcut 参数
建议方法参数。有关详细信息,请参阅建议参数。如果你愿意
显式指定建议方法的参数名称(不依赖于
检测策略),可以使用 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 的方法的稍微复杂一些的示例显示 一些与许多强类型参数结合使用的建议:
-
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(..)
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)
建议订购
当需要在同一连接点运行多个建议时(执行方法)
订购规则如建议订购中所述。优先级
方面之间通过元素中的属性或
通过将注释添加到支持该方面的 Bean 或具有
Bean 实现接口。order
<aop:aspect>
@Order
Ordered
与在同一类中定义的建议方法的优先规则相反,当在同一元素中定义的两条建议都需要
在同一连接点运行,优先级由建议的顺序决定
元素在封闭元素中声明,从最高到最低
优先。 例如,给定一个建议和一个在同一元素中定义的适用于同一连接点的建议,为了确保建议的优先级高于建议,该元素必须是
在元素之前声明。 作为一般经验法则,如果您发现您定义了多条建议
在应用于同一联接点的同一元素中,请考虑折叠
这样的建议方法转化为每个元素中每个连接点的一个建议方法
或者将建议重构为可以订购的单独元素
在方面层面。 |
介绍
引言(在 AspectJ 中称为类型间声明)让一个方面声明 建议对象实现给定的接口并提供 代表这些对象的接口。
您可以使用 .
可以使用该元素声明匹配类型具有新的父级(因此得名)。
例如,给定一个名为的接口和该接口的实现,以下方面声明服务的所有实现者
接口也实现接口。(为了公开统计数据
例如,通过 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()
}
要实现的接口由属性确定。这
属性的值是 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)
顾问
“顾问”的概念来自 Spring 中定义的 AOP 支持 并且在 AspectJ 中没有直接等价物。顾问就像一个小 独立的方面,只有一条建议。建议本身是 由 Bean 表示,并且必须实现 Spring 中的建议类型中描述的建议接口之一。顾问可以利用 AspectJ 切入点表达式。
Spring 支持带有元素的顾问概念。你最
通常看到它与交易建议结合使用,交易建议也有自己的
Spring 中的命名空间支持。以下示例显示了一个顾问:<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
要定义顾问的优先级,以便该顾问可以参与排序,
使用该属性定义顾问的值。order
Ordered
AOP 架构示例
本部分介绍使用架构支持重写 AOP 示例中的并发锁定失败重试示例时的外观。
业务服务的执行有时会由于并发问题而失败(对于
例如,死锁失败者)。如果重试操作,则很可能会成功
在下次尝试时。对于适合重试的业务服务,请在此类服务中重试
条件(不需要返回给用户进行冲突的幂等操作
resolution),我们希望透明地重试该操作,以避免客户端看到 .这是一项明确贯穿始终的要求
服务层中的多个服务,因此非常适合通过
方面。PessimisticLockingFailureException
因为我们想重试操作,所以我们需要使用周围的建议,以便我们可以
多次调用。以下列表显示了基本方面实现
(这是一个使用模式支持的常规 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
}
}
请注意,该方面实现了接口,以便我们可以设置
高于交易建议的方面(我们每次都想要新的交易
重试)。和 属性均由 Spring 配置。这
主要动作发生在 Around 建议方法中。我们尝试
进行。如果我们失败了,我们再试一次,
除非我们已经用尽了所有的重试尝试。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>
请注意,目前,我们假设所有业务服务都是幂等的。如果
事实并非如此,我们可以改进该方面,使其仅真正重试
幂等操作,通过引入注释并使用注释
批注服务操作的实现,如以下示例所示:Idempotent
-
Java
-
Kotlin
@Retention(RetentionPolicy.RUNTIME)
// marker annotation
public @interface Idempotent {
}
@Retention(AnnotationRetention.RUNTIME)
// marker annotation
annotation class Idempotent
这
更改为仅重试幂等操作涉及优化
PointCut 表达式,以便只有操作匹配,如下所示:@Idempotent
<aop:pointcut id="idempotentOperation"
expression="execution(* com.xyz.service.*.*(..)) and
@annotation(com.xyz.service.Idempotent)"/>