剖析JDK 动态代理机制

码农修炼手册
发布于 2022-6-1 17:49
浏览
3收藏

JDK 动态代理机制的原理

动态代理的实质是通过 java.lang.reflect.Proxy 的newProxyInstance(…)方法生成一个动态代理类的实例,该方法比较重要,下面对该方法进行详细介绍,其定义如下:

public static Object newProxyInstance(ClassLoader loader,//类加载器
Class<?>[] interfaces,//动态代理类需要实现的接口
 InvocationHandler h) //调用处理器
 throws IllegalArgumentException
{
...
}

此方法的三个参数介绍如下:

第一个参数为 ClassLoader 类加载器类型,此处的类加载器和被委托类的类加载器相同即可。
第二个参数为 Class[]类型,代表动态代理类将会实现的抽象接口,此接口是被委托类所实现的接口。
第三个参数为 InvocationHandler 类型,它的调用处理器实例将作为 JDK 生成的动态代理对象的内部成员,在对动态代理对象进行方法调用时,该处理器的 invoke(…)方法会被执行。

InvocationHandler 处理器的 invoke(…)方法如何实现由大家自己决定。对被委托类(真实目标类)的扩展或者定制逻辑一般都会定义在此 InvocationHandler 处理器的 invoke(…)方法中。
JVM 在调用 Proxy.newProxyInstance(…)方法时会自动为动态代理对象生成一个内部的代理类,那么是否能看到该动态代理类的 class 字节码呢?
答案是肯定的,可以通过如下方式获取其字节码,并且保存到文件中:

/**
 *获取动态代理类的 class 字节码
 */
 byte[] classFile = ProxyGenerator.generateProxyClass("Proxy0",RealRpcDemoClientImpl.class.getInterfaces());
 /**
 *在当前的工程目录下保存文件
 */
 FileOutputStream fos =new FileOutputStream(newFile("Proxy0.class"));
 fos.write(classFile);
 fos.flush();
 fos.close();

运行 测试用例

@Slf4j
public class DynamicProxyTest {
    public static void main(String[] args) {
        MockDemoClient client = new MockDemoClientImpl();
        //参数 1:类装载器
        ClassLoader classLoader = DynamicProxyTest.class.getClassLoader();
        //参数 2:被代理的实例类型
        Class[] clazz = new Class[]{DemoClient.class};
        //参数 3:调用处理器
        InvocationHandler invocationHandler =
                new DemoClientInocationHandler(client);
        //获取动态代理实例
        DemoClient proxy = (DemoClient)
                Proxy.newProxyInstance(classLoader, clazz,
                        invocationHandler);
        //执行 RPC 远程调用方法
        Result<JSONObject> result1 = proxy.hello();
        //log.info("result1={}", result1.toString());
        Result<JSONObject> result2 = proxy.echo("回显内容");
        //log.info("result2={}", result2.toString());

    }
}

在 对应 模块的根路径可以发现被新
创建的 Proxy0.class 字节码文件。如果 IDE 有反编译的能力,就可以在 IDE 中打开该文件,然后
可以看到其反编译的源码

import com.talkweb.springcloud.common.result.RestOut;
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 MockDemoClient {
 private static Method m1;
 private static Method m4;
 private static Method m3;
 private static Method m2;
 private static Method m0;
 public Proxy0(InvocationHandler var1) throws {
 	super(var1);
 }

 public final RestOut echo(String var1) throws {
 	try {
   	 return (RestOut)super.h.invoke(this, m4, new Object[]{var1});
 	} catch (RuntimeException | Error var3) {
 		throw var3;
 	} catch (Throwable var4) {
 		throw new UndeclaredThrowableException(var4);
 	}
 }
 public final RestOut hello() throws {
 	try {
 		return (RestOut)super.h.invoke(this, m3, (Object[])null);
	 } catch (RuntimeException | Error var2) {
 		throw var2;
 	} catch (Throwable var3) {
 		throw new UndeclaredThrowableException(var3);
 	}
 }
 public final String toString() throws {
 	try {
 		return (String)super.h.invoke(this, m2, (Object[])null);
 	} catch (RuntimeException | Error var2) {
 		throw var2;
 	} catch (Throwable var3) {
 		throw new UndeclaredThrowableException(var3);
 	}
 }
 ...
 static {
 try {
 	m1 = Class.forName("java.lang.Object").getMethod("equals", 
Class.forName("java.lang.Object"));
	 m4 = Class.forName("com.talkweb.demo.proxy.MockDemoClient")
.getMethod("echo", Class.forName("java.lang.String"));
 	m3 = Class.forName("com.talkweb.demo.proxy.MockDemoClient")
.getMethod("hello");
 	m2 = Class.forName("java.lang.Object").getMethod("toString");
 	m0 = Class.forName("java.lang.Object").getMethod("hashCode");
 } catch (NoSuchMethodException var2) {
 	throw new NoSuchMethodError(var2.getMessage());
 } catch (ClassNotFoundException var3) {
 	throw new NoClassDefFoundError(var3.getMessage());
 }
 }

通过代码可以看出,这个动态代理类其实只做了两件简单的事情:
(1)该动态代理类实现了接口类的抽象方法。动态代理类 Proxy0 实现了 MockDemoClient接口的 echo(String)、hello()两个方法。此外,Proxy0 还继承了 java.lang.Object 的 equals()、hashCode()、
toString()方法。
(2)该动态代理类将对自己的方法调用委托给了 InvocationHandler 调用处理器内部成员。以上代理类 Proxy0 的每一个方法实现的代码其实非常简单,并且逻辑大致一样:将方法自己的Method 反射对象和调用参数进行二次委托,委托给内部成员 InvocationHandler 调用处理器的invoke(…)方法。至于该内部 InvocationHandler 调用处理器的实例,则由大家自己编写,在通过java.lang.reflect.Proxy 的newProxyInstance(…)创建动态代理对象时作为第三个参数传入。

至此,JDK 动态代理机制的核心原理和动态代理类的神秘面纱已经彻底地揭开了。Feign 的RPC 客户端正是通过 JDK 的动态代理机制来实现的,Feign 对 RPC 调用的各种增强处理主要是通过调用处理器 InvocationHandler 来实现的。

已于2022-6-1 17:50:57修改
3
收藏 3
回复
举报
回复
    相关推荐