GoF代理模式
1.对代理模式的理解
目标对象和代理对象都具有 相同的行为,把该行为抽象为接口,目标对象和代理对象都 实现该接口。
对于生活场景二:老杜认为 另外收取费用--增加代码 是功能的增强。 我认为这不是,应该认为
自己找房子能力不强,找得慢,时间长,所以通过代理人(房屋中介),增强这方面的能力,它们找房快。
西游记场景中:八戒 是 客户端。 八戒 认为自己 所看到的 代理人(悟空幻化的高小姐)和 目标对象(高小姐)两人是同一个对象。
代理模式是GoF 的23种模式之一,属于结构型设计模式。
代理模式的作用:为其他对象 提供 一种代理 以控制这个对象的访问。
在某些情况下, 一个客户 不想或不能 直接引用一个对象,此时可以通过 一个称之为 “代理” 的第三者来实现间接引用。 代理对象 在客户端和目标对象之间 起到中介的作用, 并且可以 通过代理对象 去掉 客户不应该看到的内容 或 添加客户需要的额外服务。
通过 引入一个新的对象 来实现 对真实对象的操作 或者 将新的对象作为真实对象的一个替身,这种实现机制即为代理模式,通过引入代理对象间接访问一个对象,这就是代理模式的模式动机。
代理模式中的角色:
代理类(代理主题)
目标类(真实主题)
代理类和目标类的公共接口(抽象主题):客户端 在使用代理类时, 就感觉自己像是在使用 目标类,客户端察觉不到 自己实际上 使用的是 代理类,而不是 目标类。所以代理类和目标类要有共同的行为,也就是实现共同的接口。
代理模式的类图:
2.静态代理
前序:
有一个接口,和一个对应的实现类。 后面有了新需求:测量实现类中的所有方法的耗时
package com.proxy.service; public interface OrderService {//代理 和目标 的公共接口 // 生成订单 void generate() throws InterruptedException; // 修改订单 void modify() throws InterruptedException; // 查看订单详情 void detail() throws InterruptedException; }
/* * 项目经理提出一个新需求:要统计所有业务接口中每一个业务方法的耗时。 * 解决方案1:采用硬编码。在每一个业务接口中的每一个业务方法中直接添加统计耗时的程序。 * 该方案缺点:1.违背OCP原则。 2.代码没有得到复用(相同的代码写了很多遍) * * 解决方案2:编写业务类的子类,让子类继承业务类,对每个业务方法进行重写。 * 该方案缺点:1.虽不违背OCP。但会导致耦合度很高。因为使用了继承关系,继承关系是一种耦合度非常高的关系,不建议使用。 * 2.代码没有得到复用(相同的代码写了很多遍) * * 解决方案3:代理模式(静态代理) * 该方案的优点:1.解决了OCP问题;2.采用代理模式的has a,可以降低耦合度。 * 静态代理的缺点:类爆炸。因为每个接口都得写一个代理类,类急剧膨胀,不好维护。 * 如何解决类爆炸问题:使用动态代理。 * 动态代理也是代理模式,只不过添加了字节码生成技术,可以在内存中动态地生成一个class的字节码,这个字节码就是代理类 * */* */ public class OrderServiceImpl implements OrderService{//目标对象 @Override public void generate() throws InterruptedException {//目标方法 // 模拟生成订单的耗时 Thread.sleep(123); System.out.println("a订单已生成"); } @Override public void modify() throws InterruptedException {//目标方法 // 模拟修改订单的耗时 Thread.sleep(123); System.out.println("a订单已修改"); } @Override public void detail() throws InterruptedException {//目标方法 // 模拟查询订单的耗时 Thread.sleep(123); System.out.println("a正在查看订单详情"); } }
静态代理中的代理对象
package com.proxy.service; //代理对象(代理对象和目标对象要具有相同的行为,就要实现同一个或同一些接口。) //客户端在使用代理对象的时候就像在使用目标对象一样。 public class OrderServiceProxy implements OrderService{ //将目标对象作为代理对象中的一个属性。这种关系叫关联关系。比继承关系的耦合度低。 //代理对象中 含有 目标对象的引用。 ----关联关系:has a //注意:这里要写一个公共接口类型。因为公共接口耦合度更低。 //若用OrderServiceImpl osimpl;会使得代理与目标的耦合度更高。而用公共接口类型,可使代理与目标解耦合。 private OrderService target;//这就是目标对象。 目标对象一定是实现了OrderService接口的,所以可用接口变量来管理目标对象 //创建代理对象的时候,传一个目标对象给代理对象(因为多态,执行方法时是看实际管理的对象) public OrderServiceProxy(OrderService target) { this.target = target; } @Override public void generate() throws InterruptedException {//代理方法 //增加 long begin = System.currentTimeMillis(); //调用目标对象的方法 target.generate(); long end = System.currentTimeMillis(); System.out.println("耗时"+(end-begin)+"毫秒"); } @Override public void modify() throws InterruptedException {//代理方法 //增加 long begin = System.currentTimeMillis(); //调用目标对象的方法 target.modify(); long end = System.currentTimeMillis(); System.out.println("耗时"+(end-begin)+"毫秒"); } @Override public void detail() throws InterruptedException {//代理方法 //增加 long begin = System.currentTimeMillis(); //调用目标对象的方法 target.detail(); long end = System.currentTimeMillis(); System.out.println("耗时"+(end-begin)+"毫秒"); } }
测试程序:
// 创建目标对象(因为要传目标对象进代理对象中进行调用) OrderService target = new OrderServiceImpl(); // 创建代理对象 OrderService proxy = new OrderServiceProxy(target); // 调用代理对象的代理方法 proxy.generate(); proxy.detail(); proxy.modify();
知识点补充:
类和类之间的关系:共6种。其中两种:泛化关系 关联关系
泛化关系:继承 is a
Cat is a Animal
public class Animal{}
public class Cat extends Animal{};
关联关系: has a张安 has a 苹果
public class Person{
private Apple apple;
}
public class Apple{}
相比来说:泛化关系的耦合度 高于 关联关系。优先选择 使用 关联关系。
3.动态代理:
在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题。
在内存当中动态生成类的技术常见的包括:
JDK动态代理技术:只能代理接口。
CGLIB动态代理技术:CGLIB(Code Generation Library)是一个开源项目。
Javassist动态代理技术
3.1JDK动态代理
处理过程的思想与静态代理是一样的。
// 创建目标对象(因为要传目标对象进代理对象中进行调用) OrderService target = new OrderServiceImpl(); // 创建代理对象 关键是如何动态地建立出一个代理对象 // 调用代理对象的代理方法
// 创建代理对象 /* * 1.newProxyInstance 翻译为:新建代理对象。即通过该方法可创建代理对象。 * 本质上,Proxy.newProxyInstance()方法的执行,做了两件事: * 第一件:在内存中动态地生成了一个代理类的字节码class; * 第二件:new 了一个对象。通过内存中生成的代理类的字节码,实例化代理对象。 * 2.关于Proxy.newProxyInstance()方法的三个重要参数,参数的含义,及作用? * 第一个参数:ClassLoader loader 类加载器 * 在内存中生成的字节码也是class文件,要执行也得先加载到内存当中。加载类需要类加载器,所以这里需要指定类加载器。 * 且JDK要求,目标类的类加载器 必须 和 代理类的类加载器使用同一个。 * * 第二个参数:Class<?>[] interfaces * 代理类和目标类要实现同一个接口或同一些接口。 * 在内存中生成代理类时,代理类需指定实现哪些接口。 * * 第三个参数:InvocationHandler h * InvocationHandler 被译为:调用处理器。是一个接口。 * 在调用处理器接口中编写的就是:增强代码。 * 因为 具体 要增加什么代码,JDK动态处理技术是猜不到的。没有那么神。 * 既然是接口,就要写接口的实现类。 * * 可能会有疑问? * 自己还要动手写调用处理器接口的实现类,这不会类爆炸吗?不会 * 因为这种调用处理器写一次就好 * * */ Object proxyObj = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),调用处理器);