聊聊Java动态代理(上)
前言
在之前的文章《聊聊设计模式之代理模式》中,笔者为大家介绍了代理模式,在这里简单回顾一下。代理模式的作用是提供一个代理来控制对一个对象的访问,因此我们可以很方便地实现对一个对象的延迟加载,或者在调用一个对象的方法时加入一些业务逻辑。然而之前介绍的代理模式属于静态代理,其缺点是如果目标接口改变了,则目标类跟代理类都会受影响,不太灵活。不过在Java中还有一种代理模式叫动态代理,可以弥补静态代理的缺陷。接下来我们将进行详细介绍。
Java动态代理
在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler接口,另一个则是 Proxy类,它们是Java动态代理的基础。
我们先看下InvocationHandler接口的API介绍:每个代理类的实例都关联到了一个InvocationHandler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。也就是说我们必须实现该接口,并在invoke方法中实现代理逻辑。
接下来再来看下Proxy类的API介绍:Proxy类提供了创建动态代理类和实例的静态方法,并且由这些静态方法创建的代理类都是Proxy类的子类。简而言之,Proxy类是用来创建动态代理类和实例的。那具体要怎么使用Java的动态代理呢?接下来我们以之前的文章《聊聊设计模式之代理模式》中的例子为基础,将原来的静态代理改造成动态代理。
使用Java动态代理
首先我们来回顾一下,在文章《聊聊设计模式之代理模式》中,我们讲了一个用户登录的例子,我们的需求是在基础的用户登录功能之上,需要增加对违规用户的过滤,在该文章中,我们使用了静态代理实现该功能,接下来我们将该例子改成使用Java动态代理实现。
首先,先定义登录接口:
接着,再实现最基本的登录功能:
可以看到,最基础的登录功能是允许所有用户进行登录。接下来我们实现动态代理逻辑:
其中,代理逻辑是在invoke方法里实现的,其是接口InvocationHandler的抽象方法,在这里我们对其进行了实现。invoke方法有3个参数:proxy、method和args,proxy是代理对象的实例,method是接口方法,args是接口方法参数。在invoke方法的实现中,我们先对userId进行过滤,如果符合登陆条件就调用正常的登录逻辑进行登录。正常的登录逻辑是通过method反射调用实现的,由于method的反射调用需要传入被代理对象(即真实对象),所以我们在构造方法中传入了被代理对象,从而在invoke中能实现对被代理对象的方法的调用(也就是正常的登录逻辑的调用)。
getProxyInstance方法是用来获取代理对象的,其使用了Proxy类的静态方法newProxyInstance,其有3个参数,分别是:classLoader、interfaces和invocationHandler,classLoader是类加载器,interfaces是代理对象实现的接口数组,invocationHandler则是实现代理逻辑的InvocationHandler子类对象。客户端调用getProxyInstance方法则可获得动态代理对象,并使用该动态代理对象进行调用。
接下来,我们写一个客户端测试一下:
结果输出如下:
可以看到我们使用动态代理也能实现对违规用户进行过滤的功能。
动态代理的思考
动态代理之所以称作动态代理,是因为代理类跟代理对象是JVM在运行时动态生成的。我们在之前的文章《聊聊设计模式之代理模式》里提到代理模式的代理对象跟被代理对象需要有相同的父类,通常来讲是相同的接口,而在上述动态代理中我们并没有出现实现了LoginService接口的代理类的代码,大家不要误以为DynamicProxyHandler类就是代理类,其实它只是实现了代理逻辑而已,因为它并没有实现LoginService接口,所以不是代理类。那么我们如何验证代理类和代理对象是在运行时产生的呢?写个客户端测试一下就知道了。
其输出如下:
可以看到代理类的名称是com.sun.proxy.$Proxy0,这是JVM对代理类的同统一命名规范,如果有多个代理类,则后面的代理类名称为$Proxy1、$Proxy2……以此类推。再者,代理类实现的接口是LoginService,这符合代理模式的定义,即代理类跟被代理类需要有相同的父类。此外,我们还可以知道代理类的父类是Proxy类,这一点在上文介绍Proxy类的时候已经提及。
既然动态代理类有一个父类Proxy,由于Java单继承的特点,意味着被代理类跟代理类的共同父类只能是接口,这是Java动态代理的限制。不信的话我们可以把LoginService改成抽象类试试看,代码我就不贴出来了,这里只给大家看下运行结果:
可以看到在获取代理对象的时候报错了,原因是代理类已经继承了Proxy类,没办法再继承额外的抽象类了。
代理模式的另外的缺点就是性能问题,因为代理类跟代理对象是在运行时动态生成的,所以相比静态代理而言会损失部分性能,所以使用时需要权衡性能与其他因素。
前文提高,Java动态代理的代理类跟被代理类必须显示地实现接口,那对于遗留系统而言,可能没办法做到这一点,既然如此,有没有其他办法实现动态代理呢?当然是有的,由于篇幅所限,下次再为大家介绍Java中动态代理的其他实现方式——CGLib动态代理。
如果觉得这篇文章对你有帮助,可以扫描下方二维吗,获取更多精彩内容:

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
PHP学习1——快速入门
主要内容: 搭建PHP开发环境 第一个helloworld程序 PHP(Hypertext Preprocessor)PHP超文本预处理程序,是一种嵌入HTML的脚本语言,运行在服务器。 搭建PHP开发环境 PHP开发环境主要3部分:服务器Apache,编程语言PHP,数据库MySQL。搭建开发环境,直接使用XAMPP集成安装就可以。 XAMPP=Apache+MySQL+PHP+Perl,X表示的是跨平台。 官网下载安装即可:https://www.apachefriends.org/zh_cn/index.html (安装太简单了,没有什么可说的,不过如果以前安装过tomcat,apache,MySQL等,xampp的部分功能可能不能使用,解决办法网上很多,不再赘言) 第一个helloworld程序 php可以使用note++进行编辑。 helloworld.php <?php echo "hello world!"; ?> 然后运行XAMPP的控制面板,点击Apache的start,绿色就是正常运行了。 然后将helloworld.php文件放在路径:C:\xamp...
- 下一篇
PHP学习2——基本语法
主要内容: 二进制 数据类型 变量 常量 赋值 语句结构 函数 网站的核心功能是展现信息,文字,图片,视频,音频,对于计算机来说都是数据,这些数据按照二进制进行存储。 二进制 就是1100,0100,1010这样的数据就是二进制数,二进制数分为有符号数、无符号数。原码,反码,补码是有符号数才有的表示方法。 对于正数:原码=反码=补码 原码:0100,0100,1010 反码:0100,0100,1010 补码:0100,0100,1010 对于负数:原码!=反码,反码+1=补码 原码:1100,0100,1010 反码:1011,1011,0101 补码:1011,1011,0110 数据类型 整数(10112),浮点数(12.34),字符串("cat"/‘cat’) 变量 使用$符号申明变量,以英文字母或者_(下划线)开头,后面可是是英文字母,数字,下划线,比如$score,$mark,$name,$Password php预定义变量: $GLOBALS(当前程序中全局变量) $SERVER(全局变量,包含头信息,脚本位置的数组) $_COOKIE(通过http的cookie方法提...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Hadoop3单机部署,实现最简伪集群
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Windows10,CentOS7,CentOS8安装Nodejs环境
- 设置Eclipse缩进为4个空格,增强代码规范
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- CentOS7设置SWAP分区,小内存服务器的救世主
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- Linux系统CentOS6、CentOS7手动修改IP地址
- Docker安装Oracle12C,快速搭建Oracle学习环境