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