代理模式
很多时候我们试图发送一个请求的时候,实际上是由代理将我们的请求转发给目标对象,这种代理方式叫做正向代理,正向代理就是客户端的代理,我们知道访问目标的真实地址,而真实目标只知道这次请求是代理发送的却不知道背后的我们;又有些时候,我们输入某url发送一个请求,实际上这个url并非真实服务器,而是服务器的一个代理,这种代理方式叫做反向代理,反向代理就是服务器的代理,我们不知道访问目标的真实地址,而只知道目标代理的地址。无论是正向代理还是反向代理,都是在实际网络中极为常用的技术,内容分发服务就是一种代理。在编程当中,代理模式也是一种很有用的程序设计模式。
1.代理模式
代理模式(Proxy Pattern),为其它对象提供一种代理以控制对这个对象的访问。 ----《大话设计模式》
这里重点是控制,与装饰器模式的设计意图不同,代理模式被用作实际访问对象的接口,代理可以是任何事物的接口,比如:网络连接时,你输入一行地址,大部分时候,首先访问到的其实是代理服务,通常在代理服务器中设置有缓存,以快速响应请求而不必去请求真实服务器,而客户端感知到的效果就好像是访问到了真实服务器一样。所以,我们可以尝试理解代理中的控制,与装饰器中的装饰是不同的,代理通常是将非业务代码从业务代码中分离出来,装饰器模式则是在原有的业务代码基础上,增加额外的业务代码。另一个代理的例子是反向代理,反向代理,意思就是服务器的代理,大名鼎鼎的Nginx就是这样一种代理,它可以将密集的请求,根据管理员设定的策略,转发到分布式系统中的各个真实服务器中去,以达成负载均衡的目的,此外还可以充当防火墙。
与装饰器模式很相似,代理模式的UML类图:
- Subject: 定义了真实对象RealSubject和代理Proxy的共同接口,这样就可以在任何使用RealSubject的地方使用Proxy了;
- RealSubject: 真实对象;
- Proxy: 代理,保存了一个指向RealSubject的引用,这样就可以访问到真实对象的方法。
2. 应用场景和解决方案
什么时候使用代理模式比较合适,通常你有以下需求的时候可以考虑使用代理模式:
- 你需要控制对某对象的访问的时候,比如检查本次请求是否有权限访问对象;
- 访问某对象时,你需要增加一些额外功能时,通常,这些额外功能与访问控制有关。
创建代理模式可以按照如下方法:
- 继承或实现真实对象的抽象类或接口,使得这个类可以替代真实对象;
- 实现额外功能,以控制请求的访问。
3.代码实现
3.1 静态代理
静态代理,就是在编译阶段就已经创建了代理对象。
假设客户想要打开某一张图片,而图片保存在系统A中的数据库中,显然,每次去从数据库中读取图片是耗时的,通常地做法是第一次请求从数据库中读取图片,短时间第二次请求,就直接从距离客户较近的系统B的缓存中读取图片,这个系统B就设置了代理。
/** * 图片的接口,定义显示图片的方法 */ interface Image { void display(); }
真实图片类,实现图片接口,构造器调用私有方法loadImageFromDB,从数据库汇总读取图片信息,以初始化图片
/** * 真实图片对象 */ class RealImage implements Image { private String filename; public RealImage(final String filename) { this.filename = filename; loadImageFromDB(); } /** * 从数据库中获取图片 */ private void loadImageFromDB() { System.out.println("loading " + filename); } @Override public void display() { System.out.println("Displaying " + filename); } }
图片代理类, 其保存有真实图片对象的引用。主要display方法,是如何控制对真实对象访问的,
/** * 图片对象的代理 */ class ImageProxy implements Image { private Image realImage; private String filename; public ImageProxy(final String filename) { this.filename = filename; } /** * 控制对真实对象的访问: * 1.第一次访问时,图片不存在,创建图片类 * 2.第二次访问时,已有图片,直接调用真实图片的display方法以减少请求耗时 */ @Override public void display() { if (realImage == null) { realImage = new RealImage(filename); } realImage.display(); } }
客户端调用, 首先创建代理对象,先后两次通过代理对象访问图片资源,这是正向代理。
public class StaticProxyDemo { public static void main(String[] args) { Image proxy = new ImageProxy("10MB_Image.jpg"); System.out.println("第一次访问"); proxy.display(); System.out.println("第二次访问"); proxy.display(); } }
输出结果,可以看到,第二次访问时,没有再次去加载图片了。
第一次访问 loading 10MB_Image.jpg Displaying 10MB_Image.jpg 第二次访问 Displaying 10MB_Image.jpg
3.2 动态代理
动态代理,就是运行时,应用反射机制动态创建的代理对象。java.lang.reflect包中提供了创建动态代理的方法,通常,使用如下步骤创建动态代理:
- 实现InvocationHandler接口,代理对象调用的任何方法,都会重定向到InvocationHandler.invoke()中;
- 使用Proxy.newProxyInstance(ClassLoader classLoader, Class<?>[] interfaces, InvocationHandler handler)来创建代理对象,其中interfaces是代理类需要实现的一系列接口;
- 使用代理对象调用真实对象方法。
还是使用上面的例子,改为动态代理实现,首先实现InvocationHandler接口,invoke方法是需要重写的。所有proxy代理对象调用的方法,都会重定向到invoke中,所以这里特别适合做方法调用的统计。
proxied属性是真实对象的引用,可以看到,invoke中,利用反射技术,最终proxy调用的一切方法都会转发给真实对象proxied执行。
class ImageInvocationHandler implements InvocationHandler { private Object proxied; private static int count = 0; public ImageInvocationHandler(Object proxied) { this.proxied = proxied; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("第" + ++count + "次调用方法: " + method.getName()); if (null != args) { for (Object arg : args) { System.out.println(" " + arg); } } return method.invoke(proxied, args); } }
实例化InvocationHandler,并将其作为参数,获取到代理对象,注意,为了调用真实对象的方法,一般Proxy.newProxyInstance()之后需要将类型强转为真实对象的接口,之所以能强转,是因为代理proxy实现了真实对象实现的接口Image.
public class DynamicProxyDemo { public static void main(String[] args) { ImageInvocationHandler invocationHandler = new ImageInvocationHandler(new RealImage("10MB_PNG.png")); Image proxy = (Image) Proxy.newProxyInstance(RealImage.class.getClassLoader(), new Class[]{Image.class}, invocationHandler); proxy.display(); proxy.display(); } }
proxy代理对象调用了两次display方法,可以看到结果与静态代理一致。
loading 10MB_PNG.png 第1次调用方法: display Displaying 10MB_PNG.png 第2次调用方法: display Displaying 10MB_PNG.png
4.总结
读懂了上面的内容,也就对什么叫代理有了基本的认识,AOP(面向切面编程)就是采用动态代理来实现的,我认为这是代理模式在我们编程中最为优雅的应用,她实现了业务逻辑和其它逻辑之间的高层次的解耦,使代码更加易维护、易扩展、灵活性更强,所以,我们没有任何理由不去深入理解代理。才外,动态代理利用反射技术,可以在运行时动态地创建代理对象并指定实现的接口,由于动态代理对象调用的一切方法都会重定向到InvocationHandler.invoke方法中,所以动态代理在JavaEE中广泛地应用于日志、统计等框架中,AOP技术就是动态代理技术的应用体现。