Spring -- AOP
一、AOP
1、什么是AOP
AOP(Aspect-Oriented Programming,面向切面编程),通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
可以理解为是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构(可以理解为从上到下关系)。 简单的说,OOP是将 多个类似的对象的属性与行为封装在一起,并对其进行操作。而AOP是将 不同的对象的 相同行为 封装在一起,并进行操作(可以理解为从左到右的关系)。
比如:日志文件,使用OOP时,在不同的对象中可能需要进行同样的日志打印操作,这些代码大量重复出现,代码冗余且重用率低。而使用AOP后,其可以将这些相同的日志打印操作抽取出来,并进行一系列操作,最终降低代码冗余。
2、基本概念
(1)横切(cross-cutting)代码:
散布在各个类中,但与核心代码无关的代码。可以理解为 与业务代码无关,但是被经常使用到的代码。比如打印日志。
(2)核心关注点:
可以理解为 主要的业务流程。
(3)横切关注点:
可以理解为 与业务流程无关,但又出现在业务流程中的流程。比如:日志、权限认证、事务处理等。
(4)切面(Aspect):
一种模块化机制,横切关注点的模块化,可以理解为 将各个类中的公共行为封装到一个可重用模块。
(5)连接点(Join point):
连接点指的是在程序运行过程中,能够插入切面的一个点,这个点可以是类的某个方法调用前、调用后、方法抛出异常后等。切面代码可以利用这些点插入到应用的正常流程之中,并添加行为。
(6)通知/增强(Advice):
在特定的连接点,AOP框架执行的动作。
分类:
前置通知(Before):在目标方法被调用之前调用通知功能。
后置通知(After):在目标方法完成之后调用通知,无论该方法是否发生异常。
后置返回通知(After-returning):在目标方法成功执行之后调用通知。
后置异常通知(After-throwing):在目标方法抛出异常后调用通知。
环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
(7)切点(Pointcut):
指的是一个通知将被引发的一系列连接点的集合。AOP 通过切点定位到特定的连接点。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。每个类都可以拥有多个连接点。
(8)引入(Introduction):
添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。
(9)目标对象(Target Object):
包含连接点的对象。也被称作被通知或被代理对象。
(10)AOP代理(AOP Proxy):
AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
(11)织入(Weaving):
织入描述的是把切面应用到目标对象来创建新的代理对象的过程。 Spring AOP 的切面是在运行时被织入,原理是使用了动态代理技术。Spring支持两种方式生成代理对象:JDK动态代理和CGLib,默认的策略是如果目标类是接口,则使用JDK动态代理技术(只对实现接口的类产生代理对象),否则使用Cglib来生成代理。
二、AOP的使用
1、小案例分析
现有功能:一个简单的计算器,能进行简单的加减乘除。
【实例一:】 【Calculator.java】 package com.company; /** * 计算器接口,定义常用方法 */ public interface Calculator { /** * 加法操作 * @param a 实数 * @param b 实数 * @return 相加结果 */ public double add(double a, double b); /** * 减法操作 * @param a 实数 * @param b 实数 * @return 相减结果 */ public double sub(double a, double b); /** * 乘法操作 * @param a 实数 * @param b 实数 * @return 相乘结果 */ public double mul(double a, double b); /** * 除法操作 * @param a 实数 * @param b 实数 * @return 相除结果 */ public double div(double a, double b); } 【CalculatorImpl.java】 package com.company; /** * 计算器实现类,实现常用方法 */ public class CalculatorImpl implements Calculator { @Override public double add(double a, double b) { return a + b; } @Override public double sub(double a, double b) { return a - b; } @Override public double mul(double a, double b) { return a * b; } @Override public double div(double a, double b) { if(b == 0){ System.out.println("除数不能为0"); return 0; } return a / b; } } 【Main.java】 package com.company; /** * 测试类 */ public class Main { public static void main(String[] args) { Calculator calculator = new CalculatorImpl(); double a = 3.0; double b = 2.0; System.out.println("a + b = " + calculator.add(a, b)); // a + b = 5.0 System.out.println("a - b = " + calculator.sub(a, b)); // a - b = 1.0 System.out.println("a * b = " + calculator.mul(a, b)); // a * b = 6.0 System.out.println("a / b = " + calculator.div(a, b)); // a / b = 1.5 } } }
需增加的功能:每次加减乘除的前后,都需要进行打印日志操作。
2、不使用AOP时
(1)思路:在加减乘除方法的前后,可以增加一个方法调用,进行日志输出。
(2)实现:(在实例一的基础上修改)
【实例二:】 【CalculatorImpl.java】 package com.company; /** * 计算器实现类,实现常用方法 */ public class CalculatorImpl implements Calculator { @Override public double add(double a, double b) { beforeMethod("add", a, b); double c = a + b; afterMethod("add", c); return c; } @Override public double sub(double a, double b) { beforeMethod("sub", a, b); double c = a - b; afterMethod("sub", c); return c; } @Override public double mul(double a, double b) { beforeMethod("mul", a, b); double c = a * b; afterMethod("mul", c); return c; } @Override public double div(double a, double b) { beforeMethod("div", a, b); if (b == 0) { System.out.println("除数不能为0"); double c = 0; afterMethod("div", c); return 0; } double c = a / b; afterMethod("div", c); return c; } /** * 在业务方法前执行打印日志操作 * * @param methodName 方法名 */ public void beforeMethod(String methodName, double a, double b) { System.out.println("This method " + methodName + " begins with " + a + " , " + b); } /** * 在业务方法前执行后打印日志操作 * * @param methodName 方法名 */ public void afterMethod(String methodName, double c) { System.out.println("This method " + methodName + " ends with " + c); } } 【执行结果:】 This method add begins with 3.0 , 2.0 This method add ends with 5.0 a + b = 5.0 This method sub begins with 3.0 , 2.0 This method sub ends with 1.0 a - b = 1.0 This method mul begins with 3.0 , 2.0 This method mul ends with 6.0 a * b = 6.0 This method div begins with 3.0 , 2.0 This method div ends with 1.5 a / b = 1.5
(3)优缺点分析:
代码直观,但是冗余严重,当修改某个部分时,可能会造成大量修改。
3、不使用AOP时,代码稍微升点级
(1)实例二可能会暴露的问题:对于不同的类,可能需要使用相同的日志操作,若是使用实例二的写法,那么在每个类里面都需定义日志操作方法,造成代码冗余。
解决: 在实例二的基础上,将日志操作抽取出来,形成一个类。当需要使用日志时,继承该类即可。
(2)实现:(在实例二的基础上修改)
【实例三:】 【Message.java】 package com.company; public class Message { /** * 在业务方法前执行打印日志操作 * * @param methodName 方法名 */ public void beforeMethod(String methodName, double a, double b) { System.out.println("This method " + methodName + " begins with " + a + " , " + b); } /** * 在业务方法前执行后打印日志操作 * * @param methodName 方法名 */ public void afterMethod(String methodName, double c) { System.out.println("This method " + methodName + " ends with " + c); } } 【CalculatorImpl.java】 package com.company; /** * 计算器实现类,实现常用方法 */ public class CalculatorImpl extends Message implements Calculator { @Override public double add(double a, double b) { beforeMethod("add", a, b); double c = a + b; afterMethod("add", c); return c; } @Override public double sub(double a, double b) { beforeMethod("sub", a, b); double c = a - b; afterMethod("sub", c); return c; } @Override public double mul(double a, double b) { beforeMethod("mul", a, b); double c = a * b; afterMethod("mul", c); return c; } @Override public double div(double a, double b) { beforeMethod("div", a, b); if (b == 0) { System.out.println("除数不能为0"); double c = 0; afterMethod("div", c); return c; } double c = a / b; afterMethod("div", c); return c; } } 【结果:】 This method add begins with 3.0 , 2.0 This method add ends with 5.0 a + b = 5.0 This method sub begins with 3.0 , 2.0 This method sub ends with 1.0 a - b = 1.0 This method mul begins with 3.0 , 2.0 This method mul ends with 6.0 a * b = 6.0 This method div begins with 3.0 , 2.0 This method div ends with 1.5 a / b = 1.5
(3)优缺点:
采用纵向继承(从上到下),可以减少代码冗余,但是局限性太大。
4、使用AOP
(1)AOP思想采用横向抽取的方式(从左到右),将日志操作封装。实质是通过动态代理来实现的。
(2)动态代理分类:
JDK动态代理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
Cglib动态代理:利用第三方jar包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
(3)动态代理区别:
JDK动态代理:只能对实现了接口的类产生代理。
Cglib动态代理:对没有实现接口的类产生代理对象,即针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。
(4)JDK动态代理:
可以参考:https://www.cnblogs.com/l-y-h/p/11111539.html#_label2
【实例四:】 核心类、接口: Calculator.java 接口 CalculatorImpl.java 接口实现类 CalculatorProxy.java 接口实现类的代理类 Main.java 测试类 【Calculator.java】 package com.company; /** * 计算器接口,定义常用方法 */ public interface Calculator { /** * 加法操作 * @param a 实数 * @param b 实数 * @return 相加结果 */ public double add(double a, double b); /** * 减法操作 * @param a 实数 * @param b 实数 * @return 相减结果 */ public double sub(double a, double b); /** * 乘法操作 * @param a 实数 * @param b 实数 * @return 相乘结果 */ public double mul(double a, double b); /** * 除法操作 * @param a 实数 * @param b 实数 * @return 相除结果 */ public double div(double a, double b); } 【CalculatorImpl.java】 package com.company; /** * 计算器实现类,实现常用方法 */ public class CalculatorImpl implements Calculator { @Override public double add(double a, double b) { return a + b; } @Override public double sub(double a, double b) { return a - b; } @Override public double mul(double a, double b) { return a * b; } @Override public double div(double a, double b) { if (b == 0) { System.out.println("除数不能为0"); return 0; } return a / b; } } 【CalculatorProxy.java】 package com.company; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; /** * JDK动态代理 */ public class CalculatorProxy implements InvocationHandler { private Object calculator; // 用于保存代理对象 CalculatorProxy(Object calculator) { this.calculator = calculator; // 获取代理的对象 } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); // 获取方法名 System.out.println("This is " + methodName + " starts with " + Arrays.asList(args)); Object obj = method.invoke(this.calculator, args); System.out.println("This is " + methodName + " ends with " + obj); return obj; } /** * 获取动态代理的对象 * * @return 动态代理的对象 */ public Object getJDKProxy() { return Proxy.newProxyInstance(this.calculator.getClass().getClassLoader(), this.calculator.getClass().getInterfaces(), this); } } 【Main.java】 package com.company; import java.lang.reflect.Proxy; /** * 测试类 */ public class Main { public static void main(String[] args) { Calculator realCalculator = new CalculatorImpl(); double a = 3.0; double b = 2.0; // 方式一 CalculatorProxy calculatorProxy = new CalculatorProxy(realCalculator); Calculator calculator = (Calculator) Proxy.newProxyInstance(calculatorProxy.getClass().getClassLoader(), realCalculator.getClass().getInterfaces(), calculatorProxy); System.out.println("a + b = " + calculator.add(a, b)); System.out.println("a - b = " + calculator.sub(a, b)); System.out.println("a * b = " + calculator.mul(a, b)); System.out.println("a / b = " + calculator.div(a, b)); System.out.println(); System.out.println(); System.out.println(); System.out.println(); System.out.println(); // 方式二 Calculator calculator2 = (Calculator) (new CalculatorProxy(realCalculator).getJDKProxy()); System.out.println("a + b = " + calculator2.add(a, b)); System.out.println("a - b = " + calculator2.sub(a, b)); System.out.println("a * b = " + calculator2.mul(a, b)); System.out.println("a / b = " + calculator2.div(a, b)); } } 【结果:】 This is add starts with [3.0, 2.0] This is add ends with 5.0 a + b = 5.0 This is sub starts with [3.0, 2.0] This is sub ends with 1.0 a - b = 1.0 This is mul starts with [3.0, 2.0] This is mul ends with 6.0 a * b = 6.0 This is div starts with [3.0, 2.0] This is div ends with 1.5 a / b = 1.5 This is add starts with [3.0, 2.0] This is add ends with 5.0 a + b = 5.0 This is sub starts with [3.0, 2.0] This is sub ends with 1.0 a - b = 1.0 This is mul starts with [3.0, 2.0] This is mul ends with 6.0 a * b = 6.0 This is div starts with [3.0, 2.0] This is div ends with 1.5 a / b = 1.5
(5)Cglib动态代理
要使用cglib,需要引入jar包(比如:cglib-2.2.2.jar, asm-3.3.1.jar)。
引入jar包不对的话,可能会引起下面的问题:
Question1:java.lang.NoClassDefFoundError: org/objectweb/asm/Type
Answer1:只引入了 cglib-2.2.2.jar, 未引入 asm-3.3.1.jar
Question2:class net.sf.cglib.core.DebuggingClassWriter overrides final method visit.
Answer2:引入了 asm-3.3.1.jar, 但是 版本不对,引发冲突。
【实例五:】 核心类: CalculatorImpl.java 某个类 CalculatorCglibProxy.java 某个类的代理类 Main.java 测试类 【CalculatorImpl.java】 package com.company; /** * 计算器类,定义常用方法 */ public class CalculatorImpl { public double add(double a, double b) { return a + b; } public double sub(double a, double b) { return a - b; } public double mul(double a, double b) { return a * b; } public double div(double a, double b) { if (b == 0) { System.out.println("除数不能为0"); return 0; } return a / b; } } 【CalculatorCglibProxy.java】 package com.company; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; import java.util.Arrays; /** * Cglib实现动态代理 */ public class CalculatorCglibProxy implements MethodInterceptor{ private Object calculator; // 用于保存代理对象 CalculatorCglibProxy(Object calculator) { this.calculator = calculator; // 获取代理的对象 } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { String methodName = method.getName(); // 获取方法名 System.out.println("This is " + methodName + " starts with " + Arrays.asList(objects)); Object obj = method.invoke(this.calculator, objects); System.out.println("This is " + methodName + " ends with " + obj); return obj; } /** * 获取Cglib动态代理的对象 * * @return 动态代理的对象 */ public Object getCglibProxy(){ // 1.创建cglib的核心类对象 Enhancer enhancer = new Enhancer(); // 2.设置父类 enhancer.setSuperclass(calculator.getClass()); // 3.设置回调(类似于jdk动态代理中的InvocationHandler对象) enhancer.setCallback(this); // 4.返回代理对象 return enhancer.create(); } } 【Main.java】 package com.company; import net.sf.cglib.proxy.Enhancer; import java.lang.reflect.Proxy; /** * 测试类 */ public class Main { public static void main(String[] args) { CalculatorImpl realCalculator = new CalculatorImpl(); double a = 3.0; double b = 2.0; // 方式一 CalculatorCglibProxy calculatorCglibProxy = new CalculatorCglibProxy(realCalculator); CalculatorImpl calculator = (CalculatorImpl) new Enhancer().create(realCalculator.getClass(), calculatorCglibProxy); System.out.println("a + b = " + calculator.add(a, b)); System.out.println("a - b = " + calculator.sub(a, b)); System.out.println("a * b = " + calculator.mul(a, b)); System.out.println("a / b = " + calculator.div(a, b)); System.out.println(); System.out.println(); System.out.println(); System.out.println(); System.out.println(); // 方式二 CalculatorImpl calculator2 = (CalculatorImpl) (new CalculatorCglibProxy(realCalculator).getCglibProxy()); System.out.println("a + b = " + calculator2.add(a, b)); System.out.println("a - b = " + calculator2.sub(a, b)); System.out.println("a * b = " + calculator2.mul(a, b)); System.out.println("a / b = " + calculator2.div(a, b)); } } 【结果:】 This is add starts with [3.0, 2.0] This is add ends with 5.0 a + b = 5.0 This is sub starts with [3.0, 2.0] This is sub ends with 1.0 a - b = 1.0 This is mul starts with [3.0, 2.0] This is mul ends with 6.0 a * b = 6.0 This is div starts with [3.0, 2.0] This is div ends with 1.5 a / b = 1.5 This is add starts with [3.0, 2.0] This is add ends with 5.0 a + b = 5.0 This is sub starts with [3.0, 2.0] This is sub ends with 1.0 a - b = 1.0 This is mul starts with [3.0, 2.0] This is mul ends with 6.0 a * b = 6.0 This is div starts with [3.0, 2.0] This is div ends with 1.5 a / b = 1.5
三、Spring中使用AOP
1、切入点表达式
(1)切入点表达式的写法: (基于execution的函数完成的)
execution([访问修饰符] 返回值类型 包路径.类名.方法名(参数)) execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern (param-pattern) throws-pattern?) 其中:(?表示为可选项) modifiers-pattern? 修饰符匹配 ret-type-pattern 返回值类型 declaring-type-pattern? 类路径匹配 name-pattern 方法名匹配 (param-pattern) 参数匹配 throws-pattern? 异常类型匹配 “*” 表示任意返回值类型 “..” 表示任意参数 【举例:】 全匹配方式: execution(public void com.company.CalculatorImpl.add(..)) 访问修饰符可省略 execution(void com.company.CalculatorImpl.add(..)) 可使用*,表示任意返回值 execution(* com.company.CalculatorImpl.add(..)) 包路径可以使用*,表示任意包. 但是*.的个数要和包的层级数相匹配,可以使用*..,表示当前包,及其子包 execution(* *.*.CalculatorImpl.add(..)) 类名可以使用*,表示任意类 execution(* *..*.add(..)) 方法名可以使用*,表示任意方法 execution(* *..*.*(..)) 参数列表可以使用*,表示参数可以是任意数据类型,但是必须存在参数 execution(* *..*.*(*)) 参数列表可以使用..表示有无参数均可,有参数可以是任意类型 execution(* *..*.*(..)) 全通配方式,可以匹配匹配任意方法 execution(* *..*.*(..))
2、xml版 配置文件的写法、规则
(1)配置 通知类(切面) 与 被通知的类(目标对象)。
<!-- 配置目标对象,即被增强的对象 --> <bean id="calculator" class="com.company.CalculatorImpl"/> <!-- 将增强类(切面类)交给Spring管理 --> <bean id="calculatorEnhancer" class="com.company.CalculatorEnhancer"/>
(2)<aop:config>
用于声明AOP配置,所有的AOP配置代码均写在其中。
<aop:config> <!-- AOP配置的代码都写在此处 --> </aop:config>
(3)<aop:aspect>
用于配置切面。
其属性:
id : 为指定切面的id,
ref :为引用通知类的 id。
<aop:config> <!-- AOP配置的代码都写在此处 --> <aop:aspect id="calculatorAdvice" ref="calculatorEnhancer"> <!--配置通知的类型要写在此处--> </aop:aspect> </aop:config>
(4)<aop:pointcut>
用于配置切点表达式,用于指定对哪些方法进行增强。
其属性:
id:为指定切点表达式的id。
expression: 指定切入点表达式。
<aop:config> <!-- AOP配置的代码都写在此处 --> <!--配置切点表达式--> <aop:pointcut id="aspect1" expression="execution(* com.company.CalculatorImpl.*(..))"></aop:pointcut> <!--配置切面,通知的类型要写在此处--> <aop:aspect id="calculatorAdvice" ref="calculatorEnhancer"> </aop:aspect> </aop:config>
(5)配置通知方法
<aop:before>: 配置前置通知,指定的增强方法在切入点方法之前执行。
<aop:after-returning>: 配置后置返回通知,指定的增强方法在切入点方法正常执行之后执行。
<aop:after-throwing>: 配置后置异常通知,指定的增强方法在切入点方法产生异常后执行。
<aop:after>: 配置后置通知,无论切入点方法执行时是否发生异常,指定的增强方法都会最后执行。
<aop:around>: 配置环绕通知,可以在代码中手动控制增强代码的执行时机。
其属性:
method:用于指定 通知类中的 通知方法名。
ponitcut-ref:指定切入点的表达式的id。
poinitcut:指定切入点表达式。
<aop:config> <!-- AOP配置的代码都写在此处 --> <!--配置切点表达式--> <aop:pointcut id="aspect1" expression="execution(* com.company.CalculatorImpl.*(..))"></aop:pointcut> <!--配置通知的类型要写在此处--> <aop:aspect id="calculatorAdvice" ref="calculatorEnhancer"> <!--配置各种类型的通知--> <aop:before method="printLogBefore" pointcut-ref="aspect1"></aop:before> <aop:after-returning method="printLogAfterReturning" pointcut-ref="aspect1"></aop:after-returning> <aop:after-throwing method="printLogAfterThrowing" pointcut-ref="aspect1"></aop:after-throwing> <aop:after method="printLogAfter" pointcut-ref="aspect1"></aop:after> <!--环绕通知一般单独使用--> <!-- <aop:around method="printLogAround" pointcut-ref="aspect1"></aop:around> --> </aop:aspect> </aop:config>
3、xml版
(1)导入junit、aop、Spring等相关jar包。
注:若使用 junit 报错: java.lang.NoClassDefFoundError: org/hamcrest/SelfDescribing 。
可以导入hamcrest-core-1.3.jar。
(2)目录结构
(3)代码
注:环绕通知的返回值必须是Object,形参必须是ProceedingJoingPoint。
【实例六:】 核心类、接口: Calculator.java 接口 CalculatorImpl.java 接口实现类,被通知类 CalculatorEnhancer.java 通知(切面)类,用于定义相关通知方法 applicationContext.xml 配置文件,用于 通知类 与 被通知类 关联 CalculatorTest.java 使用Junit进行单元测试 【Calculator.java】 package com.lyh.service; /** * 计算器接口,定义常用方法 */ public interface Calculator { /** * 加法操作 * @param a 实数 * @param b 实数 * @return 相加结果 */ public double add(double a, double b); /** * 减法操作 * @param a 实数 * @param b 实数 * @return 相减结果 */ public double sub(double a, double b); /** * 乘法操作 * @param a 实数 * @param b 实数 * @return 相乘结果 */ public double mul(double a, double b); /** * 除法操作 * @param a 实数 * @param b 实数 * @return 相除结果 */ public int div(int a, int b); } 【CalculatorImpl.java】 package com.lyh.service.impl; import com.lyh.service.Calculator; /** * 计算器实现类,实现常用方法 */ public class CalculatorImpl implements Calculator{ @Override public double add(double a, double b) { return a + b; } @Override public double sub(double a, double b) { return a - b; } @Override public double mul(double a, double b) { return a * b; } @Override public int div(int a, int b) { return a / b; } } 【CalculatorEnhancer.java】 package com.lyh.enhancer; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import java.util.Arrays; /** * 定义一个切面类 */ public class CalculatorEnhancer { /** * 前置通知:在方法执行前执行的代码 * @param joinPoint 切入点 */ public void beforeExecute(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); System.out.println("beforeExecute"); System.out.println("This is " + methodName + " starts with " + Arrays.asList(joinPoint.getArgs())); System.out.println(); } /** * 后置通知:在方法执行后执行的代码(无论该方法是否发生异常),注意后置通知拿不到执行的结果 * @param joinPoint 切入点 */ public void afterExecute(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); System.out.println("afterExecute"); System.out.println(); } /** * 后置返回通知:在方法正常执行后执行的代码,可以获取到方法的返回值 * @param joinPoint 切入点 * @param result 方法执行的结果 */ public void afterReturningExecute(JoinPoint joinPoint, Object result){ String methodName = joinPoint.getSignature().getName(); System.out.println("afterReturningExecute"); System.out.println("This is " + methodName + " ends with " + result); System.out.println(); } /** * 后置异常通知:在方法抛出异常之后执行,可以访问到异常信息。 * @param joinPoint 切入点 * @param exception 异常信息 */ public void afterExceptionExecute(JoinPoint joinPoint, Exception exception){ String methodName = joinPoint.getSignature().getName(); System.out.println("afterExceptionExecute"); System.out.println("This is " + methodName + " exception with: " + exception); System.out.println(); } /** * 环绕通知。 * Spring框架为我们提供一个接口ProceedingJoinPoint,它的实例对象可以作为环绕通知方法的参数,通过参数控制被增强方法的执行时机。 * ProceedingJoinPoint对象的getArgs()方法返回被拦截的参数 * ProceedingJoinPoint对象的proceed()方法执行被拦截的方法 * @param joinPoint 连接点 * @return 方法计算的结果值 */ public Object aroundExecute(ProceedingJoinPoint joinPoint){ Object result = null; try{ System.out.println("beforeExecute"); result = joinPoint.proceed(joinPoint.getArgs()); System.out.println("afterReturningExecute"); }catch (Throwable e){ System.out.println("afterExceptionExecute"); }finally { System.out.println("afterExecute"); } return result; } } 【applicationContext.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 id="calculator" class="com.lyh.service.impl.CalculatorImpl"/> <!-- 将增强类(切面类)交给Spring管理 --> <bean id="calculatorEnhancer" class="com.lyh.enhancer.CalculatorEnhancer"/> <aop:config> <!-- AOP配置的代码都写在此处 --> <!--配置切点表达式--> <aop:pointcut id="beforeTest" expression="execution(* com.lyh.service.impl.CalculatorImpl.add(..))"></aop:pointcut> <aop:pointcut id="afterTest" expression="execution(* com.lyh.service.impl.CalculatorImpl.add(..))"></aop:pointcut> <aop:pointcut id="afterReturningTest" expression="execution(* com.lyh.service.impl.CalculatorImpl.add(..))"></aop:pointcut> <aop:pointcut id="afterThrowingTest" expression="execution(* com.lyh.service.impl.CalculatorImpl.div(..))"></aop:pointcut> <aop:pointcut id="aroundTest" expression="execution(* com.lyh.service.impl.CalculatorImpl.sub(..))"></aop:pointcut> <!--配置通知的类型要写在此处--> <aop:aspect id="calculatorAdvice" ref="calculatorEnhancer"> <!--配置各种类型的通知--> <aop:before method="beforeExecute" pointcut-ref="beforeTest"></aop:before> <aop:after method="afterExecute" pointcut-ref="afterTest"></aop:after> <aop:after-returning method="afterReturningExecute" pointcut-ref="afterReturningTest" returning="result"></aop:after-returning> <aop:after-throwing method="afterExceptionExecute" pointcut-ref="afterThrowingTest" throwing="exception"></aop:after-throwing> <aop:around method="aroundExecute" pointcut-ref="aroundTest"></aop:around> </aop:aspect> </aop:config> </beans> 【CalculatorTest.java】 package com.lyh.test; import com.lyh.service.Calculator; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import java.math.BigDecimal; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:applicationContext.xml"}) public class CalculatorTest { @Autowired private Calculator calculator; /** * 测试 前置通知,后置通知,后置返回通知 */ @Test public void test(){ double a = 3.0; double b = 2.0; System.out.println("a + b = " + calculator.add(a, b)); } /** * 测试后置异常通知, * 对于除数为0的情况(即b = 0时), * 如果二者均为int型(long也是int型),结果会抛出异常:java.lang.ArithmeticException: / by zero。 * 如果其中有一个为double或者float型,结果则是Infinity。 * 为了测试异常,此处将数据置为int型的。 */ @Test public void test2(){ int a = 3; int b = 0; System.out.println("a / b = " + calculator.div(a, b)); } /** * 测试环绕通知 */ @Test public void test3(){ int a = 3; int b = 0; System.out.println("a / b = " + calculator.sub(a, b)); } } 【测试结果:】 【运行test()】 beforeExecute This is add starts with [3.0, 2.0] afterReturningExecute This is add ends with 5.0 afterExecute a + b = 5.0 【运行test1()】 afterExceptionExecute This is div exception with: java.lang.ArithmeticException: / by zero java.lang.ArithmeticException: / by zero at com.lyh.service.impl.CalculatorImpl.div(CalculatorImpl.java:29) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333) at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:62) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) at com.sun.proxy.$Proxy13.div(Unknown Source) at com.lyh.test.CalculatorTest.test2(CalculatorTest.java:39) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75) at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86) at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70) 【运行test3()】 beforeExecute afterReturningExecute afterExecute a / b = 3.0
注:
使用xml版的时候,执行的顺序 按照 配置文件中的 顺序执行。
可能会出现 前置通知 -》 后置通知(最终通知) -》 后置返回通知 的情况。
也可能会出现 前置通知 -》 后置返回通知 -》 后置通知(最终通知) 的情况。
4、注解版
(1)在上例 xml 版的基础上,在 applicationContext.xml 中 添加 <aop:aspectj-autoproxy /> 用于自动代理,同时在相应的地方添加上注解标记。
(2)代码
注: 使用 @Aspect 后执行顺序是 前置通知,后置通知,后置返回通知,可以用 环绕通知 来解决。
【实例七:】 核心类、接口: Calculator.java 接口 CalculatorImpl.java 接口实现类,被通知类 CalculatorEnhancer.java 通知(切面)类,用于定义相关通知方法 applicationContext.xml 配置文件,用于 通知类 与 被通知类 关联 CalculatorTest.java 使用Junit进行单元测试 【Calculator.java】 package com.lyh.service; /** * 计算器接口,定义常用方法 */ public interface Calculator { /** * 加法操作 * @param a 实数 * @param b 实数 * @return 相加结果 */ public double add(double a, double b); /** * 减法操作 * @param a 实数 * @param b 实数 * @return 相减结果 */ public double sub(double a, double b); /** * 乘法操作 * @param a 实数 * @param b 实数 * @return 相乘结果 */ public double mul(double a, double b); /** * 除法操作 * @param a 实数 * @param b 实数 * @return 相除结果 */ public int div(int a, int b); } 【CalculatorImpl.java】 package com.lyh.service.impl; import com.lyh.service.Calculator; import org.springframework.stereotype.Service; import javax.xml.ws.ServiceMode; /** * 计算器实现类,实现常用方法 */ @Service public class CalculatorImpl implements Calculator{ @Override public double add(double a, double b) { return a + b; } @Override public double sub(double a, double b) { return a - b; } @Override public double mul(double a, double b) { return a * b; } @Override public int div(int a, int b) { return a / b; } } 【CalculatorEnhancer.java】 package com.lyh.enhancer; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; import java.util.Arrays; /** * 定义一个切面类 */ @Component @Aspect public class CalculatorEnhancer { /** * 前置通知:在方法执行前执行的代码 * @param joinPoint 切入点 */ @Before("execution(* com.lyh.service.impl.CalculatorImpl.add(..))") public void beforeExecute(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); System.out.println("beforeExecute"); System.out.println("This is " + methodName + " starts with " + Arrays.asList(joinPoint.getArgs())); System.out.println(); } /** * 后置返回通知:在方法正常执行后执行的代码,可以获取到方法的返回值 * @param joinPoint 切入点 * @param result 方法执行的结果 */ @AfterReturning(returning = "result", value = "execution(* com.lyh.service.impl.CalculatorImpl.add(..))") public void afterReturningExecute(JoinPoint joinPoint, Object result){ String methodName = joinPoint.getSignature().getName(); System.out.println("afterReturningExecute"); System.out.println("This is " + methodName + " ends with " + result); System.out.println(); } /** * 后置通知:在方法执行后执行的代码(无论该方法是否发生异常),注意后置通知拿不到执行的结果 * @param joinPoint 切入点 */ @After("execution(* com.lyh.service.impl.CalculatorImpl.add(..))") public void afterExecute(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); System.out.println("afterExecute"); System.out.println(); } /** * 后置异常通知:在方法抛出异常之后执行,可以访问到异常信息。 * @param joinPoint 切入点 * @param exception 异常信息 */ @AfterThrowing(throwing ="exception", value = "execution(* com.lyh.service.impl.CalculatorImpl.div(..))") public void afterExceptionExecute(JoinPoint joinPoint, Exception exception){ String methodName = joinPoint.getSignature().getName(); System.out.println("afterExceptionExecute"); System.out.println("This is " + methodName + " exception with: " + exception); System.out.println(); } /** * 环绕通知。 * Spring框架为我们提供一个接口ProceedingJoinPoint,它的实例对象可以作为环绕通知方法的参数,通过参数控制被增强方法的执行时机。 * ProceedingJoinPoint对象的getArgs()方法返回被拦截的参数 * ProceedingJoinPoint对象的proceed()方法执行被拦截的方法 * @param joinPoint 连接点 * @return 方法计算的结果值 */ @Around("execution(* com.lyh.service.impl.CalculatorImpl.sub(..))") public Object aroundExecute(ProceedingJoinPoint joinPoint){ Object result = null; try{ System.out.println("beforeExecute"); result = joinPoint.proceed(joinPoint.getArgs()); System.out.println("afterReturningExecute"); }catch (Throwable e){ System.out.println("afterExceptionExecute"); }finally { System.out.println("afterExecute"); } return result; } } 【applicationContext.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" 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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <!-- 自动扫描的包 --> <context:component-scan base-package="com.lyh"></context:component-scan> <!-- 启用AspectJ自动代理 --> <aop:aspectj-autoproxy /> </beans> 【CalculatorTest.java】 package com.lyh.test; import com.lyh.service.Calculator; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import java.math.BigDecimal; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:applicationContext.xml"}) public class CalculatorTest { @Autowired private Calculator calculator; /** * 测试 前置通知,后置通知,后置返回通知 */ @Test public void test(){ double a = 3.0; double b = 2.0; System.out.println("a + b = " + calculator.add(a, b)); } /** * 测试后置异常通知, * 对于除数为0的情况(即b = 0时), * 如果二者均为int型(long也是int型),结果会抛出异常:java.lang.ArithmeticException: / by zero。 * 如果其中有一个为double或者float型,结果则是Infinity。 * 为了测试异常,此处将数据置为int型的。 */ @Test public void test2(){ int a = 3; int b = 0; System.out.println("a / b = " + calculator.div(a, b)); } /** * 测试环绕通知 */ @Test public void test3(){ int a = 3; int b = 0; System.out.println("a / b = " + calculator.sub(a, b)); } } 【测试结果:】 【运行test()】 这里可以看到这里的执行顺序是 前置通知,后置通知,后置返回通知 beforeExecute This is add starts with [3.0, 2.0] afterExecute afterReturningExecute This is add ends with 5.0 a + b = 5.0 【运行test2()】 afterExceptionExecute This is div exception with: java.lang.ArithmeticException: / by zero java.lang.ArithmeticException: / by zero at com.lyh.service.impl.CalculatorImpl.div(CalculatorImpl.java:30) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333) at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:62) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) at com.sun.proxy.$Proxy20.div(Unknown Source) at com.lyh.test.CalculatorTest.test2(CalculatorTest.java:39) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75) at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86) at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70) 【运行test3()】 beforeExecute afterReturningExecute afterExecute a / b = 3.0