设计模式三: 代理模式(Proxy) -- JDK的实现方式

2023-11-18

简介

代理模式属于行为型模式的一种, 控制对其他对象的访问, 起到中介作用.

代理模式核心角色: 真实角色,代理角色;

按实现方式不同分为静态代理和动态代理两种;

意图

控制对其它对象的访问。

类图

Proxy模式

实现

JDK自带了Proxy的实现, 下面我们先使用JDK的API来演示代理如何使用, 随后再探究Proxy的实现原理,并自己来实现Proxy.

JDK代理类的使用: (InvocationHandler,Proxy)

    使用JDK实现的代理代码如下, 先定义业务接口`Car`,然后实现该接口`QQCar`,实现类即为真实角色. 继续定义代理类`Agent`,代理类需要实现接口`InvocationHandler`的`invoke()`方法, 最后是调用;
    注意代理类使用了`Proxy.newProxyInstance()`方法动态生成代理对象, 在稍后手写代码时我们将参考本方法定义我们自己的实现方式.
/**
 * 汽车接口,定义业务规范
 */
public interface Car {
    void sale();
}
/**
 * 接口Car的具体实现,是代理模式中的真实角色
 */
public class QQCar implements Car {
    public void sale() {
        System.out.println("卖了一辆qq");
    }
}
/**
 * 经纪公司,或者经销商
 */
public class Agent implements InvocationHandler {

    private Car car ;

    /**
     * 返回代理对象,接收被代理对象
     * @param car
     * @return
     * @throws Exception
     */
    public Object getInstance(Car car) throws Exception {
        this.car=car;
        Class clazz = car.getClass();
        // 看下代理前后的具体类型
        System.out.println("代理前对象的类型"+car.getClass().getName());
        Object obj = Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
        // 看下代理前后的具体类型
        System.out.println("代理后对象类型变为"+obj.getClass().getName());
        return obj;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Agent find some costumers");
        //this.car.sale();
        method.invoke(this.car, args);
        System.out.println("Agent saled the car");
        return null;
    }
}
try {
    Car car = (Car) new Agent().getInstance(new QQCar());
    car.sale();
}catch (Exception e){
    e.printStackTrace();
}

总结JDK原理如下:

  1. 获取真实角色对象的引用并获取其接口
  2. Proxy生成一个代理类,并实现接口的方法
  3. 获取被代理对象的引用
  4. 动态生成代理类的class字节码
  5. 编译加载

手写实现Proxy

要自己实现JDK的代理模式,我们首先要搞清楚JDK的Proxy是如何实现的, 通过调试及查看源码可以知道JDK生成了一个$Proxy0的类型,我们也将该类型名称打印到了控制台. 如果能动态生成,编译并将这个类加载到内存, 我们就可以自己实现Proxy了.

  1. 首先拿到$Proxy0的代码,供我们后面生成代理类的源码时参考
//生产接口Car对应的代理类class文件并保存到文件
byte[] data = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{Car.class});
FileOutputStream outputStream = new FileOutputStream("E:\\design-patterns\\src\\main\\java\\com\\xlx\\pattern\\proxy\\jdk\\$Proxy0.class");
outputStream.write(data);
outputStream.close();

生成的$Proxy0.class文件反编译后的代码如下, 可以看到其实现了Car接口.

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import com.xlx.pattern.proxy.jdk.Car;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements Car {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void sale() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.xlx.pattern.proxy.jdk.Car").getMethod("sale");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
  1. 参考JDK的实现,我们分别定义MyClassLoader,MyInvocationHandler,MyProxy,分别对应ClassLoader,InvocationHandler,Proxy;
    其中接口MyInvocationHandler代码如下:
/**
 * 代理类需要实现该接口
 */
public interface MyInvocationHandler {
    Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
  1. 定义代理类MyAgent对应我们使用JDK时定义的Agent, 但getInstance()方法实现全部改为2中自定义的类,实现2中定义的接口
/**
 * 代理类
 */
public class MyAgent implements MyInvocationHandler {

    private Car car;

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Agent find some costumers");
        //this.car.sale();
        method.invoke(this.car,args);
        System.out.println("Agent saled the car");
        return null;
    }

    public Object getInstance(Car car) throws Exception {
        this.car=car;
        Class clazz = car.getClass();
        Object obj = MyProxy.newProxyInstance(new MyClassLoader(),clazz.getInterfaces(),this);
        return obj;
    }
}
  1. 实现MyProxy中生成代理对象的方法newProxyInstance()
    具体又分为以下几步:

     1. 定义动态代理类的源码
     2. 保存源码文件到磁盘
     3. 编译源码文件为.class文件
     4. 加载.class字节码到内存 (具体实现见5.实现MyClassLoader))
     5. 返回代理对象
