JDK动态代理接口和接口实现类深入详解
作者:@fishv
前言
在Java开发中,JDK动态代理是一种非常有用的技术,它允许开发者在不修改目标类代码的情况下,为目标类添加额外的功能。然而,JDK动态代理的使用有一些限制,特别是它只能代理接口和接口实现类。本文将深入探讨这一限制的原因。
1.JDK动态代理原理
下面是一个简单的动态代理的例子
/** * 要代理的接口 */ public interface Target{ String say(); } /** * 真实调用对象 */ public class RealTarget { public String invoke(){ return "i'm proxy"; } } /** * JDK代理类生成 */ public class JDKProxy implements InvocationHandler { private Object target; JDKProxy(Object target) { this.target = target; } @Override public 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动态代理只能代理接口的不足。
3.总结
JDK动态代理是Java中一种强大而灵活的技术,它允许在不修改原始代码的情况下对目标对象的方法进行功能增强。然而,由于其基于接口的代理机制,它只能代理接口和接口实现类。对于需要代理没有实现接口的类的情况,可以考虑使用CGLIB动态代理等替代方案。在实际开发中,应根据具体需求选择合适的代理机制,以实现最佳的性能和可维护性。
到此这篇关于JDK动态代理接口和接口实现类深入详解的文章就介绍到这了,更多相关JDK动态代理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!