通俗易懂的讲解一下Java的代理模式
一、基本概念
代理模式是对象的结构模式。
代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用(接口的引用)
二、静态代理
静态代理是指,代理类在程序运行前就已经定义好,其与**目标类(被代理类)**的关系在程序运行前就已经确立。
静态代理类似于企业与企业的法律顾问间的关系。法律顾问与企业的代理关系,并不是在“官司“发生后才建立的,而是之前就确立好的一种关系。
而动态代理就是外面打官司一样,是官司发生了之后临时请的律师。
代理可以看做就是在被代理对象外面包裹一层(和装饰者类似但又不同):
案例: 比如我们有一个可以移动的坦克,它的主要方法是move()
,但是我们需要记录它移动的时间,以及在它移动前后做日志,其静态代理的实现模式就类似下面的图:
两个代理类以及结构关系:
代码:
public interface movable { void move(); } public class tank implements movable { @override public void move() { // 坦克移动 system.out.println("tank moving......"); try { thread.sleep(new random().nextint(5000)); // 随机产生 1~5秒, 模拟坦克在移动 } catch (interruptedexception e) { e.printstacktrace(); } } }
两个代理类: tanktimeproxy
和tanklogproxy
:
public class tanktimeproxy implements movable { private movable tank; public tanktimeproxy(movable tank) { this.tank = tank; } @override public void move() { // 在前面做一些事情: 记录开始时间 long start = system.currenttimemillis(); system.out.println("start time : " + start); tank.move(); // 在后面做一些事情: 记录结束时间,并计算move()运行时间 long end = system.currenttimemillis(); system.out.println("end time : " + end); system.out.println("spend all time : " + (end - start)/1000 + "s."); } } public class tanklogproxy implements movable { private movable tank; public tanklogproxy(movable tank) { this.tank = tank; } @override public void move() { // tank 移动前记录日志 system.out.println("tank log start......."); tank.move(); // tank 移动后记录日志 system.out.println("tank log end......."); } }
测试:
public class client { public static void main(string[] args){ movable target = new tanklogproxy(new tanktimeproxy(new tank())); //先记录时间,再记录日志 // movable target = new tanktimeproxy(new tanklogproxy(new tank())); //先记录日志,再记录时间 target.move(); } }
输出:
tank log start....... start time : 1551271511619 tank moving...... end time : 1551271514522 spend all time : 2s. tank log end.......
这其中有两个很重要的点,那就是:
- 两个代理对象内部都有着被代理对象(target)实现的接口的引用;
- 且两个代理对象都实现了被代理对象(target)实现的接口;
三、基本动态代理
上面静态代理的缺点在哪?
现在单看做时间这个代理,如果我们现在多了一个飞机,飞机里面的方法是fly()
,现在要给飞机做代理,那么我们不能用之前写的tanktimeproxy
,我们需要额外的写一个planetimeproxy
,这明显是冗余代码,所以这就是静态代理最大的缺点,这可以用动态代理解决。
动态代理是指,程序在整个运行过程中根本就不存在目标类的代理类(在jdk内部叫$proxy0
,我们看不到),目标对象的代理对象只是由代理生成工具(如代理工厂类) 在程序运行时由 jvm 根据反射等机制动态生成的。代理对象与目标对象的代理关系在程序运行时才确立。
对比静态代理,静态代理是指在程序运行前就已经定义好了目标类的代理类。代理类与目标类的代理关系在程序运行之前就确立了。
首先看动态代理的一些特点:
- 动态代理不需要写出代理类的名字,你要的代理对象我直接给你产生,是使用的时候生成的;
- 只需要调用
proxy.newproxyinstance()
就可以给你产生代理类;
jdk动态代理相关api:
下面看使用动态代理解决上面的问题(可以用timeproxy
代理一切对象):
public interface movable { void move(); } public class tank implements movable { @override public void move() { // 坦克移动 system.out.println("tank moving......"); try { thread.sleep(new random().nextint(5000)); // 随机产生 1~5秒, 模拟坦克在移动 } catch (interruptedexception e) { e.printstacktrace(); } } }
新增的飞机:
public interface flyable { void fly(); } public class plane implements flyable{ @override public void fly() { system.out.println("plane flying......"); try { thread.sleep(new random().nextint(5000)); // 随机产生 1~5秒, 飞机在飞行 } catch (interruptedexception e) { e.printstacktrace(); } } }
我们的关键处理,即编写mytimeproxyinvocationhandler
:
// 静态代理做不到既为飞机做时间代理,又为坦克做时间代理,但是动态代理可以为所有对象做代理 public class mytimeproxyinvocationhandler implements invocationhandler { private object target;//注意这里是 object ,不是movable或者flyable public mytimeproxyinvocationhandler(object target) { this.target = target; } // proxy : 代理对象 可以是一切对象 (object) // method : 目标方法 // args : 目标方法的参数 @override public object invoke(object proxy, method method, object[] args) throws throwable { // 在前面做一些事情: 记录开始时间 long start = system.currenttimemillis(); system.out.println("start time : " + start); method.invoke(target, args); // 调用目标方法 invoke是调用的意思, 可以有返回值的方法(我们这里move和fly都没有返回值) // 在后面做一些事情: 记录结束时间,并计算move()运行时间 long end = system.currenttimemillis(); system.out.println("end time : " + end); system.out.println("spend all time : " + (end - start)/1000 + "s."); return null; } }
最后测试类:
public class client { public static void main(string[] args){ movable tank = new tank(); //可以为所有对象产生时间代理的 invocationhandler mytimeproxyinvocationhandler myinvocationhandler = new mytimeproxyinvocationhandler(tank); movable tankproxy = (movable) proxy.newproxyinstance( tank.getclass().getclassloader(), tank.getclass().getinterfaces(), myinvocationhandler ); tankproxy.move(); system.out.println("--------------------"); flyable plane = new plane(); myinvocationhandler = new mytimeproxyinvocationhandler(plane); // 为飞机产生代理, 为..产生代理,这样可以为很多东西产生代理,静态代理做不到 flyable planeproxy = (flyable) proxy.newproxyinstance( plane.getclass().getclassloader(), plane.getclass().getinterfaces(), myinvocationhandler ); planeproxy.fly(); } }
输出(同时为tank
和plane
做了代理):
start time : 1551275526486 tank moving...... end time : 1551275531193 spend all time : 4s. -------------------- start time : 1551275531195 plane flying...... end time : 1551275532996 spend all time : 1s.
我们分析一下这个代理过程:
调用过程(重要):
- jdk内部的
proxy
类在内部创建了一个$proxy0
的代理对象(它实现了目标对象所在接口movable
; -
$proxy0
内部有invocationhandler
接口的引用,然后在$proxy
中调用了接口的invoke()
方法; - 而我们将
invocationhandler
接口的实现类传入了proxy
,所以我们在实现类中加入的前后逻辑就会得到执行;
如果这里还不够理解,可以看代理模式(二),会模拟实现jdk的底层实现。
四、cglib动态代理
问题: 使用 jdk 的 proxy 实现代理,要求目标类与代理类实现相同的接口。若目标类不存在接口,则无法使用该方式实现。
可以用 cglib 来解决上面的问题。
cglib 代理的生成原理是生成目标类的子类,而子类是增强过的,这个子类对象就是代理对象。
所以,使用cglib 生成动态代理,要求目标类必须能够被继承,即不能是 final 的类。
基本结构:
代码:
tank
类(没有接口)
// 没有实现接口 public class tank { public void move() { // 坦克移动 system.out.println("tank moving......"); try { thread.sleep(new random().nextint(5000)); // 随机产生 1~5秒, 模拟坦克在移动 } catch (interruptedexception e) { e.printstacktrace(); } } }
mycglibfactory
类:
import net.sf.cglib.proxy.enhancer; import net.sf.cglib.proxy.methodinterceptor; import net.sf.cglib.proxy.methodproxy; import java.lang.reflect.method; //需要实现methodinterceptor, 当前这个类的对象就是一个回调对象 // mycglibfactory 是 类a,它调用了enhancer(类b)的方法: setcallback(this),而且将类a对象传给了类b // 而类a 的 方法intercept会被类b的 setcallback调用,这就是回调设计模式 public class mycglibfactory implements methodinterceptor { //public interface methodinterceptor extends callback private tank target; public mycglibfactory(tank target) { this.target = target; } public tank mycglibcreator() { enhancer enhancer = new enhancer(); // 设置需要代理的对象 : 目标类(target) , 也是父类 enhancer.setsuperclass(tank.class); // 设置代理对象, 这是回调设计模式: 设置回调接口对象 : enhancer.setcallback(this); // this代表当前类的对象,因为当前类实现了callback return (tank) enhancer.create(); } // 这个就是回调方法(类a的方法) @override public object intercept(object obj, method method, object[] args, methodproxy proxy) throws throwable { // 在前面做一些事情: 记录开始时间 long start = system.currenttimemillis(); system.out.println("start time : " + start); method.invoke(target, args); // 在后面做一些事情: 记录结束时间,并计算move()运行时间 long end = system.currenttimemillis(); system.out.println("end time : " + end); system.out.println("spend all time : " + (end - start)/1000 + "s."); return null; } }
测试:
public class client { public static void main(string[] args){ tank proxytank = new mycglibfactory(new tank()).mycglibcreator(); proxytank.move(); } }
输出(进行了时间代理timeproxy
):
start time : 1551327522964 tank moving...... end time : 1551327526214 spend all time : 3s.
上面的设计模式用到了回调设计模式: 在 java 中,类 a
调用类 b
中的某个方法 b()
,然后类 b
又在某个时候反过来调用类 a
中的某个方法 a()
,对于 a
来说,这个 a()
方法便叫做回调方法。
java 的接口提供了一种很好的方式来实现方法回调。这个方式就是定义一个简单的接口,在接口之中定义一个我们希望回调的方法。这个接口称为回调接口。(callback
) 在前面的例子中,我们定义的 mycglibfactory
类就相当于前面所说的 a
类,而 enhancer
类则是 b
类。a
类中调用了 enhancer
类的 setcallback(this)
方法,并将回调对象 this
作为实参传递给了enhancer 类。enhancer 类在后续执行过程中,会调用a
类中的intercept()
方法,这个 intercept()方法就是回调方法。
免费java高级资料需要自己领取,涵盖了java、redis、mongodb、mysql、zookeeper、spring cloud、dubbo高并发分布式等教程,一共30g。
传送门:https://mp.weixin.qq.com/s/jzddfh-7ynudmkjt0irl8q
上一篇: 复制对象与复制引用
下一篇: Spring (4)框架