博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java设计模式9:代理模式
阅读量:5896 次
发布时间:2019-06-19

本文共 6096 字,大约阅读时间需要 20 分钟。

代理模式

代理模式的定义很简单:给某一对象提供一个代理对象,并由代理对象控制对原对象的引用

 

代理模式的结构

有些情况下,一个客户不想或者不能够直接引用一个对象,可以通过代理对象在客户端和目标对象之间起到中介作用。代理模式中的角色有:

1、抽象对象角色

声明了目标对象和代理对象的共同接口,这样一来在任何可以使用目标对象的地方都可以使用代理对象

2、目标对象角色

定义了代理对象所代表的目标对象

3、代理对象角色

代理对象内部含有目标对象的引用,从而可以在任何时候操作目标对象;代理对象提供一个与目标对象相同的接口,以便可以在任何时候替代目标对象

 

静态代理示例

这里模拟的是作为访问网站的场景,以新浪网举例。我们通常访问新浪网,几乎所有的Web项目尤其是新浪这种大型网站,是不可能采用集中式的架构的,使用的一定是分布式的架构,分布式架构对于用户来说,我们发起链接的时候,链接指向的并不是最终的应用服务器,而是代理服务器比如Nginx,用以做负载均衡。

所以,我们的例子,简化来说就是用户访问新浪网-->代理服务器-->最终服务器。先定义一个服务器接口Server,简单定义一个方法,用于获取页面标题:

1 /** 2  * 服务器接口,用于获取网站数据 3  */ 4 public interface Server { 5  6     /** 7      * 根据url获取页面标题 8      */ 9     public String getPageTitle(String url);10     11 }

我们访问的是新浪网,所以写一个SinaServer,传入url,获取页面标题:

1 /** 2  * 新浪服务器 3  */ 4 public class SinaServer implements Server { 5  6     @Override 7     public String getPageTitle(String url) { 8         if ("http://www.sina.com.cn/".equals(url)) { 9             return "新浪首页";10         } else if ("http://http://sports.sina.com.cn/".equals(url)) {11             return "新浪体育_新浪网";12         }13         14         return "无页面标题";15     }16     17 }

这里写得比较简单,就做了一个if..else if判断,大家理解意思就好。写到这里,我们说明两点:

  • 如果不使用代理,那么用户访问相当于就是直接new SinaServer()出来并且调用getPageTitle(String url)方法即可
  • 由于分布式架构的存在,因此我们这里要写一个NginxProxy,作为一个代理,到时候用户直接访问的是NginxProxy而不是和SinaServer打交道,由NginxProxy负责和最终的SinaServer打交道

因此,我们写一个NginxProxy:

