Advice应用
配置完了切入点,接下来要配置advice,也就是通知的配置。
Before advice
Before advice是前置通知
前置通知的方式是aop:before,然后有一个pointcut-ref是引用某一个切入点,还有一个method,就是前置通知会调用哪一个方法去执行。这个方法是ref对应bean中的方法。
<aop:aspect id="beforeExample" ref="aBean">
<aop:before pointcut-ref="dataAccessOperation" method="doAccessCheck"/>
...
</aop:aspect>
也可以在aop:before里边直接指定pointcut,method不变。
<aop:aspect id="beforeExample" ref="aBean">
<aop:before pointcut="execution(*com.xyz.muapp.dap..(..))"
method="doAccessCheck"/>
...
</aop:aspect>
在之前的例子里看一下怎么声明:
<bean id="moocAspect" class="com.imooc.aop.schema.advice.MoocAspect"></bean>
<bean id="aspectBiz" class="com.imooc.aop.schema.advice.biz.AspectBiz"></bean>
<aop:config>
<aop:aspect id="moocAspectAOP" ref="moocAspect">
<aop:pointcut id="moocPiontcut"
expression="execution(* com.imooc.aop.schema.advice.biz.*Biz.*(..))"/>
<aop:before method="before" pointcut-ref="moocPiontcut"/>
</aop:aspect>
</aop:config>
before的method声明成before,pointcut-ref来引用刚刚才声明好的moocPiontcut,这就是前置通知。
然后在类moocAspect里边定义一个before方法。
public class MoocAspect{
public void before(){
System.out.println("MoocAspect Before.");
}
}
之后在AspectBiz业务类里边定义一个方法
public class AspectBiz {
public void biz() {
System.out.println("AspectBiz biz.");
}
}
然后看一下单元测试类的写法
@Test
public void testBiz() {
AspectBiz biz = super.getBean("aspectBiz");
biz.biz();
}
执行单元测试方法,看到输出
MoocAspect Before.
AspectBiz biz.
MoocAspect被我们声明成了切面类,里边有一个method叫before。
aop:pointcut中的expression就是一个切入点的表达式,execution执行方法在* com.imooc.aop.schema.advice.biz这个包下所有以Biz结尾的类的所有方法,这里是AspectBiz类里边有个biz方法。这个切入点切到了这个表达上,它的id是moocPointcut。
aop:before中的before方法什么时候会被触发?就是当对应包下边所有以Biz结尾的类的方法在执行之前都会执行这个方法,也就是MoocAspect的before方法。因为这个pointcut-ref引用的就是这样的一个pointcut。
After returning advice
返回之后的通知
它是用的aop:after-returning,里边的pointcut-ref也是引用某一个切入点,method是在这个切入点上要执行的方法是什么样的。
<aop:aspect id="afterReturningExample" ref="aBean">
<aop:after-returning pointcut-ref="dataAccessOperation" method="doAccessCheck"/>
...
</aop:aspect>
下边是另外一种配置方式,除了pointcut-ref和method之外,限制了返回值。
<aop:aspect id="afterReturningExample" ref="aBean">
<aop:after-returning pointcut-ref="dataAccessOperation"
returning="retVal"
method="doAccessCheck"/>
...
</aop:aspect>
继续在那里例子里写代码来试一试。
<bean id="moocAspect" class="com.imooc.aop.schema.advice.MoocAspect"></bean>
<bean id="aspectBiz" class="com.imooc.aop.schema.advice.biz.AspectBiz"></bean>
<aop:config>
<aop:aspect id="moocAspectAOP" ref="moocAspect">
<aop:pointcut id="moocPiontcut"
expression="execution(* com.imooc.aop.schema.advice.biz.*Biz.*(..))"/>
<aop:before method="before" pointcut-ref="moocPiontcut"/>
<aop:after-returning method="afterReturning" pointcut-ref="moocPiontcut"/>
</aop:aspect>
</aop:config>
同样要找到切面类,在里边写一个afterReturning方法
public class MoocAspect{
public void before(){
System.out.println("MoocAspect Before.");
}
public void afterReturning(){
System.out.println("MoocAspect afterReturning.");
}
}
这样在执行单元测试类时候的输出为
MoocAspect Before.
AspectBiz biz.
MoocAspect afterReturning.
After throwing advice
和after returning类似,不过是在抛出异常之后返回。
<aop:aspect id="afterThrowingExample" ref="aBean">
<aop:after-throwing pointcut-ref="dataAccessOperation" method="doAccessCheck"/>
...
</aop:aspect>
这里可以使用throwing属性俩指定可被传递的异常的参数名称,和returning是一个效果。
如果在刚才的例子里继续使用,只需要在xml文件中加上这句
<aop:after-throwing method="afterThrowing" pointcut-ref="moocPointcut"/>
同时在切面类中声明一个方法
public void afterReturning(){
System.out.println("MoocAspect afterThrowing.");
}
这时候直接执行单元测试类并没有效果,因为这个方法在异常抛出后被调用,所以可以在AspectBiz类中的biz方法里抛出一个异常。
public class AspectBiz {
public void biz() {
System.out.println("AspectBiz biz.");
throw new RuntimeException();
}
}
这时候执行的话就会发现最后的afterReturning没有了,取而代之的是afterThrowing,因为抛出了异常,无法正常返回。
After(finally) advice
返回的通知
<aop:aspect id="afterFinallyExample" ref="aBean">
<aop:after pointcut-ref="dataAccessOperation" method="doReleaseLock"/>
...
</aop:aspect>
在前边的例子了进行补充
<aop:after method="after" pointcut-ref="moocPointcut"/>
在MoocAspect里要增加一个after方法
public void after(){
System.out.println("MoocAspect after.");
}
输出的顺序为
MoocAspect before.
AspectBiz biz.
MoocAspect afterReturning.
MoocAspect after.
也就是说是最后执行的。
即使是抛出异常了,也会执行after这个方法,输出为
MoocAspect before.
AspectBiz biz.
MoocAspect afterThrowing.
MoocAspect after.
Around advice
循环通知,环绕通知
这个通知的方法的第一个参数必须是ProceedingJoinPoint类型
<aop:aspect id="aroundExample" ref="aBean">
<aop:around pointcut-ref="businessService" method="doBasicProfiling"/>
...
</aop:aspect>
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable{
Object retVal=pjp.proceed();
return retVal;
}
这个方法doBasicProfiling必须有一个参数,第一个参数必须是ProceedingJoinPoint 这种类型,这个类型的参数有一个proceed方法,也就是执行当前业务的方法。在具体业务执行之前或者之后都可以进行我们想做的事情。
继续刚才的例子
<aop:around method="around" pointcut-ref="moocPiontcut"/>
切面类中添加方法,注意是有返回值的。
public Object around(ProceedingJoinPoint pjp){
Object obj=null;
try{
System.out.println("MoocAspect around 1.");
obj=pjp.proceed();
System.out.println("MoocAspect around 2.");
}catch(Throwable e){
e.printStackTrace();
}
return obj;
}
这里考虑这个异常是否要捕捉到并处理,如果不需要可以写为public Object around(ProceedingJoinPoint pjp) throw Throwable{…}
运行单元测试类,结果为
MoocAspect before.
MoocAspect around 1.
AspectBiz biz.
MoocAspect around 2.
MoocAspect afterReturning.
MoocAspect after.
Advice parameters
直接看例子
在业务方法里声明另一个方法init,里边有参数。
public class AspectBiz {
public void biz() {
System.out.println("AspectBiz biz.");
// throw new RuntimeException();
}
public void init(String bizName, int times) {
System.out.println("AspectBiz init : " + bizName + " " + times);
}
}
回到配置文件里
仍然使用around,pointcut写得具体一些,AspectBiz里的init方法,参数分别是String,int,后边要加上参数名称。
<aop:around method="aroundInit"
pointcut="execution(* com.imooc.aop.schema.advice.biz.AspectBiz.init(String, int)) and args(bizName, times)"/>
然后就是实现类的aroundInit方法,和之前around方法不同的是,要加上两个参数
public Object aroundInit(ProceedingJoinPoint pjp, String bizName, int times) {
System.out.println(bizName + " " + times);
Object obj = null;
try {
System.out.println("MoocAspect aroundInit 1.");
obj = pjp.proceed();
System.out.println("MoocAspect aroundInit 2.");
} catch (Throwable e) {
e.printStackTrace();
}
return obj;
}
再写一个单元测试方法
@Test
public void testInit() {
AspectBiz biz = super.getBean("aspectBiz");
biz.init("moocService", 3);
}
执行这个单元测试方法,输出
为了清晰,可以把xml文件里的其他东西注释掉。