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 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> 五分钟彻底理解Java Proxy原理 -> 正文阅读

[JavaScript知识库]五分钟彻底理解Java Proxy原理

今天看了下Proxy源码,我尝试以一种比较容易理解的方式把它讲透了。

首先假设我要自己写一个动态代理,我该怎么设计呢?有了,思路如下:

简单的分三步处理:

1、实现一个代理接口的空实现类
2、在类每个方法调用时,把调用权直接交给回调函数处理(其实就是接口的时候,改成调用回调函数而已~)

3、返回回调函数的调用值

如下图

按照上面思路,以代理Person接口为例。先定义一个Person接口

/**
 * 要被代理的接口
 */
public interface Person {
    /**
     * 打个招呼
     * @param name
     */
    void say(String name);
}

1、实现一个代理接口的空实现类:DefaultImpl

/**
 * 空实现
 */
public class DefaultImpl implements Person{

    @Override
    public void say(String name) {
    }
}

2、在类每个方法调用时,把调用权直接交给回调处理

这时候有两个问题:
A:如何拿到我的回调函数的对象?

B: 如何调用方法的时候调用回调函数?

对于A问题,很简单,我们把它当成构造函数传入即可,于是代码改成这样:

/**
 * 空实现
 */
public class DefaultImpl implements Person{
    //这是回调函数
    private Object callBackHandler;

    /**
     * 回调函数当成入参传进来
     */
    DefaultImpl(Object callBackHandler){
        this.callBackHandler = callBackHandler;
    }

    @Override
    public void say(String name) {
    }
}

B问题呢?也很简单,我直接掉函数就行了。这里又有个小问题,回调函数要能支持调用所有的方法,所以设计时就考虑反射调用了

首先定义回调函数接口:

/**
 * 回调接口
 */
public interface CallBackHandler {

    /**
     * 回调函数
     * @param method 反射的Method
     * @param args 代理接口的入参列表
     * @return  替代代理接口返回的值
     * @throws Throwable
     */
    public Object invoke(Method method, Object[] args) throws Throwable;
}

代入DefaultImpl后就变成了这样:

/**
 * 空实现
 */
public class DefaultImpl implements Person{
    //这是回调函数
    private CallBackHandler callBackHandler;

    /**
     * 回调函数当成入参传进来
     */
    DefaultImpl(CallBackHandler callBackHandler){
        this.callBackHandler = callBackHandler;
    }

      @Override
    public void say(String name) {
        //这行代码就是通过反射找到当前方法的Method对象,以便后面调用时使用
        Method method = Arrays.stream(DefaultImpl.class.getMethods())
                .filter(e -> e.getName().equals(Thread.currentThread() .getStackTrace()[1].getMethodName())).findFirst().orElse(null);
        //这里没有返回值,所以就不用返回了,如果有,直接return即可
        callBackHandler.invoke(method,new Object[]{name});
    }
}

OK,大功告成,我们写一个简单的测试方法测试下:

PS:要跑通下面测试时记得给CallBackHandler接口加上 @FunctionalInterface 注解(要么你换种写法也行)

public static void main(String[] args) {
        DefaultImpl defaul = new DefaultImpl((Method method, Object[] params)->{
            System.out.println("我是代理类!我截胡了.");
            return null;
        });

        defaul.say("李雷");
    }

运行结果:

我是代理类!我截胡了.
?

从上面可以看到被代理类截掉了。

如果你上面的例子看懂了,那么恭喜你,Proxy的原理你已经懂了90%了。

现在开始讲Proxy原理。其实原理跟上面的逻辑是一样的。但上面是有个问题要解决,就是每次生成的空实现类DefaultImpl ,是java文件的类,要做动态代理时,就不能事先编译好,得改成实时编译。原理很简单,就是动态编译实时生成一个空代理类,然后再用ClassLoader 加载到内存去。

动态编译生成空代理类并加载到内存,Proxy是用java.lang.reflect.Proxy.ProxyClassFactory 实现的。

Java Proxy生成的类跟上面的例子逻辑是一样的。也是构造函数传入回调接口,然后内部截取并回调。

附上图:

关于InvocationHandler接口参数的理解

让我们再回过头看下JDK里面的java.lang.reflect.InvocationHandler 的接口定义:

public interface InvocationHandler {

    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

估计很多人开始时对第一个参数Object proxy都很懵逼,不知道这是干什么的,也很容易用错,那现在也给你讲明白了。它传入的就是接口空实现的类对象的本身(就是把this传了进去)。那具体有什么作用呢,其实我也没想遇到过使用场景,但可以写代码简单测试下验证:?

public class ProxyTest {

    public static void main(String[] args) {
        Person person = (Person) Proxy.newProxyInstance(ProxyTest.class.getClassLoader(),new Class[]{Person.class},new JdkProxy());
        person.say("hello");
    }

    static class JdkProxy implements InvocationHandler {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //证明proxy实现了Person接口
            System.out.println(proxy instanceof Person);
            return null;
        }
    }
}

上面例子是使用JDK的Proxy实现了一个Person 的代理类,调用时打印invoke回调方法的第一个参数,可以看到打印结果是true ,即proxy对象是实现了Person的类,进一步验证了我们的想法。

好了。Proxy讲完了,你理解了吗?

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-11-30 15:31:56  更:2021-11-30 15:32:25 
 
开发: 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/24 7:41:29-

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