AspectJ 切面注解中五种通知注解:@Before、@After、@AfterRunning、@AfterThrowing、@Around
2017-12-16 23:52
477 查看
要在 Spring 中声明 AspectJ 切面, 只需要在 IOC 容器中将切面声明为 Bean 实例. 当在 Spring IOC 容器中初始化 AspectJ 切面之后, Spring IOC 容器就会为那些与 AspectJ 切面相匹配的 Bean 创建代理。
在切面类中需要定义切面方法用于响应响应的目标方法,切面方法即为通知方法,通知方法需要用注解标识,AspectJ 支持 5 种类型的通知注解:
@Before: 前置通知, 在方法执行之前执行
@After: 后置通知, 在方法执行之后执行 。
@AfterRunning: 返回通知, 在方法返回结果之后执行
@AfterThrowing: 异常通知, 在方法抛出异常之后
@Around: 环绕通知, 围绕着方法执行
下面分别举例5中通知方法的使用
首先建立一个目标接口ArithmeticCalculator:
然后创建接口的实现类ArithmeticCalculatorIml :
配置文件bean-aop.xml:
创建测试类:
运行结果:
上面的例子把目标类注入到IOC容器中,执行时从容器中获取目标类的bean,然后调用目标方法。
下面要在目标方法的前后等执行其它操作,打印日志,不需要改变任何目标方法,只需要增加切面类,新建切面类LogProxy,把切面类注入到IOC中,然后在切面类中定义要执行的切面方法即可。
在执行下面切面方法之前,需要先启动五种注解,配置文件中定义如下:
示例如下:
执行测试类,输出结果如下:
在目标方法add和div之前分别执行了前置通知方法。
执行测试类,输出结果如下:
发现add和div两个连接点方法执行之后都调用了后置方法。如果目标连接点方法出现异常时,也会执行后置通知方法。把测试方法改成如下:
执行测试方法,输出结果如下:
从输出结果中可以看出,即使目标方法出现异常,后置通知方法依然执行。但后置通知拿不到目标方法执行后的结果,因为目标方法有可能出现异常。如果要拿目标方法的执行结果,要用下面的通知方法。
运行测试方法,输出结果如下:
当连接点方法出现异常时,不执行返回通知方法,把测试方法该为如下:
运行测试方法,输出结果如下:
从输出结果可以看出,div(4,0)出现异常,因此该连接点对应的返回通知方法也不执行。
测试方法为:
执行测试方法,输出结果如下:
从输出结果中可以看出,add方法没有异常,因此不执行异常通知方法,div方法出现异常,执行科异常通知方法。
上面的例子中,异常类型设置的是Exception,表示捕获连接点方法的所有异常信息,也可以指定捕获指定类型的信息,例如
测试方法为:
运行测试方法:
运行测试方法,输出结果:
从输出结果中可以看出,环绕通知实现了上面几种通知的结合。
当div目标方法出现异常时,在环绕通知方法中已经用try…catch方法进行捕捉了,为什么最后输出结果中还出现了一个返回类型不匹配的错误:
那是因为在环绕通知方法中开始就定义了目标方法的返回结果
在输出结果中会抛出一个运行时异常
插曲:不可以在执行目标方法时在定义result变量:
这种方法是行不通的,在
在切面类中需要定义切面方法用于响应响应的目标方法,切面方法即为通知方法,通知方法需要用注解标识,AspectJ 支持 5 种类型的通知注解:
@Before: 前置通知, 在方法执行之前执行
@After: 后置通知, 在方法执行之后执行 。
@AfterRunning: 返回通知, 在方法返回结果之后执行
@AfterThrowing: 异常通知, 在方法抛出异常之后
@Around: 环绕通知, 围绕着方法执行
下面分别举例5中通知方法的使用
首先建立一个目标接口ArithmeticCalculator:
package lzj.com.spring.aop; public interface ArithmeticCalculator { int add(int i, int j); int div(int i, int j); }
然后创建接口的实现类ArithmeticCalculatorIml :
package lzj.com.spring.aop; import org.springframework.stereotype.Component; @Component("arithmeticCalculator") public class ArithmeticCalculatorIml implements ArithmeticCalculator { @Override public int add(int i, int j) { int result = i + j; System.out.println("add->result:" + result); return result; } @Override public int div(int i, int j) { int result = i / j; System.out.println("div->result:" + result); return result; } }
配置文件bean-aop.xml:
<context:component-scan base-package="lzj.com.spring.aop"></context:component-scan>
创建测试类:
package lzj.com.spring.aop; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Main { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("bean-aop.xml"); ArithmeticCalculator arithmetic = (ArithmeticCalculator) ctx.getBean("arithmeticCalculator"); arithmetic.add(3, 2); arithmetic.div(4, 2); } }
运行结果:
add->result:5 div->result:2
上面的例子把目标类注入到IOC容器中,执行时从容器中获取目标类的bean,然后调用目标方法。
下面要在目标方法的前后等执行其它操作,打印日志,不需要改变任何目标方法,只需要增加切面类,新建切面类LogProxy,把切面类注入到IOC中,然后在切面类中定义要执行的切面方法即可。
在执行下面切面方法之前,需要先启动五种注解,配置文件中定义如下:
<context:component-scan base-package="lzj.com.spring.aop"></context:component-scan> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
一、@Before前置通知
用@Before标识的方法为前置方法,在目标方法的执行之前执行,即在连接点之前进行执行。示例如下:
package lzj.com.spring.aop; import java.util.Arrays; import java.util.List; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class LogProxy { @Before("execution(public int lzj.com.spring.aop.ArithmeticCalculator.*(int, int))") public void beforMethod(JoinPoint point){ String methodName = point.getSignature().getName(); List<Object> args = Arrays.asList(point.getArgs()); System.out.println("调用前连接点方法为:" + methodName + ",参数为:" + args); } }
执行测试类,输出结果如下:
调用前连接点方法为:add,参数为:[3, 2] add->result:5 调用前连接点方法为:div,参数为:[4, 2] div->result:2
在目标方法add和div之前分别执行了前置通知方法。
二、@After后置通知方法
后置方法在连接点方法完成之后执行,无论连接点方法执行成功还是出现异常,都将执行后置方法。示例如下:@Aspect @Component public class LogProxy { @After(("execution(public int lzj.com.spring.aop.ArithmeticCalculator.*(int, int))")) public void afterMethod(JoinPoint point){ String methodName = point.getSignature().getName(); List<Object> args = Arrays.asList(point.getArgs()); System.out.println("调用后连接点方法为:" + methodName + ",参数为:" + args); } }
执行测试类,输出结果如下:
add->result:5 调用后连接点方法为:add,参数为:[3, 2] div->result:2 调用后连接点方法为:div,参数为:[4, 2]
发现add和div两个连接点方法执行之后都调用了后置方法。如果目标连接点方法出现异常时,也会执行后置通知方法。把测试方法改成如下:
public class Main { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("bean-aop.xml"); ArithmeticCalculator arithmetic = (ArithmeticCalculator) ctx.getBean("arithmeticCalculator"); arithmetic.add(3, 2); /*被除数为0,会抛出异常*/ arithmetic.div(4, 0); }
执行测试方法,输出结果如下:
add->result:5 调用后连接点方法为:add,参数为:[3, 2] 调用后连接点方法为:div,参数为:[4, 0] Exception in thread "main" java.lang.ArithmeticException: / by zero at lzj.com.spring.aop.ArithmeticCalculatorIml.div(ArithmeticCalculatorIml.java:17) ……
从输出结果中可以看出,即使目标方法出现异常,后置通知方法依然执行。但后置通知拿不到目标方法执行后的结果,因为目标方法有可能出现异常。如果要拿目标方法的执行结果,要用下面的通知方法。
三、@AfterRunning返回通知方法
当连接点方法成功执行后,返回通知方法才会执行,如果连接点方法出现异常,则返回通知方法不执行。返回通知方法在目标方法执行成功后才会执行,所以,返回通知方法可以拿到目标方法(连接点方法)执行后的结果。切面类中定义返回通知方法,示例如下:@Aspect @Component public class LogProxy { /*通过returning属性指定连接点方法返回的结果放置在result变量中,在返回通知方法中可以从result变量中获取连接点方法的返回结果了。*/ @AfterReturning(value="execution(public int lzj.com.spring.aop.ArithmeticCalculator.*(int, int))", returning="result") public void afterReturning(JoinPoint point, Object result){ String methodName = point.getSignature().getName(); List<Object> args = Arrays.asList(point.getArgs()); System.out.println("连接点方法为:" + methodName + ",参数为:" + args + ",目标方法执行结果为:" + result); } }
运行测试方法,输出结果如下:
add->result:5 连接点方法为:add,参数为:[3, 2],目标方法执行结果为:5 div->result:2 连接点方法为:div,参数为:[4, 2],目标方法执行结果为:2
当连接点方法出现异常时,不执行返回通知方法,把测试方法该为如下:
public class Main { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("bean-aop.xml"); ArithmeticCalculator arithmetic = (ArithmeticCalculator) ctx.getBean("arithmeticCalculator"); arithmetic.add(3, 2); arithmetic.div(4, 0); } }
运行测试方法,输出结果如下:
add->result:5 连接点方法为:add,参数为:[3, 2],目标方法执行结果为:5 Exception in thread "main" java.lang.ArithmeticException: / by zero ……
从输出结果可以看出,div(4,0)出现异常,因此该连接点对应的返回通知方法也不执行。
四、@AfterThrowing异常通知
异常通知方法只在连接点方法出现异常后才会执行,否则不执行。在异常通知方法中可以获取连接点方法出现的异常。在切面类中异常通知方法,示例如下:/*通过throwing属性指定连接点方法出现异常信息存储在ex变量中,在异常通知方法中就可以从ex变量中获取异常信息了*/ @AfterThrowing(value="execution(public int lzj.com.spring.aop.ArithmeticCalculator.*(int, int))", throwing="ex") public void afterReturning(JoinPoint point, Exception ex){ String methodName = point.getSignature().getName(); List<Object> args = Arrays.asList(point.getArgs()); System.out.println("连接点方法为:" + methodName + ",参数为:" + args + ",异常为:" + ex); }
测试方法为:
public class Main { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("bean-aop.xml"); ArithmeticCalculator arithmetic = (ArithmeticCalculator) ctx.getBean("arithmeticCalculator"); arithmetic.add(3, 2); arithmetic.div(4, 0); } }
执行测试方法,输出结果如下:
add->result:5 连接点方法为:div,参数为:[4, 0],异常为:java.lang.ArithmeticException: / by zero Exception in thread "main" java.lang.ArithmeticException: / by zero
从输出结果中可以看出,add方法没有异常,因此不执行异常通知方法,div方法出现异常,执行科异常通知方法。
上面的例子中,异常类型设置的是Exception,表示捕获连接点方法的所有异常信息,也可以指定捕获指定类型的信息,例如
@AfterThrowing(value="execution(public int lzj.com.spring.aop.ArithmeticCalculator.*(int, int))", throwing="ex") /*只捕获连接点方法中的NullPointerException 类型的异常信息*/ public void afterReturning(JoinPoint point, NullPointerException ex){ String methodName = point.getSignature().getName(); List<Object> args = Arrays.asList(point.getArgs()); System.out.println("连接点方法为:" + methodName + ",参数为:" + args + ",异常为:" + ex); }
五、@Around环绕通知
环绕通知方法可以包含上面四种通知方法,环绕通知的功能最全面。环绕通知需要携带 ProceedingJoinPoint 类型的参数,且环绕通知必须有返回值, 返回值即为目标方法的返回值。在切面类中创建环绕通知方法,示例如下:@Around("execution(public int lzj.com.spring.aop.ArithmeticCalculator.*(int, int))") public Object aroundMethod(ProceedingJoinPoint pdj){ /*result为连接点的放回结果*/ Object result = null; String methodName = pdj.getSignature().getName(); /*前置通知方法*/ System.out.println("前置通知方法>目标方法名:" + methodName + ",参数为:" + Arrays.asList(pdj.getArgs())); /*执行目标方法*/ try { result = pdj.proceed(); /*返回通知方法*/ System.out.println("返回通知方法>目标方法名" + methodName + ",返回结果为:" + result); } catch (Throwable e) { /*异常通知方法*/ System.out.println("异常通知方法>目标方法名" + methodName + ",异常为:" + e); } /*后置通知*/ System.out.println("后置通知方法>目标方法名" + methodName); return result; } }
测试方法为:
public class Main { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("bean-aop.xml"); ArithmeticCalculator arithmetic = (ArithmeticCalculator) ctx.getBean("arithmeticCalculator"); arithmetic.add(3, 2); arithmetic.div(4, 0); } }
运行测试方法:
public class Main { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("bean-aop.xml"); ArithmeticCalculator arithmetic = (ArithmeticCalculator) ctx.getBean("arithmeticCalculator"); arithmetic.add(3, 2); arithmetic.div(4, 0); } }
运行测试方法,输出结果:
前置通知方法>目标方法名:add,参数为:[3, 2] add->result:5 返回通知方法>目标方法名add,返回结果为:5 后置通知方法>目标方法名add 前置通知方法>目标方法名:div,参数为:[4, 0] 异常通知方法>目标方法名div,异常为:java.lang.ArithmeticException: / by zero 后置通知方法>目标方法名div Exception in thread "main" org.springframework.aop.AopInvocationException: Null return value from advice does not match primitive return type for: public abstract int lzj.com.spring.aop.ArithmeticCalculator.div(int,int) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:219) at com.sun.proxy.$Proxy7.div(Unknown Source) at lzj.com.spring.aop.Main.main(Main.java:12)
从输出结果中可以看出,环绕通知实现了上面几种通知的结合。
当div目标方法出现异常时,在环绕通知方法中已经用try…catch方法进行捕捉了,为什么最后输出结果中还出现了一个返回类型不匹配的错误:
Exception in thread "main" org.springframework.aop.AopInvocationException: Null return value from advice does not match primitive return type for: public abstract int lzj.com.spring.aop.ArithmeticCalculator.div(int,int) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:219) at com.sun.proxy.$Proxy7.div(Unknown Source) at lzj.com.spring.aop.Main.main(Main.java:12)
那是因为在环绕通知方法中开始就定义了目标方法的返回结果
Object result = null。当目标方法出现异常时,
result = pdj.proceed();执行时出现异常,此时result中还是null,所以在环绕通知方法最后
return result;时,返回的result就是null,但是环绕通知的返回类型我们定义的是Object类型的,null不能转化为Object类型,所以抛出了个类型转换的错误。我们可以在环绕通知方法中把异常抛出去,即为:
@Around("execution(public int lzj.com.spring.aop.ArithmeticCalculator.*(int, int))") public Object aroundMethod(ProceedingJoinPoint pdj){ /*result为连接点的放回结果*/ Object result = null; String methodName = pdj.getSignature().getName(); /*前置通知方法*/ System.out.println("前置通知方法>目标方法名:" + methodName + ",参数为:" + Arrays.asList(pdj.getArgs())); /*执行目标方法*/ try { result = pdj.proceed(); /*返回通知方法*/ System.out.println("返回通知方法>目标方法名" + methodName + ",返回结果为:" + result); } catch (Throwable e) { /*异常通知方法*/ System.out.println("异常通知方法>目标方法名" + methodName + ",异常为:" + e); /*当环绕通知方法本身还有其它异常时,非连接点方法出现的异常,此时抛出来*/ throw new RuntimeException(); } /*后置通知*/ System.out.println("后置通知方法>目标方法名" + methodName); return result; } }
在输出结果中会抛出一个运行时异常
java.lang.RuntimeException
插曲:不可以在执行目标方法时在定义result变量:
…… /*执行目标方法*/ try { Object result = pdj.proceed(); …… } catch (Throwable e) { …… } …… return result;
这种方法是行不通的,在
Object result = pdj.proceed();中,如果
pdj.proceed()执行失败,就会被try …catch捕获到异常,而不会就不会执行定义result变量那一步了,即
Object result不会执行,所以在
return result;就会出现错误。
相关文章推荐
- AspectJ 切面注解中五种通知注解:@Before、@After、@AfterRunning、@AfterThrowing、@Around
- Spring(12):使用注解(@AfterThrowing/@After/@Around)实现AOP异常增强与实例
- Spring AOP 中 advice 的四种类型 before after throwing advice around
- Spring @Around通知与@AfterThrowing共用问题
- Spring AOP 全部通知, 四种通知方式,Around, Before, After, Throws
- Spring:Aop before after afterReturn afterThrowing around 的原理
- Spring框架学习-深入理解AOP02----AOP简介,AspectJ,AOP基于注解和XML配置(5种通知,切面优先级)
- Aspectj快速上手代码示例之Before,After,Around
- spring aop的@Before,@Around,@After,@AfterReturn,@AfterThrowing执行顺序
- Spring 4.0 学习日记(8) ---AOP切面注解实现五种通知
- Spring4深入理解AOP02----AOP简介,AspectJ,AOP基于注解和XML配置(5种通知,切面优先级)
- spring AOP 中 advice 的四种类型 before after throwing advice around
- Spring AOP的bug - @Around和@AfterThrowing不兼容
- Struts2注解拦截器(Before、After、BeforeResult)
- AspectJ 基于注解的实现前置通知
- spring AOP @Around @Before @After 区别
- junit和testng对BeforeClass和AfterClass注解的不同要求
- spring AOP @Around @Before @After 区别
- 【Spring实战】—— 13 AspectJ注解切面
- spring AOP @Around @Before @After 区别