其实了解静态代理和动态代理的问题并不复杂,但是又很多的朋友都不太了解,因此呢,今天小编就来为大家分享了解静态代理和动态代理的一些知识,希望可以帮助到大家,下面我们一起来看看这个问题的分析吧!
这实际上是代理模式的一种实现,通过封装真实的对象来实现可扩展性。
典型的代理模式通常具有三个角色,这里称为**代理三要素**
通用接口
公共接口Action {public void doSomething();} 真实对象
public class RealObject 实现Action{public void doSomething() {System.out.println(‘do Something’);}}代理对象
public class Proxy 实现Action {private Action realObject;public Proxy(Action realObject) {this.realObject=realObject;}public void doSomething() {System.out.println(‘proxy do’);realObject.doSomething();}}运行代码
代理proxy=new Proxy(new RealObject());proxy.doSomething();
这种代理模式也是最简单的。它通过代理持有realObject的引用,并进行一层封装。
静态代理的优点和缺点
我们先看一下代理模式的优点: 在不侵入原有代码的情况下扩展原有功能。
我们看一下这种代理模型的缺点:
如果有这样的需求,有十个不同的RealObject,我们需要代理的方法也不同。例如,代理方法有:doSomething、doAnotherThing、doTwoAnotherThing。在添加代理之前,原来的代码可能是这样的:
realObject.doSomething();realObject1.doAnotherThing();realObject2.doTwoAnother();为了解决这个问题,我们有解决方案一:
为这些方法创建不同的代理类。代理后的代码如下:
proxy.doSomething();proxy1.doAnotherThing();proxy2.doTwoAnother();当然,还有第二种选择:
通过创建代理,持有不同的realObject,并实现Action1、Action2、Action3接口,代码就变成这样:
proxy.doSomething();proxy.doAnotherThing();proxy.doTwoAnother();所以你的代理模型将变成这样:
毫无疑问,只是为了扩展相同的功能,在一种场景下,我们会重复创建多个具有相同逻辑且只有RealObject 引用的Proxies。
方案2中,会导致代理扩容,而这种扩容往往是没有意义的。另外,如果方法签名相同,调用时还需要引入额外的判断逻辑。
java动态代理
了解静态代理的缺点非常重要,因为动态代理的目的就是为了解决静态代理的缺点。通过使用动态代理,我们可以动态生成一个持有RealObject 的Proxy,并在运行时实现代理接口,同时注入我们相同的扩展逻辑。即使您要代理的RealObject 是不同的对象,甚至是不同的方法,您也可以使用动态代理来扩展功能。
简单理解,动态代理就是我们上面提到的解决方案,只不过这些代理是在运行时自动创建和生成的。
动态代理的基本使用
要使用动态代理,需要将要扩展的函数编写在InvocableHandler 实现类中:
public class DynamicProxyHandler Implements InvocableHandler {private Object realObject;public DynamicProxyHandler(Object realObject) {this.realObject=realObject;}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//代理扩展逻辑System.out .println(‘proxy do’);return method.invoke(realObject, args);}}此Handler中的invoke方法实现了代理类要扩展的公共函数。
此时,需要看一下这个handler的用法:
public static void main(String[] args) {RealObject realObject=new RealObject();Action proxy=(Action) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Action.class}, new DynamicProxyHandler(realObject) );proxy.doSomething();}Proxy.newProxyInstance 传入一个ClassLoader、一个代理接口以及我们定义的handler,并返回一个Proxy实例。
如果你仔细理解这个过程的话,其实和我们在静态代理中提到的解决方案有些类似。生成一个代理实例Proxy,它包含我们的扩展功能,保存RealObject引用,并实现Action接口。只不过这个Proxy不是我们自己写的,而是java给我们生成的。是不是有点动感的味道呢?
我们先回顾一下代理的三要素:真实对象:RealObject、代理接口:Action、代理实例:Proxy
上面代码的实际含义是输入RealObject和Action,返回一个Proxy。适当的代理模式。
综上所述,动态生成+代理模式也是一种动态代理。
看一下源代码
原因很清楚,但是本文的标题是为了理解,所以我们来看看这个Proxy是如何自动生成的。入口在newProxyInstance方法中。核心代码如下:
私有静态最终Class?[] constructorParams={ InvocalHandler.class };公共静态对象newProxyInstance(ClassLoader加载器,Class?[]接口,InvocableHandler h)抛出IllegalArgumentException {Class? cl=getProxyClass0(loader, intfs);最终构造函数? cons=cl.getConstructor(constructorParams);if (!Modifier.isPublic(cl.getModifiers())) {AccessController.doPrivileged(new PrivilegedActionVoid() {public Void run() {cons.setAccessible(true);return null ;} });}return cons.newInstance(new Object[]{h});}整体流程为:
1.生成代理类Proxy的Class对象。
2、如果Class作用域是私有的,则支持通过setAccessible访问
3. 获取Proxy Class构造函数并创建Proxy代理实例。
生成代理类文件
在生成Class对象的方法中,首先通过传入ClassLoader参数和Class[]数组作为组件key来维护一个Proxy的Class对象的缓存。这样,当需要同一个Proxy的Class对象时,只需要创建一次。
第一次创建Class文件时,该方法为了线程安全进行了很多处理,最终来到ProxyClassFactory的apply方法,它经历了以下过程:
1、验证传入接口是否被传入的ClassLoader加载。
2. 验证传入的类对象是否是接口的Class对象。
3. 验证接口是否传入重复。
4. 将代理类包名和类名组装起来,生成.class文件的字节码。
5、调用native方法,传入字节码,生成Class对象。
proxyPkg=ReflectUtil.PROXY_PACKAGE + ‘.’;long num=nextUniqueNumber.getAndIncrement();String proxyName=proxyPkg + proxyClassNamePrefix + num;byte[] proxyClassFile=ProxyGenerator.generateProxyClass(proxyName, Interfaces, accessFlags);return DefineClass0(loader, proxyName) ,proxyClassFile, 0, proxyClassFile.length);看一下第4步生成.class文件字节码的过程,主要分为两个阶段:
addProxyMethod(hashCodeMethod, Object.class);addProxyMethod(equalsMethod, Object.class);addProxyMethod(toStringMethod, Object.class);for (int i=0; i 接口.length; i++) {Method[] 方法=接口[i ].getMethods();for(int j=0;jmethods.length;j++){addProxyMethod(methods[j],interfaces[i]);}}methods.add(this.generateConstructor());for(ListProxyMethod sigmethods : proxyMethods.values()) {for (ProxyMethod pm : sigmethods) {fields.add(new FieldInfo(pm.methodFieldName,’Ljava/lang/reflect/Method;’, ACC_PRIVATE | ACC_STATIC));methods.add(pm .generateMethod());}}methods.add(generateStaticInitializer());第一阶段的代码比较清晰,主要是添加各种方法,比如toString()、equals、传入代理接口中的方法等。添加构造方法和静态初始化方法。这构成了一个对象,存储有关生成代理的类的一些信息。
至此,要构造的Proxy方法的基本定义已经完成,接下来就是生成.class文件了。
ByteArrayOutputStream bout=new ByteArrayOutputStream();DataOutputStream doout=new DataOutputStream(bout);dout.writeInt(0xCAFEBABE);dout.writeShort(ACC_PUBLIC|ACC_FINAL|ACC_SUPER);return bout.toByteArray();参见这个CAFEBABE,你就知道第二阶段的内容了。 CAFEBABE 是Class 文件的神奇数字。相信每一个从事Java工作的人都知道Class文件的神奇数字,咖啡宝贝。没错,第二阶段就是生成字节码。根据JVM规范,编写包含权限控制、方法表、字段表等内容的Class文件,生成符合规范的Class文件。最后返回对应的字节码。
生成字节码后,通过调用原生方法defineClass解析字节码生成Proxy Class对象。
代理构造函数
看一下Proxy的构造函数的字节码生成部分:
MethodInfo minfo=new MethodInfo(‘init’, ‘(Ljava/lang/reflect/InitationHandler;)V’,ACC_PUBLIC);DataOutputStream out=new DataOutputStream(minfo.code);code_aload(0, out);code_aload(1, out );out.writeByte(opc_invokespecial);out.writeShort(cp.getMethodRef(superclassName,’init’, ‘(Ljava/lang/reflect/InitationHandler;)V’));关键是生成了一个参数如InvokingHandler的构造方法中,代码加载jvm方法区中的代码,然后通过invokespecial指令调用父类构造方法。
查看生成的Class文件
上面使用字节码生成技术生成Class文件的过程可能看起来比较晦涩难懂。其实我们可以查看一下生成的Proxy是什么样子的。
注意ProxyGenerator中有这样一个逻辑:
if(saveGenerateFiles) {.FileOutputStream file=new FileOutputStream(dotToSlash(name) + ‘.class’);file.write(classFile);} 再看一下saveGenerateFiles 变量:
私有最终静态布尔saveGenerateFiles=java.security.AccessController.doPrivileged(new GetBooleanAction(‘sun.misc.ProxyGenerator.saveGenerateFiles’)).booleanValue();这是一个最终类型变量。通过GetBooleanAction方法读取系统变量来获取系统设置。默认情况下,该值为false。查看System类的源码,发现有一个API可以设置系统变量。然后在程序的main函数中设置这个变量:
System.getProperties().setProperty(‘sun.misc.ProxyGenerator.saveGenerateFiles’, ‘true’);
这时,如果再次运行程序,就可以看到生成的Proxy Class文件。你可以直接双击它,然后使用ide来反编译它。
包com.sun.proxy;导入java.lang.reflect.InitationHandler;导入java.lang.reflect.Method;导入java.lang.reflect.Proxy;导入java.lang.reflect.UndeclaredThrowableException;公共最终类$Proxy0 扩展Proxy实现Action {private static Method m1;private static Method m3;private static Method m2;private static Method m0;public $Proxy0(InvocableHandler var1) throws {super(var1);}public final void doSomething() throws {try {super. h.invoke(this, m3, (Object[])null);} catch (RuntimeException | Error var2) {抛出var2;} catch (Throwable var3) {抛出新的UndeclaredThrowableException(var3);}}.静态{try {.m3=Class.forName(‘Action’).getMethod(‘doSomething’, new Class[0]);} catch (NoSuchMethodException var2) {抛出new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage());}}} 省略一些不相关的代码,可以看到两个重要的方法。
一个是我们的代理方法doSomething,另一个是构造函数方法。
这个$Proxy0继承了Proxy并调用父类的构造函数。回想一下上面提到的invokeSpecial。怎么样,对吧?
看一下Proxy中的这个构造函数:
protected Proxy(InvocalHandler h) {Objects.requireNonNull(h);this.h=h;} 我们看一下$Proxy0的代理方法:
super.h.invoke(this, m3, (Object[])null);
我们回顾一下生成Proxy实例的过程:
private static Final Class?[] constructorParams={ InvocalHandler.class };final 构造函数? cons=cl.getConstructor(constructorParams);return cons.newInstance(new Object[]{h});其实newInstance生成的是Proxy实例,通过$Proxy0的Class对象,选择以这个InitationHandler为构造函数参数,传入我们定义的InvocableHandler,生成Proxy0的实例! IncationHandler包含realObject的逻辑和我们的扩展逻辑。当我们调用Proxy0 的doSomething 方法时,我们的InvocableHandler 中实现的invoke 方法将被调用。
我们用一张图来总结一下上面的过程:
原创文章,作者:小su,如若转载,请注明出处:https://www.sudun.com/ask/127827.html
用户评论
回到你身边
终于找到了解释静态代理和动态代理的文章! 之前总是混淆这两者,现在看懂了区别了。感觉动态代理更加灵活,可以用反射机制控制对象的行为,真的厉害!
有8位网友表示赞同!
金橙橙。-
其实理解起来很简单,就是静态的直接转发调用,而动态的在运行时生成,这样动态代理能做一些更复杂的业务逻辑处理吧?
有14位网友表示赞同!
爱到伤肺i
我比较倾向于使用静态代理,因为感觉它的实现代码相对简洁清晰。 动态代理虽然强大,但是实现起来可能复杂一点。
有12位网友表示赞同!
反正是我
这篇文章写的很好! 帮助我更好地理解了这两个代理模式。之前总是听说过,但是没有深入了解过,现在终于明白了它们的应用场景和特点区别。
有16位网友表示赞同!
我家的爱豆是怪比i
学习计算机原理需要很多实践,理论知识往往需要结合实际案例才能真正理解。 这篇文章把静态代理和动态代理解释的通俗易懂,加上代码示例,我感觉对我的学习很有帮助!
有8位网友表示赞同!
全网暗恋者
这个"装饰者模式"我觉得也像代理的一种吧? 我之前好像没听过动态代理的说法,看起来还是挺强大的。以后有机会一定要学一hah!
有17位网友表示赞同!
微信名字
虽然文章解释的很清楚,但是我自己还是很难理解这种通过代理来更改方法行为的原理,感觉有点抽象。
有19位网友表示赞同!
颓废i
我还是更喜欢静态代理,看起来代码更加简洁易懂。
有18位网友表示赞同!
百合的盛世恋
我感觉有时候用到的都是static方法,dynamic代理能解决什么问题呢? 可以详细说说吗?
有19位网友表示赞同!
青衫负雪
我对动态代理的理解还不太深,特别是反射机制的使用。 感觉是很有用的知识点,以后要好好研究一下。
有17位网友表示赞同!
汐颜兮梦ヘ
这篇文章把静态代理和动态代理解释得非常清晰详细,我终于明白了它们的区别和应用场景。之前总是把它们混淆,现在可以自信地说出它们的优缺点了!
有14位网友表示赞同!
栀蓝
学习软件设计模式真的很重要, 希望能写出更加高质量的代码。
有8位网友表示赞同!
一个人的荒凉
我觉得有时候静态代理比动态代理更简单易理解,特别是对于新手程序员来说。
有15位网友表示赞同!
发呆
这个博文很有用,帮我在动态代理这个陌生的概念上有了更多的了解,看来这个模式在实际开发中用途广泛啊!
有14位网友表示赞同!
杰克
感觉文章里写的例子都挺直白的,我理解起来比较顺畅。希望以后还能看到更多相关的知识分享!
有15位网友表示赞同!