了解静态代理和动态代理

Java 静态代理静态代理通常用于对原有业务逻辑的扩充。比如持有二方包的某个类,并调用了其中的某些方法。然后出于某种原因,比如记录日志、打印方法执行时间,但是又

其实了解静态代理和动态代理的问题并不复杂,但是又很多的朋友都不太了解,因此呢,今天小编就来为大家分享了解静态代理和动态代理的一些知识,希望可以帮助到大家,下面我们一起来看看这个问题的分析吧!

这实际上是代理模式的一种实现,通过封装真实的对象来实现可扩展性。

典型的代理模式通常具有三个角色,这里称为**代理三要素**

通用接口

公共接口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 方法将被调用。

我们用一张图来总结一下上面的过程:

用户评论

了解静态代理和动态代理
回到你身边

终于找到了解释静态代理和动态代理的文章! 之前总是混淆这两者,现在看懂了区别了。感觉动态代理更加灵活,可以用反射机制控制对象的行为,真的厉害!

    有8位网友表示赞同!

了解静态代理和动态代理
金橙橙。-

其实理解起来很简单,就是静态的直接转发调用,而动态的在运行时生成,这样动态代理能做一些更复杂的业务逻辑处理吧?

    有14位网友表示赞同!

了解静态代理和动态代理
爱到伤肺i

我比较倾向于使用静态代理,因为感觉它的实现代码相对简洁清晰。 动态代理虽然强大,但是实现起来可能复杂一点。

    有12位网友表示赞同!

了解静态代理和动态代理
反正是我

这篇文章写的很好! 帮助我更好地理解了这两个代理模式。之前总是听说过,但是没有深入了解过,现在终于明白了它们的应用场景和特点区别。

    有16位网友表示赞同!

了解静态代理和动态代理
我家的爱豆是怪比i

学习计算机原理需要很多实践,理论知识往往需要结合实际案例才能真正理解。 这篇文章把静态代理和动态代理解释的通俗易懂,加上代码示例,我感觉对我的学习很有帮助!

    有8位网友表示赞同!

了解静态代理和动态代理
全网暗恋者

这个"装饰者模式"我觉得也像代理的一种吧? 我之前好像没听过动态代理的说法,看起来还是挺强大的。以后有机会一定要学一hah!

    有17位网友表示赞同!

了解静态代理和动态代理
微信名字

虽然文章解释的很清楚,但是我自己还是很难理解这种通过代理来更改方法行为的原理,感觉有点抽象。

    有19位网友表示赞同!

了解静态代理和动态代理
颓废i

我还是更喜欢静态代理,看起来代码更加简洁易懂。

    有18位网友表示赞同!

了解静态代理和动态代理
百合的盛世恋

我感觉有时候用到的都是static方法,dynamic代理能解决什么问题呢? 可以详细说说吗?

    有19位网友表示赞同!

了解静态代理和动态代理
青衫负雪

我对动态代理的理解还不太深,特别是反射机制的使用。 感觉是很有用的知识点,以后要好好研究一下。

    有17位网友表示赞同!

了解静态代理和动态代理
汐颜兮梦ヘ

这篇文章把静态代理和动态代理解释得非常清晰详细,我终于明白了它们的区别和应用场景。之前总是把它们混淆,现在可以自信地说出它们的优缺点了!

    有14位网友表示赞同!

了解静态代理和动态代理
栀蓝

学习软件设计模式真的很重要, 希望能写出更加高质量的代码。

    有8位网友表示赞同!

了解静态代理和动态代理
一个人的荒凉

我觉得有时候静态代理比动态代理更简单易理解,特别是对于新手程序员来说。

    有15位网友表示赞同!

了解静态代理和动态代理
发呆

这个博文很有用,帮我在动态代理这个陌生的概念上有了更多的了解,看来这个模式在实际开发中用途广泛啊!

    有14位网友表示赞同!

了解静态代理和动态代理
杰克

感觉文章里写的例子都挺直白的,我理解起来比较顺畅。希望以后还能看到更多相关的知识分享!

    有15位网友表示赞同!

原创文章,作者:小su,如若转载,请注明出处:https://www.sudun.com/ask/127827.html

(0)
小su's avatar小su
上一篇 2024年9月1日 下午11:18
下一篇 2024年9月1日 下午11:27

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注