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

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

简单的分三步处理:

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

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

如下图

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

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

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

/*** 空实现*/
public class DefaultImpl implements Person{@Overridepublic void say(String name) {}
}

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

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

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

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

/*** 空实现*/
public class DefaultImpl implements Person{//这是回调函数private Object callBackHandler;/*** 回调函数当成入参传进来*/DefaultImpl(Object callBackHandler){this.callBackHandler = callBackHandler;}@Overridepublic 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;}@Overridepublic 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 {@Overridepublic 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讲完了,你理解了吗?

五分钟彻底理解Java Proxy原理相关推荐

  1. 深入理解Java Proxy和CGLIB动态代理原理

    点击上方关注,每天进步一点点 动态代理在Java中有着广泛的应用,比如Spring AOP,Hibernate数据查询.测试框架的后端mock.RPC,Java注解对象获取等.静态代理的代理关系在编译 ...

  2. 深入理解Java Proxy机制

    为什么80%的码农都做不了架构师?>>>    动态代理其实就是java.lang.reflect.Proxy类动态的根据您指定的所有接口生成一个class byte,该class会 ...

  3. html图片查看器工作原理,五分钟了解浏览器的工作原理

    Web 浏览器无疑是用户访问互联网最常见的入口.浏览器凭借其免安装和跨平台等优势,逐渐取代了很多传统的富客户端. Web 浏览器通过向 URL 发送网络请求来访问 Web 服务器资源,并以交互性的方式 ...

  4. rpc 调用webservice怎样传递参数_五分钟让你了解RPC原理详解

    欢迎关注专栏[以架构赢天下]--每天持续分享Java相关知识点 以架构赢天下​zhuanlan.zhihu.com 以架构赢天下--持续分享Java相关知识点 每篇文章首发此专栏 欢迎各路Java程序 ...

  5. 五分钟快速理解 Reactor 模型

    点击上方"朱小厮的博客",选择"设为星标" 后台回复"书",获取 后台回复"k8s",可领取k8s资料 本文将介绍基于进 ...

  6. java注释的理解,java注解原理——记录一下自己的理解

    最近因为系统可能要更换成java语言,于是每天都在拼命的研究java的相关知识和框架.之前学习注解的时候,没有太深入的去理解它,只是觉得标注一下挺好用,但是现在在学到spring aop的时候,突然发 ...

  7. 五分钟深入理解Handler

    Handler大家最熟悉不过,每次重新回顾Handler都有不同的感觉,学到新的东西.现分享一些自己对Handler的理解. Handler发送消息 首先Handler通过sendMessage方法将 ...

  8. 【堆内存】动态图+代码五分钟轻松理解学会

    2019独角兽企业重金招聘Python工程师标准>>> 前言背景 堆(heap)又被为优先队列(priority queue).尽管名为优先队列,但堆并不是队列. 因为队列中允许的操 ...

  9. 五分钟通俗理解自动驾驶

    大家好,我是良许. 这几年,自动驾驶这个概念非常火热,无论是百度还是谷歌,都做出了还不错的原型机,但是你真的知道什么是自动驾驶吗? 本文就花 5 分钟左右的时间,向大家科普一下什么是自动驾驶. 自动驾 ...

最新文章

  1. RDKit |基于集成学习(Ensemble learning)预测溶解度
  2. Pocket PC 2003 SE设备仿真器网络设置
  3. 阿里云上git clone公司内网仓库遇到的错误消息 - cannot resolve host
  4. php中介者,PHP设计模式 - 中介者模式
  5. latex在论文中添加行号
  6. python网页登录验证码不显示_进网页需要验证码?不好意思,Python从来不惧各种验证码!...
  7. m1笔记本android开发,Apple M1设备开发Android小tips
  8. aws rds监控慢sql_将AWS S3存储桶与AWS RDS SQL Server集成
  9. git clone 某一特定分支转
  10. 8086cpu标志寄存器
  11. 文件夹删不掉,显示有文件打开怎么办
  12. JavaWeb kuangshen汇总
  13. android 吉他模拟器,真实吉他模拟器怎么玩 新手玩法攻略详解
  14. linux-----基本操作指令(2)
  15. 幽默感也有套路:可用数学模型量化
  16. TypeScript Essential Notes 2 - ES6 Language Features
  17. js首次修改html无效,浅谈jQuery添加的HTML,JS失效的问题
  18. android打开位置服务,Android - 位置定位(Location)服务(Service)类的基本操作
  19. win10 代理服务器出现问题 或者地址有误
  20. Ubuntu16.04搭建和运行DSO

热门文章

  1. 联想小新Pro14外接显示器无法显示
  2. CTF 六大方向基础工具合集
  3. autojs教程:成语红包群app自动脚本代码
  4. 中职计算机基础必考知识,中职计算机基础知识整理 简化
  5. 基于构件复用的软件方法与COM支持
  6. 网易云信短信验证码验证php实现
  7. yolov5调用手机摄像头
  8. 我什么时候应该使用 ‘self‘ 而不是 ‘$this‘?
  9. 数据库集群分类和介绍
  10. 特斯拉AI高级总监Andrej Karpathy详解Tesla纯视觉自动驾驶方案