RPC实现原理之核心技术-动态代理

1. 动态代理调用流程

file

RPC的调用对用户来讲是透明的,内部具体是如何实现呢?

核心技术采用的就是动态代理,RPC 会自动给接口生成一个代理类,当我们在项目中注入初始化接口的时候,运行过程中实际绑定的是这个接口生成的代理类。
在接口方法被调用的时候,它实际上是被生成代理类拦截到了,这样就可以在生成的代理类里面,加入其他调用处理逻辑。

2. 为什么要加入动态代理

  1. 如果没有动态代理, 服务端大量的接口将不便于管理,需要大量的if判断,如果扩展了新的接口,需要更改调用逻辑, 不利于扩展维护。
  2. 动态代理可以做到内部方法级拦截,并且添加其他额外功能, 比如连接负载管理,权限控制,日志记录等等。

3. JDK动态代理实现

  1. JDK动态代理实现流程:
    file
  2. 实现代码:

      public class JDKDynamicProxy {
    
             public static void main(String[] args){
                     // 构建代理器
                     JDKProxy proxy = new JDKProxy(new Teacher());
                     ClassLoader classLoader = JDKDynamicProxy.class.getClassLoader();
    
                     // 生成代理类
                     User user = (User) Proxy.newProxyInstance(classLoader, new Class[]{User.class}, proxy);
    
                     // 接口调用
                     System.out.println(user.job());
             }
             /**
              * 定义用户的接口
              */
             public interface User {
                     String job();
             }
    
             /**
              * 实际的调用对象
              */
             public static class Teacher  {
    
                     public String invoke(){
                             return "i'm a Teacher";
                     }
             }
    
             /**
              * 创建JDK动态代理类
              */
             public static class JDKProxy implements InvocationHandler {
                     private Object target;
    
                     JDKProxy(Object target) {
                             this.target = target;
                     }
    
                     @Override
                     public Object invoke(Object proxy, Method method, Object[] paramValues) {
                             return ((Teacher)target).invoke();
                     }
             }
     }

反编译生成的代理类可以知道,代理类 $Proxy里面会定义相同签名的接口(也就是上面代码User的job接口),然后内部会定义一个变量绑定JDKProxy代理对象,当调用User.job接口方法,实质上调用的是JDKProxy.invoke()方法,从而实现了接口的动态代理。

4. Cglib动态代理实现

  1. 服务接口

    编写订单服务的下单接口:
      public class OrderService {
    
        /**
         * 下单接口
         */
        public String placeOrder(String userName) {
            System.out.println("用户:" + userName + "开始下单");
            return "成功";
        }
    }
  2. 代理实现

     实现MethodInterceptor接口, 重写intercept方法, 可以追加校验逻辑、日志记录等功能。
    public class OrderServiceCglib implements MethodInterceptor {
    
        public Object getProxy(Object target) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(target.getClass());
            // 设置回调方法
            enhancer.setCallback(this);
            // 创建代理对象
            return enhancer.create();
        }
    
        /**
         * 实现MethodInterceptor接口中重写的方法
         */
        @Override
        public Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    
            System.out.println("校验下单参数:" + Arrays.asList(args));
            Object result = proxy.invokeSuper(object, args);
            System.out.println("记录下单日志结果: " + result);
            return result;
        }
    
    }

5. 开源动态代理技术解析

file

(1) Cglib 动态代理

Cglib是一个强大的、高性能的代码生成包,它广泛被许多AOP框架使用,支持方法级别的拦截。它是高级的字节码生成库,位于ASM之上,ASM是低级的字节码生成工具,ASM的使用对开发人员要求较高,相比较来讲, ASM性能更好。

(2) Byte Buddy 字节码增强库

Byte Buddy是致力于解决字节码操作和 简化操作复杂性的开源框架。Byte Buddy 目标是将显式的字节码操作隐藏在一个类型安全的领域特定语言背后。它属于后起之秀,在很多优秀的项目中,像 Spring、Jackson 都用到了 Byte Buddy 来完成底层代理。相比 Javassist,Byte Buddy 提供了更容易操作的 API,编写的代码可读性更高。

(3) Javassist 动态代理

一个开源的分析、编辑和创建Java字节码的类库。javassist是jboss的一个子项目,它直接使用java编码的形式,不需要了解虚拟机指令,可以动态改变类的结构,或者动态生成类。Javassist 的定位是能够操纵底层字节码,所以使用起来并不简单,Dubbo 框架的设计者为了追求性能花费了不少精力去适配javassist。

6. 开源动态代理性能比较

Byte Buddy、 CGLIB、 Javassist、 JDK动态代理性能比较:

综合结果:

file

单位是纳秒。大括号内代表的是样本标准差,综合结果:Byte Buddy > CGLIB > Javassist> JDK。

7. Dubbo动态代理源码剖析

  1. 源码调用流程
    file
  2. 核心源码:
    file
本文由mirson创作, 希望对大家有所帮助, 谢谢!

你可能感兴趣的:(java)