当前位置: 首页 > article >正文

(六)趣学设计模式 之 代理模式!

在这里插入图片描述

目录

    • 一、啥是代理模式?
    • 二、为什么要用代理模式?
    • 三、代理模式的实现方式
      • 1. 静态代理
      • 2. JDK动态代理
      • 3. CGLIB动态代理
    • 四、三种代理的对比
    • 五、代理模式的优缺点
    • 六、代理模式的应用场景
    • 七、总结

🌟我的其他文章也讲解的比较有趣😁,如果喜欢博主的讲解方式,可以多多支持一下,感谢🤗!
🌟了解建造者模式请看: (五)趣学设计模式 之 建造者模式!
🌟同时我的 JDK动态代理 vs CGLIB:一场经纪人之战,谁才是你的最佳选择?也不错

这篇文章带你详细认识一下设计模式中的代理模式

一、啥是代理模式?

代理模式,就像找了个替身 👯! 你想做一件事情,但是自己不想做,或者不能做,就找个代理人(替身)来帮你做 🤝。 代理人可以帮你处理一些额外的事情,比如权限控制 👮、日志记录 📝、缓存 📦 等等,让你更专注于核心业务 🎯!

简单来说,就是你不想直接面对某个对象,就找个代理来帮你!

  • 你想做的事情比较敏感: 就像访问一些需要权限的资源 🔑,你需要先验证身份,才能访问!
  • 你想做的事情比较耗时: 就像下载一个大文件 💾,你需要等待很长时间,才能完成!
  • 你想做的事情比较复杂: 就像购买机票 ✈️,你需要比较不同的航班、价格和时间,才能做出选择!

二、为什么要用代理模式?

用代理模式,好处多多 👍:

  • 保护目标对象: 代理可以控制对目标对象的访问,防止恶意操作 🛡️! 就像保镖保护明星 🌟,防止粉丝的疯狂行为!
  • 增强目标对象的功能: 代理可以在目标对象执行前后做一些额外的事情,比如日志记录、性能监控 📈! 就像经纪人帮明星安排行程、处理事务,让明星更专注于表演!
  • 延迟加载: 代理可以在需要的时候才创建目标对象,节省资源 ⏳! 就像懒加载图片 🖼️,只有当图片出现在屏幕上时,才加载图片!
  • 远程代理: 代理可以代表远程对象,让你像访问本地对象一样访问远程对象 🌐! 就像 VPN,让你访问被墙的网站!
  • 降低耦合度: 客户端不需要直接依赖目标对象,只需要依赖代理对象,降低了系统的耦合度 🔗!

三、代理模式的实现方式

代理模式主要分为三种:

  • 静态代理: 代理类在编译时就已经确定,就像提前找好的替身演员 🎬!
  • JDK动态代理: 代理类在运行时动态生成,需要实现接口,就像临时找来的替身演员 🎭!
  • CGLIB动态代理: 代理类在运行时动态生成,不需要实现接口,通过继承实现,就像 AI 换脸技术 🤖!

1. 静态代理

静态代理,顾名思义,代理类是提前写好的,就像提前找好的替身演员,随时可以上场 💃!

案例:火车站卖票(经典案例 🚂)

如果要买火车票的话,需要去火车站买票,坐车到火车站,排队等一系列的操作,显然比较麻烦 😫。而火车站在多个地方都有代售点,我们去代售点买票就方便很多了 😊。这个例子其实就是典型的代理模式,火车站是目标对象,代售点是代理对象。

代码示例:

// 卖票接口
public interface SellTickets {
    void sell(); // 卖票
}

// 火车站  火车站具有卖票功能,所以需要实现SellTickets接口
public class TrainStation implements SellTickets {
    public void sell() {
        System.out.println("火车站卖票"); // 卖票
    }
}

// 代售点
public class ProxyPoint implements SellTickets {
    private TrainStation station = new TrainStation(); // 持有火车站对象的引用

    public void sell() {
        System.out.println("代理点收取一些服务费用"); // 增强功能
        station.sell(); // 调用火车站的卖票方法
    }
}

// 测试类
public class Client {
    public static void main(String[] args) {
        ProxyPoint pp = new ProxyPoint(); // 创建代理对象
        pp.sell(); // 调用代理对象的卖票方法
    }
}

分析:

从上面代码中可以看出,测试类直接访问的是 ProxyPoint 类对象,也就是说 ProxyPoint 作为访问对象和目标对象的中介 🤝。同时也对 sell 方法进行了增强(代理点收取一些服务费用 💰)。

输出结果:

代理点收取一些服务费用
火车站卖票

2. JDK动态代理

JDK动态代理,代理类是在运行时动态生成的,需要实现接口,就像临时找来的替身演员,需要会表演 💃!

案例:还是火车站卖票(升级版 🚂)

还是上面的火车站卖票的例子,但是这次我们使用JDK动态代理来实现 🚀!

