Java 代理模式的实现和原理详细分析
文章目录
- 代理模式
- 静态代理
- 1. 静态代理的概念
- 2. 静态代理的实现
- 动态代理
- 1. 动态代理的概念
- 2. 动态代理的实现
- 2.1 如何创建一个动态代理对象
- 2.2 完整的动态代理的例子
- 3.动态代理的优势
- 4. 动态代理的原理
- 4.1 提出问题
- 4.2 剖析原理
- 总结
- 参考
代理模式
代理模式是常用的 java 设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。
代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。简单的说就是,我们在访问实际对象时,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。在后面我会解释这种间接性带来的好处。
代理模式结构图(图片来自《大话设计模式》):
对于代理设计模式而言,根据创建代理类的时间点的不同,又可以分为静态代理和动态代理。
静态代理
1. 静态代理的概念
静态代理:由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来。在程序运行之前,代理类的 .class 文件就已经生成。
2. 静态代理的实现
根据上面代理模式的类图,来写一个简单的静态代理的例子。
我这儿举一个比较粗糙的例子,假如一个班的同学要向老师交班费,但是都是通过班长把自己的钱转交给老师。这里,班长就是代理学生上交班费,班长就是学生的代理。
首先,我们创建一个 Person 接口。这个接口就是学生(被代理类),和班长(代理类)的公共接口,他们都有上交班费的行为。这样,学生上交班费就可以让班长来代理执行。
/*** 创建Person接口* @author Gonjan*/
public interface Person {//上交班费void giveMoney();
}
Student 类实现 Person 接口。Student 可以具体实施上交班费的动作。
public class Student implements Person {private String name;public Student(String name) {this.name = name;}@Overridepublic void giveMoney() {System.out.println(name + "上交班费50元");}
}
StudentsProxy 类,这个类也实现了 Person 接口,但是还另外持有一个学生类对象,由于实现了Person 接口,同时持有一个学生对象,那么他可以代理学生类对象执行上交班费(执行 giveMoney()方法)行为。
/*** 学生代理类,也实现了Person 接口,保存一个学生实体,这样即可以代理学生产生行为* @author xzy**/
public class StudentsProxy implements Person{// 被代理的学生Student stu;public StudentsProxy(Person stu) {// 只代理学生对象if(stu.getClass() == Student.class) {this.stu = (Student)stu;}}// 代理上交班费,调用被代理学生的上交班费行为public void giveMoney() {stu.giveMoney();}
}
下面测试一下,看如何使用代理模式:
public class StaticProxyTest {public static void main(String[] args) {// 被代理的学生张三,他的班费上交有代理对象 monitor(班长)完成Person zhangsan = new Student("张三");// 生成代理对象,并将张三传给代理对象Person monitor = new StudentsProxy(zhangsan);// 班长代理上交班费monitor.giveMoney();}
}
运行结果:
这里并没有直接通过张三(被代理对象)来执行上交班费的行为,而是通过班长(代理对象)来代理执行了。这就是代理模式。
代理模式最主要的就是有:
一个公共接口(Person),一个具体的类(Student),一个代理类(StudentsProxy),代理类持有具体类的实例,代为执行具体类实例方法。
上面说到,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。这里的间接性就是指不直接调用实际对象的方法,那么我们在代理过程中就可以加上一些其他用途。就这个例子来说,加入班长在帮张三上交班费之前想要先反映一下张三最近学习有很大进步,通过代理模式很轻松就能办到:
public class StudentsProxy implements Person{// 被代理的学生Student stu;public StudentsProxy(Person stu) {// 只代理学生对象if(stu.getClass() == Student.class) {this.stu = (Student)stu;}}// 代理上交班费,调用被代理学生的上交班费行为public void giveMoney() {System.out.println("张三最近学习有进步!");stu.giveMoney();}
}
运行结果:
可以看到,只需要在代理类中帮张三上交班费之前,执行其他操作就可以了。这种操作,也是使用代理模式的一个很大的优点。
最直白的就是在 Spring 中的面向切面编程(AOP),我们能在一个切点之前执行一些操作,在一个切点之后执行一些操作,这个切点就是一个个方法。这些方法所在类肯定就是被代理了,在代理过程中切入了一些其他操作。
动态代理
1. 动态代理的概念
代理类在程序运行时创建的代理方式被称之为动态代理。
我们上面静态代理的例子中,代理类 (StudentProxy) 是自己定义好的,在程序运行之前就已经编译完成。然而动态代理,代理类并不是在 Java 代码中定义的,而是在运行时根据我们在 Java 代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。 比如说,想要在每个代理的方法前都加上一个处理方法:
public void giveMoney() {// 调用被代理方法前加入处理方法beforeMethod();stu.giveMoney();}
这里只有一个 giveMoney 方法,就写一次 beforeMethod 方法,但是如果除了 giveMonney 还有很多其他的方法,那就需要写很多次 beforeMethod 方法,比较麻烦。那看看下面动态代理如何实现。
2. 动态代理的实现
在 java 的 java.lang.reflect 包下提供了一个 Proxy 类和一个 InvocationHandler 接口,通过这个类和这个接口可以生成 JDK 动态代理类和动态代理对象。
2.1 如何创建一个动态代理对象
创建一个动态代理对象步骤,具体代码见后面的讲解,我们先来看看如何创建一个 InvocationHandler 对象。
- 创建一个 InvocationHandler 对象
// 创建一个与代理对象相关联的InvocationHandlerInvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);
- 使用 Proxy 类的 getProxyClass 静态方法生成一个动态代理类 stuProxyClass
Class<?> stuProxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), new Class<?>[] {Person.class});
- 获得 stuProxyClass 中一个带 InvocationHandler 参数的构造器 constructor
Constructor<?> constructor = PersonProxy.getConstructor(InvocationHandler.class);
- 通过构造器 constructor 来创建一个动态实例 stuProxy
Person stuProxy = (Person) cons.newInstance(stuHandler);
就此,一个动态代理对象就创建完毕,当然,上面四个步骤可以通过 Proxy 类的 newProxyInstances 方法来简化:
// 创建一个与代理对象相关联的InvocationHandlerInvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);
// 创建一个代理对象stuProxy,代理对象的每个执行方法都会替换执行Invocation中的invoke方法Person stuProxy= (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);
到这里肯定都会很疑惑,这动态代理到底是如何执行的,是如何通过代理对象来执行被代理对象的方法的,先不急,我们先看看一个简单的完整的动态代理的例子。还是上面静态代理的例子,班长需要帮学生代交班费。
2.2 完整的动态代理的例子
首先是定义一个 Person 接口:
/*** 创建Person接口* @author Gonjan*/
public interface Person {// 上交班费void giveMoney();
}
创建需要被代理的实际类:
public class Student implements Person {private String name;public Student(String name) {this.name = name;}@Overridepublic void giveMoney() {try {//假设数钱花了一秒时间Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(name + "上交班费50元");}
}
再定义一个检测方法执行时间的工具类,在任何方法执行前先调用 start 方法,执行后调用 finsh 方法,就可以计算出该方法的运行时间,这也是一个最简单的方法执行时间检测工具。
public class MonitorUtil {private static ThreadLocal<Long> tl = new ThreadLocal<>();public static void start() {tl.set(System.currentTimeMillis());}//结束时打印耗时public static void finish(String methodName) {long finishTime = System.currentTimeMillis();System.out.println(methodName + "方法耗时" + (finishTime - tl.get()) + "ms");}
}
创建 StuInvocationHandler 类,实现 InvocationHandler 接口,这个类中持有一个被代理对象的实例 target。InvocationHandler 中有一个 invoke 方法,所有执行代理对象的方法都会被替换成执行 invoke 方法。
再在 invoke 方法中执行被代理对象 target 的相应方法。当然,在代理过程中,我们在真正执行被代理对象的方法前加入自己其他处理。这也是 Spring 中的 AOP 实现的主要原理,这里还涉及到一个很重要的关于 java 反射方面的基础知识(可以看我之前写的 Java 反射小结)。
public class StuInvocationHandler<T> implements InvocationHandler {// invocationHandler 持有的被代理对象T target;public StuInvocationHandler(T target) {this.target = target;}/*** proxy:代表动态代理对象* method:代表正在执行的方法* args:代表调用目标方法时传入的实参*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("代理执行" +method.getName() + "方法");*/ // 代理过程中插入监测方法,计算该方法耗时MonitorUtil.start();Object result = method.invoke(target, args);MonitorUtil.finish(method.getName());return result;}
}
做完上面的工作后,我们就可以具体来创建动态代理对象了,上面简单介绍了如何创建动态代理对象,我们使用简化的方式创建动态代理对象:
public class ProxyTest {public static void main(String[] args) {// 创建一个实例对象,这个对象是被代理的对象Person zhangsan = new Student("张三");// 创建一个与代理对象相关联的InvocationHandlerInvocationHandler stuHandler = new StuInvocationHandler<Person>(zhangsan);// 创建一个代理对象stuProxy来代理zhangsan,代理对象的每个执行方法都会替换执行Invocation中的invoke方法Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(),new Class<?>[]{Person.class}, stuHandler);// 代理执行上交班费的方法stuProxy.giveMoney();}
}
我们执行这个 ProxyTest 类,先想一下,我们创建了一个需要被代理的学生张三,将 zhangsan 对象传给了 stuHandler 中,我们在创建代理对象 stuProxy 时,将 stuHandler 作为参数了的,上面也有说到所有执行代理对象的方法都会被替换成执行invoke 方法,也就是说,最后执行的是 StuInvocationHandler 中的 invoke 方法。所以在看到下面的运行结果也就理所当然了。
3.动态代理的优势
上面说到,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。**是因为所有被代理执行的方法,都是通过在 InvocationHandler 中的 invoke 方法调用的,所以我们只要在 invoke 方法中统一处理,就可以对所有被代理的方法进行相同的操作了。**例如,这里的方法计时,所有的被代理对象执行的方法都会被计时,然而我只做了很少的代码量。
4. 动态代理的原理
4.1 提出问题
动态代理的过程,代理对象和被代理对象的关系不像静态代理那样一目了然,清晰明了。因为动态代理的过程中,我们并没有实际看到代理类,也没有很清晰地的看到代理类的具体样子,而且动态代理中被代理对象和代理对象是通过 InvocationHandler 来完成的代理过程的,其中具体是怎样操作的,为什么代理对象执行的方法都会通过 InvocationHandler 中的 invoke 方法来执行。带着这些问题,我们就需要对java动态代理的源码进行简要的分析,弄清楚其中缘由。
4.2 剖析原理
上面我们利用 Prox y类的 newProxyInstance 方法创建了一个动态代理对象,查看该方法的源码,发现它只是封装了创建动态代理类的步骤(红色标准部分):
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException{Objects.requireNonNull(h);final Class<?>[] intfs = interfaces.clone();final SecurityManager sm = System.getSecurityManager();if (sm != null) {checkProxyAccess(Reflection.getCallerClass(), loader, intfs);}/** Look up or generate the designated proxy class.*/Class<?> cl = getProxyClass0(loader, intfs);/** Invoke its constructor with the designated invocation handler.*/try {if (sm != null) {checkNewProxyPermission(Reflection.getCallerClass(), cl);}final Constructor<?> cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;if (!Modifier.isPublic(cl.getModifiers())) {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {cons.setAccessible(true);return null;}});}return cons.newInstance(new Object[]{h});} catch (IllegalAccessException|InstantiationException e) {throw new InternalError(e.toString(), e);} catch (InvocationTargetException e) {Throwable t = e.getCause();if (t instanceof RuntimeException) {throw (RuntimeException) t;} else {throw new InternalError(t.toString(), t);}} catch (NoSuchMethodException e) {throw new InternalError(e.toString(), e);}}
其实,我们最应该关注的是 Class<?> cl = getProxyClass0(loader, intfs); 这句,这里产生了代理类,后面代码中的构造器也是通过这里产生的类来获得,可以看出,这个类的产生就是整个动态代理的关键,由于是动态生成的类文件,我这里不具体进入分析如何产生的这个类文件,只需要知道这个类文件是缓存在 java 虚拟机中的,我们可以通过下面的方法将其打印到文件里面,一睹真容:
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", Student.class.getInterfaces());String path = "G:/javacode/javase/Test/bin/proxy/StuProxy.class";try(FileOutputStream fos = new FileOutputStream(path)) {fos.write(classFile);fos.flush();System.out.println("代理类class文件写入成功");} catch (Exception e) {System.out.println("写文件错误");}
对这个 class 文件进行反编译,我们看看 jdk 为我们生成了什么样的内容:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.Person;public final class $Proxy0 extends Proxy implements Person
{private static Method m1;private static Method m2;private static Method m3;private static Method m0;/***注意这里是生成代理类的构造方法,方法参数为InvocationHandler类型,看到这,是不是就有点明白*为何代理对象调用方法都是执行InvocationHandler中的invoke方法,而InvocationHandler又持有一个*被代理对象的实例,不禁会想难道是....? 没错,就是你想的那样。**super(paramInvocationHandler),是调用父类Proxy的构造方法。*父类持有:protected InvocationHandler h;*Proxy构造方法:* protected Proxy(InvocationHandler h) {* Objects.requireNonNull(h);* this.h = h;* }**/public $Proxy0(InvocationHandler paramInvocationHandler)throws {super(paramInvocationHandler);}//这个静态块本来是在最后的,我把它拿到前面来,方便描述static{try{//看看这儿静态块儿里面有什么,是不是找到了giveMoney方法。请记住giveMoney通过反射得到的名字m3,其他的先不管m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);m3 = Class.forName("proxy.Person").getMethod("giveMoney", new Class[0]);m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);return;}catch (NoSuchMethodException localNoSuchMethodException){throw new NoSuchMethodError(localNoSuchMethodException.getMessage());}catch (ClassNotFoundException localClassNotFoundException){throw new NoClassDefFoundError(localClassNotFoundException.getMessage());}}/*** *这里调用代理对象的giveMoney方法,直接就调用了InvocationHandler中的invoke方法,并把m3传了进去。*this.h.invoke(this, m3, null);这里简单,明了。*来,再想想,代理对象持有一个InvocationHandler对象,InvocationHandler对象持有一个被代理的对象,*再联系到InvacationHandler中的invoke方法。嗯,就是这样。*/public final void giveMoney()throws {try{this.h.invoke(this, m3, null);return;}catch (Error|RuntimeException localError){throw localError;}catch (Throwable localThrowable){throw new UndeclaredThrowableException(localThrowable);}}//注意,这里为了节省篇幅,省去了toString,hashCode、equals方法的内容。原理和giveMoney方法一毛一样。}
jdk 为我们生成了一个叫 $Proxy0(这个名字后面的 0 是编号,有多个代理类会依次递增)的代理类,这个类文件是放在内存中的,我们在创建代理对象时,就是通过反射获得这个类的构造方法,然后创建的代理实例。通过对这个生成的代理类源码的查看,我们很容易能看出,动态代理实现的具体过程。
我们可以把 InvocationHandler 看做一个中介类,中介类持有一个被代理对象,在 invoke 方法中调用了被代理对象的相应方法。通过聚合方式持有被代理对象的引用,把外部对 invoke 的调用最终都转为对被代理对象的调用。
代理类调用自己方法时,通过自身持有的中介类对象来调用中介类对象的 invoke 方法,从而达到代理执行被代理对象的方法。也就是说,动态代理通过中介类实现了具体的代理功能。
总结
生成的代理类:$Proxy0 extends Proxy implements Person,我们看到代理类继承了 Proxy 类,所以也就决定了 java 动态代理只能对接口进行代理,Java的继承机制注定了这些动态代理类们无法实现对 class (类)的动态代理。
上面的动态代理的例子,其实就是 AOP 的一个简单实现了,在目标对象的方法执行之前和执行之后进行了处理,对方法耗时统计。Spring 的 AOP 实现其实也是利用了 Proxy 代理类和 InvocationHandler 接口类这两个东西的。
参考
- java动态代理实现与原理详细分析
- JAVA动态代理
- https://github.com/hgncxzy/DesignModeDemo
Java 代理模式的实现和原理详细分析相关推荐
- Java代理模式汇编
文章目录 Java 代理模式实现方式,主流如下五种方法 Notes 静态代理实现 实现步骤 Cat接口 委托类 Lion 代理类角色(FeederProxy) 静态代理类测试 动态代理类(基于接口实现 ...
- java代理模式_Java代理
java代理模式 本文是我们名为" 高级Java "的学院课程的一部分. 本课程旨在帮助您最有效地使用Java. 它讨论了高级主题,包括对象创建,并发,序列化,反射等. 它将指导您 ...
- java代理模式(java代理模式和适配器模式)
Java设计模式的中介者模式是怎样的? 如果对象之间的关系原本一目了然,中介对象的加入便是"画蛇添足". 来看下中介者模式的组成部分吧. 1) 抽象中介者(Mediator)角色: ...
- java代理模式总结
Java代理模式根据代理类生成时间的不同,可以分为静态代理和动态代理,它如同中介机构,可以为目标类提供代理服务,以控制对对象的访问,目标类的任何方法在执行前都必须经过代理类,这样代理类就可以用来负责请 ...
- java代理模式实现
java代理模式实现 @(代理模式)[静态代理,动态代理,InvocationHandler] java的代理模式 分为两种,静态代理和动态代理,学习下什么是代理和静态动态代理的作用. java代理模 ...
- 静态代理模式(多线程底部原理)
静态代理模式总结(线程底部原理) 真实对象和代理对象都要实现同一个接口 代理对象要代理真实角色 好处: - 代理对象可以做很多真实对象做不了的事情 - 真实对象专注做自己的事情 创建静态代理模式:一个 ...
- [Java] 代理模式 Proxy Mode
[Java] 代理模式 Proxy Mode 文章目录 [Java] 代理模式 Proxy Mode 1. 代理思想 2.java.lang.reflect.Proxy类 2.1 利用反射创建prox ...
- 浅谈自己对Java代理模式的理解--即为什么要用怎么用
首先,国际惯例,上Java代理模式的定义: Java代理模式:对其他对象提供一种代理以控制对这个对象的访问. 定义很简单,就一句话,怎么去理解,不急,先听一个小故事: 故事 ...
- 阿里十年资深程序员吐血总结之Java代理模式
阿里十年资深程序员吐血总结之Java代理模式 文章目录 阿里十年资深程序员吐血总结之Java代理模式 1.接口代理 2.类代理 3.动态代理都是通过反射实现的吗 4.jdk动态代理和cglib动态代理 ...
最新文章
- android系统密码设置功能,手机锁屏密码怎么设置 三种安卓手机锁屏方式推荐
- web网站无法启动报错
- 创建原生JS insertafter()方法实现
- Ubuntu基础知识
- Python基础:对象的深拷贝和浅拷贝的区别
- python哲学翻译_Python
- pypinyin 获取多音字的拼音组合
- 主题图标_iPhone一键更换主题、图标神器
- AndroidStudio出现 Unknown verification type [95] in stack map frame 问题的解决办法
- 使用Python的pip方法安装第三方包时,很慢或者失败的问题
- 零基础学启发式算法(5)-遗传算法 (Genetic Algorithm)
- 升级Windows 10 正式版过程记录与经验
- 详细剖析PS软件中的通道原理,让你完全理解颜色通道与Alpha通道
- 计算机二级ms office2021教材,全国计算机等级考试二级MS Office高级应用教材(2021年版)...
- 宜远公众号H5网页AI测肤报告分享
- 人类小行星探测任务回顾
- win rar如何注册破解
- 企业微信为何出现信息发不出去的情况
- XMind、Axure、Visio这三个软件产品经理需要掌握哪个?要掌握到什么程度?
- 动画函数封装 —— 筋头云图案跟随鼠标移动