前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >聊聊Java动态代理(上)

聊聊Java动态代理(上)

作者头像
Bug开发工程师
发布2018-04-17 13:28:04
8240
发布2018-04-17 13:28:04
举报
文章被收录于专栏:码农沉思录码农沉思录

前言

在之前的文章《聊聊设计模式之代理模式》中,笔者为大家介绍了代理模式,在这里简单回顾一下。代理模式的作用是提供一个代理来控制对一个对象的访问,因此我们可以很方便地实现对一个对象的延迟加载,或者在调用一个对象的方法时加入一些业务逻辑。然而之前介绍的代理模式属于静态代理,其缺点是如果目标接口改变了,则目标类跟代理类都会受影响,不太灵活。不过在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动态代理。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-02-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码农沉思录 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档