Spring学习手册(9)—— Spring AOP入门讲述了AOP技术以及AOP基本概念,最后我们了解了Spring对AOP的支持。本文我们将以XML配置的方式来学习Spring AOP的具体使用。
一、引入aop模式
如果想使用XML的方式配置AOP信息,我们需要先在XML配置文件中引入aop模式(aop schema),因此我们的XML配置文件头头信息如下:
<?xml version="1.0" encoding="UTF-8"?>
<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 http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- bean definitions here -->
</beans>
这样配之后,我们就可以在xml文件里面直接引用aop
标签了。
二、定义一个切面
一个Spring AOP的切面(Aspect)在xml中也是一个传统的bean
,而使用标签<aop:aspect>
定义一个切面,并且使用ref
来执行被定义为该切面的bean。
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
...
</aop:aspect>
</aop:config>
<bean id="aBean" class="...">
...
</bean>
如上,我们定义了一个id为aBean的bean实例,然后使用<aop:aspect>
标签定义了一个id为myAspect的切面,而该切面指向aBean。
三、定义一个切点(pointcut)
上篇我们说过,切点表达式用于匹配连接点(join point),然后根据配置的增强(Advice)方法在连接点运行时选择合适时间执行。因为Spring AOP目前仅支持运行方法类型的连接点,所以也可以认为与一个切点匹配一个bean的执行方法。
一个切点包含两部分:
- 包含方法和名字的签名;
- 切点表达式:用于匹配具体的方法
因此我们使用xml定义切点如下所示:
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
...
</aop:aspect>
</aop:config>
<aop:pointcut>
必须在<aop:aspect>
内部使用定义切点,使用expression
的值指明切点表达式,id为该切点定义类唯一标示方便配置引用。这里并没有定义切点(pointcut) 签名,切点签名定义一般在使用@AspectJ
注解方式定义切点时定义。下面我们着重说明下切点表达式的语法情况。
支持的切点(pointcut)标示
Spring AOP 支持以下AspectJ 的切点标示(AspectJ pointcut designators)简称PCD,由于Spring AOP并没有全部支持所有的PCD,因此若使用了不存在该列表内的标示则会抛出异常。
标签名 | 说明 |
---|---|
execution | 匹配运行方法的连接点 |
within | 使匹配连接点限定在特定类型 |
this | 限定匹配的连接点是给定类型的实例 |
target | 限定匹配连接点的目标是给定类型的实例 |
args | 限定连接点的参数是给定类型的实例 |
@target | 限定匹配的连接点的运行的对象有该类型的注解 |
@args | 运行时传递的参数拥有给定类型的注解 |
@within | 限定匹配给定指定注解类型的连接点 |
@annotation | 限定连接点拥有指定的注解 |
bean | 使得连接点匹配特质的bean或bean集合 |
Tip:我们可以使用
&&
、||
、!
来将连接点表达式关联起来。
然而在XML配置中该类字符需要转换,因此我们可以使用更语义化的句子符号and
、or
、not
。
例子
使用execution
例子
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)
以上除ret-type-pattern
、name-pattern
、param-pattern
为必须外,其他皆为可选。
- 匹配所有的公有方法
execution(public * *(..)) - 匹配所有以set开头的方法
execution(* set*(..)) - 匹配所有AccountService接口定义的方法
execution(* com.xyz.service.AccountService.*(..)) - 匹配所有service包内的方法
execution(* com.xyz.service..(..)) - 匹配所有service包以及子包内的方法
execution(* com.xyz.service...(..)) - 所有在service包内的连接点
within(com.xyz.service.*) - 所有service包以及子包内的连接点
within(com.xyz.service..*) - 所有实现AccountService接口的连接点
this(com.xyz.service.AccountService) - 所有的目标对象实现AccountService接口的连接点
target(com.xyz.service.AccountService) - 有一个参数并且运行时传入参数为Serializable的连接点
args(java.io.Serializable) - 目标对象有@Transactional注解
@target(org.springframework.transaction.annotation.Transactional) - 定义的目标对象类型含有@Transactional注解
@within(org.springframework.transaction.annotation.Transactional) - 含有@Transactional注解的可运行方法
@annotation(org.springframework.transaction.annotation.Transactional) - 含有一个参数并且在运行时传入的参数含有@Classified注解
@args(com.xyz.security.Classified) - bean名字定义为tradeService的连接点
bean(tradeService) - bean名字满足Service命名的所有连接点
bean(Service)
四、定义增强方法(Advice)
Before advice
<aop:aspect id="beforeExample" ref="aBean">
<aop:pointcut id="dataAccessOperation"
expression="execution(* com.xyz.myapp.dao.*.*(..))"/>
<aop:before
pointcut-ref="dataAccessOperation"
method="doAccessCheck"/>
...
</aop:aspect>
<aop:before>
使用pointcut-ref
引用一个切点,并且指定增强执行方法为doAccessCheck。值的注意的是名为aBean的bean(也就是我们定义的切面)必须实现doAccessCheck方法。该方法会在目标方法执行前执行。
After returning advice
<aop:aspect id="afterReturningExample" ref="aBean">
<aop:pointcut id="dataAccessOperation"
expression="execution(* com.xyz.myapp.dao.*.*(..))"/>
<aop:after-returning
pointcut-ref="dataAccessOperation"
method="doAccessCheck"/>
...
</aop:aspect>
同上,该方法会在切点执行正常返回后执行。
当然如果你需要获取返回对象的话,你需要将配置信息改为如下所示:
<aop:aspect id="afterReturningExample" ref="aBean">
...
<aop:after-returning
pointcut-ref="dataAccessOperation"
returning="retVal"
method="doAccessCheck"/>
...
</aop:aspect>
其中doAccessCheck方法定义如下,其中参数名必须与XML里面配置相同,也就是说必须与XML中的retVal一致。
public void doAccessCheck(Object retVal) {...}
After throwing advice
<aop:aspect id="afterThrowingExample" ref="aBean">
...
<aop:after-throwing
pointcut-ref="dataAccessOperation"
method="doRecoveryActions"/>
...
</aop:aspect>
当然如果需要获取抛出的异常时我们可以如下配置:
<aop:aspect id="afterThrowingExample" ref="aBean">
<aop:after-throwing
pointcut-ref="dataAccessOperation"
throwing="dataAccessEx"
method="doRecoveryActions"/>
...
</aop:aspect>
其中doRecoveryActions方法定义如下且必须有名为dataAccessEx参数:
public void doRecoveryActions(DataAccessException dataAccessEx) {...}
After (finally) advice
切点返回后执行如下增强方法(无论正常返回还是异常退出):
<aop:aspect id="afterFinallyExample" ref="aBean">
<aop:after
pointcut-ref="dataAccessOperation"
method="doReleaseLock"/>
...
</aop:aspect>
Around advice 环绕增强方法
该类型的Advice环绕着连接点,该增强方法可以选择调用或者不掉用连接点方法。
<aop:aspect id="aroundExample" ref="aBean">
<aop:around
pointcut-ref="businessService"
method="doBasicProfiling"/>
...
</aop:aspect>
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
如上所示,我们使用aop:around
来定义环绕增强,方法定义的第一个参数必须为ProceedingJoinPoint类,在Around增强方法中,需要调用processed方法才会执行真正的切点方法,否则则会放弃执行切点方法。
Advice 参数
有时我们需要获取到目标方法的参数信息,而Spring AOP为我们提供了方便的获取方式,接下来我们就来了解下如何通过XML配置来获取目标方法的参数信息。
我们定义如下代码
package com.aop.learn.service;
public interface StudentQueryService {
/**
* 根据姓名和年龄查询学生信息,
* 假设无重复现象
*/
Student queryStdent(String name,int age);
//...
}
package com.aop.learn.service.impl;
public class StudentQueryServiceImpl implements StudentQueryService {
public Student queryStdent(String name, int age) {
// do something
// query database or create a new object
}
}
切面例子(拦截器)
package com.aop.learn.interceptor;
public class InterceptorSample {
public Object interceptorMethod(ProceedingJoinPoint call, String name, int age) throws Throwable {
// do something
Object result = call.proceed();
//do something
}
}
配置信息如下
<!--定义bean信息-->
<bean id="aspectSample",class="com.aop.learn.interceptor.InterceptorSample">
<!--配置切面信息-->
<aop:config>
<aop:aspect ref="aspectSample">
<aop:pointcut id="pointsample"
expression="execution(* com.aop.learn.service.StudentQueryService.queryStdent(..))
and args(name, age)"/>
<aop:around pointcut-ref="pointsample"
method="interceptorMethod"/>
</aop:aspect>
</aop:config>
如上配置信息,在expression语句中增加了args(name,age)
,该表达式定义了变量名为name和age的参数,该参数名必须和增强方法中名字一致,这样我们就能在增强方法中获取目标方法的参数信息了。
五、总结
本文我们主要学习了如何使用XML配置方式来完成Spring AOP的使用。文章中我们学习了很多配置标签,完成了切面定义、切点定义以及多种增强方法的定义。至此我们已经掌握了Spring AOP的基本知识点,可在具体的项目开发中使用。不过学了那么多的知识点,我们还是需要一定的实践来进行消化吸收。接下来我们将构造例子用来巩固学习到的知识点。
网友评论