一、概要
代理在生活中随处可以看到,如“厂家“委托”代理为其销售商品”、“明星的经济人”等,在程序中也被广泛应用。
优点一:可以隐藏代理与被代理对象
优点二:可以实现客户与委托类间的解耦,在不修改委托类代码的情况下能够做一些额外的处理。
先来了解一些关于代理的概念:正向代理、反向代理、静态代理与动态代理:
1.1、正向代理
比如X花店代A,B,C,D,E五位男生向Candy女生送匿名的生日鲜花,这里的X花店就是5位顾客的代理,花店代理的是客户,隐藏的是客户。这就是我们常说的代理。
正向代理隐藏了真实的请求客户端。服务端不知道真实的客户端是谁,客户端请求的服务都被代理服务器代替来请求,某些科学上网工具扮演的就是典型的正向代理角色。用浏览器访问http://www.google.com时被墙了,于是你可以在国外搭建一台代理服务器,让代理帮我去请求google.com,代理把请求返回的相应结构再返回给我。
当多个客户端访问服务器时服务器不知道真正访问自己的客户端是那一台。正向代理中,proxy和client同属一个LAN,对server透明;
1.2、反向代理
拨打10086客服电话,接线员可能有很多个,调度器会智能的分配一个接线员与你通话。这里的调度器就是一个代理,只不过他代理的是接线员,客户端不能确定真正与自己通话的人,隐藏与保护的是目标对象。
反向代理隐藏了真实的服务端,当我们请求 ww.baidu.com 的时候,就像拨打10086一样,背后可能有成千上万台服务器为我们服务,但具体是哪一台,你不知道,也不需要知道,你只需要知道反向代理服务器是谁就好了,ww.baidu.com 就是我们的反向代理服务器,反向代理服务器会帮我们把请求转发到真实的服务器那里去。Nginx就是性能非常好的反向代理服务器,用来做负载均衡。
反向代理中,proxy和server同属一个LAN,对client透明。
1.3、静态代理
若代理类在程序运行前就已经存在,那么这种代理方式被成为 静态代理。静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。设计模式中所说的代理默认就是指的静态代理。
特点:
1.可以做到在不修改目标对象的功能前提下,对目标功能扩展。
2.缺点是因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.
如何解决静态代理中的缺点呢?答案是可以使用动态代理方式。
示例:
//接口:IUserDao.java /** * 接口 */ public interface IUserDao { void save(); } //目标对象:UserDao.java /** * 接口实现 * 目标对象 */ public class UserDao implements IUserDao { public void save() { System.out.println("----已经保存数据!----"); } } //代理对象:UserDaoProxy.java /** * 代理对象,静态代理 */ public class UserDaoProxy implements IUserDao{ //接收保存目标对象 private IUserDao target; public UserDaoProxy(IUserDao target){ this.target=target; } public void save() { System.out.println("开始事务..."); target.save();//执行目标对象的方法 System.out.println("提交事务..."); } } //测试类:App.java /** * 测试类 */ public class App { public static void main(String[] args) { //目标对象 UserDao target = new UserDao(); //代理对象,把目标对象传给代理对象,建立代理关系 UserDaoProxy proxy = new UserDaoProxy(target); proxy.save();//执行的是代理的方法 } }
1.4、动态代理
代理类在程序运行时创建的代理方式被成为动态代理。 也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。
1.代理对象,不需要实现接口
2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
3.动态代理也叫做:JDK代理,接口代理
代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理
代码示例:
接口类IUserDao.java以及接口实现类,目标对象UserDao是一样的,没有做修改.在这个基础上,增加一个代理工厂类(ProxyFactory.java),将代理类写在这个地方,然后在测试类(需要使用到代理的代码)中先建立目标对象和代理对象的联系,然后代用代理对象的中同名方法,更多
//代理工厂类:ProxyFactory.java /** * 创建动态代理对象 * 动态代理不需要实现接口,但是需要指定接口类型 */ public class ProxyFactory{ //维护一个目标对象 private Object target; public ProxyFactory(Object target){ this.target=target; } //给目标对象生成代理对象 public Object getProxyInstance(){ return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("开始事务2"); //执行目标对象方法 Object returnValue = method.invoke(target, args); System.out.println("提交事务2"); return returnValue; } } ); } } //测试类:App.java /** * 测试类 */ public class App { public static void main(String[] args) { // 目标对象 IUserDao target = new UserDao(); // 【原始的类型 class cn.itcast.b_dynamic.UserDao】 System.out.println(target.getClass()); // 给目标对象,创建代理对象 IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance(); // class $Proxy0 内存中动态生成的代理对象 System.out.println(proxy.getClass()); // 执行方法 【代理对象】 proxy.save(); } }
二、代理模式
代理模式,为其他对象提供一种代理以控制对这个对象的访问
举个例子来说明代理的作用:假设我们想邀请一位明星,那么并不是直接连接明星,而是联系明星的经纪人,来达到同样的目的.明星就是一个目标对象,他只要负责活动中的节目,而其他琐碎的事情就交给他的代理人(经纪人)来解决,这就是代理思想在现实中的一个例子。
2.1、代理模式的意义
控制对被访问对象的访问
简化对被访问对象的访问(比如Web Service)
实现AOP(AOP:面向切面编程)
添加非业务相关的代码,比如添加日志
2.2、理解代理模式
代理模式中主要有3个角色:抽象主题,真实主题与代理
主题类:定义主题的相关功能接口
具体主题类:实现主题类中定义的功能
代理类:控制对具体主题类的访问
高家三小姐的礼貌就是:主题
高家三小姐本人:具体主题类
悟空:代理类
2.3、实现代理模式一
主题类ICarDAO
package DP04.demo41; /**汽车表的访问接口 抽象主题*/ public interface ICarDAO { /**新增*/ void add(); /**编辑*/ void edit(); }
具体主题:CarDAO
package DP04.demo41; /**具体主题类*/ public class CarDAO implements ICarDAO { @Override public void add() { System.out.println("保存汽车对象到数据库成功!"); } @Override public void edit() { System.out.println("编辑汽车对象到数据库成功!"); } }
代理类:CarDAOProxy
package DP04.demo41; /**代理*/ public class CarDAOProxy implements ICarDAO { /**目标对象*/ private ICarDAO cardao; public CarDAOProxy(ICarDAO cardao){ this.cardao=cardao; } @Override public void add() { System.out.println("写入新增日志"); cardao.add(); System.out.println("关闭日志文件"); } @Override public void edit() { System.out.println("写入编辑日志"); cardao.edit(); System.out.println("关闭日志文件"); } }
测试客户端Client
package DP04.demo41; public class Client { public static void main(String[] args) { ICarDAO dao=new CarDAOProxy(new CarDAO()); dao.add(); dao.edit(); } }
运行结果:
2.4、实现代理模式二
某些情况如果没有接口或者抽象类(Subject),那么GOF的那种实现方式就不太现实,特别是在一些面向切面(AOP)的框架中,经常会出现动态生成一个继承于实际类型的代理类
真实主题类CarDAO
package DP04.demo042; /** * 具体主题类 */ public class CarDAO { public void del() { System.out.println("删除汽车对象成功!"); } public void select() { System.out.println("查询汽车对象成功!"); } }
代理类Proxy:
package DP04.demo042; /** * 代理类,继承重写 * */ public class Proxy extends CarDAO { @Override public void del() { System.out.println("开始写入日志"); super.del(); System.out.println("结束写入日志"); } @Override public void select() { System.out.println("开始写入日志"); super.select(); System.out.println("结束写入日志"); } }
测试客户端Client:
package DP04.demo042; public class Client { public static void main(String[] args) { Proxy proxy=new Proxy(); proxy.del(); proxy.select(); } }
运行结果:
通过继承这种方式实现的代理并不是由GOF提出的一种模式,只不过是依照GOF提出的代理模式的理念的一种变形
主要用在动态生成代理类的一些框架中,这里非常明显违背了“依赖倒置”的原则。
三、总结
代理模式适用场合
远程代理:比如Web Service
虚拟代理:所谓虚拟代理是根据需要创建开销很大的对象,通过它来存放实例化需要很长时间的真实对象,这样就可以达到性能的最优化
安全代理:用来控制真实对象访问时的权限。一般用于对象应该有不同的访问权限的时候。
智能指引 :是指当调用真实的对象时,代理处理另外一些事。如计算真实对象的引用次数,这样当该对象没有引用是,可以自动释放他
四、示例
示例:https://coding.net/u/zhangguo5/p/DP01/git
五、视频
视频:https://www.bilibili.com/video/av15867320/
六、作业与资料
作业:
1、请写一个代理模式实现经济人(Proxy)与明星(Star)之间的简单业务模式, 明星可以唱歌(Sing)与拍戏(Play),经济人可以完成”签订合同“与”收款功能“。
2、请使用继承的方式实现第一题。
3、请将第二章切换数据库的作业增加代理模式,实现日志写入功能(可以静态或动态代理)