Schema-based AOP Support
如果您更喜欢基于 XML 的格式,Spring 还提供使用 aop
名称空间标签定义方面的支持。在使用 @AspectJ 样式时,支持完全相同的切入点表达式和建议类型。因此,在本节中,我们重点关注该语法,并将读者转到前一节(@AspectJ support)中的讨论,以了解书写切入点表达式和建议参数绑定。
If you prefer an XML-based format, Spring also offers support for defining aspects
using the aop
namespace tags. The exact same pointcut expressions and advice kinds
as when using the @AspectJ style are supported. Hence, in this section we focus on
that syntax and refer the reader to the discussion in the previous section
(@AspectJ support) for an understanding of writing pointcut expressions and the binding
of advice parameters.
要使用本部分中描述的 aop 命名空间标记,您需要导入 spring-aop
架构,如 XML Schema-based configuration 中所述。有关如何在 aop
命名空间中导入标记,请参阅 the AOP schema。
To use the aop namespace tags described in this section, you need to import the
spring-aop
schema, as described in XML Schema-based configuration
. See the AOP schema
for how to import the tags in the aop
namespace.
在 Spring 配置中,所有切面和通知元素都必须放在 <aop:config>
元素内(你可以在应用程序上下文配置中有多个 <aop:config>
元素)。一个 <aop:config>
元素可以包含切入点、通知和切面元素(请注意,它们必须按照此顺序声明)。
Within your Spring configurations, all aspect and advisor elements must be placed within
an <aop:config>
element (you can have more than one <aop:config>
element in an
application context configuration). An <aop:config>
element can contain pointcut,
advisor, and aspect elements (note that these must be declared in that order).
<aop:config>
样式的配置大量使用了 Spring 的 auto-proxying 机制。如果你已经通过使用 BeanNameAutoProxyCreator
这样的显式自动代理,这可能会导致问题(例如不织入建议)。建议的使用模式是仅使用 <aop:config>
样式或仅使用 AutoProxyCreator
样式,并且永远不要将它们混合在一起。
The <aop:config>
style of configuration makes heavy use of Spring’s
auto-proxying mechanism. This can cause issues (such as advice
not being woven) if you already use explicit auto-proxying through the use of
BeanNameAutoProxyCreator
or something similar. The recommended usage pattern is to
use either only the <aop:config>
style or only the AutoProxyCreator
style and
never mix them.
Declaring an Aspect
当您使用模式支持时,切面是在 Spring 应用程序上下文中定义为 bean 的常规 Java 对象。状态和行为被捕获在该对象的字段和方法中,切入点和建议信息被捕获在 XML 中。
When you use the schema support, an aspect is a regular Java object defined as a bean in your Spring application context. The state and behavior are captured in the fields and methods of the object, and the pointcut and advice information are captured in the XML.
可以使用 <aop:aspect>
元素声明一个方面,并使用 ref
属性引用支持 bean,如下例所示:
You can declare an aspect by using the <aop:aspect>
element, and reference the backing bean
by using the ref
attribute, as the following example shows:
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
...
</aop:aspect>
</aop:config>
<bean id="aBean" class="...">
...
</bean>
支持外观的 Bean(在本例中为 aBean
)当然可以像任何其他 Spring Bean 一样进行配置和依赖注入。
The bean that backs the aspect (aBean
in this case) can of course be configured and
dependency injected just like any other Spring bean.
Declaring a Pointcut
你可以在 <aop:config>
元素中声明 命名切入点,让切入点定义在多个方面和顾问间共享。
You can declare a named pointcut inside an <aop:config>
element, letting the pointcut
definition be shared across several aspects and advisors.
可以按照如下方式定义一个切点来表示服务层中任何业务服务的执行:
A pointcut that represents the execution of any business service in the service layer can be defined as follows:
<aop:config>
<aop:pointcut id="businessService"
expression="execution(* com.xyz.service.*.*(..))" />
</aop:config>
请注意,切入点表达式本身使用与 @AspectJ support 中描述的相同 AspectJ 切入点表达式语言。如果您使用基于模式的声明样式,您还可以引用 @Aspect
类型中 named pointcuts 定义的在切入点表达式中。因此,另一种定义上述切入点的方法如下:
Note that the pointcut expression itself uses the same AspectJ pointcut expression
language as described in @AspectJ support. If you use the schema based declaration
style, you can also refer to named pointcuts defined in @Aspect
types within the
pointcut expression. Thus, another way of defining the above pointcut would be as follows:
<aop:config>
<aop:pointcut id="businessService"
expression="com.xyz.CommonPointcuts.businessService()" /> 1
</aop:config>
1 | References the businessService named pointcut defined in Sharing Named Pointcut Definitions. |
如以下示例所示,在切面内声明一个连接点与声明一个顶级连接点非常相似:
Declaring a pointcut inside an aspect is very similar to declaring a top-level pointcut, as the following example shows:
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.service.*.*(..))"/>
...
</aop:aspect>
</aop:config>
与使用基于模式定义样式声明的 @AspectJ 方面的方式相同,切入点可以通过收集连接点上下文。例如,以下切入点收集“this”对象作为连接点上下文并将它传递给建议:
In much the same way as an @AspectJ aspect, pointcuts declared by using the schema based
definition style can collect join point context. For example, the following pointcut
collects the this
object as the join point context and passes it to the advice:
<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>
若要接收收集的联接点上下文,必须声明建议,方法是包括匹配名称的参数,如下所示:
The advice must be declared to receive the collected join point context by including parameters of the matching names, as follows:
-
Java
-
Kotlin
public void monitor(Object service) {
// ...
}
fun monitor(service: Any) {
// ...
}
在组合切点子表达时,XML 文档中的 &&
使用起来很不方便,因此您可以使用 and
, or
和 not
关键字来分别代替 &&
,||
和 !
。例如,前一个切点可以写得更好如下:
When combining pointcut sub-expressions, &&
is awkward within an XML
document, so you can use the and
, or
, and not
keywords in place of &&
,
||
, and !
, respectively. For example, the previous pointcut can be better written as
follows:
<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 样式所提供的支持更有限。
Note that pointcuts defined in this way are referred to by their XML id
and cannot be
used as named pointcuts to form composite pointcuts. The named pointcut support in the
schema-based definition style is thus more limited than that offered by the @AspectJ
style.
Declaring Advice
基于模式的 AOP 支持使用与 @AspectJ 样式相同的 5 种建议,并且它们具有完全相同的语义。
The schema-based AOP support uses the same five kinds of advice as the @AspectJ style, and they have exactly the same semantics.
Before Advice
建议在匹配的方法执行前执行。它通过使用 <aop:before>
元素在<aop:aspect>
中声明,如以下示例所示:
Before advice runs before a matched method execution. It is declared inside an
<aop:aspect>
by using the <aop:before>
element, as the following example shows:
<aop:aspect id="beforeExample" ref="aBean">
<aop:before
pointcut-ref="dataAccessOperation"
method="doAccessCheck"/>
...
</aop:aspect>
在上面的示例中,dataAccessOperation
是在顶部(<aop:config>
)级别定义的 named pointcut 的 id
(请参阅 Declaring a Pointcut)。
In the example above, dataAccessOperation
is the id
of a named pointcut defined at
the top (<aop:config>
) level (see Declaring a Pointcut).
正如我们在对 @AspectJ 样式的讨论中指出的,使用 named pointcuts 可以大大提高代码的可读性。详情请参阅 Sharing Named Pointcut Definitions。 |
As we noted in the discussion of the @AspectJ style, using named pointcuts can significantly improve the readability of your code. See Sharing Named Pointcut Definitions for details. |
要内联定义切点,请使用 pointcut
属性替换 pointcut-ref
属性,如下所示:
To define the pointcut inline instead, replace the pointcut-ref
attribute with a
pointcut
attribute, as follows:
<aop:aspect id="beforeExample" ref="aBean">
<aop:before
pointcut="execution(* com.xyz.dao.*.*(..))"
method="doAccessCheck"/>
...
</aop:aspect>
method
属性标识提供通知正文的方法 (doAccessCheck
)。必须为包含通知的 aspect 元素引用的 bean 定义此方法。在执行数据访问操作(切点表达式匹配的方法执行连接点)之前,将调用 aspect bean 上的 doAccessCheck
方法。
The method
attribute identifies a method (doAccessCheck
) that provides the body of
the advice. This method must be defined for the bean referenced by the aspect element
that contains the advice. Before a data access operation is performed (a method execution
join point matched by the pointcut expression), the doAccessCheck
method on the aspect
bean is invoked.
After Returning Advice
返回通知在匹配的方法执行正常完成之后运行。它在 <aop:aspect>
内声明,方式与 before 通知相同。以下示例显示了如何声明 it:
After returning advice runs when a matched method execution completes normally. It is
declared inside an <aop:aspect>
in the same way as before advice. The following example
shows how to declare it:
<aop:aspect id="afterReturningExample" ref="aBean">
<aop:after-returning
pointcut="execution(* com.xyz.dao.*.*(..))"
method="doAccessCheck"/>
...
</aop:aspect>
与 @AspectJ 样式相同,您可以在通知主体中获取返回值。为此,请使用 returning
属性指定应将返回值传递给的参数的名称,如下例所示:
As in the @AspectJ style, you can get the return value within the advice body.
To do so, use the returning
attribute to specify the name of the parameter to which
the return value should be passed, as the following example shows:
<aop:aspect id="afterReturningExample" ref="aBean">
<aop:after-returning
pointcut="execution(* com.xyz.dao.*.*(..))"
returning="retVal"
method="doAccessCheck"/>
...
</aop:aspect>
doAccessCheck
方法必须声明一个名为 retVal
的参数。此参数的类型对匹配的约束与为 @AfterReturning
描述的相同。例如,您可以将方法签名声明如下:
The doAccessCheck
method must declare a parameter named retVal
. The type of this
parameter constrains matching in the same way as described for @AfterReturning
. For
example, you can declare the method signature as follows:
-
Java
-
Kotlin
public void doAccessCheck(Object retVal) {...
fun doAccessCheck(retVal: Any) {...
After Throwing Advice
退出通知在匹配的方法执行通过抛出异常退出时运行。它通过使用 after-throwing
元素在 <aop:aspect>
内声明,如下例所示:
After throwing advice runs when a matched method execution exits by throwing an
exception. It is declared inside an <aop:aspect>
by using the after-throwing
element,
as the following example shows:
<aop:aspect id="afterThrowingExample" ref="aBean">
<aop:after-throwing
pointcut="execution(* com.xyz.dao.*.*(..))"
method="doRecoveryActions"/>
...
</aop:aspect>
与 @AspectJ 样式相同,您可以在通知主体中获取抛出的异常。为此,请使用 throwing
属性指定应将异常传递给的参数的名称,如下例所示:
As in the @AspectJ style, you can get the thrown exception within the advice body.
To do so, use the throwing
attribute to specify the name of the parameter to
which the exception should be passed as the following example shows:
<aop:aspect id="afterThrowingExample" ref="aBean">
<aop:after-throwing
pointcut="execution(* com.xyz.dao.*.*(..))"
throwing="dataAccessEx"
method="doRecoveryActions"/>
...
</aop:aspect>
doRecoveryActions
方法必须声明一个名为 dataAccessEx
的参数。此参数的类型对匹配的约束与为 @AfterThrowing
描述的相同。例如,可以将方法签名声明如下:
The doRecoveryActions
method must declare a parameter named dataAccessEx
.
The type of this parameter constrains matching in the same way as described for
@AfterThrowing
. For example, the method signature may be declared as follows:
-
Java
-
Kotlin
public void doRecoveryActions(DataAccessException dataAccessEx) {...
fun doRecoveryActions(dataAccessEx: DataAccessException) {...
After (Finally) Advice
无论匹配的方法执行如何退出,After(finally)通知都会运行。您可以使用 after
元素声明它,如下例所示:
After (finally) advice runs no matter how a matched method execution exits.
You can declare it by using the after
element, as the following example shows:
<aop:aspect id="afterFinallyExample" ref="aBean">
<aop:after
pointcut="execution(* com.xyz.dao.*.*(..))"
method="doReleaseLock"/>
...
</aop:aspect>
Around Advice
最后一种通知是 around 通知。Around 通知在匹配方法的执行周围运行。它有机会在方法运行之前和之后都进行工作,并确定方法何时、如何,甚至是否真正运行。如果您需要在多线程方式下在方法执行之前和之后共享状态(例如,启动和停止计时器),则经常使用 around 通知。
The last kind of advice is around advice. Around advice runs "around" a matched method’s execution. It has the opportunity to do work both before and after the method runs and to determine when, how, and even if the method actually gets to run at all. Around advice is often used if you need to share state before and after a method execution in a thread-safe manner – for example, starting and stopping a timer.
始终使用满足您要求中最不强大的通知形式。 Always use the least powerful form of advice that meets your requirements. 例如,如果 before 通知足以满足您的需要,请勿使用 around 通知。 For example, do not use around advice if before advice is sufficient for your needs. |
您可以使用 aop:around
元素声明环绕通知。通知方法应声明其返回类型为 Object
,并且方法的第一个参数必须是 ProceedingJoinPoint
类型。在通知方法的主体中,您必须在 ProceedingJoinPoint
上调用 proceed()
以运行底层方法。没有任何参数地调用 proceed()
将导致在调用底层方法时为其提供调用方的原始参数。对于高级用例,proceed()
方法有一个重载变体,它接受参数数组(Object[]
)。数组中的值将作为在调用底层方法时的参数。有关使用 Object[]
调用 proceed
的注释,请参阅 Around Advice。
You can declare around advice by using the aop:around
element. The advice method should
declare Object
as its return type, and the first parameter of the method must be of
type ProceedingJoinPoint
. Within the body of the advice method, you must invoke
proceed()
on the ProceedingJoinPoint
in order for the underlying method to run.
Invoking proceed()
without arguments will result in the caller’s original arguments
being supplied to the underlying method when it is invoked. For advanced use cases, there
is an overloaded variant of the proceed()
method which accepts an array of arguments
(Object[]
). The values in the array will be used as the arguments to the underlying
method when it is invoked. See Around Advice for notes on calling
proceed
with an Object[]
.
以下示例显示了如何在 XML 中声明 around 通知:
The following example shows how to declare around advice in XML:
<aop:aspect id="aroundExample" ref="aBean">
<aop:around
pointcut="execution(* com.xyz.service.*.*(..))"
method="doBasicProfiling"/>
...
</aop:aspect>
doBasicProfiling
通知的实现与 @AspectJ 示例完全相同(当然,减去注释),如下例所示:
The implementation of the doBasicProfiling
advice can be exactly the same as in the
@AspectJ example (minus the annotation, of course), as the following example shows:
-
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 Parameters
基于模式的声明样式通过按名称对比切点参数和通知方法参数,以与 @AspectJ 支持相同的方式支持完全类型的通知。有关详细信息,请参阅 Advice Parameters。如果您希望明确指定通知方法的参数名称(不依赖于前述检测策略),您可以使用通知元素的 arg-names
属性来执行此操作,该属性的处理方式与通知注释中的 argNames
属性相同(如 Determining Argument Names 所述)。以下示例展示了如何在 XML 中指定参数名称:
The schema-based declaration style supports fully typed advice in the same way as
described for the @AspectJ support — by matching pointcut parameters by name against
advice method parameters. See Advice Parameters for details. If you wish
to explicitly specify argument names for the advice methods (not relying on the
detection strategies previously described), you can do so by using the arg-names
attribute of the advice element, which is treated in the same manner as the argNames
attribute in an advice annotation (as described in Determining Argument Names).
The following example shows how to specify an argument name in XML:
<aop:before
pointcut="com.xyz.Pointcuts.publicMethod() and @annotation(auditable)" 1
method="audit"
arg-names="auditable" />
1 | References the publicMethod named pointcut defined in Combining Pointcut Expressions. |
arg-names
属性接受一个用逗号分隔的参数名称列表。
The arg-names
attribute accepts a comma-delimited list of parameter names.
以下是基于 XSD 的方法的一个稍微复杂一些的示例,展示了一些与许多强类型参数结合使用的 around 通知:
The following slightly more involved example of the XSD-based approach shows some around advice used in conjunction with a number of strongly typed parameters:
-
Java
-
Kotlin
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);
}
}
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”建议,如下例所示:
Next up is the aspect. Notice the fact that the profile(..)
method accepts a number of
strongly-typed parameters, the first of which happens to be the join point used to
proceed with the method call. The presence of this parameter is an indication that the
profile(..)
is to be used as around
advice, as the following example shows:
-
Java
-
Kotlin
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());
}
}
}
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 配置会对特定连接点执行前一个建议:
Finally, the following example XML configuration effects the execution of the preceding advice for a particular join point:
<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:
-
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
,我们会在标准输出上获得类似以下的输出:
With such a Boot
class, we would get output similar to the following on standard output:
StopWatch 'Profiling for 'Pengo' and '12': running time (millis) = 0
ms % Task name
00000 ? execution(getFoo)
Advice Ordering
当多条通知需要在同一点连接点(执行方法)上运行时,排序规则如 Advice Ordering 中所述。方面之间的优先级通过 <aop:aspect>
元素中的 order
属性确定,或通过将 @Order
注释添加到支持该方面的 Bean,或通过让 Bean 实现 Ordered
接口。
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.
与在同一 In contrast to the precedence rules for advice methods defined in the same 例如,假设在同一 For example, given an 一个普遍的经验法则,如果你发现你有多条建议在同一 As a general rule of thumb, if you find that you have multiple pieces of advice defined
in the same |
Introductions
引入(在 AspectJ 中称为跨类型声明)允许一个方面声明所建议对象实现给定接口,代表这些对象提供此接口的实现。
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.
你可以在 aop:aspect
中使用 aop:declare-parents
元素进行引入。你可以使用 aop:declare-parents
元素声明匹配类型有新的父级(这就是其名称的由来)。例如,给定一个名为 UsageTracked
的接口和一个名为 DefaultUsageTracked
的该接口的实现,以下方面声明服务接口的所有实现者也实现 UsageTracked
接口。(例如,为了通过 JMX 公开统计信息。)
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.)
<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 的类随后将包含以下方法:
The class that backs the usageTracking
bean would then contain the following method:
-
Java
-
Kotlin
public void recordUsage(UsageTracked usageTracked) {
usageTracked.incrementUseCount();
}
fun recordUsage(usageTracked: UsageTracked) {
usageTracked.incrementUseCount()
}
要实现的接口由 implement-interface
属性决定。types-matching
属性的值是 AspectJ 类型模式。任何匹配类型的 bean 都实现了 UsageTracked
接口。请注意,在前一个示例的建议之前,服务 bean 可以直接用作 UsageTracked
接口的实现。若要以编程方式访问 bean,你可以编写以下内容:
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:
-
Java
-
Kotlin
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.
Advisors
“顾问” 的概念源自 Spring 中定义的 AOP 支持,并且在 AspectJ 中没有直接等效项。顾问就像一个小型的、独立的方面,只包含一条通知。通知本身由 Bean 表示,并且必须实现 Advice Types in Spring 中描述的一个通知接口。顾问可以利用 AspectJ 切点表达式。
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 通过 <aop:advisor>
元素支持顾问概念。你最常见地看到它与事务建议结合使用,事务建议在 Spring 中也有自己的命名空间支持。以下示例显示了一个顾问:
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:
<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
属性来内联定义切入点表达式。
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.
若要定义顾问的优先级以便建议能参与排序,请使用 order
属性来定义顾问的 Ordered
值。
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.
An AOP Schema Example
本节展示了当使用模式支持重新编写时,An AOP Example 中的并发锁定失败重试示例是如何表现的。
This section shows how the concurrent locking failure retry example from An AOP Example looks when rewritten with the schema support.
由于并发问题(例如,死锁失败),业务服务的执行有时会失败。如果重新运行该操作,它在下次尝试时很可能会成功。对于在这种情况(在解决冲突时无需返回给用户的幂等操作)下适合重试的业务服务,我们希望透明地重试操作,以避免客户端看到 PessimisticLockingFailureException
。这是在服务层中明显跨越多个服务的要求,因此非常适合通过一个方面来实现。
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.
因为我们想要重试该操作,因此我们需要使用around建议,以便我们可以多次调用`proceed`。以下清单显示了基本切面实现(这是一个使用模式支持的常规Java类):
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):
-
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
}
}
请注意,切面实现了`Ordered`接口,以便我们可以将切面的优先级设置得高于事务建议(我们希望每次重试都会有一个新的事务)。`maxRetries`和`order`属性都由Spring配置。主操作发生在around建议方法`doConcurrentOperation`中。我们尝试继续。如果我们因`PessimisticLockingFailureException`失败,我们会重新尝试,除非我们已经用尽了所有重试尝试。
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.
此类与在 @AspectJ 示例中使用的类相同,但已删除注释。 |
This class is identical to the one used in the @AspectJ example, but with the annotations removed. |
相应的Spring配置如下:
The corresponding Spring configuration is as follows:
<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`注释并使用该注释注释服务操作的实现,如下例所示:
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:
-
Java
-
Kotlin
@Retention(RetentionPolicy.RUNTIME)
// marker annotation
public @interface Idempotent {
}
@Retention(AnnotationRetention.RUNTIME)
// marker annotation
annotation class Idempotent
仅重试幂等操作的切面变化涉及优化切入表达式,以便仅匹配`@Idempotent`操作,如下所示:
The
change to the aspect to retry only idempotent operations involves refining the
pointcut expression so that only @Idempotent
operations match, as follows:
<aop:pointcut id="idempotentOperation"
expression="execution(* com.xyz.service.*.*(..)) and
@annotation(com.xyz.service.Idempotent)"/>