首页 > 其他分享 >动态代理-RPC实现核心原理

动态代理-RPC实现核心原理

时间:2023-02-04 21:00:12浏览次数:61  
标签:调用 Object 代理 接口 生成 RPC proxy 动态

实现过统一拦截吗?如授权认证、性能统计,可以用 Spring AOP,不需要改动原有代码前提下,还能实现非业务逻辑跟业务逻辑的解耦。核心就是动态代理,通过对字节码进行增强,在方法调用时进行拦截,以便于在方法调用前后,增加处理逻辑。

1 远程调用的魔法

使用 RPC,一般先找服务提供方要接口,通过 Maven 或其他工具把接口依赖到我们项目。

编写业务逻辑时,若要调用提供方的接口,只需通过依赖注入把接口注入到项目,然后在代码里面直接调用接口的方法。

接口里并不包含真实业务逻辑,业务逻辑都在服务提供方应用,但通过调用接口方法,确实拿到了想要结果,RPC怎么完成这魔术的?核心就是动态代理。

RPC会自动给接口生成一个代理类,当我们在项目中注入接口时,运行过程中实际绑定的是这个接口生成的代理类。这样在接口方法被调用时,它实际上是被生成代理类拦截,就可在生成的代理类里,加入远程调用逻辑。

“偷梁换柱”,帮用户屏蔽远程调用细节,实现像调用本地一样地调用远程的体验。

调用流程:

动态代理-RPC实现核心原理_代理类

2 实现原理

package com.javaedge.design.pattern.structural.proxy.dynamicproxy.jdkdynamicproxy.v1;

/**
* 要代理的接口
*
* @author JavaEdge
* @date 2023/2/4
*/
public interface Hello {
String say();
}
package com.javaedge.design.pattern.structural.proxy.dynamicproxy.jdkdynamicproxy.v1;

/**
* 真实调用对象
*
* @author JavaEdge
* @date 2023/2/4
*/
public class RealHello {

public String invoke(){
return "i'm proxy";
}
}
package com.javaedge.design.pattern.structural.proxy.dynamicproxy.jdkdynamicproxy.v1;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
* JDK代理类生成
*
* @author JavaEdge
* @date 2023/2/4
*/
public class JDKProxy implements InvocationHandler {
private Object target;

JDKProxy(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] paramValues) {
return ((RealHello)target).invoke();
}
}
package com.javaedge.design.pattern.structural.proxy.dynamicproxy.jdkdynamicproxy.v1;

import org.azeckoski.reflectutils.ClassLoaderUtils;

import java.lang.reflect.Proxy;

/**
* 测试例子
*
* @author JavaEdge
* @date 2023/2/4
*/
public class TestProxy {

public static void main(String[] args) {
// 构建代理器
JDKProxy proxy = new JDKProxy(new RealHello());
ClassLoader classLoader = ClassLoaderUtils.getCurrentClassLoader();
// 把生成的代理类保存到文件
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 生成代理类
Hello test = (Hello) Proxy.newProxyInstance(classLoader, new Class[]{Hello.class}, proxy);
// 方法调用
System.out.println(test.say());
}
}

给 Hello 接口生成一个动态代理类,并调用接口say(),但真实返回值来自 RealHello#invoke()的返回值。

Proxy.newProxyInstance

动态代理-RPC实现核心原理_RPC_02

生成字节码节点,即 ProxyGenerator.generateProxyClass() 用参数 saveGeneratedFiles 控制是否把生成的字节码保存本地。把参数 saveGeneratedFiles 设置成true,但这个参数的值是由key为“sun.misc.ProxyGenerator.saveGeneratedFiles”的Property来控制的,动态生成的类会保存在工程根目录下的 com/sun/proxy 目录里面。现在我们找到刚才生成的 $Proxy0.class,通过反编译工具打开class文件:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.sun.proxy;

import com.javaedge.design.pattern.structural.proxy.dynamicproxy.jdkdynamicproxy.v1.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 Hello {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;

public $Proxy0(InvocationHandler var1) throws {
super(var1);
}

public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

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);
}
}

