网易首页 > 网易号 > 正文 申请入驻

详解Java中的静态代理和动态代理

0
分享至

在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。目的:为其他对象提供一种代理以控制对这个对象的访问。

类关系图:

创建一个接口,然后创建被代理的类实现该接口并且实现该接口中的抽象方法。之后再创建一个代理类,同时使其也实现这个接口。在代理类中持有一个被代理对象的引用,而后在代理类方法中调用该对象的方法。

代码如下:

接口

public interface HelloInterface{ void sayHello();}

被代理类

public class Hello implements HelloInterface{ public void sayHello() { System.out.println("Hello Kevin!"); }}

代理类

public class HelloProxy implements HelloInterface{ private HelloInterface helloInterface=newHello(); public void sayHello() { System.out.println("Beforeinvoke sayHello" ); helloInterface.sayHello(); //调用被代理类Hello中的sayHello方法 System.out.println("Afterinvoke sayHello"); }}

代理类调用

被代理类被传递给了代理类HelloProxy,代理类在执行具体方法时通过所持用的被代理类完成调用。

public class ProxyTest { public static void main(String[] args) { HelloProxyhelloProxy=newHelloProxy(); helloProxy.sayHello(); }}

静态代理的本质:由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。

动态代理

动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。JDK中关于动态代理的重要api如下:

java.lang.reflect.Proxy 这是Java 动态代理机制生成的所有动态代理类的父类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。 最重要的方法是:

static Object newProxyInstance(ClassLoader loader, Class[]interfaces, InvocationHandler h)

该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例

java.lang.reflect.InvocationHandler 这是调用处理器接口,定义了一个invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。每次生成动态代理类对象时都要指定一个对应的调用处理器对象。Object invoke(Object proxy, Method method, Object[] args) 该方法负责集中处理动态代理类上的所有方法调用。第一个参数既是代理类实例,第二个参数是被调用的方法对象 ,第三个方法是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行

java.lang.ClassLoader 这是类装载器类,负责将类的字节码装载到Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。Proxy静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由JVM 在运行时动态生成的而非预先存在于任何一个.class 文件中。 每次生成动态代理类对象时都需要指定一个类装载器对象。我们来看一下动态代理的实例:

接口

public interface HelloInterface{ void sayHello();}

被代理类

public class Hello implements HelloInterface{ public void sayHello() { System.out.println("Hello Kevin!"); }}

实现InvocationHandler接口,创建自己的调用处理器

import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;public class ProxyHandler implements InvocationHandler{ private Object object; public ProxyHandler(Object object){ this.object = object; } public Object invoke(Object proxy,Method method, Object[] args) throws Throwable { System.out.println("Before invoke " + method.getName()); method.invoke(object, args); System.out.println("After invoke" + method.getName()); return null; }}

测试类

import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;public class DynamicProxyTest{ public static void main(String[] args) { HelloInterface hello = new Hello(); //把hello实例传入动态代理处理器 InvocationHandler handler = new ProxyHandler(hello); //生成动态代理类实例 HelloInterface proxyHello = (HelloInterface)Proxy.newProxyInstance(hello.getClass().getClassLoader(),hello.getClass().getInterfaces(), handler); proxyHello.sayHello(); }}

运行代码

Before invoke sayHello

Hello Kevin!

After invoke sayHello

我们可以看到,动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke),生成不同类的代理实例我们只需要在类DynamicProxyTest中处理即可;而静态代理需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,则会遇到下面的问题:

只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大;

新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类当接口;

需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护。

动态代理类也有小小的遗憾,那就是它只能为接口创建代理!如果想对没有实现接口的类创建代理则无能为力。为了解决这种情况,我们通常使用cglib技术,其在AOP(例如spring)和ORM(例如Hibernate)中有广泛的应用,在这里就不对cglib进行展开介绍了。

动态代理类的生成

我们再来看一个实例,修改类DynamicProxyTest,代码如下:

public static void main(String[] args) { System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true"); HelloInterface hello = new Hello(); InvocationHandler handler = new ProxyHandler(hello); HelloInterface proxyHello=(HelloInterface) Proxy.newProxyInstance(hello.getClass().getClassLoader(), hello.getClass().getInterfaces(),handler); proxyHello.sayHello(); System.out.println(proxyHello.getClass().getName()); }

运行结果

Before invoke sayHello

Hello Kevin!

After invoke sayHello

com.sun.proxy.$Proxy0

我们发现proxyHello的类型是.$Proxy0而不是HelloInterface。我们通过反编译来查看$Proxy0的源码,在工程的com.sun.proxy目录下。注意:必须添加下面的代码

System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

$Proxy0就是由JDK创建的动态代理类,是在运行时创建生成的。动态代理类的格式是“$ProxyN”,其中 N 是一个逐一递增的阿拉伯数字,代表Proxy 类第N 次生成的动态代理类,并不是每次调用Proxy 的静态方法创建动态代理类都会使得N 值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会很聪明地返回先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率。$Proxy0源码如下:

import com.my.demo2.HelloInterface;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;public final class $Proxy0 extends Proxy implements HelloInterface { private static Method m1; private static Method m3; private static Method m2; private static Method m0; public $Proxy0(InvocationHandlerparamInvocationHandler) { super(paramInvocationHandler); } public final boolean equals(ObjectparamObject) { try { return ((Boolean)this.h.invoke(this,m1, new Object[] { paramObject })).booleanValue(); } catch (Error|RuntimeExceptionerror) { throw null; } catch (Throwable throwable) { throw newUndeclaredThrowableException(throwable); } } public final void sayHello() { try { this.h.invoke(this, m3, null); return; } catch (Error|RuntimeExceptionerror) { throw null; } catch (Throwable throwable) { throw newUndeclaredThrowableException(throwable); } } public final String toString() { try { return (String)this.h.invoke(this,m2, null); } catch (Error|RuntimeExceptionerror) { throw null; } catch (Throwable throwable) { throw newUndeclaredThrowableException(throwable); } } public final int hashCode() { try { return ((Integer)this.h.invoke(this,m0, null)).intValue(); } catch (Error|RuntimeExceptionerror) { throw null; } catch (Throwable throwable) { throw newUndeclaredThrowableException(throwable); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals",new Class[] { Class.forName("java.lang.Object") }); m3 = Class.forName("com.my.demo2.HelloInterface").getMethod("sayHello",new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString",new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode",new Class[0]); return; } catch (NoSuchMethodExceptionnoSuchMethodException) { throw newNoSuchMethodError(noSuchMethodException.getMessage()); } catch (ClassNotFoundExceptionclassNotFoundException) { throw new NoClassDefFoundError(classNotFoundException.getMessage()); } }}

从上面的代码中我们可以看到:

1. 在代理类$ProxyN的实例上调用其代理的接口中所声明的方法时,这些方法最终都会由调用处理器的invoke 方法执行;

2. 代理类的根类java.lang.Object 中的三个方法:hashCode,equals 和 toString也同样会被分派到调用处理器的invoke 方法中执行。

静态代理和动态代理最重要的四个知识点

1.在程序运行前就已经存在代理类的字节码文件中确认了代理类和委托类的关系;

2.动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。 动态代理根据接口或目标对象,计算出代理类的字节码,然后再加载到JVM中使用。其实现原理如下:由于JVM通过字节码的二进制信息加载类的,那么,如果我们在运行期系统中,遵循Java编译系统组织.class文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类,这样,就完成了在代码中,动态创建一个类的能力了。

3.静态代理的缺点是在程序规模稍大时,维护代理类的成本高,静态代理无法胜任;

4.动态代理只能为实现了接口的类创建代理。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
菲防长扬言血战到底,新加坡媒体罕见站台中国:巴丹群岛属于中国

菲防长扬言血战到底,新加坡媒体罕见站台中国:巴丹群岛属于中国

浮黎礼
2026-07-12 18:47:47
心理学最新发现:拖延懒散、执行力差的人,不是意志弱,也不是不努力,而是大脑里有这两个很容易被忽视的应激反应模式

心理学最新发现:拖延懒散、执行力差的人,不是意志弱,也不是不努力,而是大脑里有这两个很容易被忽视的应激反应模式

心理观察局
2026-07-13 06:56:11
一名学术不端的博士,尴尬了两所名牌大学

一名学术不端的博士,尴尬了两所名牌大学

谭浩俊
2026-07-13 08:56:24
阿坎吉社媒晒防梅西照片:以这样的方式结束世界杯之旅很遗憾

阿坎吉社媒晒防梅西照片:以这样的方式结束世界杯之旅很遗憾

懂球帝
2026-07-12 21:21:21
皇马对世界杯铁卫说不:2500万兜售,穆里尼奥看不上

皇马对世界杯铁卫说不:2500万兜售,穆里尼奥看不上

晚风知我意21
2026-07-13 00:53:09
奇怪!南极有1.2亿只企鹅,每天排便量约5000吨,为何还那么干净

奇怪!南极有1.2亿只企鹅,每天排便量约5000吨,为何还那么干净

抽象派大师
2026-07-03 02:12:41
男子当街对女子施暴,老人上前制止被对方咬掉鼻子还吞入肚中,但始终没松手,死死按住嫌疑人直到民警到来,现已被评为见义勇为勇士

男子当街对女子施暴,老人上前制止被对方咬掉鼻子还吞入肚中,但始终没松手,死死按住嫌疑人直到民警到来,现已被评为见义勇为勇士

极目新闻
2026-07-12 17:05:26
五角大楼放第四批UFO档案!称史上最清晰视频,关键内容全涂黑了

五角大楼放第四批UFO档案!称史上最清晰视频,关键内容全涂黑了

大厂编外实习生
2026-07-12 13:43:59
“升糖大户”被揪出,提醒:想要血糖正常,尽量少吃这5种食物

“升糖大户”被揪出,提醒:想要血糖正常,尽量少吃这5种食物

奇妙的本草
2026-07-12 11:59:21
LV告遍全中国没人敢吭声,周大福直接把柜台搬脸上:你告一个试试

LV告遍全中国没人敢吭声,周大福直接把柜台搬脸上:你告一个试试

小虎新车推荐员
2026-07-12 03:32:12
快检查自家阳台!有人家里已大量出现,官方提醒:千万别摸!

快检查自家阳台!有人家里已大量出现,官方提醒:千万别摸!

三农老历
2026-07-10 15:40:28
一家3代短命!蔡磊处境曝光,斥资续命收效甚微,儿子也成牺牲品

一家3代短命!蔡磊处境曝光,斥资续命收效甚微,儿子也成牺牲品

周哥一影视
2026-07-04 20:40:16
曼联24小时叫停两笔交易,埃德松告吹背后不只是体检问题

曼联24小时叫停两笔交易,埃德松告吹背后不只是体检问题

刘哥谈体育
2026-07-13 06:39:47
最高院:提供 “口交” “肛交”等进入式性服务,是否属卖淫行为?

最高院:提供 “口交” “肛交”等进入式性服务,是否属卖淫行为?

周军律师聊案子
2026-04-21 09:50:16
詹皇自愿大幅降薪!自减4900万工资,或将创下NBA最大降薪纪录

詹皇自愿大幅降薪!自减4900万工资,或将创下NBA最大降薪纪录

锐评利物浦
2026-07-12 10:49:35
2018年,买英国男子精子的山东美女,生了3个混血儿,如今咋样?

2018年,买英国男子精子的山东美女,生了3个混血儿,如今咋样?

阿纂看事
2026-07-11 11:54:46
2026世联赛分站赛收官:八强对阵揭晓,中国女排1/4决赛战美国

2026世联赛分站赛收官:八强对阵揭晓,中国女排1/4决赛战美国

好球去哪了
2026-07-13 03:30:20
真实的殷商王朝或许比你想象中还恐怖:食人宴会都是常态

真实的殷商王朝或许比你想象中还恐怖:食人宴会都是常态

心中的麦田
2026-07-09 23:28:26
森林是不是越多越好?3.06亿年前的地球,已经交了一次昂贵的学费

森林是不是越多越好?3.06亿年前的地球,已经交了一次昂贵的学费

抽象派大师
2026-07-12 14:52:18
2026高考录取实况:大量211存在缺额计划,公费师范生杀疯了!

2026高考录取实况:大量211存在缺额计划,公费师范生杀疯了!

高三倒计时
2026-07-12 18:35:07
2026-07-13 09:48:49
少年游戏人生
少年游戏人生
智能硬件爱好者
455文章数 2010关注度
往期回顾 全部

科技要闻

苹果为了AI,连Mac芯片节奏都改了

头条要闻

浙江苍南一家三口躲过一劫:刚被成功劝离 房子就塌了

头条要闻

浙江苍南一家三口躲过一劫:刚被成功劝离 房子就塌了

体育要闻

被3个队友锁死,哈兰德以最憋屈的方式出局

娱乐要闻

台媒曝S妈许雅钧 诱使具俊晔放弃遗产

财经要闻

扫开就近2元,共享单车涨价到哪里是个头

汽车要闻

纯电/增程双动力 一汽悦意08正式上市售9.99万起

态度原创

本地
房产
旅游
亲子
公开课

本地新闻

重庆人有自己的避暑桃花源 | 夏天就去「酉」风的地方!

房产要闻

重磅学校规划曝光!西海岸教育,正强得可怕!

旅游要闻

借环青赛热度 西宁解锁八条高原文旅新路线

亲子要闻

长胎不长肉,孕期分阶段吃法,精准食量不超标。

公开课

李玫瑾:为什么性格比能力更重要?

无障碍浏览 进入关怀版