IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> 【专栏】RPC系列(理论)-动态代理 -> 正文阅读

[网络协议]【专栏】RPC系列(理论)-动态代理

| 前言

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

| 静态代理

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

public interface Hello {
    void sayHello();
}
class?Target?implements??Hello{
    public void sayHello(){
        System.out.println("----This is a log----");
    }
}

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

  1. 直接在sayHello中加日志

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

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

interface Hello {
    void sayHello();
}
class Target implements  Hello{
    @Override
    public void sayHello(){
        System.out.println("Hello world");
    }
}
//实现一个代理类
class ProxyTarget{
    public ProxyTarget(Target trg){
        this.trg=trg;
    }
    public void loggerHello(){
        System.out.println("----This is a log----");
        trg.sayHello();
    }
    private Target trg;
}
public class Main {
    public static void main(String[] args) {
        Target trg = new Target();
        ProxyTarget ptrg = new ProxyTarget(trg);
        ptrg.loggerHello();
    }
}

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

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

| 动态代理

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

public class Main {
    public static void main(String[] args) throws Throwable{
        //得到一个代理类
        Class hello_proxy = Proxy.getProxyClass(Hello.class.getClassLoader(),Hello.class);
        //得到代参构造器
        Constructor constructor =  hello_proxy.getConstructor(InvocationHandler.class);
        //反射创建代理
        Hello hello = (Hello) constructor.newInstance(new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Target trg = new Target();
                System.out.println("This is a Dynamic Log");
                Object result = method.invoke(trg,args);
                return result;
            }
        });
        hello.say_hello();
    }
}

大致的流程就是

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

  2. 拿到构造器

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

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

然后我们优化一下代码

public class Proxy_Object {
    private Object obj;
    public Object bind(Object obj){
        this.obj=obj;
        return Proxy.newProxyInstance(
                this.obj.getClass().getClassLoader(),this.obj.getClass().getInterfaces(),this::invoke
        );
    }
    public Object invoke(Object proxy, Method method,Object[] args) throws Throwable{
        Object result = null;
        try{
            System.out.println("This is a Dynamic Log");
            result = method.invoke(this.obj,args);
        }
        catch (Exception e){
            e.printStackTrace();
        }
        return result;
    }
}

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

public class Main {
    public static void main(String[] args) {
        Proxy_Object p_obj = new Proxy_Object();
        Target trg = new Target();
        Hello hello = (Hello) p_obj.bind(trg);
        hello.say_hello();
    }
}

这样无论我们在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系列(理论)-协议与序列化

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2022-08-19 19:37:39  更:2022-08-19 19:37:41 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 23:44:44-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码