/**
 * 生成代理对象的代码, Proxy的具体原理在这里体现
 */
public class MyProxy {

    private static final String ln = "\r\n";

    public static Object newProxyInstance(MyClassLoader loader, Class<?>[] interfaces, MyInvocationHandler h) {
        File f = null;
        try {
            // 第一步: 生成源代码
            String src = generateSrc(interfaces[0]);

            // 第二步: 保存生成的源码文件
            String filePath = MyProxy.class.getResource("").getPath();
            f = new File(filePath + "/$Proxy0.java");
            FileWriter writer = new FileWriter(f);
            writer.write(src);
            writer.flush();
            writer.close();

            // 第三步: 编译生成.class文件
            JavaCompiler compliler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager manager = compliler.getStandardFileManager(null, null, null);
            Iterable iterable = manager.getJavaFileObjects(f);
            JavaCompiler.CompilationTask task = compliler.getTask(null, manager, null, null, null, iterable);
            ((JavaCompiler.CompilationTask) task).call();
            manager.close();

            // 第四步: 加载class字节码到内存(MyClassLoader类实现)
            Class proxyClass = loader.findClass("$Proxy0");
            // 第五步: 返回代理对象
            Constructor constructor = proxyClass.getConstructor(MyInvocationHandler.class);
            return constructor.newInstance(h);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != f) {
                f.delete();
            }
        }
        return null;
    }

    /**
     * 生成源码的方法
     *
     * @param interfaces 为了演示,按一个接口处理
     * @return
     */
    private static String generateSrc(Class<?> interfaces) {
        StringBuffer src = new StringBuffer();
        src.append("package com.xlx.pattern.proxy.my;" + ln);
        src.append("import java.lang.reflect.Method;" + ln);
        src.append("public class $Proxy0 extends MyProxy implements " + interfaces.getName() + "{" + ln);
        src.append("MyInvocationHandler h;" + ln);

        src.append("public $Proxy0(MyInvocationHandler h){" + ln);
        src.append("this.h=h;" + ln);
        src.append("}" + ln);

        // 循环定义方法,与被代理类的方法同名
        for (Method m : interfaces.getMethods()) {
            src.append("public " + m.getReturnType().getName() + " " + m.getName() + "(){" + ln);

            src.append("try{" + ln);
            src.append("Method m =" + interfaces.getName() + ".class.getMethod(\"" + m.getName() + "\",new Class[]{});" + ln);
            src.append("this.h.invoke(this,m,null);" + ln);
            src.append("}catch(Throwable e){e.printStackTrace();}" + ln);
            src.append("}" + ln);
        }

        src.append("}" + ln);
        return src.toString();
    }
}
  1. 实现MyClassLoaderfindClass()方法,最终由父类ClassLoader.defineClass()方法加载,完成最后拼图
/**
 * 代码生成,编译,重新加载到内存
 * 类加载器, 使用ClassLoader
 */
public class MyClassLoader extends ClassLoader{

    File basePath ;

    public MyClassLoader(){
        String basePath = MyClassLoader.class.getResource("").getPath();
        this.basePath = new File(basePath) ;
    }

    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException{

        String className = MyClassLoader.class.getPackage().getName()+"."+name;

        if (null!=basePath){
            File classFile = new File(basePath,name.replaceAll("\\.","/")+".class");
            if (classFile.exists()){
                FileInputStream in = null;
                ByteArrayOutputStream out= null;
                try {
                    in = new FileInputStream(classFile);
                    out = new ByteArrayOutputStream();
                    byte[] buffer = new byte[1024];
                    int len;
                    while ((len=in.read(buffer))!=-1){
                        out.write(buffer,0,len);
                    }
                    return defineClass(className,out.toByteArray(),0,out.size());
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    classFile.delete();
                    if (null!=in){
                        try{
                            in.close();
                        }catch (Exception e){
                            e.printStackTrace();
                        }
                    }
                    if (null!=out){
                        try{
                            out.close();
                        }catch (Exception e){
                            e.printStackTrace();
                        }
                    }
                }
            }
        }

        return null;
    }
}

总结

上面我仿照JDK自带API用自己的代码实现了Proxy, 这样写了一次之后对Proxy的实现原理加深了很多.Proxy作为一种重要的模式已经大量用在了目前流行的很多框架上, 理解了原理就更有信心去学习框架以及框架的实现思想了.

