AOP和代理模式
回 顾IoC

拿掉IoC容器后的系统如下图所示

简单来讲,我没可以把IoC容器理解为一个和黑箱,它起到了类似于一种“粘合剂”的作用,得到了对象的控制权,使得我们系统各个对象的依赖关系降到了最低限度。
关于OOP
在说AOP之前,我们必须得先聊一下OOP。
OOP,即面向对象编程。OOP是对于真实世界的映射,即将万事万物进行归类并建立模型,抽象出属性和方法,万物皆是对象。运用OOP的思想,我们在编程时就像搭积木一样,一点一点建立。简单来讲,OOP更接近于人类对于现实世界构建的真实想法。
引入AOP
我们要思考这样一个问题,既然OOP就能解决问题,为什么还要创造出AOP呢?
对于面向对象编程,我们的系统功能设计总体上来看是纵向的。以web开发为例。控制器调用服务,服务调用Dao。我们假设这样一种需求场景:
你有一段业务代码,有一天,老板让你统计一下每天使用功能A的人数。于是,你在A的代码后面加上了一段代码,实现了统计每天的使用人数;
又过了一周,老板说为了提高系统运行效率,我们需要看一下功能A每次的执行时间。于是,你在A的代码前后加上了新的代码,实现记录功能A执行时间的功能;
又过了一周,老板说咱们这个系统万一出错了,得记录下日志。于是,你又在功能A的代码后面加上了一段实现日志功能的代码;
又过了一周.......
时间久了,你会发现功能A的内部代码不仅变得臃肿无比,而且大量充斥着和功能A的业务无关的代码。更为恐怖的是,新加的功能也许不仅仅是功能A需要,或许功能B、功能C、功能D....都需要,如果不想出一个优雅的解决方案,我么就需要对功能B、功能C、功能D....一个个进行方法注入,工作量巨大无比且不说,代码也极为冗余。就算你把这些代码写的再规范,再怎么抽时间去优化,这些代码也已经破坏了OOP特性之一的封装性,造成了代码污染。
所以,AOP就诞生了。AOP就是为了解决OOP无法满足的需求,即提供一个横切的方法,不破原始代码的封装性,对已有的代码进行扩展,从而实现更为丰富的功能。而对于上面的例子而言,我们可以运用AOP的思想,把和主干业务无关的功能代码给抽取出来,然后在这些功能运行时将其动态的横向插入进去,减少耦合度和代码冗余。
那么,AOP是如何实现这种“动态的横向插入”的呢?这里就要说到另一种设计模式:代理模式。
代理模式
举一个现实中的例子,假设我们此刻要去租一间房子住,有两种方法

