文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2024-12-03 02:14

关注

代理是一种设计模式

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

类关系图:


静态代理

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

代码如下:

接口

  1. public interface HelloInterface{ 
  2.         void sayHello(); 

 被代理类

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

 代理类

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

 代理类调用

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

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

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

动态代理

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

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

  1. 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 文件中。 每次生成动态代理类对象时都需要指定一个类装载器对象。我们来看一下动态代理的实例:

接口

  1. public interface HelloInterface{ 
  2.         void sayHello(); 

 被代理类

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

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

  1. import java.lang.reflect.InvocationHandler; 
  2. import java.lang.reflect.Method; 
  3. public class ProxyHandler implements InvocationHandler{ 
  4.     private Object object; 
  5.     public ProxyHandler(Object object){ 
  6.         this.object = object; 
  7.     } 
  8.        public Object invoke(Object proxy,Method method, Object[] args) throws Throwable { 
  9.              System.out.println("Before invoke "  + method.getName()); 
  10.              method.invoke(object, args); 
  11.              System.out.println("After invoke" + method.getName()); 
  12.              return null
  13.        } 

 测试类

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

 运行代码

  1. Before invoke sayHello 
  2.  
  3. Hello Kevin! 
  4.  
  5. After invoke sayHello 

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

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

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

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

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

动态代理类的生成

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

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

 运行结果

  1. Before invoke sayHello 
  2.  
  3. Hello Kevin! 
  4.  
  5. After invoke sayHello 
  6.  
  7. com.sun.proxy.$Proxy0 

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

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

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

  1. import com.my.demo2.HelloInterface; 
  2. import java.lang.reflect.InvocationHandler; 
  3. import java.lang.reflect.Method; 
  4. import java.lang.reflect.Proxy; 
  5. import java.lang.reflect.UndeclaredThrowableException; 
  6. public final class $Proxy0 extends Proxy implements HelloInterface { 
  7.   private static Method m1; 
  8.   private static Method m3; 
  9.   private static Method m2; 
  10.   private static Method m0; 
  11.   public $Proxy0(InvocationHandlerparamInvocationHandler) { super(paramInvocationHandler); } 
  12.   public final boolean equals(ObjectparamObject) { 
  13.     try { 
  14.       return ((Boolean)this.h.invoke(this,m1, new Object[] { paramObject })).booleanValue(); 
  15.     } catch (Error|RuntimeExceptionerror) { 
  16.       throw null
  17.     } catch (Throwable throwable) { 
  18.       throw newUndeclaredThrowableException(throwable); 
  19.     } 
  20.   } 
  21.   public final void sayHello() { 
  22.     try { 
  23.       this.h.invoke(this, m3, null); 
  24.       return
  25.     } catch (Error|RuntimeExceptionerror) { 
  26.       throw null
  27.     } catch (Throwable throwable) { 
  28.       throw newUndeclaredThrowableException(throwable); 
  29.     } 
  30.   } 
  31.   public final String toString() { 
  32.     try { 
  33.       return (String)this.h.invoke(this,m2, null); 
  34.     } catch (Error|RuntimeExceptionerror) { 
  35.       throw null
  36.     } catch (Throwable throwable) { 
  37.       throw newUndeclaredThrowableException(throwable); 
  38.     } 
  39.   } 
  40.   public final int hashCode() { 
  41.     try { 
  42.       return ((Integer)this.h.invoke(this,m0, null)).intValue(); 
  43.     } catch (Error|RuntimeExceptionerror) { 
  44.       throw null
  45.     } catch (Throwable throwable) { 
  46.       throw newUndeclaredThrowableException(throwable); 
  47.     } 
  48.   } 
  49.   static { 
  50.     try { 
  51.       m1 = Class.forName("java.lang.Object").getMethod("equals",new Class[] { Class.forName("java.lang.Object") }); 
  52.       m3 = Class.forName("com.my.demo2.HelloInterface").getMethod("sayHello",new Class[0]); 
  53.       m2 = Class.forName("java.lang.Object").getMethod("toString",new Class[0]); 
  54.       m0 = Class.forName("java.lang.Object").getMethod("hashCode",new Class[0]); 
  55.       return
  56.     } catch (NoSuchMethodExceptionnoSuchMethodException) { 
  57.       throw newNoSuchMethodError(noSuchMethodException.getMessage()); 
  58.     } catch (ClassNotFoundExceptionclassNotFoundException) { 
  59.       throw new NoClassDefFoundError(classNotFoundException.getMessage()); 
  60.     } 
  61.   } 

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

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

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

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

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

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


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

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

 

来源:今日头条内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