优点: 1. 职责清晰,真实角色专注实现业务逻辑,代理角色去完成具体的事务,代码结构简洁清晰;2. 可扩展

补充

上面研究了JDK动态代理的实现, 首先定义了接口,然后用一个类实现这个接口,这个实现类就是要代理的具体对象;

cglib库也实现了Proxy模式,与JDK不同的是, cglib不需要定义接口, 而是通过生成被代理类的子类来实现代理模式.这使得代理模式的使用更加简单. 一般类型均可以作为被代理类型.

大致的实现如下(原理跟jdk实现差不多,只不过cglib使用的是类继承实现):

/**
 * 演示 cglib 代理方式
 */
public class CGAgent implements MethodInterceptor {

    public Object getInstance(Class clazz) throws Exception{
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("代理开始了....");
        methodProxy.invokeSuper(o,objects);
        System.out.println("代理结束了....");
        return null;
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

设计模式三: 代理模式(Proxy) -- JDK的实现方式 的相关文章

  • 如何将本机库链接到 IntelliJ 中的 jar?

    我正在尝试在 IntelliJ 中设置 OpenCV 但是我一直在弄清楚如何告诉 IntelliJ 在哪里可以找到本机库位置 在 Eclipse 中 添加 jar 后 您可以在 Build Config 屏幕中设置 Native 库的位置
  • 如何让 BlazeDS 忽略属性?

    我有一个 java 类 它有一个带有 getter 和 setter 的字段 以及第二对 getter 和 setter 它们以另一种方式访问 该字段 public class NullAbleId private static final
  • 不同帐户上的 Spring Boot、JmsListener 和 SQS 队列

    我正在尝试开发一个 Spring Boot 1 5 应用程序 该应用程序需要侦听来自两个不同 AWS 帐户的 SQS 队列 是否可以使用 JmsListener 注解创建监听器 我已检查权限是否正确 我可以使用 getQueueUrl 获取
  • 序列的排列?

    我有具体数量的数字 现在我想以某种方式显示这个序列的所有可能的排列 例如 如果数字数量为3 我想显示 0 0 0 0 0 1 0 0 2 0 1 0 0 1 1 0 1 2 0 2 0 0 2 1 0 2 2 1 0 0 1 0 1 1 0
  • Mockito:如何通过模拟测试我的服务?

    我是模拟测试新手 我想测试我的服务方法CorrectionService correctPerson Long personId 实现尚未编写 但这就是它将执行的操作 CorrectionService将调用一个方法AddressDAO这将
  • 为 java 游戏创建交互式 GUI

    大家好 我正在创建一个类似于 java 中的 farmville 的游戏 我只是想知道如何实现用户通常单击以与游戏客户端交互的交互式对象 按钮 我不想使用 swing 库 通用 Windows 看起来像对象 我想为我的按钮导入自定义图像 并
  • 动态选择端口号?

    在 Java 中 我需要获取端口号以在同一程序的多个实例之间进行通信 现在 我可以简单地选择一些固定的数字并使用它 但我想知道是否有一种方法可以动态选择端口号 这样我就不必打扰我的用户设置端口号 这是我的一个想法 其工作原理如下 有一个固定
  • 如何在 Spring 中禁用使用 @Component 注释创建 bean?

    我的项目中有一些用于重构逻辑的通用接口 它看起来大约是这样的 public interface RefactorAwareEntryPoint default boolean doRefactor if EventLogService wa
  • 来自 dll 的 Java 调用函数

    我有这个 python 脚本导入zkemkeeperdll 并连接到考勤设备 ZKTeco 这是我正在使用的脚本 from win32com client import Dispatch zk Dispatch zkemkeeper ZKE
  • java.lang.IllegalStateException:应用程序 PagerAdapter 更改了适配器的内容,而没有调用 PagerAdapter#notifyDataSetChanged android

    我正在尝试使用静态类将值传递给视图 而不是使用意图 因为我必须传递大量数据 有时我会收到此错误 但无法找出主要原因是什么 Error java lang IllegalStateException The application s Pag
  • 从最终实体获取根证书和中间证书

    作为密码学的菜鸟 我每天都会偶然发现一些简单的事情 今天只是那些日子之一 我想用 bouncy castle 库验证 java 中的 smime 消息 我想我几乎已经弄清楚了 但此时的问题是 PKIXparameters 对象的构建 假设我
  • 检测并缩短字符串中的所有网址

    假设我有一条字符串消息 您应该将 file zip 上传到http google com extremelylonglink zip http google com extremelylonglink zip not https stack
  • 将 MOXy 设置为 JAXB 提供程序,而在同一包中没有属性文件

    我正在尝试使用 MOXy 作为我的 JAXB 提供程序 以便将内容编组 解组到 XML JSON 中 我创建了 jaxb properties 文件 内容如下 javax xml bind context factory org eclip
  • 内部类的构造函数引用在运行时失败并出现VerifyError

    我正在使用 lambda 为内部类构造函数创建供应商ctx gt new SpectatorSwitcher ctx IntelliJ建议我将其更改为SpectatorSwitcher new反而 SpectatorSwitcher 是我正
  • 使用 AsyncTask 传递值

    我一直在努力解决这个问题 但我已经到了不知道该怎么办的地步 我想做的是使用一个类下载文件并将其解析为字符串 然后将该字符串发送到另一个类来解析 JSON 内容 所有部件都可以单独工作 并且我已经单独测试了所有部件 我只是不知道如何将值发送到
  • 专门针对 JSP 的测试驱动开发

    在理解 TDD 到底是什么之前 我就已经开始编写测试驱动的代码了 在没有实现的情况下调用函数和类可以帮助我以更快 更有效的方式理解和构建我的应用程序 所以我非常习惯编写代码 gt 编译它 gt 看到它失败 gt 通过构建其实现来修复它的过程
  • 我如何在java中读取二进制数据文件

    因此 我正在为学校做一个项目 我需要读取二进制数据文件并使用它来生成角色的统计数据 例如力量和智慧 它的设置是让前 8 位组成一个统计数据 我想知道执行此操作的实际语法是什么 是不是就像读文本文件一样 这样 File file new Fi
  • 在java中为组合框分配键

    我想添加一个JComboBox在 Swing 中这很简单 但我想为组合中的每个项目分配值 我有以下代码 JComboBox jc1 new JComboBox jc1 addItem a jc1 addItem b jc1 addItem
  • 使用 svn 1.8.x、subclise 1.10 的 m2e-subclipse 连接器在哪里?

    我读到 m2e 的生产商已经停止生产 svn 1 7 以外的任何版本的 m2e 连接器 Tigris 显然已经填补了维护 m2e subclipse 连接器的空缺 Q1 我的问题是 使用 svn 1 8 x 的 eclipse 更新 url
  • Spring Rest 和 Jsonp

    我正在尝试让我的 Spring Rest 控制器返回jsonp但我没有快乐 如果我想返回 json 但我有返回的要求 完全相同的代码可以正常工作jsonp我添加了一个转换器 我在网上找到了用于执行 jsonp 转换的源代码 我正在使用 Sp

随机推荐

  • 分布式日志收集(ELK)

    ELK简介 ELK Elasticsearch Logstash Kibana 是同一家公司开发的3个开源工具 可组合起来搭建海量日志分析平台 目前很多公司都在使用这种方式搭建日志分析平台进行大数据分析 参考 初识ES数据库 Logstas
  • 无向图邻接表实现

    无向图邻接表实现 顶点 按照编号顺序将顶点数据存储在一维数组当中 关联同一个顶点的边 以顶点为尾的弧 用线性链表存储 头结点 data firstarc 表结点 adjvex 邻接点的序号 存放与vi邻接的顶点在表头数组中的位置 nexta
  • Scrapy 存数据到Hbase

    网上很多教程都是使用Scrapy存数据到MongoDB Mysql或者直接存入Excel中的 很少有存入到Hbase里面的 前言 为什么没有像大多数网上那样将数据存入到MongoDB Mysql中呢 因为项目中使用到Hbase加上阿里云的推
  • 唯一标识一台计算机解决方法:

    首先 网上介绍最多的方法就是cpu baseboard等硬件设备的序列号 但是 这两种获取方法都有问题 wmic cpu get processorid获取的cpu序列号 其实只是某个系列的代号 并不是唯一的 比如 12代i9都用的是一个C
  • Qt学习之QList类

    QList的定义 一 简介 QList lt T gt 常用的容器类 它是一个列表 存储了给定类型的值 而这些值可以通过索引访问 二 定义 QList
  • [项目管理-27]:任务的目的,背后的原因是任务实施首要思考的问题。

    案例 无论是一个项目 还是一项任务 在实施之前 弄清楚原因 是项目经理必须有的思维模式 而不是无条件的盲目的执行 只有弄清楚目的和原因 才能在执行过程中 遇到问题时 发挥主观能动性 采用各种灵活变通的方法解决问题 最后确保项目的成功 另一方
  • Android Studio设计APP实现与51单片机通过WIFI模块(ESP8266-01S)通讯控制LED灯亮灭的设计源码【详解】

    目录 一 前言 二 效果展示 1 APP界面展示 2 C51硬件展示 三 Android Studio APP源代码 1 AndroidManifest xml 1 请求联网 2 开放明文传输 2 MainActivity java 3 L
  • python同步系统时间

    公司的电脑比较老旧 主板上的电池也没电了 每天都得手动调时间 自动同步也因为日期每天都被重置了而无法同步ntp服务器 想拆开换电池发现机箱也打开不了 emmm 无奈之下 自己做一个同步时间的脚本吧 然后用bat运行 加入开机启动项每天开机自
  • 员工分组-STL案例

    案例描述 1 公司今天招聘了10个员 ABCDEFGHUIJ 10名员工进入公司后 需要指派员工在那个部门工作 2 员工信息有 姓名 工资组成 部门分为 策划 美术 研发 3 随机给10名员工分配部门和工资 4 通过multimap进行信息
  • MySQL必知必会-笔记-Part3

    MySQL必知必会 笔记 Part3 Cha7 数据过滤 本章讲授如何组合WHERE子句以建立功能更强的更高级的搜索条件 以及NOT和IN操作符的使用 7 1 组合WHERE子句 第6章中介绍的所有WHERE子句在过滤数据时使用的都是单一的
  • JVM系列笔记(一)

    JVM的位置 JVM是运行在操作系统之上的 它与硬件没有直接的交互 JVM的整体结构 HotSpot VM是目前市面上高性能虚拟机的代表作之一 它采用解释器与即时编译器并存的架构 在今天 Java程序的运行性能早已脱胎换股 已经达到了可以和
  • xmind使用学习

    1 Background 这年头不画个思维导图都不好意思搬砖了 闲暇之余学习了下用xmind来画图 记录于此 2 Concept 主题 有中心主题和分支主题 子主题 一个主题的下一级主题叫子主题 自由主题 独立于中心主题 分支主题外的主题
  • 中山三院挂号服务器维护中,于广州中山三院的一次郁闷就诊

    最近不时地看到关于医院不负责任的报道 感叹的同时总觉得不可思议 那一直以为很神圣的地方现在对待生命的态度真的有这么随便么 没想到 一不小心 自己竟成为了被随便对待的那一个 事情的经过是这样的 这几天妈妈的胃一直很不舒服 于是10月8日早上陪
  • Matlab中readmatrix用法

    目录 语法 说明 示例 从文本文件中读取矩阵 从电子表格文件中读取矩阵 使用导入选项从指定的工作表和范围中读取矩阵 从指定的工作表和范围中读取矩阵 readmatrix是从文件中读取矩阵 语法 A readmatrix filename A
  • Minimal API in .NET 6 Using Dapper and SQL - Minimal API Project

    快捷键 prop public int MyProperty get set property ctor 创建一个构造函数 constructor part1 数据库 存储过程处理 最小的API 这里新建项目的时候没有用控制器 创建数据库文
  • position:absolute详解

    position absolute 日常开发中经常涉及元素的定位 我们都知道 绝对定位相对于最近position不为static的父级元素来定位 但其中定位的位置还是有细微的差别的 绝对定位根据left和top属性来规定绝对定位元素的位置
  • 使用克拉默法则进行三点定圆(三维)

    目录 1 三维圆 2 python代码 3 计算结果 本文由CSDN点云侠原创 爬虫网站请自重 1 三维圆 已知不共线的三个点 设其坐标为 x 1 y 1
  • 斯坦福cs224n教程--- 学习笔记1

    一 前言 自然语言是人类智慧的结晶 自然语言处理是人工智能中最为困难的问题之一 而对自然语言处理的研究也是充满魅力和挑战的 通过经典的斯坦福cs224n教程 让我们一起和自然语言处理共舞 也希望大家能够在NLP领域有所成就 二 先修知识 学
  • Python爬虫市场简单分析

    Python爬虫是目前互联网行业中最重要的组成部分之一 Python作为一门易学易懂的编程语言 不需要过多的软件环境和部署条件 基本覆盖了爬虫开发的大部分需求 是网络数据爬取和处理的首选技术之一 Python通过一系列优秀的爬虫框架和库的支
  • 设计模式三: 代理模式(Proxy) -- JDK的实现方式

    简介 代理模式属于行为型模式的一种 控制对其他对象的访问 起到中介作用 代理模式核心角色 真实角色 代理角色 按实现方式不同分为静态代理和动态代理两种 意图 控制对其它对象的访问 类图 实现 JDK自带了Proxy的实现 下面我们先使用JD