巩义网站建设哪家专业,如何用visual studio做网站,烟台市建设局网站,教务管理系统学生登录入口在Java开发中#xff0c;JDK动态代理是一种非常有用的技术#xff0c;它允许开发者在不修改目标类代码的情况下#xff0c;为目标类添加额外的功能。然而#xff0c;JDK动态代理的使用有一些限制#xff0c;特别是它只能代理接口和接口实现类。本文将深入探讨这一限制的原… 在Java开发中JDK动态代理是一种非常有用的技术它允许开发者在不修改目标类代码的情况下为目标类添加额外的功能。然而JDK动态代理的使用有一些限制特别是它只能代理接口和接口实现类。本文将深入探讨这一限制的原因。 1.JDK动态代理原理
下面是一个简单的动态代理的例子 /*** 要代理的接口*/
public interface Target{String say();
}/*** 真实调用对象*/
public class RealTarget {public String invoke(){return im proxy;}
}/*** JDK代理类生成*/
public class JDKProxy implements InvocationHandler {private Object target;JDKProxy(Object target) {this.target target;}Overridepublic Object invoke(Object proxy, Method method, Object[] paramValues) {System.out.println(Before method invocation);Object result ((RealTarget)target).invoke();System.out.println(After method invocation);return result;}
}/*** 测试例子*/
public class TestProxy {public static void main(String[] args){// 构建代理器JDKProxy proxy new JDKProxy(new RealTarget());ClassLoader classLoader ClassLoaderUtils.getCurrentClassLoader();// 把生成的代理类保存到文件System.setProperty(sun.misc.ProxyGenerator.saveGeneratedFiles,true);// 生成代理类Target test (Target) Proxy.newProxyInstance(classLoader, new Class[]{Target.class}, proxy);// 方法调用System.out.println(test.say());}
}
这段代码想表达的意思就是给 Target 接口生成一个动态代理类并调用接口 say() 方法但真实返回的值居然是来自 RealTarget 里面的 invoke() 方法返回值。你看短短50行的代码就完成了这个功能是不是还挺有意思的
那既然重点是代理类的生成那我们就去看下 Proxy.newProxyInstance 里面究竟发生了什么
一起看下下面的流程图具体代码细节你可以对照着 JDK 的源码看上文中有类和方法可以直接定位我是按照 1.7.X 版本梳理的。 在生成字节码的那个地方也就是 ProxyGenerator.generateProxyClass() 方法里面通过代码我们可以看到里面是用参数 saveGeneratedFiles 来控制是否把生成的字节码保存到本地磁盘。同时为了更直观地了解代理的本质我们需要把参数 saveGeneratedFiles 设置成true但这个参数的值是由key为“sun.misc.ProxyGenerator.saveGeneratedFiles”的Property来控制的动态生成的类会保存在工程根目录下的 com/sun/proxy 目录里面。现在我们找到刚才生成的 $Proxy0.class通过反编译工具打开class文件你会看到这样的代码
package com.sun.proxy;import com.proxy.Hello;
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 Target {private static Method m3;private static Method m1;private static Method m0;private static Method m2;public $Proxy0(InvocationHandler paramInvocationHandler) {super(paramInvocationHandler);}public final String say() {try {return (String)this.h.invoke(this, m3, null);} catch (Error|RuntimeException error) {throw null;} catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);} }public final boolean equals(Object paramObject) {try {return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();} catch (Error|RuntimeException error) {throw null;} catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);} }public final int hashCode() {try {return ((Integer)this.h.invoke(this, m0, null)).intValue();} catch (Error|RuntimeException error) {throw null;} catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);} }public final String toString() {try {return (String)this.h.invoke(this, m2, null);} catch (Error|RuntimeException error) {throw null;} catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);} }static {try {m3 Class.forName(com.proxy.Target).getMethod(say, new Class[0]);m1 Class.forName(java.lang.Object).getMethod(equals, new Class[] { Class.forName(java.lang.Object) });m0 Class.forName(java.lang.Object).getMethod(hashCode, new Class[0]);m2 Class.forName(java.lang.Object).getMethod(toString, new Class[0]);return;} catch (NoSuchMethodException noSuchMethodException) {throw new NoSuchMethodError(noSuchMethodException.getMessage());} catch (ClassNotFoundException classNotFoundException) {throw new NoClassDefFoundError(classNotFoundException.getMessage());} }
}
我们可以看到 $Proxy0 类里面有一个跟 Target 一样签名的 say() 方法其中 this.h 绑定的是刚才传入的 JDKProxy 对象所以当我们调用 Target.say() 的时候其实它是被转发到了JDKProxy.invoke()。到这儿整个魔术过程就透明了。 2.回答JDK动态代理的疑问 为何只能代理具有接口的类 这是因为JDK动态代理的机制所限。在Java中动态代理通过Proxy.newProxyInstance()方法实现此方法要求传入一个接口类作为被代理对象。这源于JDK动态代理的底层实现它在程序运行时动态生成一个名为$Proxy0的代理类该代理类继承自java.lang.reflect.Proxy并实现了被代理的接口。由于Java不支持多重继承每个动态代理类都继承自Proxy因此只能代理接口而无法代理具体实现类。 JDK动态代理能否代理类 JDK中的Proxy类主要用于保存动态代理的处理器InvocationHandler。理论上如果不通过Proxy类而直接在动态生成的代理类内部设置处理器可能实现对类的动态代理。然而JDK的设计者并未采取这种方式这主要是出于设计上的考虑和限制。 为何这样设计 使用场景与需求动态代理的主要用途是在不修改原始实现的前提下对方法进行拦截以实现功能增强或扩展。在实际开发中基于接口编程是常见模式因此基于接口实现动态代理符合需求和场景。当然也存在没有实现接口的类此时JDK动态代理无法满足需求。代码重用与扩展性在Java中类的设计更注重共性能力的抽象以提高代码的重用性和扩展性。动态代理也遵循这一原则它封装了代理类的生成逻辑、接口判断以及InvocationHandler的持有等将这些抽象逻辑放在Proxy父类中是一个合理的选择。 其他实现方式 对于需要代理没有接口的类可以选择使用CGLIB动态代理。CGLIB通过动态生成被代理类的子类并重写非final修饰的方法在子类中拦截父类方法的调用从而实现动态代理。这种方式弥补了JDK动态代理只能代理接口的不足。
三、总结
JDK动态代理是Java中一种强大而灵活的技术它允许在不修改原始代码的情况下对目标对象的方法进行功能增强。然而由于其基于接口的代理机制它只能代理接口和接口实现类。对于需要代理没有实现接口的类的情况可以考虑使用CGLIB动态代理等替代方案。在实际开发中应根据具体需求选择合适的代理机制以实现最佳的性能和可维护性。