1 /** 2  * Nginx代理 3  */ 4 public class NginxProxy implements Server { 5      6     /** 7      * 新浪服务器列表 8      */ 9     private static final List
SINA_SERVER_ADDRESSES = Lists.newArrayList("192.168.1.1", "192.168.1.2", "192.168.1.3"); 10 11 private Server server;12 13 public NginxProxy(Server server) {14 this.server = server;15 }16 17 @Override18 public String getPageTitle(String url) {19 // 这里就简单传了一个url,正常请求传入的是Request,使用UUID模拟请求原始Ip20 String remoteIp = UUID.randomUUID().toString();21 // 路由选择算法这里简单定义为对remoteIp的Hash值的绝对值取模22 int index = Math.abs(remoteIp.hashCode()) % SINA_SERVER_ADDRESSES.size();23 // 选择新浪服务器Ip24 String realSinaIp = SINA_SERVER_ADDRESSES.get(index);25 26 return "【页面标题:" + server.getPageTitle(url) + "】,【来源Ip:" + realSinaIp + "】";27 }28 29 }

这里同样为了简单起见,服务器列表写死几个ip,同时由于只传一个url而不是具体的Request,每次随机一个UUID,对UUID的HashCode绝对值取模,模拟这次请求被路由到哪台服务器上。

调用方这么写:

1 /** 2  * 静态代理测试 3  */ 4 public class StaticProxyTest { 5  6     @Test 7     public void testStaticProxy() { 8         Server sinaServer = new SinaServer(); 9         Server nginxProxy = new NginxProxy(sinaServer);10         System.out.println(nginxProxy.getPageTitle("http://www.sina.com.cn/"));11     }12     13 }

第8行表示的是要访问的是新浪服务器,第9行表示的是用户实际访问的是Nginx代理而不是真实的新浪服务器,由于新浪服务器和代理服务器实际上都是服务器,因此他们可以使用相同的接口Server。

程序最终运行的结果为:

【页面标题:新浪首页】,【来源Ip:192.168.1.2】

当然,多运行几次,来源Ip一定是会变的,这就是一个静态代理的例子,即用户不和最终目标对象角色(SinaServer)打交道,而是和代理对象角色(NginxProxy)打交道,由代理对象角色(NginxProxy)控制用户的访问

 

静态代理的缺点

静态代理的特点是静态代理的代理类是程序员创建的,在程序运行之前静态代理的.class文件已经存在了

从静态代理模式的代码来看,静态代理模式确实有一个代理对象来控制实际对象的引用,并通过代理对象来使用实际对象。这种模式在代理量较小的时候还可以,但是代理量一大起来,就存在着两个比较大的缺点:

1、静态代理的内容,即NginxProxy的路由选择这几行代码,只能服务于Server接口而不能服务于其他接口,如果其它接口想用这几行代码,比如新增一个静态代理类。久而久之,由于静态代理的内容无法复用,必然造成静态代理类的不断庞大

2、Server接口里面如果新增了一个方法,比如getPageData(String url)方法,实际对象实现了这个方法,代理对象也必须新增方法getPageData(String url),去给getPageData(String url)增加代理内容(假如需要的话)

 

利用JDK中的代理类Proxy实现动态代理的示例

由于静态代理的局限性,所以产生了动态代理的概念。

上面的例子我们采用动态代理的方式,动态代理的核心就是将公共的逻辑抽象到InvocationHandler中。关于动态代理,JDK本身提供了支持,因此实现一下InvocationHandler接口:

1 /** 2  * Nginx InvocationHandler 3  */ 4 public class NginxInvocationHandler implements InvocationHandler { 5  6     /** 7      * 新浪服务器列表 8      */ 9     private static final List
SINA_SERVER_ADDRESSES = Lists.newArrayList("192.168.1.1", "192.168.1.2", "192.168.1.3"); 10 11 private Object object;12 13 public NginxInvocationHandler(Object object) {14 this.object = object;15 }16 17 @Override18 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {19 String remoteIp = UUID.randomUUID().toString();20 int index = Math.abs(remoteIp.hashCode()) % SINA_SERVER_ADDRESSES.size();21 String realSinaIp = SINA_SERVER_ADDRESSES.get(index);22 23 StringBuilder sb = new StringBuilder();24 sb.append("【页面标题:");25 sb.append(method.invoke(object, args));26 sb.append("】,【来源Ip:");27 sb.append(realSinaIp);28 sb.append("】");29 return sb.toString();30 }31 32 }

这里就将选择服务器的逻辑抽象成为了公共的代码了,因为调用的是Object里面的method,Object是所有类的超类,因此并不限定非要是Sever,A、B、C都是可以的,因此这个NginxInvocationHandler可以灵活地被各个地方给复用。

调用的时候这么写:

1 /** 2  * 动态代理测试 3  */ 4 public class DynamicProxyTest { 5  6     @Test 7     public void testDynamicProxy() { 8         Server sinaServer = new SinaServer(); 9         InvocationHandler invocationHandler = new NginxInvocationHandler(sinaServer);10         Server proxy = (Server)Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{Server.class}, invocationHandler);11         12         System.out.println(proxy.getPageTitle("http://www.sina.com.cn/"));13     }14     15 }

Proxy本身也是JDK提供给开发者的,使用Proxy的newProxyInstance方法可以产生对目标接口的一个代理,至于代理的内容,即InvocatoinHandler的实现。

看一下运行结构,和静态代理是一样的:

【页面标题:新浪首页】,【来源Ip:192.168.1.2】

动态代理写法本身有点不好理解,需要开发者多实践,多思考,才能真正明白动态代理的含义及其实际应用。

 

动态代理的优点

1、最直观的,类少了很多

2、代理内容也就是InvocationHandler接口的实现类可以复用,可以给A接口用、也可以给B接口用,A接口用了InvocationHandler接口实现类A的代理,不想用了,可以方便地换成InvocationHandler接口实现B的代理

3、最重要的,用了动态代理,就可以在不修改原来代码的基础上,就在原来代码的基础上做操作,这就是AOP即面向切面编程

 

动态代理的缺点

动态代理有一个最大的缺点,就是它只能针对接口生成代理,不能只针对某一个类生成代理,比方说我们在调用Proxy的newProxyInstance方法的时候,第二个参数传某个具体类的getClass(),那么会报错:

Exception in thread "main" java.lang.IllegalArgumentException: proxy.DynamicHelloWorldImpl is not an interface

这是因为java.lang.reflect.Proxy的newProxyInstance方法会判断传入的Class是不是一个接口:

.../*  * Verify that the Class object actually represents an  * interface.  */ if (!interfaceClass.isInterface()) { throw new IllegalArgumentException(    interfaceClass.getName() + " is not an interface");}...

而实际使用中,我们为某一个单独的类实现一个代理也很正常,这种情况下,我们就可以考虑使用CGLIB(一种字节码增强技术)来为某一个类实现代理了。

转载地址:http://tsxsx.baihongyu.com/

你可能感兴趣的文章
ASP.NET视频教程 手把手教你做企业论坛网站 视频教程
查看>>
[LeetCode] Meeting Rooms II
查看>>
从Swift学习iOS开发的路线指引
查看>>
Scribes:小型文本编辑器,支持远程编辑
查看>>
ssh 安装笔记
查看>>
游戏音效下载网站大全
查看>>
实验五
查看>>
3-继承
查看>>
海归千千万 为何再无钱学森
查看>>
vue2.0 仿手机新闻站(六)详情页制作
查看>>
FreeRTOS的内存管理
查看>>
JSP----九大内置对象
查看>>
Java中HashMap详解
查看>>
delphi基本语法
查看>>
沙盒目录介绍
查看>>
260. Single Number III
查看>>
Hadoop生态圈-Kafka的完全分布式部署
查看>>
css的border的solid
查看>>
[MODx] Build a CMP (Custom manager page) using MIGX in MODX 2.3 -- 1
查看>>
jQuery自动完成点击html元素
查看>>