[Java散记]静态代理和动态代理

何为代理?

代理在我们生活中随处可见,例如我们买票,可以直接去售票处买,也可以在网上买票,那网上APP就相当于是售票处的一个代理。再比如超市和批发市场,我们可以直接去批发市场买东西,也可以在超市买东西,但是作为去批发市场会比较麻烦(一般是偏远的地方),而作为代理的超市,我们同样可以买,但是价格就可能较批发市场贵一些,但是却省下了我们去批发市场的麻烦。

代理类和被代理类的共同点

这两者有没有共同点呢?答案是有的。对于上面的例子,我们会发现无论是在售票处还是在网上买,它们都有一个共同的方法,那就是售票;同样的,批发市场和超市都可以卖东西。

这个也容易理解,你想代理别人,那么你就得可以实现别人的功能才行。

静态代理

Java中的静态代理其实就和我们上面的例子一样,下面我想举一个售票员和黄牛的例子。

在这个例子中,黄牛代理类,而售票员被代理类

其关系图如下所示:

售票员,黄牛及游客之间的关系

这里的访问其实就是买票,返回结果呢其实就是得到票。

下面我们通过编码方式来实现这个功能:

/** * Worker接口,有一个方法可以卖票; */ interface Worker { // 售票方法; void sell(); } /** * 售票员类,实现了Worker接口,可以进行售票,也就是"被代理类"对象; */ class Seller implements Worker { @Override public void sell() { System.out.println("成功把票卖出去了!"); } } /** * 黄牛类,也实现了Worker接口,也可以售票,称为"代理类"对象; */ class Scalper implements Worker { // 私有一个被代理类的父类引用,这样做是为了适应所有的被代理类对象,只要实现了接口就好; private Worker worker; // 传入被代理类对象,这里的作用是初始化"代理类"中的"被代理类"对象; public Scalper(Worker worker) { this.worker = worker; } /** * 增强服务和功能; */ @Override public void sell() { // 代理服务; worker.sell(); // 额外服务; noQueue(); } // 代理类本身自带功能; public void noQueue() { System.out.println("不用排队哟!!!"); } } /** * 主函数; */ public class StaticProxyDemo { public static void main(String[] args){ // 首先是原始类,也就是被代理类; Worker worker = new Seller(); worker.sell(); System.out.println("-----------------------"); // 其次是代理类,传入被代理类对象; Worker pw = new Scalper(worker); pw.sell(); } }

测试结果:

成功把票卖出去了! ----------------------- 成功把票卖出去了! 不用排队哟!!!

可以观察到结果,虚线以上的是被代理类(也就是售票员)的方法的执行,下面的是代理类(也就是黄牛)的方法的执行;可以看到代理类在不改变原有类(也就是被代理类)的情况下,对其功能进行扩展

静态代理的优劣

优:

职责清晰。可以看到,代理类的职责是很清楚的,对于上面的例子,我们可以很容易了解到代理类(黄牛)他可以做什么,因为代码已经明明白白的写清楚了。扩展性好。如果要多增加一个代理类,上述例子可以增加一个代理类(如网上购票APP),也可以实现购票的功能,实现方法如“黄牛”类一样。同时这样的扩展不会影响到原本的类。运行速度快。对于静态代理而言,在程序运行之前,代理类和被代理类的.class文件就已经存在了,因为安排的明明白白,所以运行起来的时候会比动态代理快。

劣:

可维护性低。由于代理类和被代理类都实现了同一个接口,如果接口发生了更改,那么被代理类和所有的代理类都要进行修改,比如接口新增一个方法,那么所有的代理类和被代理类都要重写这个方法,这无疑增加了巨大的工作量。可重用性低。通过观察可以发现,代理类们的代码大体上其实是差不多的,但是由于个别的差异,导致我们不得不重新写一个新的代理类。

动态代理

说完了静态代理,了解到其弊端之后,就有必要说说动态代理了。

动态代理解决了静态代理多个代理类对象的问题,其实你可以理解为它把这些多个代理类都归为一个抽象的对象了(这里的抽象不是指抽象类,而是动态创建的一个代理类,不需要像静态代理那样显示的写出来,就例如上述的例子中,手机售票app和黄牛都通过动态创建,而不是写死)。

首先我们看看代码如何实现:

import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 首先是一个接口Subject,这个接口就类似于上述静态代理中的Worker接口; */ interface Subject { void action(); } /** * 被代理类,也就是真实的类,就类似于上述静态代理中的Seller类; */ class RealSubject implements Subject { @Override public void action() { System.out.println("我是被代理类哦!!"); } } /** * 代理类,也就是代理对象,就类似于上述静态代理中的Scalper类; */ class ProxySubject implements InvocationHandler { // 涉及到动态代理需要实现这个接口InvocationHandler // 实现了接口的被代理类的对象引用声明; private Object object; public Object getNewInstance(Object object) { // 实例化被代理类的对象; this.object = object; // 返回一个代理类的对象; /** * 这里的newProxyInstance的三个参数:(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) *1.第一个参数是需要传入类的加载器,这里指的是被代理类的类加载器,简单来说就是和被代理类使用相同的类加载器; *2.第二个参数是需要传入类的接口,也就是说,这个类实现了哪些接口,我都要传过来; *3.第三个参数是需要传入的一个InvocationHandler对象,指的是代理类对象,也就是调用这个函数的this对象(ProxySubject对象); */ return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), this); } // 当通过代理类的对象发起对被重写的方法的调用是,都会转换为对以下invoke方法的调用; @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 增强代码(前); System.out.println("前增强功能!!"); // 被代理类的方法; Object value = method.invoke(object, args); // 增强代码(后); System.out.println("后增强功能!!"); return value; } } public class DynamicProxyDemo { public static void main(String[] args){ // 1.创建被代理类对象; RealSubject realSubject = new RealSubject(); // 2.创建一个实现了InvocationHandler接口的类的对象; ProxySubject proxySubject = new ProxySubject(); // 3.父类引用指向子类对象; Subject subject = (Subject)proxySubject.getNewInstance(realSubject); // 4.执行代理类的方法; subject.action(); // 使用前面静态代理的例子,创建一个Seller的被代理类对象; Seller seller = new Seller(); // 创建一个Worker的子类对象,指向Seller的代理类对象; Worker worker = (Worker)proxySubject.getNewInstance(seller); // 执行其方法; worker.sell(); } }

首先简单的来解释一下上述代码:

接口Subject:

就是代理类和被代理类都需要实现的接口功能;

RealSubject类

这个类就是真实的类,可以理解为静态代理中的Seller类,实现了Subject的方法;

ProxySubject类

该类为代理类。其持有一个被代理类的父类引用object,在getNewInstance这个函数中,我们传入需要被代理的类对象,将ProxySubject中的Object实例化,同时根据这个传入的object对象,获取其类加载器接口,以及其代理类对象;通过Proxy类本身的静态方法newProxyInstance得到一个代理类对象,并将其返回,后续我们就会操作这个返回的代理类对象。

其中还有一个invoke方法,这里大概的意思就是当我们上述方法创建的代理类对象进行方法调用的时候,都会转化为对invoke方法的调用,例如我们调用通过getNewInstance方法产生的代理类的sell方法,这个sell方法就会传入invoke中,然后转化成对invoke的调用(这里的invoke方法其实就相当于代理类的sell方法,但是它没有写死,而是你传入什么方法,我就调用这个方法,实现了动态调用,与被代理类完全分割开来,完成解耦)。

动态代理的优劣

优:

代码重用性强。同样的,如果我们代理类的增强功能都一样,使用动态代理可以大大减少代码的编写量。代理类与被代理类完全解耦。可以观察到代理类的代码中没有任何与被代理类相关的片段,这就实现了两者的解耦,使得代理类只需要去实现的逻辑,其他的并不关心。

劣:

不易理解。确实不好理解,因为代理类被抽象了。不够灵活。这怎么说起呢?因为在所有的代理类在访问函数的时候,会转化为对invoke函数的调用,也就是说在invoke函数里面新增的功能(如例子中的前后增强功能),都会去执行,可是有些时候我们并不想去执行这些功能,这就不得不再去实现一个代理类了。

追加:

代理模式和装饰者模式的区别

如果你去研究一下这两个模式,可能你会发现这两个模式的代码可能是完全一样的,同样是持有被代理对象/被装饰者的一个引用,对这个对象进行进一步的操作;

在Java自带类中,关于装饰者模式非IO流的各种类莫属了(一层层装饰,使得功能多样化)。但是为什么它们使用装饰者模式而不是代理模式呢?

这两个模式的最基本的区别在于,代理模式对其被代理对象有控制权,而装饰者模式对其装饰者没有控制权

对于代理模式对其被代理对象有控制权的意思是,在代理类中,可以完全不使用被代理对象的方法,而是用自己的方法,也就是尽管持有一个被代理类对象的引用,这个引用也可以不使用。

对于装饰者模式没有对其装饰者的控制权的意思是,装饰者永远是对其被装饰者的功能进行提升的,就是被装饰着原有的东西在装饰者中不可以舍弃。例如你有一个刚刚买了的房子,在装修之后,人把厨房的门砌死了,装修完后少了个厨房,那就亏了,装饰者模式就是这个意思,它只是对被装饰者进行一层修饰,原有的东西不会改变。