代理模式 PROXY Surrogate 结构型 设计模式(十四)

代理模式 PROXY 别名Surrogate

意图

为其余的对象提供一种代理以控制对这个对象的访问。
代理模式含义比较清晰,就是中间人,中介公司,经纪人...
在计算机程序中,代理就表示一个客户端不想或者不可以直接引用一个对象
而代理对象能够在客户端和目标对象之间起到中介的做用

结构

代理模式的根本在于隔离,以下图所示,间接访问
image_5c074938_56cd
代理对象如何可以真的代理真实对象?
在Java语言中,看起来像的一个方式就是实现同一接口
image_5c074938_5f89
 
代理角色和真实对象角色拥有共同的抽象类型,他们拥有相同的对外接口request()方法
ProxySubject内部拥有一个RealSubject
你应该能感受到组合模式的思想-----他们都是Subject,属于同一个Component
对外有一致的接口
抽象主题角色Subject
声明了真实主题和代理主题的共同接口,任何使用真实主题的地方,均可以使用代理主题
代理主题角色ProxySubject
代理主题角色内部含有对真实对象的引用,从而能够在任什么时候候操做真实主题
代理主题提供与真实主题的相同的接口,以便任什么时候刻,均可以替代真实主题
并且,代理主题还能够在真实主题执行先后增长额外的处理,好比:经纪人要先收下费~
真实主题角色RealSubject
被代理的真实主题对象,真正工做的是他,好比经纪人总不会站在舞台上去~

示例代码

Subject 抽象角色 定义了真正的处理请求 的request()方法 
package proxy;
public interface Subject {
void request();
}
RealSubject真实主题角色,实现了处理请求的方法
package proxy;
public class RealSubject implements Subject {
@Override
public void request() {
    System.out.println("realSubject process request....");
}
}
Proxy代理角色
实现了request()方法,用于替代真实主题,内部调用真实主题完成请求
而且额外的提供了pre和after操做
package proxy;
public class Proxy implements Subject{
    private Subject realSubject;
    @Override
    public void request() {
        preRequest();
    realSubject.request();
        afterRequest();
    }
     
    public Proxy(Subject realSubject){
        this.realSubject = realSubject;
    }
    public void preRequest(){
        System.out.println("pre request do sth....");
    }
     
    public void afterRequest(){
        System.out.println("after request do sth....");
    }
}
测试类
package proxy;
public class Test {
/**请求subject执行请求
* @param subject
*/
public static void askForSth(Subject subject){
    subject.request();
    System.out.println("################");
 }
   
public static void main(String[] args){
     Subject real = new RealSubject();
     Subject proxy = new Proxy(real);
     askForSth(proxy);
     askForSth(real);
    }
}
定义了真实对象,也定义了一个代理对象
查看他们分别处理请求的结果
image_5c074938_777b
 
从下面的时序图中,能更好的感觉到“间接”的感受
在真正调用真实对象方法前,须要先执行preRequest方法
真实对象方法调用后,在执行afterRequest方法
image_5c074939_77de

代理实现

代理的实现分类有两种,静态代理和动态代理
前面形式描述的代理,就是静态代理
在编译时期,就已经编写生成好了代理类的源代码,程序运行以前class文件就已经生成了
这种按照咱们上面模式编写了代理类和真实类的形式就是 静态代理
静态代理常常被用来对原有逻辑代码进行扩展,原有的逻辑不须要变动,可是能够增长更多的处理逻辑
可是,可是若是有不少的对象须要被代理怎么办?
若是按照静态代理的形式,那么将会出现不少的代理类,势必致使代码的臃肿。
因此后来出现了动态代理

JDK代理机制

所谓动态代理,按照字面意思就是动态的进行代理, 动态相对于静态的含义是不须要事先主动的建立代理类, 能够在运行时须要的时候,动态的建立一个代理类。
动态代理的动态关键在于代理类的动态生成,不须要咱们实现建立,从class文件的角度来看的话,是与静态代理同样的,仍旧有一个代理类的Class文件
在Java中提供了内置的动态代理的支持。
Java在 java.lang.reflect包中提供了三个核心 ProxyInvocationHandlerMethod  能够用于动态代理的使用
 