代码示例:

// 卖票接口
public interface SellTickets {
    void sell(); // 卖票
}

// 火车站  火车站具有卖票功能,所以需要实现SellTickets接口
public class TrainStation implements SellTickets {
    public void sell() {
        System.out.println("火车站卖票"); // 卖票
    }
}

// 代理工厂,用来创建代理对象
public class ProxyFactory {
    private TrainStation station = new TrainStation(); // 持有火车站对象的引用

    public SellTickets getProxyObject() {
        // 使用Proxy获取代理对象
        /*
            newProxyInstance()方法参数说明:
                ClassLoader loader : 类加载器,用于加载代理类,使用真实对象的类加载器即可
                Class<?>[] interfaces : 真实对象所实现的接口,代理模式真实对象和代理对象实现相同的接口
                InvocationHandler h : 代理对象的调用处理程序
         */
        SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),
                station.getClass().getInterfaces(),
                new InvocationHandler() {
                    /*
                        InvocationHandler中invoke方法参数说明:
                            proxy : 代理对象
                            method : 对应于在代理对象上调用的接口方法的 Method 实例
                            args : 代理对象调用接口方法时传递的实际参数
                     */
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("代理点收取一些服务费用(JDK动态代理方式)"); // 增强功能
                        // 执行真实对象
                        Object result = method.invoke(station, args); // 调用火车站的卖票方法
                        return result; // 返回结果
                    }
                });
        return sellTickets; // 返回代理对象
    }
}

// 测试类
public class Client {
    public static void main(String[] args) {
        // 获取代理对象
        ProxyFactory factory = new ProxyFactory();
        SellTickets proxyObject = factory.getProxyObject(); // 获取代理对象
        proxyObject.sell(); // 调用代理对象的卖票方法
    }
}

分析:

使用了动态代理,我们思考下面问题 🤔:

  • ProxyFactory 是代理类吗? 🙅

    ProxyFactory 不是代理模式中所说的代理类,而代理类是程序在运行过程中动态的在内存中生成的类 💫。

  • 动态代理的执行流程是什么样? ➡️

    1. 客户端调用代理对象的 sell() 方法。
    2. 代理对象调用 InvocationHandlerinvoke() 方法。
    3. invoke() 方法中,我们可以增强功能,比如收取服务费用。
    4. invoke() 方法中,通过反射调用真实对象的 sell() 方法。
    5. 真实对象执行 sell() 方法,返回结果。
    6. invoke() 方法将结果返回给代理对象。
    7. 代理对象将结果返回给客户端。

输出结果:

代理点收取一些服务费用(JDK动态代理方式)
火车站卖票

3. CGLIB动态代理

CGLIB动态代理,代理类是在运行时动态生成的,不需要实现接口,通过继承实现,就像 AI 换脸技术,直接把你的脸换成别人的脸 🎭!

案例:还是火车站卖票(终极版 🚂)

还是上面的火车站卖票的例子,但是这次我们使用CGLIB动态代理来实现 🚀! 如果没有定义 SellTickets 接口,只定义了 TrainStation (火车站类)。很显然JDK代理是无法使用了,因为JDK动态代理要求必须定义接口,对接口进行代理 😫。

代码示例:

// 火车站
public class TrainStation {
    public void sell() {
        System.out.println("火车站卖票"); // 卖票
    }
}

// 代理工厂
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class ProxyFactory implements MethodInterceptor {
    private TrainStation target = new TrainStation(); // 持有火车站对象的引用

    public TrainStation getProxyObject() {
        // 创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
        Enhancer enhancer = new Enhancer(); // 创建 Enhancer 对象
        // 设置父类的字节码对象
        enhancer.setSuperclass(target.getClass()); // 设置父类
        // 设置回调函数
        enhancer.setCallback(this); // 设置回调
        // 创建代理对象
        TrainStation obj = (TrainStation) enhancer.create(); // 创建代理对象
        return obj; // 返回代理对象
    }

    /*
        intercept方法参数说明:
            o : 代理对象
            method : 真实对象中的方法的Method实例
            args : 实际参数
            methodProxy :代理对象中的方法的method实例
     */
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("代理点收取一些服务费用(CGLIB动态代理方式)"); // 增强功能
        Object result = methodProxy.invokeSuper(o, args); // 调用火车站的卖票方法
        return result; // 返回结果
    }
}

// 测试类
public class Client {
    public static void main(String[] args) {
        // 创建代理工厂对象
        ProxyFactory factory = new ProxyFactory(); // 创建代理工厂
        // 获取代理对象
        TrainStation proxyObject = factory.getProxyObject(); // 获取代理对象
        proxyObject.sell(); // 调用代理对象的卖票方法
    }
}

分析:

