当相同的切面里的两个增强处理需要在相同的连接点被织入时,Spring AOP将以随机的方式来织入这两个增强处理,没有办法指定他们的顺序,如果一定要它们以特定的顺序被织入,则可以考虑把它们压缩到一个增强处理中,或者是把它们分别放在不同的切面,在通过切面的优先等级来排序
如果只要访问目标方法的参数,Spring还提供了一个更加简单的方式:我们可以在程序中使用args来绑定目标方法的参数,如果args表达是中指定一个或多个参数,则该切入点只匹配具有对应形参的方法,且目标方法的参数值将被出入增强处理方法。
如:
@Aspect
publicclass AccessArgsAspect {
@AfterReturning(pointcut ="execution(* cn.hb.spring.service1.*.*(..))"
+"&&args(food,time)", returning ="retValue")
publicvoid access(String food, Date time, Object retValue) {
System.out.println("目标方法中的String参数" + food);
System.out.println("目标方法的time参数" + time);
System.out.println("模拟日志记录功能......");
System.out.println("目标参数的返回值:"+retValue);
}
}
@Component
publicclass Chinese3implements Person2 {
@Override
public String sayHello(String word) {
return word;
}
publicvoid eat(String food, Date time) {
System.out.println("我正在吃:" + food+",现在的时间是:" + time);
}
}
为什么目标方法的返回值是null,因为该切入点只匹配 public void eat(String food, Date time)方法。
从上面可以得出,使用args表达式有如下两个作用:
--提供更简单的方式访问目标方法的参数;
--可用于对切入表达式增加额外限制。
除此之外,使用args表达式时还可以使用如下形式:args(name,age,..),这表明增强处理方法中可通过name,age来访问目标方法的参数,上面表达式括号中的2点,它表示可匹配更多的参数,如:
public void doSomething(String name,int age)
这意味着只要目标方法第一个参数是String类型,第二个参数是int则该方法就可以匹配该切入点。
定义切入点:
正如前面的FourAdviceTest程序中看到的,这个切面类定义了4个增强处理,这4个增强处理分别指定了相同的切入点表达式,这种做法显然不符合软件设计的原则:我们将那个切入点表达式重复了4次,如果需要该这个切入点,那么就要修改4处。
Spring AOP只支持以Spring Bean的方法执行组作为连接点.
例如:
@Pointcut("execution(* transfer(..))")
private void anyOldTransfer(){}
一旦采用上面的代码片段定义了名为anyOldTrandser的切入点之后,程序就可以重复使用该切入点了,甚至可以在其他切面类、其他包的切面类里使用该切入点,不过这取决于该方法签名前的访问修饰符。
切入点指示符:
正如前面的execution就是一个切入点指示符,Spring AOP仅仅支持部分AspectJ切入点指示符,不仅如此Spring AOP只支持使用方法调用作为连接点,所以Spring AOP的切入点指示符仅匹配方法执行的连接点。
Spring AOP一共支持如下几种切入点指示符:
execution:用于匹配执行方法的连接点,是Spring AOP最主要的切入点指示符,execution表达式的格式如下:
execution(modifies-pattern? ret-type-pattern declaring-type-parttern? name--pattern(parm-pattern) throws-pattern?)
以上打了问号的都可以省略。
上面格式中的execution是不变的,用于作为execution表达式的开头,整个表示式各个部分的解释为:
modifies-pattern:指定方法的修饰符,支持通配符,该部分可以省略。
ret-type-pattern:指定方法的返回值类型,支持通配符,可以使用“*”通配符来匹配所有返回值类型。
declaring-type-parttern:指定方法所属的类,支持通配符,该部分可以省略。
name--pattern:指定匹配指定方法名,支持通配符,可以使用“*”通配符来匹配所有方法。
parm-pattern:指定方法声明中的形参列表,支持两个通配符:“”、“..”,其中表示一个任意类型的参数,而“..”表示零个或多个任意类型的参数。
throws-pattern:指定方法声明抛出的异常,支持通配符,该部分可以省略。
例如下面几个execution表达式:
//匹配任意public方法的执行。
execution(public * * (..))
//匹配任意方法名以set开始的方法。
execution(* set* (..))
//匹配AccountService里定义的任意方法的执行。
execution(* org.hb.AccountService.* (..))
//匹配Service包中任意类的任意方法的执行。
execution(* org.hb.service.*.*(..))
within:限定匹配特定类型的连接点,当使用Spring AOP的时候,只能匹配方法执行的连接点,例如下面几个within表达式:
//在Service包中的任意连接点。
within(* org,hb.service.*)
//在Service包或者子包的任意连接点
within(* org.hb.service..*)
this:用于限定AOP代理必须指定类型的实例,用于匹配该对象的所有连接点。当使用Spring AOP的时候,只能匹配方法执行的连接点
例如:
//匹配实现了AccountService接口的代理对象的所有连接点
this(org.hb.service.AccountService)
target:用于限定目标对象必须是指定类型的实例,用于匹配该对象的所有连接点,当使用Spring AOP只匹配执行方法的连接点。例如:
//匹配实现了AccountService接口的目标对象的所有连接点
target(org.hb.service.AccountService)
args:用于对连接点的参数类型进行限制,要求参数类型是指定类型的实例,例如:
//匹配只接受一个参数,且传入的参数类型是Serializable的所有连接点
args(java.io.Serializable)
注意:该例中给出的切入点表达式与execution(* *(java.io.Serializable))不同:args版本只匹配动态运行时传入参数值是Serializable类型的情况,而execution版本只匹配方法签名只包含一个Serializable类型的参数的方法。
bean:用于指定只匹配指定Bean实例内连接点,实际上只能使用方法执行作为连接点,定义bean表达式需要传入id或name,表示只匹配Bean实例内连接点,支持“*”通配符。
例如:
/匹配tradeService Bean实例内方法执行的连接点
bean(tradeService)
//匹配名字以Service结尾的Bean实例内方法执行的连接点。
bean(*Service)
组合切入点表达式:
Spring支持3中逻辑运算符来组合切入点表达式:
&&:要求连接点同时匹配两个切入点表达式;
||:只要连接点匹配任意一个切入点表达式;
!:要求连接点不匹配指定切入点表达式。
⑤AOP 基于配置Xml文件的管理方式
除了前面介绍的基于JDK1.5的Annotation方式来定义切面、切入点和增强处理,Spring AOP也允许使用XML文件来定义管理它们。
实际上,使用XML定义AOP也是@AspectJ一样的同样需要指定相关数据:配置切面、切入点、增强处理所需要的信息完全一样,只是提供这些信息的位置不一样而已。使用XMLA文件配置AOPd的方式有很多优点但是也有一些缺点:
xml配置方式比@AspectJ方式有更多的限制:仅支持“singleton”切面的Bean,不能在xml中组合多个命名连接点的声明。
在Spring的配置文件中,所有的切面、切入点和增强处理都必须定义在<aop:config../>元素内部。<beans../>元素下可以包含多个<aop:config../>元素。
一个<aop:config../>可以包含多个pointcut、advisor和aspect元素,且这3个元素必须按照此顺序类定义。
注意:当我们使用<aop:config../>方式进行配置时,可能与Spring的自动代理方式冲突,例如我们使用BeanNameAutoProxyCreator或类似的方式显示启用了自动代理,则它可能导致问题(例如有些增请处理没有被织入)因此要么全部使用自动代理的方式,要么全部使用<aop:config../>配置方式。不要不两者混合使用。
配置切面:
定义切面使用<aop:aspect../>元素,使用该元素来定义切面时,其实质是将一个已有的Spring Bean转换成切面Bean。
因为切面Bean可以当成一个普通的SpringBean来配置,所以可以为该切面Bean配置依赖注入。
配置<aop:aspect../>元素时可以指定如下3个属性:
id:定义该切面的标识名;
ref:指定以指定ref所引用的的普通Bean作为切面Bean。
order:指定该切面Bean的优先等级,数字越小,等级越大。
配置增强处理:
<aop:before.../>:Before增强处理
<aop:after../>:After增强处理
<aop:after-returning.../>:afterReturning增强处理
<aop:after-throwing../>:afterThrowing增强处理
<aop:around.../>:Around增强处理
上面的元素不能配置子元素,但可以配置如下属性:
pointcut:该属性指定一个切入点表达式,Spring将在匹配该表达式的连接点时织入增强处理。
pointcut-ref:该属性指定一个已经存在的切入点的 名称,通常pointcut与pointcut-ref只需要使用其中的一个。
method:该属性指定一个方法名,指定切面Bean的该方法将作为增强处理。
throwing:该属性只对<aop:after-throwing../>起作用,用于指定一个形参名,afterThrowing增强处理方法可以通过该形参访问目标方法所抛出的异常。
returning:该属性只对<aop:after-returning.../>起作用,用于指定一个形参名,afterReturning增强处理方法可以通过该形参访问目标方法的返回值。
当定义切入点表达式时,XML文件配置方式和@AspectJ Annotation方式支持完全相同的切入点指示符,一样支持execution、within、args、this、target和bean等切入点指示符。
XML配置文件方式和@AspectJ Annotation方式一样支持组合切入点表达式,但XML配置方式不再使用简单的&&、||、!作为组合运算符,而是使用and(相当于&&)、or(||)和not(!)。
如:
Spring配置文件:
<aop:config>
<aop:aspect id="fourAdviceAspect"ref="fourAdviceBean"
order="2">
<aop:after pointcut="execution(* cn.hb.spring.lee.*.*(..))"
method="release"/>
<aop:before pointcut="execution(* cn.hb.spring.lee.*.*(..))"
method="authority"/>
<aop:after-returning pointcut="execution(* cn.hb.spring.lee.*.*(..))"
method="log"returning="obj"/>
<aop:around pointcut="execution(* cn.hb.spring.lee.*.*(..))"
method="proceedTX"/>
</aop:aspect>
</aop:config>
<aop:config>
<aop:aspectid="secondAspect"ref="secondAdviceBean"order="1">
<aop:before pointcut="execution(* cn.hb.spring.lee.*.*(..)) and args(aa)"
method="authority"/>
</aop:aspect>
</aop:config>
<!-- 定义一个普通组件Bean -->
<bean id="chinese"class="cn.hb.spring.lee.Chinese"/>
<bean id="fourAdviceBean"class="cn.hb.spring.lee.FourAdviceTest"/>
<bean id="secondAdviceBean"class="cn.hb.spring.lee.SecondAdvice"/>
//业务类:
public class Chinese implements Person {
public String sayHello(String word) {
System.out.println("sayHello方法开始执行...");
return word;
}
public void eat(String food) {
System.out.println("我正在吃:" + food);
}
public void divide() {
int a = 5 / 0;
System.out.println("divide执行完毕/" + a);
}
}
//切面Bean:
public class FourAdviceTest {
public Object proceedTX(ProceedingJoinPoint pre)throws Throwable {
System.out.println("Around增强处理:执行目标方法前,执行模拟 开启事务.......");
Object[] objs = pre.getArgs();
if (objs !=null && objs.length > 0
&& objs[0].getClass() == String.class) {
objs[0] ="被修改的参数";
}
Object obj = pre.proceed(objs);
System.out.println("Around增强处理:执行目标方法之后,模拟结 束事务.....");
return obj +",新增加的内容";
}
public void authority(JoinPoint jp) {
System.out.println("Before增强:模拟权限检查");
System.out.println("Before增强:被织入增强处理的目标方法:"
+ jp.getSignature().getName());
System.out.println("Before增强:目标方法的参数为:" + Arrays.toString(jp.getArgs()));
System.out.println("Before增强:被注入增强的处理的目标对象:" + jp.getTarget());
}
public void log(JoinPoint jp, Object obj) {
System.out.println("AfterReturning增强:获取目标方法的返回值:" + obj);
System.out.println("AfterReturning增强:模拟日志记录功能.....");
System.out.println("AfterReturning增强:被织入增强处理的目标方法:"
+ jp.getSignature().getName());
System.out.println("AfterReturning增强:目标方法的参数为:"
+ Arrays.toString(jp.getArgs()));
System.out.println("AfterReturning增强:被注入增强的处理的目标对象:" + jp.getTarget());
}
public void release(JoinPoint jp) {
System.out.println("After增强:模拟方法结束后,资源释放.....");
System.out.println("After增强:被织入增强处理的目标方法:"
+ jp.getSignature().getName());
System.out.println("After增强:目标方法的参数为:" + Arrays.toString(jp.getArgs()));
System.out.println("After增强:被注入增强的处理的目标对象:" + jp.getTarget());
}
}
配置切入点:
类似于@AspectJ方式,允许定义切入点来重用切入点表达式,XML配置方式也可以通过定义切入点来重用切入点表达式。
配置<aop:pointcut../>元素时通常需要指定如下两个属性:
id:指定该切入点的标识名;
expression:指定该切入点关联的切入点表达式。
如下面代码:
<aop:pointcut id="myPointcut" expression="execution(* lee.*.*(..))"/>
除此之外,如果程序已经使用Annotation定义了切入点,在<aop:pointcut ../>元素中指定切入点表达式时还有另一种用法:
<aop:pointcut expression="org.huaxia.SystemArchitecture.myPointcut()">