Java动态代理简单示例
package proxy.MyDynamicProxy;
public interface Subject {
void doSth();
}
package proxy.MyDynamicProxy;
public class RealSubject implements Subject {
@Override
public void doSth() {
System.out.println("real Object do something...");
}
}
package proxy.MyDynamicProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class DynamicProxyHandler implements InvocationHandler {
private Object realSubject;
public DynamicProxyHandler(Object realSubject) {
this.realSubject = realSubject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("proxy do something....");
return method.invoke(realSubject, args);
}
}
package proxy.MyDynamicProxy;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args){
RealSubject realSubject = new RealSubject();
Subject proxy = (Subject) Proxy
.newProxyInstance(Test.class.getClassLoader(), new Class[]{Subject.class}, new DynamicProxyHandler(realSubject));
proxy.doSth();
}
}
测试结果为:
image_5c074939_41fd
 
动态代理到底都作了什么?
对于静态代理,咱们有一个RealSubject,以及他的超接口Subject
Subject定义了方法,RealSubject实现了方法。
而后咱们建立了代理类,这个代理类实现了Subject接口,而且将新增的逻辑添加进来,而后经过代理类进行方法调用。 
 
在上面的例子中, RealSubject,以及他的超接口Subject含义不变,与静态代理中的逻辑同样。
而后咱们 建立了一个调用处理器DynamicProxyHandler 实现了 InvocationHandler接口
该接口只有一个方法 invoke
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
他有三个参数
proxy - 在其上调用方法的代理实例
method - 对应于在代理实例上调用的接口方法的 Method 实例。Method 对象的声明类将是在其中声明方法的接口,该接口能够是代理类赖以继承方法的代理接口的超接口。
args - 包含传入代理实例上方法调用的参数值的对象数组,若是接口方法不使用参数,则为 null。基本类型的参数被包装在适当基本包装器类(如 java.lang.Integer 或 java.lang.Boolean)的实例中。
 
最后经过Java提供的代理机制建立了一个代理
    Subject proxy = (Subject) Proxy
        .newProxyInstance(Test.class.getClassLoader(), new Class[]{Subject.class}, new DynamicProxyHandler(realSubject));
核心就是 newProxyInstance方法,他建立了一个实现了Subject接口的代理类
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
                               throws IllegalArgumentException
这个方法也有三个参数
loader - 定义代理类的类加载器
interfaces - 代理类要实现的接口列表
h - 指派方法调用的调用处理程序
 
为何须要这三个参数呢?
首先,Proxy.newProxyInstance帮你动态的建立方法,确定要有一个类加载器,上面的示例中咱们直接使用的测试类的类加载,这个通常是应用程序  类加载器
再者,动态代理与静态代理同样,须要实现一样的接口,那你实现了哪些接口呢?因此你得把接口列表告诉我
最后,你但愿有哪些处理呢?你要把处理器给我
 
proxy.doSth();执行时,会将当前代理实例,以及当前方法,以及当前方法的参数传递给invoke方法,因此就完成代理的功能。
 
再来重头理一下:
  1. 如同静态代理,须要被代理的对象RealSubject,以及他的超接口Subject
  2. 须要实现InvocationHandler接口建立一个处理器,新增长的方法逻辑封装在invoke方法中
  3. Proxy.newProxyInstance建立代理实例
  4. 使用建立的代理实例执行方法
简言之,动态代理与静态代理如出一辙,差异就在于不用你事先去本身主动地建立一个代理类
静态的时候编写了代理类,而后编译为class而后须要时被加载到JVM,而后调用
动态是运行时在须要的时候,直接生成class文件
 
依照上面的步骤流程,你就能够借助于Java的机制实现动态代理
可是你会发现,Proxy.newProxyInstance方法的参数须要一个 Class<?>[] interfaces,这意味着什么?这意味着被代理的对象必须实现一个接口
若是被代理的对象未曾实现任何接口怎么办?
给每一个被代理的对象增长一个标记接口(形式接口)?若是只是为了使用JDK的动态代理实现,而添加了无心义的接口这是否稳当?

CGLIB

还有另一种形式的动态代理CGLIB
须要两个Jar  
image_5c074939_55fa
package proxy.cglib;
public class RealSubject{
public void doSth() {
System.out.println("realSubject process request....");
}
}
package proxy.cglib;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class MyHandler implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
throws Throwable {
System.out.println("before do something...");
Object object = methodProxy.invokeSuper(o,objects);
System.out.println("after do something...");
return object;
}
}
package proxy.cglib;
import net.sf.cglib.proxy.Enhancer;
public class Test {
public static void main(String[] args){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealSubject.class);
enhancer.setCallback(new MyHandler());
RealSubject subject = (RealSubject)enhancer.create();
subject.doSth();
}
}
在这个示例中,再也不须要接口,仅仅只有一个真是对象RealSubject
实现了一个处理器 MyHandler 继承自 MethodInterceptor,实现了intercept方法
在测试客户端中,经过四个步骤建立了代理对象,而后借助于代理对象执行
image_5c074939_666
 
