代理模式
定义:给某个对象提供一个代理对象,代理对象持有被代理对象的引用。
对于代理模式定义的剖析
1、因为有两个对象(一个代理对象,一个被代理对象),所以就会有两个实体类。
2、代理对象要能够代理另一个对象,就必须有和那个被代理对象的被代理方法一模一样的方法。(这就时两个实体类实现同一个接口的理由)。
3、“外界通过访问代理对象达到间接访问被代理对象” 这句话精确解释是:外界访问的是代理对象,代理对象中必然有一个能够通往被代理对象的通道,从而拿到被代理对象的数据,再加上代理对象的自己的部分数据(如果有需要的话)返回给调用者。
静态代理示例
示例一:
1 package com.daili; 2 3 public interface Action 4 { 5 public String playBasketball(); 6 public String swim(); 7 public String playCards(); 8 } 9 10 public class XiaoMing implements Action11 {12 13 @Override14 public String playBasketball()15 {16 return "小明打篮球";17 18 }19 20 21 @Override22 public String swim() 23 {24 return "小明学游泳";25 26 }27 28 @Override29 public String playCards()30 {31 return "小明打扑克";32 33 }34 35 }36 37 public class ProxyXiaoMing implements Action38 {39 private XiaoMing xiaoMing = null; // 或者直接创建出该对象40 41 @Override42 public String playBasketball()43 {44 if(xiaoMing == null){45 xiaoMing = new XiaoMing();46 }47 return xiaoMing.playBasketball();48 }49 50 @Override51 public String swim()52 {53 if(xiaoMing == null){54 xiaoMing = new XiaoMing();55 }56 return xiaoMing.swim();57 }58 59 @Override60 public String playCards()61 {62 if(xiaoMing == null){63 xiaoMing = new XiaoMing();64 }65 return xiaoMing.playCards();66 }67 }68 69 class Test70 {71 public static void main(String[] args)72 {73 ProxyXiaoMing proxyXiaoMing = new ProxyXiaoMing();74 String string = proxyXiaoMing.playBasketball();75 System.out.println(string);76 77 String string1 = proxyXiaoMing.swim();78 System.out.println(string1);79 80 String string2 = proxyXiaoMing.playCards();81 System.out.println(string2);82 }83 }
示例二:
1 package com.daili; 2 3 public class ProxyXiaoMing implements Action 4 { 5 private Action action; 6 public ProxyXiaoMing(Action action) 7 { 8 this.action = action; 9 }10 11 @Override12 public String playBasketball()13 {14 return action.playBasketball(); // 和第一种方式的形式不同,这种方式不是直接 new 的;但是本质是一样的,外界传过来的还是 new XiaoMing() 的具体实现类15 }16 17 @Override18 public String swim()19 {20 return action.swim();21 }22 23 @Override24 public String playCards()25 {26 return action.swim();27 }28 }29 30 class Test31 {32 public static void main(String[] args)33 {34 35 XiaoMing xiaoMing = new XiaoMing();36 ProxyXiaoMing proxyXiaoMing = new ProxyXiaoMing(xiaoMing);37 System.out.println(proxyXiaoMing.playBasketball());38 System.out.println(proxyXiaoMing.playCards());39 System.out.println(proxyXiaoMing.swim());40 41 }42 }
以上个示例只是解释了代理对象的本质,但是并没有解释我们为什么要使用代理对象?换句话说,我们可以直接使用被代理对象不就行了吗,为何要使用代理对象呢?以下给出实例三代码;
示例三:
1 /** 2 * 服务器接口,用于获取网站数据 3 */ 4 public interface Server { 5 6 /** 7 * 根据url获取页面标题 8 */ 9 public String getPageTitle(String url);10 11 }12 13 /**14 * 新浪服务器15 */16 class SinaServer implements Server {17 18 @Override19 public String getPageTitle(String url) {20 if ("http://www.sina.com.cn/".equals(url)) {21 return "新浪首页";22 } else if ("http://http://sports.sina.com.cn/".equals(url)) {23 return "新浪体育_新浪网";24 }25 26 return "无页面标题";27 }28 29 }30 31 /**32 * Nginx代理33 */34 class NginxProxy implements Server {35 36 /**37 * 新浪服务器列表38 */39 private static final ListSINA_SERVER_ADDRESSES = Lists.newArrayList("192.168.1.1", "192.168.1.2", "192.168.1.3"); 40 41 private Server server; // 静态代理只能传入这个接口,如果像动态代理一样传入 object ,那么下面的 Server 的 getPageTitle() 方法就拿不到42 43 public NginxProxy(Server server) {44 this.server = server;45 }46 47 @Override48 public String getPageTitle(String url) {49 // 这里就简单传了一个url,正常请求传入的是Request,使用UUID模拟请求原始Ip50 String remoteIp = UUID.randomUUID().toString();51 // 路由选择算法这里简单定义为对remoteIp的Hash值的绝对值取模52 int index = Math.abs(remoteIp.hashCode()) % SINA_SERVER_ADDRESSES.size();53 // 选择新浪服务器Ip54 String realSinaIp = SINA_SERVER_ADDRESSES.get(index);55 56 return "【页面标题:" + server.getPageTitle(url) + "】,【来源Ip:" + realSinaIp + "】";57 }58 59 }60 61 /**62 * 静态代理测试63 */64 public class StaticProxyTest {65 66 @Test67 public void testStaticProxy() {68 Server sinaServer = new SinaServer();69 Server nginxProxy = new NginxProxy(sinaServer);70 System.out.println(nginxProxy.getPageTitle("http://www.sina.com.cn/"));71 }72 73 }
程序运行结果:
【页面标题:新浪首页】,【来源Ip:192.168.1.2】
多运行几次,来源 Ip 就会发生变化,这就是静态代理。这个代理对象实现了 Nginx 轮询的操作,因为对于服务器来说来源 IP 是发生改变了的。
静态代理的缺点
静态代理的特点是静态代理的代理类是程序员创建的,在程序运行之前静态代理的.class文件已经存在了(这一点很重要,和后面的动态代理会形成对比)。
从静态代理模式的代码来看,静态代理模式确实有一个代理对象来控制实际对象的引用,并通过代理对象来使用实际对象。这种模式在代理量较小的时候还可以,但是代理量一大起来,就存在着两个比较大的缺点:
①:静态代理的内容,即NginxProxy的路由选择这几行代码,只能对特定的接口,如:Server 接口进行代理。如果其它接口想用这几行代码,那你可以新增一个静态代理类,并且在这个实现类里面实现那个方法,然后再对这个方法进行修改(其实就是把上面的重写一遍),久而久之,由于静态代理的内容无法复用,必然造成静态代理类的不断庞大。或者就像有的人说的你可以使用这个代理类进行多实现,比如说你可以实现两个接口,那你理论上就可以代理两个接口,但是你这个代理类里面的方法就很杂,显得不伦不类。。
②:Server 接口里面如果新增了一个方法,比如 getPageData(String url) 方法,实际对象实现了这个方法,代理对象也必须新增方法 getPageData(String url),去给 getPageData(String url) 增加代理内容(假如需要的话)。
由于静态代理的缺陷,于是有了动态代理。上面的案例使用动态代理的方式来展示
动态代理
动态代理的核心就是将公共的逻辑抽象到 InvocationHandler 中。关于动态代理,JDK本身提供了支持,因此实现一下InvocationHandler接口
1 /** 2 * Nginx InvocationHandler 3 */ 4 public class NginxInvocationHandler implements InvocationHandler { 5 6 /** 7 * 新浪服务器列表 8 */ 9 private static final ListSINA_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 // 有一个问题:动态代理怎么对一个对象的多个方法做代理呢?如果 method 不同,那么内容怎么修改呢?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 }33 /**34 * 动态代理测试35 */36 public class DynamicProxyTest {37 38 @Test39 public void testDynamicProxy() {40 Server sinaServer = new SinaServer();41 InvocationHandler invocationHandler = new NginxInvocationHandler(sinaServer);42 Server proxy = (Server)Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{Server.class}, invocationHandler);43 44 System.out.println(proxy.getPageTitle("http://www.sina.com.cn/"));45 }46 47 }
和静态代理相比较的优点:
①:这里就将选择服务器的逻辑抽象成为了公共的代码了(可以实现接口的复用),因为调用的是 Object 里面的 method,Object 是所有类的超类,因此并不限定非要是 Sever;A、B、C 都是可以的,因此这个NginxInvocationHandler 可以灵活地被各个地方给复用。
②:针对静态代理的第二个缺点动态代理也没有很好的解决。
示例:
1 public Object invoke(Object object, Method method, Object[] args) throws Throwable { 2 if (method.getName().equals("getPageTitle")) { 3 add("代理内容"); 4 return method.invoke(object,args); 5 } 6 else if (method.getName().equals("getBookTitle")) 7 { 8 add("代理内容"); 9 return method.invoke(object,args);10 }else if(······)11 ·12 ·13 ·14 ·15 // 如果接口中有新添加的方法,被代理对象中也实现了这个方法而且这个方法还要代理对象明确代理,那即使是动态代理方式还是要重新添加代码16 }
总结:动态代理只解决了静态代理的内容无法针对接口复用的问题。
第三个案例参考于:http://www.cnblogs.com/xrq730/p/4907999.html