public final String say() throws {
try {
return (String)super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (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"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.javaedge.design.pattern.structural.proxy.dynamicproxy.jdkdynamicproxy.v1.Hello").getMethod("say");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}

$Proxy0类有跟 Hello 一样签名的 say() 方法,其中 this.h 绑定的是刚才传入的 JDKProxy 对象,所以当我们调用 Hello.say(),其实它是被转发到JDKProxy.invoke()。

3 实现方案

3.1 JDK默认代理

要求被代理的类只能是接口,因为生成的代理类会继承 Proxy 类,但Java不支持多继承。

对服务调用方,在使用RPC时正好本就是面向接口编程。使用JDK默认代理,最大问题就是性能。它生成后的代理类是使用反射完成方法调用。

3.2 Javassist

能操纵底层字节码,要生成动态代理类有点复杂,但无需反射,所以性能更好。通过Javassist生成一个代理类后,此 CtClass 对象会被冻结,不允许再修改;否则,再次生成时会报错。

3.3 Byte Buddy

后起之秀,Spring、Jackson都用Byte Buddy完成底层代理,其提供更易操作的API,代码可读性更高,生成的代理类执行速度比Javassist更快。

区别就只是如何生成代理类、生成的代理类里怎么完成方法调用。正因为这些细小差异,才导致不同代理框架性能不同。

4 总结

动态代理框架选型:

  • 因为代理类是在运行中生成的,那么代理框架生成代理类的速度、生成代理类的字节码大小等等,都会影响到其性能——生成的字节码越小,运行所占资源就越小。
  • 还有就是我们生成的代理类,是用于接口方法请求拦截的,所以每次调用接口方法的时候,都会执行生成的代理类,这时生成的代理类的执行效率就需要很高效。
  • 最后一个是从我们的使用角度出发的,我们肯定希望选择一个使用起来很方便的代理类框架,比如我们可以考虑:API设计是否好理解、社区活跃度、还有就是依赖复杂度等。

FAQ

如果没有动态代理帮我们完成方法调用拦截,用户该怎么完成RPC调用?

就需要使用静态代理来实现,就需要用户对原始类中所有的方法都重新实现一遍,并且为每个方法附加相似的代码逻辑,如果在RPC中,这种需要代理的类有很多个,就需要针对每个类都创建一个代理类。

调用双方可以通过定义一套消息id和消息结构(才有protobuf定义),也可完成远程调用。

参考:

标签:调用,Object,代理,接口,生成,RPC,proxy,动态
From: https://blog.51cto.com/u_11440114/6037283

相关文章

  • 动态规划DP
    动态规划DP一般DP的组成一般DP主要分为两个部分:表示状态,状态转移这里以P1216[USACO1.5][IOI1994]数字三角形NumberTriangles为例表示状态答案为从上到下的路径......
  • 接口无法连接需要配置代理跨域位置
    vue.config.js中devServer:{  port:port,  open:true,  overlay:{   warnings:false,   errors:true  },  //配置代理跨......
  • 2023网络爬虫 -- 获取动态加载数据
    1、爬取的网址http://www.kfc.com.cn/kfccda/storelist/index.aspx2、要爬取的内容,输入关键字,点击查询,获取餐厅名称和餐厅地址3、F12,打开开发者工具,点击查询,抓包4、点......
  • 动态规划(一)
    1.贪心贪心能解的题,搜索也可以解贪心只是提高的效率,不保证正确性860.柠檬水找零(贪心模板)在柠檬水摊上,每一杯柠檬水的售价为5美元。顾客排队购买你的产品,(按账单......
  • 动态规划及优化
    决策单调性定义:单调矩阵:每行最值位置单调不降。完全单调矩阵:每个子矩阵都是单调矩阵,这里的子矩阵可以不连续。蒙日矩阵:满足四边形不等式的矩阵,蒙日矩阵一定是完全单调......
  • 速度快高匿又稳定的HTTP代理?
    开春刚上班,还没啥事做,上来知乎摸个鱼,第一时间就看到这个问题推荐给我了。本着摸鱼(划掉)分享的精神,废话不多说,我直接把市面上具体动态短效代理HTTP代理厂商的价格给你搬过来:青......
  • Vue使用:style动态给css中某样式赋值
    template中<spanclass="successOrError":style="{'--fontColor':"green"}">成功</span>css中<stylelang="scss"scoped>.successOrError{font-size:14px;......
  • 动态sql之Foreach
            ......
  • 动态sql之IF语句
                ......
  • 如何设计可向后兼容的RPC协议
    HTTP协议(本文HTTP默认1.X)跟RPC协议又有什么关系呢?都属于应用层协议。1HTTP协议浏览器收到命令后会封装一个请求,并把请求发送到DNS解析出来的IP上,抓包:2协议的作用没有协议......