如果您更喜欢基于 XML 的格式,Spring 还支持使用aop命名空间标签定义切面。支持与使用 @AspectJ 样式时完全相同的切入点表达式和通知类型。因此,在本节中,我们将重点放在该语法上,并请读者参考上一节中的讨论(@AspectJ 支持),以了解编写切入点表达式和通知参数的绑定。
要使用本节中描述的 aop 命名空间标签,您需要导入 spring-aop架构,如基于 XML 架构的配置中所述。 有关如何在aop命名空间中导入标签的信息,请参阅AOP 模式。
在您的 Spring 配置中,所有切面和顾问元素都必须放在一个<aop:config>元素中(您可以在应用程序上下文配置中拥有多个<aop:config>元素)。一个<aop:config>元素可以包含切入点、顾问和切面元素(请注意,这些元素必须按此顺序声明)。
<aop:config>配置风格大量使用了 Spring 的 自动代理机制。如果您已经通过使用BeanNameAutoProxyCreator或类似的方式使用显式自动代理,这可能会导致问题(例如未编织通知) 。推荐的使用模式是仅使用<aop:config>样式或仅使用AutoProxyCreator样式并且从不混合使用它们。
5.5.1. 声明一个切面
当您使用模式支持时,切面是在 Spring 应用程序上下文中定义为 bean 的常规 Java 对象。在对象的字段和方法中捕获状态和行为,在 XML 中捕获切入点和通知信息。
基于模式的声明风格以与@AspectJ 支持相同的方式支持完全类型化的通知——通过按名称匹配切入点参数与通知方法参数。有关详细信息,请参阅通知参数。如果您希望为通知方法显式指定参数名称(不依赖于前面描述的检测策略),您可以使用 通知元素的arg-names属性来实现,该属性的处理方式与 通知注解中的argNames属性相同(如确定参数名称中所述)。以下示例显示如何在 XML 中指定参数名称:
<aop:before
pointcut="com.xyz.lib.Pointcuts.anyPublicMethod() and @annotation(auditable)"
method="audit"
arg-names="auditable"/>
该arg-names属性接受以逗号分隔的参数名称列表。
以下基于 XSD 的方法稍微复杂一些的示例显示了一些与许多强类型参数结合使用的环绕通知:
package x.y.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 x.y;
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());
}
}
}
最后,以下示例 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="x.y.service.DefaultPersonService"/>
<!-- this is the actual advice itself -->
<bean id="profiler" class="x.y.SimpleProfiler"/>
<aop:config>
<aop:aspect ref="profiler">
<aop:pointcut id="theExecutionOfSomePersonServiceMethod"
expression="execution(* x.y.service.PersonService.getPerson(String,int))
and args(name, age)"/>
<aop:around pointcut-ref="theExecutionOfSomePersonServiceMethod"
method="profile"/>
</aop:aspect>
</aop:config>
</beans>
考虑以下驱动程序脚本:
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import x.y.service.PersonService;
public final class Boot {
public static void main(final String[] args) throws Exception {
BeanFactory ctx = new ClassPathXmlApplicationContext("x/y/plain.xml");
PersonService person = (PersonService) ctx.getBean("personService");
person.getPerson("Pengo", 12);
}
}
使用这样的 Boot 类,我们将在标准输出中获得类似于以下内容的输出:
StopWatch 'Profiling for 'Pengo' and '12': running time (millis) = 0
-----------------------------------------
ms % Task name
-----------------------------------------
00000 ? execution(getFoo)
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;
}
}
请注意,切面实现了Ordered接口,以便我们可以将切面的优先级设置为高于事务通知(我们希望每次重试时都有一个新事务)。maxRetries和order属性都是由 Spring 配置的。主要动作doConcurrentOperation发生在around 通知方法中。我们尝试继续。如果我们以 PessimisticLockingFailureException失败,我们会再试一次,除非我们已经用尽了所有的重试尝试。