动态代理-rpc实现核心原理(代码片段)

JavaEdge. JavaEdge.     2023-03-03     472

关键词:

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

1 远程调用的魔法

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

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

接口里并不包含真实业务逻辑,业务逻辑都在服务提供方应用,但通过调用接口方法,确实拿到了想要结果,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

生成字节码节点,即 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定义),也可完成远程调用。

参考:

  • https://www.baeldung.com/jdk-com-sun-proxy
  • https://github.com/wangzheng0822/codedesign/tree/master/com/xzg/cd/rpc

java核心技术动态代理的原理(代码片段)

编程语言通常有各种不同的分类角度,动态类型和静态类型就是其中一种分类角度,简单区分就是语言类型信息是在运行时检查,还是编译期检查与其近似的还有一个对比,就是所谓强类型和弱类型,就是不... 查看详情

cglib动态代理实现及原理(代码片段)

JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要Cglib了。Cglib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采... 查看详情

cglib动态代理实现及原理(代码片段)

JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要Cglib了。Cglib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采... 查看详情

java基础干货动态代理核心代码(代码片段)

JDK动态代理//自定义的接口类,JDK动态代理的实现必须有对应的接口类publicinterfaceExInterfacevoidexecute();//A类,实现了ExInterface接口类publicclassAimplementsExInterfacepublicvoidexecute()System.out.println("执行A的exec 查看详情

java核心技术动态代理的原理(代码片段)

编程语言通常有各种不同的分类角度,动态类型和静态类型就是其中一种分类角度,简单区分就是语言类型信息是在运行时检查,还是编译期检查与其近似的还有一个对比,就是所谓强类型和弱类型,就是不... 查看详情

静态代理和动态代理原理及实现(代码片段)

目录静态代理jdk动态代理CGLIB动态代理@(静态代理(StaticProxy)和动态代理(DynamicProxy))静态代理静态代理要先抽象出一个接口,并且写一个实现类实现这个接口。//主业务接口publicinterfaceSomeServiceStringfirst();Stringsecond();//目标类pub... 查看详情

cglib动态代理实现及其原理浅析(代码片段)

JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采... 查看详情

java动态代理demo实现原理参数理解(代码片段)

  动态代理两个重要的组成:  Proxy  与  InvocationHandlerHello1hello=(Hello1)Proxy.newProxyInstance(Hello.class.getClassLoader(),newClass[]Hello1.class,newHelloProxy<Hel 查看详情

cglib动态代理实现原理.md(代码片段)

1.Cglib库介绍CGLIB是一个强大的、高性能的代码生成库。它被广泛使用在基于代理的AOP框架(例如SpringAOP和dynaop)提供方法拦截。Hibernate作为最流行的ORM工具也同样使用CGLIB库来代理单端关联(集合懒加载除外,它使用另外一种机... 查看详情

mybatis中mapper动态代理的实现原理(代码片段)

一、概述我们知道,Mybatis实现增删改查需要进行XML的配置,其基本的配置如下:<?xmlversion="1.0"encoding="UTF-8"?><!DOCTYPEmapperPUBLIC"-//mybatis.org//DTDMapper3.0//EN"" 查看详情

jdk和cglib动态代理原理(代码片段)

...我的的Github:https://github.com/h2pl/  AOP的基础是Java动态代理,了解和使用两种动态代理能让我们更好地理解AOP,在讲解AOP之前,让我们先来看看Java动态代理的使用方式以及底层实现原理。转自https://www.jianshu.com/u/668d0795a95b... 查看详情

简述rpc原理实现(代码片段)

   前言架构的改变,往往是因为业务规模的扩张。随着业务规模的扩张,为了满足业务对技术的要求,技术架构需要从单体应用架构升级到分布式服务架构,来降低公司的技术成本,更好的适应业务的发展。分布式服... 查看详情

设计模式----代理模式(代码片段)

代理模式代理模式示例代码静态代理静态代理简单实现动态代理动态代理的介绍原理代码模拟动态代理流程动态代理jdk源码流程分析相关的类和接口代理机制及其特点InvocationHandler接口和Proxy类详解JDK动态代理总结Cglib代理介绍mav... 查看详情

cglib动态代理实现及其原理浅析(代码片段)

JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采... 查看详情

hdfs(二)底层通信原理——rpc及动态代理

一、RPC(RemoteProcedureCall ):远程过程调用    1、RPC是远程过程调用协议,实现调用者和被调用者二地之间的连接和通信。其基本通信模型是基于client/server进程间相互通信模型,如图1所示。      &n... 查看详情

aop动态代理

AOP动态代理的作用:解耦合,提高扩展性;动态代理的应用场景:测试代码的执行效率,打印日志信息等;动态代理模式:应用反射技术+代理实现实现动态代理的核心:InvocationHandler(代理对象的功能方法)实现原理实现步骤1.创... 查看详情

深入浅出spring原理及实战「原理分析专题」不看源码就带你剖析aop容器核心流程以及运作原理(代码片段)

...是AOP容器机制,主要负责承接前一篇代理模式机制中动态代理:JDKProxy和CglibProxy的功能机制之后,我们开始研究一下如何实现一下相关的AOP容器代理机制的。AOP入口机制如何实现将Aspectj的动态weave织入到Spring容器的Bea... 查看详情

rpc----基于zookeeper为注册中心实现的rpc(代码片段)

...衡1、接口2、随机、轮询代码3、客户端服务发现代码三、动态感知服务器状态1、文字描述2、代码部分实现3、测试截图服务器下线服务器上线四、总结五、版本三特点六、项目地址一、原理一个能够动态注册和获取服务信息的地... 查看详情