Spring 详解(一)——- AOP前序
- 2019 年 10 月 6 日
- 筆記
目录
1. AOP 简介
AOP(Aspect Oriented Programming),通常称为面向切面编程。它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
2. 示例需求
想要为写好的 ArithmeticCalculator 添加日志 ,即每次运算前后添加 采用以下方法太过繁琐,修改内容需要每个跟着都修改,可维护性差
public interface ArithmeticCalculator { int add(int i, int j); int sub(int i, int j); int mul(int i, int j); int div(int i, int j); }
public class MyArithmeticCalculatorImp implements ArithmeticCalculator { public int add(int i, int j) { System.out.println("The method add begins with["+i+","+j+"]"); int result = i + j; System.out.println("The method add ends with["+result+"]"); return result; } public int sub(int i, int j) { System.out.println("The method sub begins with["+i+","+j+"]"); int result = i - j; System.out.println("The method sub ends with["+result+"]"); return result; } public int mul(int i, int j) { System.out.println("The method mul begins with["+i+","+j+"]"); int result = i * j; System.out.println("The method mul ends with["+result+"]"); return result; } public int div(int i, int j) { System.out.println("The method div begins with["+i+","+j+"]"); int result = i / j; System.out.println("The method div ends with["+result+"]"); return result; } }
结果
The method add begins with[1,2] The method add ends with[3] -->3 The method mul begins with[5,2] The method mul ends with[10] -->10
问题: 代码混乱:越来越多的非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀,每个方法在处理核心逻辑的同时还必须兼顾替他多个关注点。
代码分散:以日志需求为例,只是为了满足这个单一需求,就不得不在多个模块(方法)里多次重复相同的日志代码,如果日志需求发生变化,必须修改所有模块。
3. 解决方法一:使用静态代理
创建干净的实现类
public class ArithmeticCalculatorImpl implements ArithmeticCalculator { @Override public int add(int i, int j) { return i + j; } @Override public int sub(int i, int j) { return i - j; } @Override public int mul(int i, int j) { return i * j; } @Override public int div(int i, int j) { return i / j; } }
创建日志类 MyLogger
/** * 创建日志类 */ public class MyLogger { /** * 入参日志 * @param a * @param b */ public void showParam(int a, int b) { System.out.println("The method add begins with["+a+","+b+"]"); } /** * 运算结果日志 * @param result */ public void showResult(int result) { System.out.println("The method add ends with["+3+"]"); } }
创建静态代理类
/** * 代理类 */ public class ProxyLogger implements ArithmeticCalculator { //目标类 private ArithmeticCalculator target; //日志类 private MyLogger logger; public ProxyLogger(ArithmeticCalculator target, MyLogger logger) { this.target = target; this.logger = logger; } @Override public int add(int i, int j) { logger.showParam(i, j); int result = target.add(i,j); logger.showResult(result); return result; } @Override public int sub(int i, int j) { logger.showParam(i, j); int result = target.sub(i,j); logger.showResult(result); return result; } @Override public int mul(int i, int j) { logger.showParam(i, j); int result = target.mul(i,j); logger.showResult(result); return result; } @Override public int div(int i, int j) { logger.showParam(i, j); int result = target.div(i,j); logger.showResult(result); return result; } }
结果测试
public class Main { public static void main(String[] args) { ArithmeticCalculator arithmeticCalculator = new ArithmeticCalculatorImpl(); MyLogger logger = new MyLogger(); ProxyLogger proxy = new ProxyLogger(arithmeticCalculator, logger); System.out.println(proxy.add(1, 9)); System.out.println(proxy.mul(3, 3)); } } /** The method add begins with[1,9] The method add ends with[3] 10 The method add begins with[3,3] The method add ends with[3] 9 */
总结
这是一个很基础的静态代理,业务类 ArithmeticCalculatorImpl 只需要关注业务逻辑本身,保证了业务的重用性,这也是代理类的优点,没什么好说的。我们主要说说这样写的缺点:
- 代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
- 如果接口增加一个方法,比如 ArithmeticCalculatorImpl 增加归零 changeZero()方法,则除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
4. 解决方法二:使用动态代理
我们去掉 ProxyLogger. 类,增加一个 ObjectInterceptor.java 类
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class ObjectInterceptor implements InvocationHandler { //目标类 private Object target; //切面类(这里指 日志类) private MyLogger logger; public ObjectInterceptor(Object target, MyLogger logger) { this.target = target; this.logger = logger; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //参数日志 logger.showParam((int)args[0], (int)args[1]); System.out.println(args[0]+"--"+args[1]); int result = (int)method.invoke(target, args); //结果日志 logger.showResult(result); return result; } }
测试
public class Main { public static void main(String[] args) { MyLogger logger = new MyLogger(); ProxyLogger proxy = new ProxyLogger(arithmeticCalculator, logger); System.out.println(proxy.add(1, 9)); System.out.println(proxy.mul(3, 3));*/ Object object = new ArithmeticCalculatorImpl(); MyLogger logger = new MyLogger(); ObjectInterceptor objectInterceptor = new ObjectInterceptor(object, logger); /** * 三个参数的含义: * 1、目标类的类加载器 * 2、目标类所有实现的接口 * 3、拦截器 */ ArithmeticCalculator calculator = (ArithmeticCalculator) Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), objectInterceptor); System.out.println(calculator.add(1, 5)); /* The method add begins with[1,5] 1--5 The method add ends with[3] 6 */
那么使用动态代理来完成这个需求就很好了,后期在 ArithmeticCalculator 中增加业务方法,都不用更改代码就能自动给我们生成代理对象。而且将 ArithmeticCalculator 换成别的类也是可以的。也就是做到了代理对象能够代理多个目标类,多个目标方法。
注意:我们这里使用的是 JDK 动态代理,要求是必须要实现接口。与之对应的另外一种动态代理实现模式 Cglib,则不需要,我们这里就不讲解 cglib 的实现方式了。
不管是哪种方式实现动态代理。本章的主角:AOP 实现原理也是动态代理