前言
经过我们之前的学习我们对IoC有了一定的了解,并已经学会了IoC的基本使用。接下来,我们将要学习Spring另外一个核心机制————AOP。
AOP
为什么要学习AOP?
AOP全称是Aspect Oriented Programming,意思是面向切面编程。
AOP是对面向对象编程的一个补充,在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面编程。将不同方法的同一个位置抽象成一个切面对象,对该切面对象进行编程就是AOP。
AOP的优点:
- 使系统更加容易扩展。
- 更好的提高代码的复用性。
- 降低代码模块之间的耦合度。
- 使业务代码更加简洁纯粹,不参杂其他的非业务代码的影响。
- 使非业务代码更加集中,与业务代码区分开来,不分散,便于统一管理。
案例分析
我们先写一个不使用AOP的案例
1.首先创建一个Maven工程。
(不懂Maven的小伙伴移步到我这个专栏的第一篇文章)
2.在pom.xml文件中引入Spring的AOP依赖。
<dependencies><dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.11.RELEASE</version>
</dependency>
</dependencies>
3.定义一个实现两个数加减的接口。
package com;public interface Cal {
public int add(int num1,int num2);
public int sub(int num1,int num2);
}
4.实现方法。
package com.impl;import com.Cal;
public class CalImpl implements Cal {
public int add(int num1, int num2) {
System.out.println("执行加法,参数是("+num1+","+num2+")");
int result = num1 + num2;
System.out.println("计算结果是:"+result);
return result;
}
public int sub(int num1, int num2) {
System.out.println("执行减法,参数是("+num1+","+num2+")");
int result = num1 - num2;
System.out.println("计算结果是:"+result);
return result;
}
}
5.调用方法。
Cal cal = new CalImpl();cal.add(1,2);
cal.sub(20,10);
打印结果:
执行加法,参数是(1,2)计算结果是:3
执行减法,参数是(20,10)
计算结果是:10
这样是我们不使用AOP的一个案例,这样我们的业务代码和打印日志的代码就会混杂在一起,并且两个方法有重复的部分,这时候我们就可以使用AOP将其打印日志的代码提取出来统一管理。这就是面向切面编程的一个思想。
如何实现AOP?
使用动态代理来实现AOP,日志信息的打印可以交给代理去做,由代理统一管理,提高代码的可扩展性和维护性。
代理就类似于我们现实生活中的中介。
什么是动态代理呢?动态代理就是在我们程序运行过程中,动态创建的一个代理类。
我们接下来用动态代理类来实现AOP。
1.创建MyInvocationHandler类。
package com;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class MyInvocationHandler implements InvocationHandler {
//接受委托对象
private Object object = null;
//返回代理对象
public Object bind(Object object){
this.object = object;
return Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),this);
}
public Object invoke(Object prox, Method method,Object[] args)throws Throwable{
System.out.println(method.getName()+"方法的参数是"+ Arrays.toString(args));
Object result = method.invoke(this.object,args);
System.out.println(method.getName()+"方法的结果是"+result);
return result;
}
}
MyInvocationHandler并不是我们上面所说的动态代理类,这个类是用来创建我们动态代理类的一个类。这里可能会有点绕。我们这里通过实现InvocationHandler这个接口来完成动态代理的功能。
Spring使用Proxy.newProxyInstance类加载器来实现动态代理类的创建。使用object.getClass().getInterfaces()获取委托对象的全部方法。
invoke方法介绍
在代理实例上处理方法调用并返回结果, 当在与之关联的代理实例上调用方法时,将在调用处理程序中调用此方法。我们的日志打印信息就写在invoke方法之中。
参数
- proxy:在其上调用方法的代理实例,也就是动态代理类。
- method: 对应于在代理实例上调用的接口方法的 Method 实例。 Method 对象的声明类将是在其中声明方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口。
- args:包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,则为 null。基本类型的参数被包装在适当基本包装器类(如 java.lang.Integer 或 java.lang.Boolean)的实例中。
2.通过操作代理对象来调用。
Cal cal = new CalImpl();MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
Cal cal1 = (Cal) myInvocationHandler.bind(cal);
cal1.add(1,2);
cal1.sub(20,10);
通过myInvocationHandler中的bind方法,我们传一个委托对象进去,会返回一个代理对象。从而实现调用代理对象的方法。
3.将业务代码中之前的日志打印信息删除。
打印结果:
add方法的参数是[2, 5]add方法的结果是7
sub方法的参数是[7, 4]
sub方法的结果是3
这样我们就使用AOP实现了相同功能,并且将业务代码和非业务代码分开,模块之间的耦合性降低了,代码也更加的简洁。
总结
我们面向切面编程是通过反射机制来实现的,我们学习Spring的IoC和AOP更多的是学习一种编程思想,理解了这种思想,技术自然就水到渠成。这就是通过动态代理的方式来实现AOP的过程了。下一篇文章讲解下通过面向对象的方式来实现AOP。