Java设计模式9:代理模式
代理模式
代理模式的定义很简单:给某一对象提供一个代理对象,并由代理对象控制对原对象的引用。
代理模式的结构
有些情况下,一个客户不想或者不能够直接引用一个对象,可以通过代理对象在客户端和目标对象之间起到中介作用。代理模式中的角色有:
1、抽象对象角色
声明了目标对象和代理对象的共同接口,这样一来在任何可以使用目标对象的地方都可以使用代理对象
2、目标对象角色
定义了代理对象所代表的目标对象
3、代理对象角色
代理对象内部含有目标对象的引用,从而可以在任何时候操作目标对象;代理对象提供一个与目标对象相同的接口,以便可以在任何时候替代目标对象
静态代理示例
这里模拟的是作为访问网站的场景,以新浪网举例。我们通常访问新浪网,几乎所有的Web项目尤其是新浪这种大型网站,是不可能采用集中式的架构的,使用的一定是分布式的架构,分布式架构对于用户来说,我们发起链接的时候,链接指向的并不是最终的应用服务器,而是代理服务器比如Nginx,用以做负载均衡。
所以,我们的例子,简化来说就是用户访问新浪网-->代理服务器-->最终服务器。先定义一个服务器接口Server,简单定义一个方法,用于获取页面标题:
/**
* 服务器接口,用于获取网站数据
*/
public interface Server { /**
* 根据url获取页面标题
*/
public String getPageTitle(String url); }
我们访问的是新浪网,所以写一个SinaServer,传入url,获取页面标题:
/**
* 新浪服务器
*/
public class SinaServer implements Server { @Override
public String getPageTitle(String url) {
if ("http://www.sina.com.cn/".equals(url)) {
return "新浪首页";
} else if ("http://http://sports.sina.com.cn/".equals(url)) {
return "新浪体育_新浪网";
} return "无页面标题";
} }
这里写得比较简单,就做了一个if..else if判断,大家理解意思就好。写到这里,我们说明两点:
- 如果不使用代理,那么用户访问相当于就是直接new SinaServer()出来并且调用getPageTitle(String url)方法即可
- 由于分布式架构的存在,因此我们这里要写一个NginxProxy,作为一个代理,到时候用户直接访问的是NginxProxy而不是和SinaServer打交道,由NginxProxy负责和最终的SinaServer打交道
因此,我们写一个NginxProxy:
/**
* Nginx代理
*/
public class NginxProxy implements Server { /**
* 新浪服务器列表
*/
private static final List<String> SINA_SERVER_ADDRESSES = Lists.newArrayList("192.168.1.1", "192.168.1.2", "192.168.1.3"); private Server server; public NginxProxy(Server server) {
this.server = server;
} @Override
public String getPageTitle(String url) {
// 这里就简单传了一个url,正常请求传入的是Request,使用UUID模拟请求原始Ip
String remoteIp = UUID.randomUUID().toString();
// 路由选择算法这里简单定义为对remoteIp的Hash值的绝对值取模
int index = Math.abs(remoteIp.hashCode()) % SINA_SERVER_ADDRESSES.size();
// 选择新浪服务器Ip
String realSinaIp = SINA_SERVER_ADDRESSES.get(index); return "【页面标题:" + server.getPageTitle(url) + "】,【来源Ip:" + realSinaIp + "】";
} }
这里同样为了简单起见,服务器列表写死几个ip,同时由于只传一个url而不是具体的Request,每次随机一个UUID,对UUID的HashCode绝对值取模,模拟这次请求被路由到哪台服务器上。
调用方这么写:
/**
* 静态代理测试
*/
public class StaticProxyTest { @Test
public void testStaticProxy() {
Server sinaServer = new SinaServer();
Server nginxProxy = new NginxProxy(sinaServer);
System.out.println(nginxProxy.getPageTitle("http://www.sina.com.cn/"));
} }
第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接口:
/**
* Nginx InvocationHandler
*/
public class NginxInvocationHandler implements InvocationHandler { /**
* 新浪服务器列表
*/
private static final List<String> SINA_SERVER_ADDRESSES = Lists.newArrayList("192.168.1.1", "192.168.1.2", "192.168.1.3"); private Object object; public NginxInvocationHandler(Object object) {
this.object = object;
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String remoteIp = UUID.randomUUID().toString();
int index = Math.abs(remoteIp.hashCode()) % SINA_SERVER_ADDRESSES.size();
String realSinaIp = SINA_SERVER_ADDRESSES.get(index); StringBuilder sb = new StringBuilder();
sb.append("【页面标题:");
sb.append(method.invoke(object, args));
sb.append("】,【来源Ip:");
sb.append(realSinaIp);
sb.append("】");
return sb.toString();
} }
这里就将选择服务器的逻辑抽象成为了公共的代码了,因为调用的是Object里面的method,Object是所有类的超类,因此并不限定非要是Sever,A、B、C都是可以的,因此这个NginxInvocationHandler可以灵活地被各个地方给复用。
调用的时候这么写:
/**
* 动态代理测试
*/
public class DynamicProxyTest { @Test
public void testDynamicProxy() {
Server sinaServer = new SinaServer();
InvocationHandler invocationHandler = new NginxInvocationHandler(sinaServer);
Server proxy = (Server)Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{Server.class}, invocationHandler); System.out.println(proxy.getPageTitle("http://www.sina.com.cn/"));
} }
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(一种字节码增强技术)来为某一个类实现代理了。
Java设计模式9:代理模式的更多相关文章
- Java设计模式之代理模式(静态代理和JDK、CGLib动态代理)以及应用场景
我做了个例子 ,需要可以下载源码:代理模式 1.前言: Spring 的AOP 面向切面编程,是通过动态代理实现的, 由两部分组成:(a) 如果有接口的话 通过 JDK 接口级别的代理 (b) 如果没 ...
- java设计模式6——代理模式
java设计模式6--代理模式 1.代理模式介绍: 1.1.为什么要学习代理模式?因为这就是Spring Aop的底层!(SpringAop 和 SpringMvc) 1.2.代理模式的分类: 静态代 ...
- 夜话JAVA设计模式之代理模式(Proxy)
代理模式定义:为另一个对象提供一个替身或者占位符以控制对这个对象的访问.---<Head First 设计模式> 代理模式换句话说就是给某一个对象创建一个代理对象,由这个代理对象控制对原对 ...
- Java设计模式:代理模式(转)
代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.这里使用到编程中的一 ...
- Java 设计模式_代理模式(2016-08-19)
概念: 代理模式是对象的结构模式.代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用. 就是一个人或者机构代表另一个人或者机构采取行动.在一些情况下,一个客户不想或者不能够直接引用一 ...
- JAVA设计模式:代理模式&& 装饰模式区别
在前面学习了代理模式和装饰模式后,发现对两者之间有时候会混淆,因此对两者进行了区别和理解: 装饰模式你可以这样理解,就像糖一样,卖的时候商家大多要在外面包一层糖纸,其实原本还是糖. public in ...
- Java设计模式:代理模式(二)
承接上文 三.计数代理 计数代理的应用场景是:当客户程序需要在调用服务提供者对象的方法之前或之后执行日志或者计数等额外功能时,就可以用到技术代理模式.计数代理模式并不是把额外操作的代码直接添加到原服务 ...
- java设计模式之代理模式 ,以及和java 回调机制的区别
java 代理模式就是: 将自己要做的事交给别人去做(这个别人就是代理者,自己就是被代理者),为什么自己能做的要交给别人去做了?假如一个小学生小明,现在要写作业,但是又想玩游戏,他更想玩游戏,并且不想 ...
- Java设计模式之代理模式(Proxy)
前言: 最近在研究Retrofit开源框架的时候,其主要核心代码是通过注解标示参数,动态代理模式实现具体接口,反射机制进行参数解析,最终实现发送请求.其实之前在学习Xutils源码的时候,Xutils ...
- Java设计模式 之 代理模式
所谓的代理模式就是为其它类或对象提供一个代理以控制对这个对象的访问.那么常见的代理有远程代理,虚拟代理,保护代理,智能代理. 1. 远程代理:为一个不同地址空间的对象提供一个本地代理对象. 2. 虚拟 ...
随机推荐
- B-tree
2-3 Tree 二叉搜索树的每个节点只带有一个值,这个值将数据区间划分成两部分,值左边的部分(也就是小于这个值的数据)保存到节点的左子树,值右边的部分保存到节点的右子树.因此,每个非叶子节最多能够拥 ...
- Spring Boot项目的打包和部署
补充一点:搜索了下别人Spring Boot部署方案,大多都说:①packaging设为war:②要添加Spring Boot的tomcat依赖:③修改output路径,但是使用STS新建Spring ...
- 自动化-Appium
1.手把手教你 Android 标准 APP 的四大自动化测试法宝:https://testerhome.com/topics/5846 2.中文 Appium API 文档:https://test ...
- 简单跳转到微信分享,基于libWeiChatSDK 和简单的自定义UIActivityViewController
一.自定义UIActivity: 如果想要自定义UIActivity必须知道UIActivityViewController.首先这个类主要是用于接受字符串,RUL类型和图片类型的数据用于分享和操作的 ...
- Android中如何收听特定应用安装成功的广播
一.manifest的配置 <receiver android:name=".AppReceiver"> <intent-filter> <actio ...
- LinkedHahsMap和HashMap的比较
http://www.cnblogs.com/hubingxu/archive/2012/02/21/2361281.html#commentform 一般情况下,我们用的最多的是HashMap,在M ...
- viewpage滑动查看图片并再有缩略图预览
首先看下效果图, 主要功能分为3大块 一是滑动查看,通过viewpage来实现,方法见 http://www.cnblogs.com/lovemo1314/p/6109312.html 二.点击放大 ...
- sqlserver游标的使用方式
----臨時表 把數據抄寫到此表,然後做2個表的同步 SELECT [FA_NUMBER] ,[STATUS] ,[FA_REQUESTOR] ,[CALI_NUMBER] ,[AMT] FROM [ ...
- 可以编辑R代码的eclipse插件
说到强大的IDE,eclipse肯定是首先会被想到的几个之一,幸运地是,R也能使用它.在http://www.walware.de/goto/statet上有个StatET的插件,专门为R而做,从此R ...
- [UCSD白板题] Sorting: 3-Way Partition
Problem Introduction The goal in this problem is to redesign a given implementation of the randomize ...