CGLIB动态代理不需要接口,它是通过继承来实现的。

  • ProxyFactory 创建了一个 Enhancer 对象,类似于 JDK 动态代理的 Proxy 类。
  • enhancer.setSuperclass(target.getClass()) 设置父类为目标对象。
  • enhancer.setCallback(this) 设置回调函数为 MethodInterceptor 接口的实现类,也就是 ProxyFactory 本身。
  • enhancer.create() 创建代理对象,实际上是创建了一个目标对象的子类。
  • 当调用代理对象的 sell() 方法时,会调用 MethodInterceptor 接口的 intercept() 方法。
  • intercept() 方法中,我们可以增强功能,比如收取服务费用。
  • methodProxy.invokeSuper(o, args) 调用父类(目标对象)的 sell() 方法。

输出结果:

代理点收取一些服务费用(CGLIB动态代理方式)
火车站卖票

四、三种代理的对比

我的文章: JDK动态代理 vs CGLIB:一场经纪人之战,谁才是你的最佳选择?讲的更好一点,可以看看

  • JDK代理 vs CGLIB代理:

    特性JDK代理CGLIB代理
    接口必须实现接口不需要实现接口
    实现方式通过 Proxy.newProxyInstance() 动态生成代理类通过继承目标类动态生成子类
    性能相对较慢相对较快
    使用场景目标对象实现了接口目标对象没有实现接口
    依赖JDK自带,无需额外依赖需要引入 CGLIB 库
  • 动态代理 vs 静态代理:

    特性静态代理动态代理
    代理类编译时确定运行时动态生成
    灵活性较低较高
    代码量较大较小
    维护性较差较好
    适用场景代理对象数量较少,代理逻辑比较固定代理对象数量较多,代理逻辑比较灵活

五、代理模式的优缺点

优点:

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用 🛡️! 就像保镖保护明星,防止粉丝的疯狂行为!
  • 代理对象可以扩展目标对象的功能 📈! 就像经纪人帮明星安排行程、处理事务,让明星更专注于表演!
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度 🔗! 就像中间商,连接买家和卖家,让双方不用直接接触!
  • 符合开闭原则,可以在不修改目标对象的情况下,增加新的代理类,扩展功能 🆕!

缺点:

  • 增加了系统的复杂度 😫! 需要创建多个类,代码量比较大!
  • 可能会降低性能 🐌! 代理对象会增加额外的开销,可能会影响性能!

六、代理模式的应用场景

  • 远程代理: 就像 VPN,让你访问被墙的网站 🌐!
  • 虚拟代理: 就像懒加载图片 🖼️,只有当图片出现在屏幕上时,才加载图片!
  • 保护代理: 就像访问一些需要权限的资源 🔑,你需要先验证身份,才能访问!
  • 缓存代理: 就像 CDN,缓存静态资源,提高访问速度 🚀!
  • 防火墙代理: 就像防火墙,保护你的电脑免受病毒攻击 🛡️!
  • 事务代理: 控制数据库事务的提交和回滚 💱!

七、总结

  • 代理模式就像找了个替身,帮你做一些事情! 👯
  • 主要分为静态代理、JDK动态代理和CGLIB动态代理三种! 🎭
  • 优点是保护目标对象、扩展功能、降低耦合度、符合开闭原则! 👍
  • 缺点是增加复杂度、可能降低性能! 👎
  • 适用于需要控制对目标对象的访问、增强目标对象的功能、延迟加载、远程代理等场景! 🎯

希望这篇文章能让你彻底理解代理模式! 💯 祝你学习愉快! 😄
看完请看:(七)趣学设计模式 之 适配器模式!


http://www.kler.cn/a/560274.html

相关文章:

  • Hyperledger Fabric 入门笔记(十九)Fabric V2.5 杂项 - 在开发模式下运行链码
  • OpenCV计算摄影学Computational Photography
  • 【嵌入式Linux应用开发基础】网络编程(1):TCP/IP协议栈
  • 【备赛】点亮LED
  • 【信息系统项目管理师-案例真题】2010下半年案例分析答案和详解
  • UE5实现角色二段跳
  • DIP的实际举例
  • 垂类大模型微调(一):认识LLaMA-Factory
  • clickhouse--本地表和分布式表,副本机制,分片集群
  • DeepSeek-R1蒸馏模型与其他模型的区别
  • 【Linux知识】Linux上从源码编译到软件安装全过程详细说明
  • 冒泡排序:简单又易于实现的排序算法
  • 智能生成ER图工具。使用 SQL 生成 ER 图:让数据库设计更高效
  • C/C++高性能Web开发框架全解析:2025技术选型指南
  • 快手弹幕 websocket 分析
  • 用Deepseek直接在word中完成论文的润色(中-中,中-英, 英-中)
  • VantUI官网更新2025,移动端前端开发
  • Python在实际工作中的运用-CSV转XLSX的几个方法
  • 【多模态】46、通俗理解 RLHF/PPO/DPO/GRPO
  • Linux-SaltStack配置