当前位置: 首页 > 科技观察

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

时间:2023-03-13 07:58:25 科技观察

代理是一种设计模式在代理模式(ProxyPattern)中,一个类代表另一个类的功能。这种类型的设计模式是一种结构模式。在代理模式中,我们使用现有对象创建对象,以便为外界提供功能接口。目的:为其他对象提供代理以控制对该对象的访问。类图:静态代理创建一个接口,然后创建被代理的类来实现接口,并实现接口中的抽象方法。然后创建一个代理类,让它也实现这个接口。在代理类中持有对代理对象的引用,然后在代理类方法中调用该对象的方法。代码如下:interfacepublicinterfaceHelloInterface{voidsayHello();}代理类publicclassHelloimplementsHelloInterface{publicvoidsayHello(){System.out.println("HelloKevin!");}}代理类publicclassHelloProxyimplementsHelloInterface{privateHelloInterfacehelloInterface=newHello();publicvoidsayHello(){系统。out.println("BeforeinvokesayHello");helloInterface.sayHello();//调用代理类Hello中的sayHello方法System.out.println("AfterinvokesayHello");}}代理类调用代理类传递给代理类HelloProxy,代理类在执行具体方法时通过持有的代理类完成调用。publicclassProxyTest{publicstaticvoidmain(String[]args){HelloProxyhelloProxy=newHelloProxy();helloProxy.sayHello();}}静态代理的本质:代理类的源代码由程序员创建或由工具生成,然后代理类已编译。所谓静态就是代理类的字节码文件在程序运行之前就已经存在,代理类和委托类的关系在运行之前就已经确定了。动态代理动态代理类的源代码是JVM在程序运行过程中根据反射等机制动态生成的,所以没有代理类的字节码文件。代理类和委托类的关系是在程序运行时确定的。JDK中关于动态代理的重要api如下:java.lang.reflect.Proxy这是Java动态代理机制生成的所有动态代理类的父类,它提供了一组静态方法来动态生成一个代理类一组接口及其对象。最重要的方法是:staticObjectnewProxyInstance(ClassLoaderloader,Class[]interfaces,InvocationHandlerh)该方法用于为指定的类加载器、一组接口和一个调用处理程序生成一个动态代理类实例java.lang.reflect.InvocationHandler。这就是调用处理implementer接口定义了一个invoke方法,用于专注于对动态代理类对象的方法调用,通常在该方法中实现对代理类的代理访问。每次生成动态代理类对象时,都必须指定相应的调用处理程序对象。Objectinvoke(Objectproxy,Methodmethod,Object[]args)该方法负责集中处理动态代理类上的所有方法调用。第一个参数是代理类实例,第二个参数是要调用的方法对象,第三个方法是调用参数。调用处理器根据这三个参数对委托类实例进行预处理或者分派进行反射执行java.lang.ClassLoader这是类加载器类,负责将类的字节码加载到Java虚拟机(JVM)中在可以使用类之前定义类对象。Proxy静态方法生成的动态代理类也需要通过类加载器加载后才能使用。它与普通类的唯一区别是它的字节码是由JVM在运行时动态生成的,而不是预先存在于任何.class文件中间。每次生成动态代理类对象时,都需要指定一个类加载器对象。我们来看一个动态代理的例子:接口publicinterfaceHelloInterface{voidsayHello();}代理类publicclassHelloimplementsHelloInterface{publicvoidsayHello(){System.out.println("HelloKevin!");}}实现了InvocationHandler接口并创建它自己的调用处理器System.out("调用前"+method.getName());方法调用(对象,参数);System.out.println("调用后"+method.getName());returnnull;}}测试类importjava.lang.reflect。InvocationHandler;importjava.lang.reflect.Proxy;publicclassDynamicProxyTest{publicstaticvoidmain(String[]args){HelloInterfacehello=newHello();//将hello实例调用到动态代理处理器中InvocationHandlerhandler=newProxyHandler(hello);//生成动态代理类实例HelloInterfaceproxyHello=(HelloInterface)Proxy.newProxyInstance(hello.getClass().getClassLoader(),hello.getClass().getInterfaces(),handler);proxyHello.sayHello();}}运行代码BeforeinvokesayHelloHelloKevin!AfterinvokesayHello我们可以看到动态代理最大的好处是相对于静态代理接口中声明的所有方法都被转移到调用处理程序(InvocationHandler.invoke)的集中方法。生成不同类型的代理实例,我们只需要在DynamicProxyTest类中进行处理即可;而静态代理需要代理多个类,由于代理对象必须实现与目标对象相同的接口,会遇到如下问题:只维护一个代理类,而这个代理类实现了多个接口,但是这样会导致代理类太大;一个代理类,每个目标对象对应一个代理类,但是这样会生成过多的代理类作为接口;当需要增删改方法时,必须同时修改目标对象和代理类,动态代理类不容易维护还有一个小遗憾,就是只能创建接口代理!如果你想为不实现接口的类创建代理,你无能为力。为了解决这种情况,我们通常会使用cglib技术,这种技术在AOP(如spring)和ORM(如Hibernate)中都有广泛的应用,这里不介绍cglib。再看一个动态代理类生成的例子,修改类DynamicProxyTest,代码如下:=newHello();InvocationHandlerhandler=newProxyHandler(hello);HelloInterfaceproxyHello=(HelloInterface)Proxy.newProxyInstance(hello.getClass().getClassLoader(),hello.getClass().getInterfaces(),handler);proxyHello.sayHello();系统输出。println(proxyHello.getClass().getName());}运行结果BeforeinvokesayHelloHelloKevin!AfterinvokesayHellocom.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值原因是如果你试图为同一组接口(包括相同顺序的接口)重复创建动态代理类已经安排好了),返回之前创建的代理类的类对象,而不是尝试创建一个新的Proxy类,这样会省去不必要的代码重复,提高代理类创建的效率。$Proxy0源码如下:importcom.my.demo2.HelloInterface;importjava.lang.reflect.InvocationHandler;importjava.lang.reflect.Method;importjava.lang.reflect.Proxy;importjava.lang.reflect.UndeclaredThrowableException;publicfinalclass$Proxy0extendsProxyimplementsHelloInterface{privatestaticMethodm1;privatestaticMethodm3;privatestaticMethodm2;privatestaticMethodm0;public$Proxy0(InvocationHandlerparamInvocationHandler){super(paramInvocationHandler);}publicfinalbooleanequals(ObjectparamObject){try{return((Boolean)this.h.invoke(this,m1,newObject[]{paramObject})).booleanValue();}catch(Error|RuntimeExceptionerror){thrownull;}catch(Throwablethrowable){thrownewUndeclaredThrowableException(throwable);}}publicfinalvoidsayHello(){try{this.h.invoke(this,m3,null);return;}捕获(错误|RuntimeExceptionerror){thrownull;}catch(Throwablethrowable){thrownewUndeclaredThrowableException(throwable);}}publicfinalStringtoString(){try{return(String)this.h.invoke(this,m2,null);}catch(Error|RuntimeExceptionerror){thrownull;}catch(Throwablethrowable){thrownewUndeclaredThrowableException(throwable);}}publicfinalinthashCode(){try{return((Integer)this.h.invoke(this,m0,null)).intValue();}捕获(错误|RuntimeExceptionerror){thrownull;}catch(Throwablethrowable){thrownewUndeclaredThrowableException(throwable);}}static{try{m1=Class.forName(“java.lang.Object”).getMethod(“等于”,newClass[]{Class.forName("java.lang.Object")});m3=Class.forName("com.my.demo2.HelloInterface").getMethod("sayHello",newClass[0]);m2=Class.forName("java.lang.Object").getMethod("toString",newClass[0]);m0=Class.forName("java.lang.Object").getMethod("hashCode",newClass[0]);返回;}catch(NoSuchMethodExceptionnoSuchMethodException){thrownewNoSuchMethodError(noSuchMethodException.getMessage());}catch(ClassNotFoundExceptionclassNotFoundException){thrownewNoClassDefFoundError(classNotFoundException.getMessage());}}}从上面的代码我们可以看出:1.当在代理类$ProxyN的实例上调用代理接口中声明的方法时,这些方法最终会被调用processor2.代理类的根类java.lang.Object中的三个方法:hashCode、equals和toString也会被调度到调用处理器的invoke方法中执行静态代理和动态代理。最重要的四个知识点1、静态代理在程序运行前就已经存在于代理类的字节码文件中,确认代理类与委托类的关系;2、动态代理类的源代码是JVM在程序运行过程中根据反射等方式生成的。该机制是动态生成的,因此没有代理类的字节码文件。代理类和委托类的关系是在程序运行时确定的。动态代理根据接口或目标对象计算出代理类的字节码,然后加载到JVM中使用。实现原理如下:由于JVM是通过字节码的二进制信息来加载类的,如果我们按照Java编译系统的格式和结构,在运行系统中组织.class文件,生成相应的二进制数据,然后把这个二进制数据加载并转换成对应的类,这样就完成了在代码中动态创建类的能力。3、静态代理的缺点是当程序规模稍大时,维护代理类的成本高,静态代理无法胜任;4.动态代理只能为实现接口的类创建代理。