从    enhancer.setSuperclass(RealSubject.class);这一句或许猜获得,CGLIB不依赖于接口,而是代理类继承了真实主题类
 
流程
真实主题对象RealSubject是必不可少的,不然代理模式就没有意义了
相似JDK的代理模式,处理器也是解耦的,在CGLIB中借助于MethodInterceptor接口约定,这一步要作的事情的本质与InvocationHandler并无什么太多不一样---封装附加的处理逻辑
借助于Enhancer用来组装处理建立逻辑,而且建立代理类
setSuperclass设置须要继承的类(也就是被代理的类)
setCallback设置回调函数
create建立真正的代理对象。
 
CGLIB采用继承的机制,若是一个类是final的怎么办?那就歇菜了

JDK代理机制与CGLIB对比

目前到JDK8 听说性能已经优于CGLIB了
JDK机制不须要第三方Jar,JDK默认集成,CGLIB须要引入第三方Jar包
JDK须要依赖真实主题对象实现接口,CGLIB则不须要,CGLIB继承了真实主题
CGLIB虽然不依赖真实主题实现接口,可是被代理的类不能为final,那样的类是没法继承的
一般的作法是若是实现了接口,那么使用JDK机制,若是没有实现接口,使用CGLIB

代理用途分类

代理模式的根本在于隔离,“间接”,只要隔离,间接,那么就能够隐藏真实对象,而且增长额外的服务,优化,管理等
好比
隐藏了真实的对象,好比你经过中介租房子,可能到期也没见过房东
 
提供了代理层,能够 提供更多服务
好比买卖房屋经过中介能够节省你合同的审校工做,不少人不懂合同中暗藏的猫腻
 
隐藏真实对象,天然可以起到必定的 保护做用,避免了直接接触
好比去学校见孩子,须要先通过老师赞成
 
经过代理,也至关于有一个管家,能够 管理外界对真实对象的接触访问
好比,真实对象是电脑,管家类软件至关于代理,能够限制小孩子对电脑的使用时长
 
围绕着代理带来的特色“隐藏真实对象,而且增长额外的服务,优化,限制”
在多种场景下,延伸出来一些分类
远程代理 Remote
为一个位于不一样的地址空间的对象提供一个局域表明对象,这个不一样的地址空间能够是本机器的,也能够是另外一台机器的
虚拟代理 Virtual
根据须要建立一个资源消耗较大的对象,使得此对象只在须要时才会被真正建立
保护代理 Protect or Access
控制对一个对象的访问,若是须要,能够给不一样的用户提供不一样级别的使用权限
Cache代理
为一个目标操做的结果提供临时的存储空间,以便多个客户端能够共享这些结果 
防火墙代理 Firewall
保护目标,防止恶意行为
同步代理 Synchronization
使几个用户可以同时使用一个对象而没有冲突

智能引用 Smart Reference
当一个对象被引用时,提供一些额外的操做,好比将对象调用次数记录下来
 
很显然,这些分类其实只是代理的不一样应用场景,之后可能还会有更多的分类出来
可是永远也脱离不了代理的“隔离”“间接”的根本核心。

总结

代理角色虽然是真实角色的“代理人”,虽然代理角色内部依赖真实角色
可是真实角色能够彻底脱离代理人,单独出现
好比上面示例中的

        askForSth(proxy); html

        askForSth(real);  java

只不过,经过代理角色会有不一样的效果
 
代理人只是会“帮助”解决他能解决的问题,它能提供的服务,他作不了的事情
好比经纪人不会出唱片,对于出唱片的任务仍是会委托给真实角色
现实世界中,咱们一般说真实角色委托代理角色,好比,房东找中介
在程序世界中,一般却说代理角色将任务委托给真实角色,委托与被委托都是相对的
要看你究竟是站在什么视角看待问题,无所谓~
 
再次强调,代理模式的重点在于增长对真实受访对象的控制,也能够增长额外的服务。
相关文章
相关标签/搜索