Spring(十八):Spring AOP(二):通知(前置、后置、返回、异常、环绕)
AspectJ支持5种类型的通知注解:
- @Before:前置通知,在方法执行之前执行;
- @After:后置通知,在方法执行之后执行;
- @AfterRunning:返回通知,在方法返回结果之后执行(因此该通知方法在方法抛出异常时,不能执行);
- @AfterThrowing:异常通知,在方法抛出异常之后执行;
- @Around:环绕通知,围绕着方法执行。
示例项目新建:
第一步:新建spring aop项目
第二步:添加spring-aop.xml Spring配置文件:
<?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" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> <!-- 配置自动扫描的包 --> <context:component-scan base-package="com.dx.spring.beans.aop"></context:component-scan> <!-- 配置是AspectJ注解起作用 :自动为匹配的类生成代理对象 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
第三步:添加接口类IArithmeticCalculator.java,ArithmeticCalculatorImpl.java Spring组件
package com.dx.spring.beans.aop; /** * Description:Addition, subtraction, multiplication, and division */ public interface IArithmeticCalculator { int add(int i, int j); int sub(int i, int j); int multi(int i, int j); int div(int i, int j); }
package com.dx.spring.beans.aop; import org.springframework.stereotype.Component; @Component("arithmeticCalculator") public class ArithmeticCalculatorImpl implements IArithmeticCalculator { @Override public int add(int i, int j) { int result = i + j; return result; } @Override public int sub(int i, int j) { int result = i - j; return result; } @Override public int multi(int i, int j) { int result = i * j; return result; } @Override public int div(int i, int j) { int result = i / j; return result; } }
第四步:添加LoggingAspect.java切面
package com.dx.spring.beans.aop; import java.util.List; import java.util.Arrays; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; //把这个类声明为一个切面:需要把该类放入到IOC容器中、再声明为一个切面。 @Aspect @Component public class LoggingAspect { // 在这里注册通知方法。 }
第五步:添加测试类Client.java
package com.dx.spring.beans.aop; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Client { public static void main(String[] args) { // 1:创建Spring的IOC容器; ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-aop.xml"); // 2.从IOC容器中获取Bean的实例 IArithmeticCalculator arithmeticCalculator = (IArithmeticCalculator) ctx.getBean("arithmeticCalculator"); // 3.使用Bean } }
@Before:前置通知
在切面类LoggingAspect中添加前置通知:
// 声明该方法为一个前置通知:在目标方法开始之前执行 @Before("execution(public int com.dx.spring.beans.aop.IArithmeticCalculator.*(int, int))") public void beforeMethod(JoinPoint joinpoint) { String methodName = joinpoint.getSignature().getName(); List<Object> args = Arrays.asList(joinpoint.getArgs()); System.out.println("before method " + methodName + " with " + args); }
Client.java添加测试代码&执行测试:
int result = arithmeticCalculator.add(1, 3); System.out.println(result); result = arithmeticCalculator.div(4, 1); System.out.println(result);
before method add with [1, 3]
before method div with [4, 0]
@After:后置通知
在切面类LoggingAspect中添加后置通知方法:
// 声明该方法为一个后置通知:在目标方法结束之后执行(无论方法是否抛出异常)。 // 但是因为当方法抛出异常时,不能返回值为null,因此这里无法获取到异常值。 @After(value = "execution(public int com.dx.spring.beans.aop.IArithmeticCalculator.*(int, int))") public void afterMethod(JoinPoint joinpoint) { String methodName = joinpoint.getSignature().getName(); List<Object> args = Arrays.asList(joinpoint.getArgs()); System.out.println("after method " + methodName); }
Client.java添加测试代码&执行测试:
int result = arithmeticCalculator.add(1, 3); System.out.println(result); result = arithmeticCalculator.div(4, 0); System.out.println(result);
执行结果:
备注:从执行结果中我们可以看出,即使方法抛出异常,后置通知方法也会执行。
@AfterRunning:返回通知
修改切面类LoggingAspect,添加返回通知方法:
/** * 声明该方法为一个返回通知:在目标方法返回结果时后执行(当方法抛出异常时,无法执行)。 * 但是因为当方法抛出异常时,类似代码 * {@code * try <br/> * { <br/> * // before 前置通知 <br/> * // do action method <br/> * // after returning 返回通知,可以访问到方法的返回值 <br/> * } * catch(Exception e){ * e.printStackTrace(); * // after throwing 异常通知,可以访问到方法出现的异常 * } * // after 后置通知,因为方法可以抛出异常,所以访问不到方法的返回值 * } * */ @AfterReturning(value = "execution(public int com.dx.spring.beans.aop.IArithmeticCalculator.*(int, int))", returning = "result") public void afterReturningMethod(JoinPoint joinpoint, Object result) { String methodName = joinpoint.getSignature().getName(); List<Object> args = Arrays.asList(joinpoint.getArgs()); System.out.println( "after method " + methodName + " with returning " + (result == null ? "NULL" : result.toString())); }
注意:返回通知注解中,参数是多一个了returning参数,该参数定义通知方法接收返回值的别名。
在测试类Client.java中追加测试代码:
int result = arithmeticCalculator.add(1, 3); System.out.println(result); result = arithmeticCalculator.div(4, 0); System.out.println(result);
@AfterThrowing:异常通知
修改切面类LoggingAspect,添加异常通知方法:
/** * 定义一个异常通知函数: * 只有在方法抛出异常时,该方法才会执行,而且可以接受异常对象。 * */ @AfterThrowing(value = "execution(public int com.dx.spring.beans.aop.IArithmeticCalculator.*(int, int))", throwing = "ex") public void afterThrowingMethod(JoinPoint joinpoint, Exception ex) { String methodName = joinpoint.getSignature().getName(); List<Object> args = Arrays.asList(joinpoint.getArgs()); System.out.println( "after method " + methodName + " occurs exception: " + (ex == null ? "NULL" : ex.getMessage())); }
备注:只有在方法抛出异常时,异常通知方法才会执行,而且可以接受异常对象。
在测试类Client.java中追加测试代码:
int result = arithmeticCalculator.add(1, 3); System.out.println(result); result = arithmeticCalculator.div(4, 0); System.out.println(result);
打印信息
@Around:环绕通知
环绕通知其他方式不同,它相当于代理对象使用时效果一样。可以在方法前、后、返回、异常打印信息。
我们先看代理方法打印信息时如何处理,之后再看环绕通知如何处理。
代理方法实现通知:
package com.dx.spring.beans.aop; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class ArithmeticCalculatorProxy implements InvocationHandler { private Object obj; public ArithmeticCalculatorProxy(Object obj) { this.obj = obj; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object object = null; try { // *** 前置通知,在方法执行之前执行 System.out.println("before " + method); // 真实的调用方法操作 object = method.invoke(obj, args); // *** 返回通知,在方法返回结果之后执行(因此该通知方法在方法抛出异常时,不能执行); System.out.println("before returning " + method); } catch (Exception ex) { ex.printStackTrace(); // *** 异常通知,在方法抛出异常之后执行; System.out.println("before throwing " + method); throw ex; } // *** 后置通知,在方法执行之后执行;因为方法可以抛出异常,所以访问不到方法的返回值 System.out.println("after " + method); return object; } }
环绕通知:
修改切面类LoggingAspect,添加环绕通知:
package com.dx.spring.beans.aop; import java.util.List; import java.util.Arrays; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; //把这个类声明为一个切面:需要把该类放入到IOC容器中、再声明为一个切面。 @Aspect @Component public class LoggingAspect { // // 声明该方法为一个前置通知:在目标方法开始之前执行 // @Before("execution(public int com.dx.spring.beans.aop.IArithmeticCalculator.*(int, int))") // public void beforeMethod(JoinPoint joinpoint) { // String methodName = joinpoint.getSignature().getName(); // List<Object> args = Arrays.asList(joinpoint.getArgs()); // System.out.println("before method " + methodName + " with " + args); // } // // // 声明该方法为一个后置通知:在目标方法结束之后执行(无论方法是否抛出异常)。 // // 但是因为当方法抛出异常时,不能返回值为null,因此这里无法获取到异常值。 // @After(value = "execution(public intcom.dx.spring.beans.aop.IArithmeticCalculator.*(int,int))") // public void afterMethod(JoinPoint joinpoint) { // String methodName = joinpoint.getSignature().getName(); // List<Object> args = Arrays.asList(joinpoint.getArgs()); // System.out.println("after method " + methodName); // } // // /** // * 声明该方法为一个返回通知:在目标方法返回结果时后执行(当方法抛出异常时,无法执行)。 但是因为当方法抛出异常时,类似代码 {@code // * try <br/> // * { <br/> // * // before 前置通知 <br/> // * // do action method <br/> // * // after returning 返回通知,可以访问到方法的返回值 <br/> // * } catch(Exception e){ e.printStackTrace(); // after throwing // * 异常通知,可以访问到方法出现的异常 } // after 后置通知,因为方法可以抛出异常,所以访问不到方法的返回值 } // */ // @AfterReturning(value = "execution(public intcom.dx.spring.beans.aop.IArithmeticCalculator.*(int,int))", returning = "result") // public void afterReturningMethod(JoinPoint joinpoint, Object result) { // String methodName = joinpoint.getSignature().getName(); // List<Object> args = Arrays.asList(joinpoint.getArgs()); // System.out.println( // "after method " + methodName + " with returning " + (result == null ? "NULL" : result.toString())); // } // // /** // * 定义一个异常通知函数: 只有在方法跑吹异常时,该方法才会执行,而且可以接受异常对象。 // */ // @AfterThrowing(value = "execution(public intcom.dx.spring.beans.aop.IArithmeticCalculator.*(int,int))", throwing = "ex") // public void afterThrowingMethod(JoinPoint joinpoint, Exception ex) { // String methodName = joinpoint.getSignature().getName(); // List<Object> args = Arrays.asList(joinpoint.getArgs()); // System.out.println( // "after method " + methodName + " occurs exception: " + (ex == null ? "NULL" : ex.getMessage())); // } @Around(value = "execution(public int com.dx.spring.beans.aop.IArithmeticCalculator.*(int, int))") public Object aroundMethod(ProceedingJoinPoint pJoinPoint) throws Exception { String methodName = pJoinPoint.getSignature().getName(); List<Object> args = Arrays.asList(pJoinPoint.getArgs()); Object result = null; try { // 前置通知 System.out.println("before method " + methodName + " with " + args); // 执行目标方法 result = pJoinPoint.proceed(); // 返回通知 System.out.println("after method " + methodName + " returning " + result); } catch (Throwable ex) { // 异常通知 System.out.println("after method " + methodName + " occurs exception: " + ex.getMessage()); throw new Exception(ex); } // 后置通知 System.out.println("after method " + methodName); return result; } }
修改Client.java测试类,添加代码:
int result = arithmeticCalculator.add(1, 3); System.out.println(result); result = arithmeticCalculator.div(4, 0); System.out.println(result);
执行结果
基础才是编程人员应该深入研究的问题,比如:
1)List/Set/Map内部组成原理|区别
2)mysql索引存储结构&如何调优/b-tree特点、计算复杂度及影响复杂度的因素。。。
3)JVM运行组成与原理及调优
4)Java类加载器运行原理
5)Java中GC过程原理|使用的回收算法原理
6)Redis中hash一致性实现及与hash其他区别
7)Java多线程、线程池开发、管理Lock与Synchroined区别
8)Spring IOC/AOP 原理;加载过程的。。。
【+加关注】。