基本介绍
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
上面这个是百度百科的介绍,如果是第一次接触,那肯定是看不懂的,应为aop颠覆了传统的oop的思想,提出了一种切面编程的思想,下面通过一个案例来说明aop的基本思想。
案例说明
现在有一个接口Vehicle,两个实现子类,分别是Car和Aircraft,内容如下
Vehicle接口
public interface Vehicle {
public void run();
}
Car实现类
public class Car implements Vehicle{
@Override
public void run() {
System.out.println("小汽车开始运行....");
}
}
Aircraft实现类
public class Aircraft implements Vehicle{
@Override
public void run() {
System.out.println("飞机开始起飞了.....");
}
}
现在有一个要求,在调用run方法的时候,要在调用前输出,run方法开始执行了,调用结束后输出run方法执行完毕。
传统解决思路
我们要解决上面问题非常简单,只需要在实现类的run方法最开始和最后加上这条输出语句就行了,如下:
public class Car implements Vehicle {
@Override
public void run() {
System.out.println("run方法开始执行了");
System.out.println("小汽车开始运行....");
System.out.println("run方法执行完毕");
}
}
public class Aircraft implements Vehicle{
@Override
public void run() {
System.out.println("run方法开始执行了");
System.out.println("飞机开始起飞了.....");
System.out.println("run方法执行完毕");
}
}
这样就轻松实现了,但是这种方法好吗?这只是2个子类,如果有100个呢?我们可以发现,传统的实现方法存在着代码冗余,不利于维护,并且可扩展性很差。
动态代理方式实现
我们通过反射的方式,在原本代码不变的基础上,写一个代理对象类,这个类提供一个方法,返回一个代理对象。代码如下
public class VehicleProxyProvider {
public static Vehicle getProxy(Vehicle vehicle) {
//得到类构造器
ClassLoader classLoader = vehicle.getClass().getClassLoader();
//得到所有接口信息
Class<?>[] interfaces = vehicle.getClass().getInterfaces();
//通过Proxy得到代理对象,返回的是实现Vehicle的一个代理类
Object o = Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
/**
* @param proxy 表示代理对象
* @param method 表示要被代理的方法
* @param args 方法的参数
* @return 方法的返回值
* @throws Throwable 抛出的异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName() + "方法开始执行");
//执行方法
Object invoke = method.invoke(vehicle, args);
System.out.println(method.getName() + "方法执行完毕");
//将方法执行的结果返回
return invoke;
}
});
//返回代理对象
return (Vehicle) o;
}
}
我们将要进行代理的对象先创建,然后调用getProxy方法得到代理后的对象,然后再调用run方法,而我们在代理对象中,就已经在原本的方法调用前和调用后做了一些事情。我们创建一个测试方法来看是否有效,代码如下
@Test
public void t1(){
Car car = new Car();
Aircraft aircraft = new Aircraft();
Vehicle carProxy = VehicleProxyProvider.getProxy(car);
Vehicle aircraftProxy = VehicleProxyProvider.getProxy(aircraft);
carProxy.run();
System.out.println("--------------------------");
aircraftProxy.run();
}
输出如下
可以发现,我们通过动态代理,实现方法的统一处理就方便多了。
横切关注点
我们对上面的Proxy.newProxyInstance里面匿名内部类中的invoke进行一点扩展,变化后的代码如下
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object invoke = null;
try {
System.out.println(method.getName() + "方法开始执行--前置通知");
//执行方法
invoke = method.invoke(vehicle, args);
System.out.println(method.getName() + "方法执行完毕--返回通知");
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
System.out.println(method.getName()+"--异常通知");
e.printStackTrace();
} finally {
System.out.println(method.getName()+"后置通知");
}
//将方法执行的结果返回
return invoke;
}
});
上面的代码就考虑了执行方法发生异常的情况,同时也发现了有4个特殊的位置,方法执行前,方法正常执行后,方法发生异常后,方法执行后。这几个特殊的位置就被称作横切关注点。如下图
AOP引出
可以发现,上面我们使用动态代理,成功解决了问题,但是又出现了新的问题,就是我们的这个动态代理只能基于某一个接口的子类,这就说明我们要为每一个接口都写一个这样的类,这样实在是太麻烦了。并且这种传统的动态代理,不够灵活,复用性差,本质上还是一种硬编码。
这时,我们就要用到spring中的AOP了,AOP就是针对上面的4个横切关注点,实现对方法的切入,AOP叫切面编程也是这样来的,就好像一把刀一样,将方法切成好几个部分,然后对不同的部分进行处理。
总结
在这篇文章中,我们通过一个案例,先通过传统的oop来解决,然后通过动态代理解决,引出了横切关注点,发现了问题所在,最终引出了AOP的思想,就是切面编程,即对方法的不同部分进行处理。