Spring基础20——AOP基础 1、什么是AOP 2.使用动态代理对程序进行改进 3.AOP中的一些概念及AOP的好处
AOP(Aspect-Oriented Programming)即面向切面编程,是一种新的方法论,是对那个传统OOP面向对象编程的补充。AOP的主要编程对象是切面(aspect),而切面模块化横切关注点,在应用AOP编程时,仍需要定义公共功能但可以明确功能在哪里,以什么方式应用,并且不必修改受影响的类,这样一来横切关注点就模块化到了特殊得对象(切面)里,那么什么是面向切面编程呢?
下面我们通过一个例子来理解:
我们需要实现一个计算器,并且有以下两个需求:
需求1:日志,在程序执行期间追寻正在发生的活动。
需求2:希望计算器只能处理正数的运算。
首先我们来定义一个接口,来描述计算器的功能:
1 public interface ArithmeticCalculator { 2 /** 3 * 加法 4 * @param i 5 * @param j 6 * @return 7 */ 8 int add(int i, int j); 9 10 /** 11 * 减法 12 * @param i 13 * @param j 14 * @return 15 */ 16 int sub(int i, int j); 17 18 /** 19 * 乘法 20 * @param i 21 * @param j 22 * @return 23 */ 24 int mul(int i, int j); 25 26 /** 27 * 除法 28 * @param i 29 * @param j 30 * @return 31 */ 32 int div(int i, int j); 33 }
之后我们实现这个接口并对方法进行实现:
1 public class ArithmeticCalculatorImpl implements ArithmeticCalculator { 2 @Override 3 public int add(int i, int j) { 4 int result = i + j; 5 return result; 6 } 7 @Override 8 public int sub(int i, int j) { 9 int result = i - j; 10 return result; 11 } 12 @Override 13 public int mul(int i, int j) { 14 int result = i * j; 15 return result; 16 } 17 @Override 18 public int div(int i, int j) { 19 int result = i / j; 20 return result; 21 } 22 }
编写Main测试类对功能进行测试:
1 public class Main { 2 public static void main(String[] args) { 3 ArithmeticCalculator calculator = new ArithmeticCalculatorImpl(); 4 System.out.println(calculator.add(1,2)); 5 System.out.println(calculator.sub(2,1)); 6 System.out.println(calculator.div(4,2)); 7 System.out.println(calculator.mul(2,4)); 8 } 9 }
我们可以观察到输出结果是正常的。
在上面的基础上,修改ArithmeticCalculatorImpl 我们下面来完成上面两个需求:
1 public class ArithmeticCalculatorImpl implements ArithmeticCalculator { 2 @Override 3 public int add(int i, int j) { 4 if (i < 0 ||j < 0 ) { 5 System.out.println("操作数必须为正数"); 6 return -1; 7 } 8 System.out.println("The add method begin [ " + i + ", " + j + " ]" ); 9 int result = i + j; 10 System.out.println("The add method end [ " + result + " ]" ); 11 return result; 12 } 13 @Override 14 public int sub(int i, int j) { 15 if (i < 0 ||j < 0 ) { 16 System.out.println("操作数必须为正数"); 17 return -1; 18 } 19 System.out.println("The sub method begin [ " + i + ", " + j + " ]" ); 20 int result = i - j; 21 System.out.println("The sub method end [ " + result + " ]" ); 22 return result; 23 } 24 @Override 25 public int mul(int i, int j) { 26 if (i < 0 ||j < 0 ) { 27 System.out.println("操作数必须为正数"); 28 return -1; 29 } 30 System.out.println("The mul method begin [ " + i + ", " + j + " ]" ); 31 int result = i * j; 32 System.out.println("The mul method end [ " + result + " ]" ); 33 return result; 34 } 35 @Override 36 public int div(int i, int j) { 37 if (i < 0 ||j < 0 ) { 38 System.out.println("操作数必须为正数"); 39 return -1; 40 } 41 System.out.println("The div method begin [ " + i + ", " + j + " ]" ); 42 int result = i / j; 43 System.out.println("The div method end [ " + result + " ]" ); 44 return result; 45 } 46 }
再次运行测试类:
1 public class Main { 2 public static void main(String[] args) { 3 ArithmeticCalculator calculator = new ArithmeticCalculatorImpl(); 4 System.out.println(calculator.add(-1,2)); 5 System.out.println(calculator.sub(2,1)); 6 System.out.println(calculator.div(4,2)); 7 System.out.println(calculator.mul(2,4)); 8 } 9 }
输出结果:我们看到是实现了之前的需求。
2.使用动态代理对程序进行改进
我们用上面的代码实现了之前的两个需求,但是我们可以观察到,每个方法中都包含大量的重复代码,如果一旦需要修改日志或参数校验的规则,我们每个方法都要去修改,这显然是非常不利于维护的,那么我们可以采用——动态代理的方式来进行改进。代理模式原理:使用一个代理将对象包装起来,然后用该代理对象取代原始对象任何原始对象的调用都要通过代理,代理对象决定是否以及何时将方法调用转到原始对象上。
代理类ArithmeticCalculatorLoggingProxy:
public class ArithmeticCalculatorLoggingProxy { private ArithmeticCalculator calculator; public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator calculator) { this.calculator = calculator; } public ArithmeticCalculator getCalculatorProxy() { InvocationHandler handler = new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) { System.out.println("The method " + method.getName() + " begin with " + Arrays.asList(args)); Object result = null; try { //前置通知 result = method.invoke(calculator, args); //返回通知,可以访问到方法的返回值 } catch (Exception e) { e.printStackTrace(); //异常通知,可以访问到方法出现的异常 } //后置通知,方法可能会出异常,所以访问不到方法的返回值 System.out.println("The method add " + method.getName() +" with " + result); return result; } }; return (ArithmeticCalculator) Proxy.newProxyInstance(calculator.getClass().getClassLoader(), calculator.getClass().getInterfaces(), handler); } }
将ArithmeticCalculatorImpl类改到最初的版本,重新运行测试类,我们可以得到同样的结果
3.AOP中的一些概念及AOP的好处
AOP中的一些概念:
切面(Aspect):横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象,如上图日志切面和验证切面(在后面还会讲spring aop切面的优先级)。
通知(Advice):切面必须要完成的工作。(切面里的每一个方法,比如前置日志和后置日志)
目标(Target):被通知的对象(被代理对象)
代理(Proxy):向目标对象应用通知之后创建的对象(代理对象)
连接点(JoinPoint):程序执行的某个特定位置:如类某个方法调用前、调用后、方法抛出异常后等。连接点由两个信息确定:方法表示程序执行点;相对点表示的方位。例如ArithmethicCalculator#add()方法执行前的连接点执行点为ArithmethicCalculator#add()方位为该方法执行前位置。
切点(Pointcut):每个类都拥有多个连接点:例如ArithmethicCalculator的所有方法实际上都是连接点,即连接点是程序中客观存在的事物,AOP通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一一对应的关系,一个切点匹配多个连接点,切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件。
AOP的好处:
每个事物逻辑位于一个位置,代码不分散,便于维护和升级
业务模块更加简洁只包含核心业务代码。