专栏首页noteless代理模式 PROXY Surrogate 结构型 设计模式(十四)

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

代理模式 PROXY 别名Surrogate

意图

为其他的对象提供一种代理以控制对这个对象的访问。

代理模式含义比较清晰,就是中间人,中介公司,经纪人...

在计算机程序中,代理就表示一个客户端不想或者不能够直接引用一个对象

而代理对象可以在客户端和目标对象之间起到中介的作用

结构

代理模式的根本在于隔离,如下图所示,间接访问

代理对象如何能够真的代理真实对象?

在Java语言中,看起来像的一个方式就是实现同一接口

代理角色和真实对象角色拥有共同的抽象类型,他们拥有相同的对外接口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);
    }
}

定义了真实对象,也定义了一个代理对象

查看他们分别处理请求的结果

从下面的时序图中,能更好的感受到“间接”的感觉

在真正调用真实对象方法前,需要先执行preRequest方法

真实对象方法调用后,在执行afterRequest方法

代理实现

代理的实现分类有两种,静态代理和动态代理

前面形式描述的代理,就是静态代理

在编译时期,就已经编写生成好了代理类的源代码,程序运行之前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();
}
}

测试结果为:

动态代理到底都做了什么?

对于静态代理,我们有一个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  

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方法

在测试客户端中,通过四个步骤创建了代理对象,然后借助于代理对象执行

从    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);

        askForSth(real); 

只不过,通过代理角色会有不同的效果

代理人只是会“帮助”解决他能解决的问题,它能提供的服务,他做不了的事情

比如经纪人不会出唱片,对于出唱片的任务还是会委托给真实角色

现实世界中,我们通常说真实角色委托代理角色,比如,房东找中介

在程序世界中,通常却说代理角色将任务委托给真实角色,委托与被委托都是相对的

要看你到底是站在什么视角看待问题,无所谓~

再次强调,代理模式的重点在于增加对真实受访对象的控制,也可以增加额外的服务。

原文地址:代理模式 PROXY Surrogate 结构型 设计模式(十四)

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • [十一]基础数据类型之Character

    该类提供了几种方法来确定字符的类别(小写字母、数字等),并将字符从大写转换为小写,反之亦然

    noteless
  • 9.java web的发展 javaweb是什么 J2EE发展历史 规范 J2EE是什么 发展背景 组件标准 J2EE好处作用 Servlet 含义 本质 发展 java在web中的发展 servl

    javaweb 本质上来说就是使用java 语言来解决企业web应用中一系列技术体系与规范;

    noteless
  • [十八]JavaIO之FileReader 和 FileWriter

    FileReader 和 FileWriter继承了InputStreamReader 和OutputStreamWriter

    noteless
  • 小白也能看懂的插件化DroidPlugin原理(一)-- 动态代理

    前言:插件化在Android开发中的优点不言而喻,也有很多文章介绍插件化的优势,所以在此不再赘述。前一阵子在项目中用到 DroidPlugin 插件框架 ,近...

    codingblock
  • 浅谈Java【代理设计模式】——看这篇文章就懂了

    抽象主题角色(Subject):可以是接口,也可以是抽象类 委托类角色(proxied):真实主题角色,业务逻辑的具体执行者 代理类角色(Proxy):...

    须臾之余
  • 利用 Flask+Redis 维护 IP 代理池

    目前有很多网站提供免费代理,而且种类齐全,比如各个地区、各个匿名级别的都有,不过质量实在不敢恭维,毕竟都是免费公开的,可能一个代理无数个人在用也说不定。所以我们...

    崔庆才
  • 爬虫代理哪家强?十大付费代理详细对比评测出炉!

    前言 随着大数据时代的到来,爬虫已经成了获取数据的必不可少的方式,做过爬虫的想必都深有体会,爬取的时候莫名其妙 IP 就被网站封掉了,毕竟各大网站也不想自己的...

    崔庆才
  • 所有和Java中代理有关的知识点都在这了。

    对于每一个Java开发来说,代理这个词或多或少都会听说过。你可能听到过的有代理模式、动态代理、反向代理等。那么,到底什么是代理,这么多代理又有什么区别呢。本文就...

    java思维导图
  • Java 动态代理,看这篇就够了

    这篇文章需要用到 Java 的反射知识,如果对反射还不清楚的小伙伴,可以先移步到这里 《Java进阶-反射》。

    Wizey
  • JAVA 动态代理

    为了使代理类和被代理类对第三方有相同的函数,代理类和被代理类一般实现一个公共的interface,该interface定义如下

    哲洛不闹

扫码关注云+社区

领取腾讯云代金券