学习路线指引(点击解锁) | 知识定位 | 人群定位 |
---|---|---|
Python实战微信订餐小程序 | 进阶级 | 本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。 |
Python量化交易实战 | 入门级 | 手把手带你打造一个易扩展、更安全、效率更高的量化交易系统 |
举个简单的例说明代理模式就是:假如现在需要买一辆二手车,可以自己去找车源、做质量检测等一系列车辆过户的流程,但是这实在太浪费时间和精力了,其实可以通过找中介的方式,同样会找车源、做质量检测等一系列车辆过户的流程,但是这样自己就只需要选车、付钱即可。
在实际开发中,代理模式根据其目的和实现方式的不同可分为很多种类,如下是常用的几种代理模式:
远程代理是一种常见的代理模式,远程代理对象承担了大部分的网络通信工作,使得客户端程序可以访问在远程主机上的对象。
对于客户端而言,无需关心实现具体业务的是谁,只需要按照服务接口所定义的方法直接与本地主机中的代理对象交互即可。
在 Java 语言中,可以通过 RMI(Remote Method Invocation, 远程方法调用) 机制来实现远程代理。代码示例如下:
服务端部分代理
| | import java.net.MalformedURLException; |
| | import java.rmi.Naming; |
| | import java.rmi.Remote; |
| | import java.rmi.RemoteException; |
| | import java.rmi.registry.LocateRegistry; |
| | import java.rmi.server.UnicastRemoteObject; |
| | |
| | // 远程接口 |
| | public interface IMyRemote extends Remote { |
| | String SayHello() throws RemoteException; |
| | } |
| | |
| | // 远程接口实现 - 远程对象 |
| | public class MyRemoteImpl extends UnicastRemoteObject implements IMyRemote { |
| | public MyRemoteImpl() throws RemoteException { |
| | super(); |
| | } |
| | |
| | @Override |
| | public String SayHello() throws RemoteException { |
| | return "Server says, 'Hey'"; |
| | } |
| | |
| | public static void main(String[] args) throws RemoteException, MalformedURLException { |
| | IMyRemote service = new MyRemoteImpl(); |
| | // 启动本地 RMI 服务,默认端口是 1099 |
| | LocateRegistry.createRegistry(1099); |
| | // 注册远程对象 |
| | Naming.rebind("rmi://localhost:1099/RemoteHello", service); |
| | } |
| | } |
客户端部分代理
| | import java.net.MalformedURLException; |
| | import java.rmi.Naming; |
| | import java.rmi.NotBoundException; |
| | import java.rmi.RemoteException; |
| | |
| | public class MyRemoteClient { |
| | private void go() throws RemoteException, NotBoundException, MalformedURLException { |
| | IMyRemote service = (IMyRemote) Naming.lookup("rmi://localhost:1099/RemoteHello"); |
| | System.out.println(service.sayHello()); |
| | } |
| | |
| | public static void main(String[] args) throws RemoteException, NotBoundException, MalformedURLException { |
| | new MyRemoteClients().go(); |
| | } |
| | } |
虚拟代理会在真实对象创建成功之前扮演其替身,而当真实对象创建成功之后,虚拟代理再将用户的请求转发给真实对象。
使用虚拟代理的场景非常容易理解,主要有以下两种:
代理模式的具体实现从运行时的角度可以分成两种:一种是静态代理,即在代码运行之前就已经确定好代理关系;另一种是动态代理,可以在代码运行时才决定如何实现代理关系。
静态代理是比较好理解的实现方式,在这种实现方式中,代理类所实现的接口和所代理的方法都被固定,需要在编译期就预先对原始类编写代理类。
一般情况下,参考基于接口而非实现编程的设计思想,为了让代码的改动尽量少,代理类和原始类应该实现同样的接口。
如下是使用图片展示作为例子的代码示例:
图片 Image
接口:描述图片具有的行为
| | public interface Image { |
| | void display(); |
| | } |
展示图片 ShowImage
类:实际的图片原始类
| | public class ShowImage implements Image { |
| | public ShowImage() {} |
| | |
| | @Override |
| | public void display() { |
| | System.out.println("ShowImage display!"); |
| | } |
| | } |
代理图片 ProxyImage
类:在真实图片前包装一层的代理类
| | public class ProxyImage implements Image { |
| | // 通过依赖注入的方式 |
| | private ShowImage showImage; |
| | |
| | public ProxyImage(ShowImage showImage) { |
| | this.showImage = showImage; |
| | } |
| | |
| | @Override |
| | public void display() { |
| | System.out.println("ProxyImage display start!"); |
| | this.showImage.display(); |
| | System.out.println("ProxyImage display end!"); |
| | } |
| | } |
如果原始类并没有实现接口,并且原始类代码由其他人开发维护,可以通过代理类继承原始类的方法来实现代理模式。
假设上述展示图片 ShowImage
类没有实现接口,可以重新定义代理图片 ProxyImage
类如下:
| | public class ProxyImage extends ShowImage { |
| | public ProxyImage() {} |
| | |
| | @Override |
| | public void display() { |
| | System.out.println("ProxyImage display start!"); |
| | super.display(); |
| | System.out.println("ProxyImage display end!"); |
| | } |
| | } |
静态代理需要针对每个类都创建一个代理类,并且每个代理类中的代码都有点像模板式的“重复”代码,增加了开发成本和维护成本。
对于静态代理存在的问题,可以通过动态代理来解决。
动态代理的原理是:不事先为每个原始类编写代理类,而是在代码运行的时候动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。
在 Java 中,主要就是利用反射机制在运行时创建代理类。代码示例如下:
| | // 定义接口,描述行为 |
| | public interface Subject { |
| | void hello(String param); |
| | } |
| | |
| | // 实现接口 |
| | public class SubjectImpl implements Subject { |
| | public SubjectImpl() {} |
| | |
| | @Override |
| | public void hello(String param) { |
| | System.out.println("hello " + param); |
| | } |
| | } |
| | |
| | // 创建代理类 |
| | public class SubjectProxy implements InvocationHandler { |
| | private Subject subject; |
| | |
| | public SubjectProxy(Subject subject) { |
| | this.subject = subject; |
| | } |
| | |
| | @Override |
| | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { |
| | System.out.println("----- Start -----"); |
| | Object invoke = method.invoke(subject, args); |
| | System.out.println("----- End -----"); |
| | return invoke; |
| | } |
| | } |
| | |
| | // 实际调用 |
| | public class Main { |
| | public static void main(String[] args) { |
| | Subject subject = new SubjectImpl(); |
| | InvocationHandler subjectProxy = new SubjectProxy(subject); |
| | // 代理类的类加载器 |
| | // 被代理类的接口,如果有多个就是以数组形式传入 |
| | // 代理类实例 |
| | Subject proxyInstance = (Subject) Proxy.newProxyInstance( |
| | subjectProxy.getClass().getClassLoader(), |
| | subject.getClass().getInterfaces(), |
| | subjectProxy |
| | ); |
| | // 执行代理方法 |
| | proxyInstance.hello("world"); |
| | } |
| | } |
常见的 Java 动态代理实现方式有 JDK 代理、CGLib 代理。基于 JDK 的动态代理必须实现一个接口,而 CGLib 动态代理没有这个限制,是另一种不错的选择。
代理模式的主要优点如下:
代理模式的主要缺点如下:
代理模式的适用场景如下:
在 JDK 中,提供了 java.lang.reflect.Proxy
支持创建动态代理类。