这个实例其实可以大概说明代理模式的含义。在这里,中介其实充当的就是租客的代理,租客之和中介接触,不直接接触房东。也就是说,中介其实帮租客把找房过程中的找地段、谈租金等,和房东签房屋租赁合同的事情给干了,租客只需要完成租房这个抽象的主干业务即可。代理模式又分为静态代理和动态代理,我们先来看一下静态代理的代码实现。
静态代理
1 /* 静态代理 2 第一步:定义抽象的主干业务,即租房 3 */ 4 public interface RentHouse { 5 void rentHouse(); 6 } 7 /* 8 第二步:定义租客,实现租房接口,表明租客有租房的需求 9 */ 10 public class Tenant implements RentHouse{ 11 public void rentHouse() { 12 System.out.println("租客:我想租房!"); 13 } 14 } 15 /* 16 第三步:定义中介,中介是要帮租户实现租房子的 17 需求,所以也要实现租房接口 18 */ 19 public class RentHouseProxy implements RentHouse{ 20 private RentHouse rentHouse; 21 public RentHouseProxy(RentHouse rentHouse){ 22 this.rentHouse=rentHouse; 23 } 24 public void rentHouse() { 25 System.out.println("中介:搜集房源......"); 26 System.out.println("中介:对比市场价格......"); 27 System.out.println("中介:确定租金......"); 28 System.out.println("中介:前房屋租赁合同......"); 29 System.out.println("中介:装修装修,摆点物件......"); 30 rentHouse.rentHouse(); 31 System.out.println("中介:租房完毕......"); 32 } 33 } 34 /* 35 测试 36 结果:中介:搜集房源...... 37 中介:对比市场价格...... 38 中介:确定租金...... 39 中介:前房屋租赁合同...... 40 中介:装修装修,摆点物件...... 41 租客:我想租房! 42 中介:租房完毕...... 43 */ 44 public class ProxyDemo { 45 public static void main(String[] args) { 46 RentHouse rentHouse=new Tenant(); 47 RentHouseProxy proxy=new RentHouseProxy(rentHouse); 48 proxy.rentHouse(); 49 } 50 }
通过以上案例代码我们可以看到,代理模式在不修改被代理对象的基础下,进行了一些功能的附加与增强。至于为什么被称为静态代理,那是因为我们的代理类是事先预定好的,即已经写死了。同时,我们也应该注意到它的缺陷之处:每一个抽象的事务都是一个接口,我们的代理类需要实现这个接口,即我们得为每一个服务都创建一个代理类,工作量太大,不易管理。而且,接口一旦发生改变,代理类也得同步进行相应的修改
动态代理
动态代理我们不需要再手动创建代理类,只需要编写一个动态代理处理器即可,真正的代理对象由JVM在运行时为我们动态的创建,这就是它之所以被称为动态代理的由来。我们看一下案例代码
1 /* 2 动态代理 3 前两步与静态代理相同 4 */ 5 public class DynamicRentHouseProxy implements InvocationHandler { 6 private Object rentHouse; 7 8 public DynamicRentHouseProxy(Object rentHouse) { 9 this.rentHouse = rentHouse; 10 } 11 12 @Override 13 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 14 System.out.println("中介:搜集房源......"); 15 System.out.println("中介:对比市场价格......"); 16 System.out.println("中介:确定租金......"); 17 System.out.println("中介:前房屋租赁合同......"); 18 System.out.println("中介:装修装修,摆点物件......"); 19 Object result=method.invoke(rentHouse,args); 20 System.out.println("中介:租房完毕......"); 21 return result; 22 } 23 } 24 public class ProxyDemo { 25 public static void main(String[] args) { 26 /*RentHouse rentHouse=new Tenant(); 27 RentHouseProxy proxy=new RentHouseProxy(rentHouse); 28 proxy.rentHouse();*/ 29 RentHouse tenant=new Tenant(); 30 InvocationHandler handler=new DynamicRentHouseProxy(tenant); 31 //newProxyInstance运用反射为我们生成一个代理类对象 32 RentHouse rentHouse=(RentHouse) Proxy.newProxyInstance(Tenant.class.getClassLoader(), 33 Tenant.class.getInterfaces(),handler); 34 rentHouse.rentHouse(); 35 } 36 }
看下Proxy类newProxyInstance()的源码
1 //ClassLoader:根据类的字节码直接生成这个类的Class对象 2 //interfaces:由委托实现的接口的Class对象数组,主要是包含了最重要的代理类需要 3 // 实现的接口方法的信息 4 //h:一个实现了InvocationHandler接口的对象 5 //简单来讲第一个参数ClassLoader和第二参数接口的Class对象是用来动态生成委托类的 6 //包括类名,方法名,继承关系在内的一个空壳。只有接口定义的方法名,没有实际操作。实 7 //际的操作是由第三个参数InvocationHandler的invoke()方法来执行 8 public static Object newProxyInstance(ClassLoader loader, 9 Class<?>[] interfaces, 10 InvocationHandler h) { 11 Objects.requireNonNull(h); 12 13 final Class<?> caller = System.getSecurityManager() == null 14 ? null 15 : Reflection.getCallerClass(); 16 17 /* 18 * Look up or generate the designated proxy class and its constructor. 19 */ 20 Constructor<?> cons = getProxyConstructor(caller, loader, interfaces); 21 22 return newProxyInstance(caller, cons, h); 23 }
以上就是动态代理的案例代码。显而易见,我们的代理类并不知道事务的具体名称,也就是说,就算接口中的方法变了,我们的代理类也丝毫不受影响,代理逻辑与业务逻辑是相互独立的,没有耦合。
代理模式还有一种方式,叫做CGLib,这里暂且不说。
总结

浙公网安备 33010602011771号