• 【专栏】RPC系列(理论)-动态代理


    | 前言

        前一章我们讲解了RPC的协议与序列化,有了这两层的铺垫,我们可以以约定的格式将对象转换成二进制数据在两端之间传递了,然而我们漏掉了一个重要步骤,从你调用了UserService的getUserByName到客户端携带着你传递的方法参数去请求服务端之间,是谁在暗渡陈仓,谁在帮你负重前行?这边便引出了动态代理,真正帮我们完成这部分工作的是代理类。

    | 静态代理

        比如我们现在有一个需求,需要在执行sayHello之前执行一条日志:

    1. public interface Hello {
    2. void sayHello();
    3. }
    4. class Target implements  Hello{
    5. public void sayHello(){
    6. System.out.println("----This is a log----");
    7. }
    8. }

    这里有一个Target类继承接口Hello,里面的sayHello方法我们希望在执行它之前输出一条日志,那我们的做法有下面几种:

    1. 直接在sayHello中加日志

    2. 创建另一个class,在另一个class中写日志逻辑

    第一种就不说了,我们实现了第二种:

    1. interface Hello {
    2. void sayHello();
    3. }
    4. class Target implements Hello{
    5. @Override
    6. public void sayHello(){
    7. System.out.println("Hello world");
    8. }
    9. }
    10. //实现一个代理类
    11. class ProxyTarget{
    12. public ProxyTarget(Target trg){
    13. this.trg=trg;
    14. }
    15. public void loggerHello(){
    16. System.out.println("----This is a log----");
    17. trg.sayHello();
    18. }
    19. private Target trg;
    20. }
    21. public class Main {
    22. public static void main(String[] args) {
    23. Target trg = new Target();
    24. ProxyTarget ptrg = new ProxyTarget(trg);
    25. ptrg.loggerHello();
    26. }
    27. }

    我们实现了一个代理类 Proxy_Target,在构造函数中把Target类对象作为参数输入进来,然后在logger_hello方法中先打印日志,然后继续执行原来的say_hello,这就是 静态代理的写法.

        如果我们的需求是在Hello中每一个方法前都打印一条日志,那么代理类会很复杂,没有提效,这也是静态代理的问题所在。那么,解决这个问题的办法就在于,我们这个代理类是否可以帮我们自动生成,不用自己去写,我们只用关注“在执行方法前打印一条日志”这个需求就行了,就很nice,这就是动态代理做的事情。

    | 动态代理

        Java中动态代理分为两种:JDK动态代理Cglib动态代理。我们以jdk动态代理为主介绍下用法,我们先硬编码看看怎么实现上面日志的需求

    1. public class Main {
    2. public static void main(String[] args) throws Throwable{
    3. //得到一个代理类
    4. Class hello_proxy = Proxy.getProxyClass(Hello.class.getClassLoader(),Hello.class);
    5. //得到代参构造器
    6. Constructor constructor = hello_proxy.getConstructor(InvocationHandler.class);
    7. //反射创建代理
    8. Hello hello = (Hello) constructor.newInstance(new InvocationHandler() {
    9. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    10. Target trg = new Target();
    11. System.out.println("This is a Dynamic Log");
    12. Object result = method.invoke(trg,args);
    13. return result;
    14. }
    15. });
    16. hello.say_hello();
    17. }
    18. }

    大致的流程就是

    1. 先通过Proxy工具类创建一个新的Class对象

    2. 拿到构造器

    3. 用构造器创建一个新的Hello实例,并重写invoke逻辑

    这个invoke函数在每次调用代理类中的每个方法时都会被调用,其中的method就是用户指定调用的方法。因为动态代理其实涉及到了反射的知识,如果读者阅读廖雪峰老师的文章浅学一下作为铺垫:https://www.liaoxuefeng.com/wiki/1252599548343744/1255945147512512

    然后我们优化一下代码

    1. public class Proxy_Object {
    2. private Object obj;
    3. public Object bind(Object obj){
    4. this.obj=obj;
    5. return Proxy.newProxyInstance(
    6. this.obj.getClass().getClassLoader(),this.obj.getClass().getInterfaces(),this::invoke
    7. );
    8. }
    9. public Object invoke(Object proxy, Method method,Object[] args) throws Throwable{
    10. Object result = null;
    11. try{
    12. System.out.println("This is a Dynamic Log");
    13. result = method.invoke(this.obj,args);
    14. }
    15. catch (Exception e){
    16. e.printStackTrace();
    17. }
    18. return result;
    19. }
    20. }

    这里我们推荐直接使用Proxy.newProxyInstance方法创建代理类,里面的参数指定了构造器、Class以及invoke函数,然后我们就可以这样去调用:

    1. public class Main {
    2. public static void main(String[] args) {
    3. Proxy_Object p_obj = new Proxy_Object();
    4. Target trg = new Target();
    5. Hello hello = (Hello) p_obj.bind(trg);
    6. hello.say_hello();
    7. }
    8. }

    这样无论我们在Hello中新增什么方法,都能实现“调用前打印日志”的需求

    那么放到RPC中,我们的代理类需要帮我们做的,不是打印日志而是完成对指定远程方法的调用而已,也就是我们只需要关注invoke中的逻辑,我们可以有下面的伪代码

    public Object invoke(Object proxy, Method method,Object[] args){    //1.组装请求    RpcRequest request = buildRequest();    //2.调用远程服务    Object result = callRemoteService(request);    return result;}

    这样我们调用每个服务的方法,都能像调用本地方法一样,拿到远程服务的返回结果了,这部分“繁重”的工作就交给了动态代理帮我们做掉了。

    | 小结

        这一章我们讲解了动态代理在RPC中作用,以及简单介绍了一下动态代理,如果大家对动态代理想要更深的了解,可以自行查阅,毕竟是什么和怎么用是每个工具的第一步,而后再深入了解才会更有用的更有底气。

    下一章我们将进入实战阶段,理论的部分我们暂时到这里,我们下周见~

    【RPC系列合集】

    【专栏】RPC系列(理论)-夜的第一章

    【专栏】RPC系列(理论)-协议与序列化

  • 相关阅读:
    动态内存分配——malloc,calloc,realloc,free
    LeetCode2.两数相加
    网格bfs,LeetCode 2684. 矩阵中移动的最大次数
    FreeRTOS教程7 事件组
    GitHub上有助于开发微信小程序的仓库
    本地源制作
    Docker数据卷volume的使用详解
    单片机,0.07
    (二十)mmdetection源码解读:config配置文件models Faster R-CNN
    后端-打开抖音互联网会发生什么
  • 原文地址:https://blog.csdn.net/scwMason/article/details/126336178