【 设计模式 | 结构型模式 代理模式 】

摘要:本文介绍设计模式中的代理模式,核心是通过代理在不修改真实对象代码的前提下实现功能增强。含**静态代理、动态代理(JDK 动态代理,CGLIB 动态代理)**实现方式。最后还对比了三种代理的实现基础、限制、性能与适用场景。

思维导图

1. 代理模式

1.1 概述

代理模式是一种旨在控制目标对象访问的设计模式:当外部访问对象因目标对象存在访问限制、需附加额外逻辑(如日志记录、性能监控)等原因,不适合或无法直接引用目标对象时,会通过一个 "代理对象" 作为中介,间接实现对目标对象的交互。

在 Java 技术体系中,代理的实现可根据 "代理类生成时机" 分为两大类别:

1. 静态代理: 代理类的代码在项目编译阶段就已生成,与目标类的字节码一同存在于最终产物中;**2. 动态代理:**代理类并非提前编译好,而是在 Java 程序运行过程中,根据实际业务需求动态创建并加载,无需手动编写代理类代码。

1.2 结构

代理模式分为三种角色:

  1. 抽象主题类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。

  2. 真实主题类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终对象。

  3. 代理类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

1.3 静态代理

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

代码如下:

md-end-block 复制代码
//卖票接口
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方法进行了增强(代理点收取一些服务费用)。

1.4 JDK动态代理

接下来我们使用动态代理实现上面案例,先说说JDK提供的动态代理。Java中提供了一个动态代理类Proxy,Proxy并不是我们上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance方法)来获取代理对象。

代码如下:

md-end-block 复制代码
//卖票接口
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();
    }
}

核心角色说明

(1)SellTickets 接口

  • 这是一个功能接口,定义了 "卖票" 的规范(只有sell()方法)。
  • 作用:作为真实对象代理对象的共同接口,保证代理对象能以与真实对象相同的方式被调用(符合 "里氏替换原则")。

(2)TrainStation 类(真实主题)

  • 实现了SellTickets接口,是实际执行业务逻辑的类。
  • sell()方法的实现是 "火车站卖票" 的核心功能(打印 "火车站卖票")。
  • 角色:被代理的对象,专注于核心业务逻辑。

(3)ProxyFactory 类(代理工厂)

  • 用于创建代理对象 的工厂类,内部持有一个真实对象(TrainStation实例)。
  • 核心方法getProxyObject()通过Proxy.newProxyInstance()生成代理对象,需要 3 个参数:
    • ClassLoader loader:类加载器,直接使用真实对象的类加载器。
    • Class<?>[] interfaces:真实对象实现的接口。
    • InvocationHandler h:代理对象的 "方法调用处理器",是实现功能增强的核心。

(4)InvocationHandler 接口(方法调用处理器)

  • 这是一个匿名内部类实现,定义了代理对象调用方法时的 "增强逻辑"。
  • 核心方法invoke()在代理对象调用任何方法时都会被自动触发,包含 3 个参数:
    • proxy:当前的代理对象(一般不用)。
    • method:被调用的方法(这里是sell()方法的Method实例)。
    • args:调用方法时传递的参数(这里sell()无参数,所以为null)。
  • 逻辑: 先执行增强操作,再通过method.invoke(station, args)调用真实对象的sell()方法,实现 "增强 + 核心业务" 的组合执行。

1.5 CGLIB动态代理

当没有定义 SellTickets 接口,仅存在 TrainStation 类时,由于 JDK 动态代理依赖接口实现,无法直接使用,此时可采用 CGLIB 代理来解决。

CGLIB 作为一个高性能的代码生成包,能为没有实现接口的类创建代理,它通过继承目标类(TrainStation)生成代理子类的方式实现功能增强,很好地补充了 JDK 动态代理在无接口场景下的不足,从而实现在不修改 TrainStation 类代码的前提下,对其卖票功能进行扩展(如添加服务费用收取等操作)。

引入依赖:

md-end-block 复制代码
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>

代码如下:

md-end-block 复制代码
//火车站
public class TrainStation {

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

//代理工厂
public class ProxyFactory implements MethodInterceptor {

    private TrainStation target = new TrainStation();

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

    /*
        intercept方法参数说明:
            o : 代理对象
            method : 真实对象中的方法的Method实例
            args : 实际参数
            methodProxy :代理对象中的方法的method实例
     */
    public TrainStation intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("代理点收取一些服务费用(CGLIB动态代理方式)");
        TrainStation result = (TrainStation) 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();
    }
}

1.6 三种代理的对比

JDK 动态代理 vs CGLIB 代理

  • 实现基础:JDK 动态代理基于接口实现,要求目标类必须实现接口,代理对象是接口的实现类;CGLIB 代理基于继承实现,无需目标类实现接口,通过生成目标类的子类作为代理类。
  • 限制:JDK 代理仅能代理接口中的方法;CGLIB 不能代理 final 类或 final 方法。
  • 性能:JDK 1.6 及之前,CGLIB 因基于字节码生成,效率高于 JDK 代理;JDK 1.8 优化后,JDK 代理在多数场景(调用次数较少时)效率更高,仅在大量调用时与 CGLIB 接近或略优。
  • 使用场景有接口时优先用 JDK 代理,无接口时只能用 CGLIB 代理。

动态代理(JDK/CGLIB)vs 静态代理

  • 代码维护:静态代理需为每个接口方法编写代理逻辑,接口新增方法时,所有实现类和代理类都需修改,维护成本高;动态代理通过集中处理器(如 InvocationHandler)统一处理所有方法,接口变更时无需修改代理逻辑,灵活性更高。
  • 生成时机:静态代理的代理类在编译期手动编写完成;动态代理的代理类在运行时动态生成。
  • 适用场景:静态代理适用于接口方法少、变更少的简单场景;动态代理适用于接口方法多、频繁变更的复杂场景,可大幅减少重复代码。

综上,动态代理在灵活性和维护性上优于静态代理,而 JDK 代理与 CGLIB 代理的选择主要取决于是否有接口及 JDK 版本。

1.7 应用场景

静态代理 :代理类在编译期手动编写,与目标类实现同一接口,一对一代理。

常见场景

  • 简单功能增强 :如对单个类的特定方法添加日志计时等简单增强(方法数量少且稳定)。
  • 访问控制 :如对敏感接口(如支付接口)设置临时访问权限校验,代理类可在调用真实方法前拦截非法请求
  • 测试环境模拟 :在单元测试中,用静态代理模拟第三方服务(如模拟支付接口返回固定结果,避免真实调用)。

JDK 动态代理 :基于接口动态生成代理类,运行时创建,需目标类实现接口。

常见场景

  • 框架级通用增强:如 Spring AOP 的默认代理方式(对实现接口的 Bean),用于事务管理、日志记录、异常处理等横切逻辑。
  • RPC 框架:如 Dubbo 中对服务接口的代理,通过代理类实现远程调用(序列化、网络传输等逻辑封装)。
  • 权限校验:在分布式系统中,对接口统一添加权限拦截(如登录态校验、接口访问限流)。
  • 数据源路由:在多数据源场景中,通过代理动态切换数据源(如读写分离,根据方法名路由到主库或从库)。

CGLIB 代理 :基于继承动态生成代理子类,无需目标类实现接口,可代理非 final 类 / 方法。

常见场景

  • 无接口类的增强:如对 POJO 类、工具类等未实现接口的类进行增强(如添加缓存逻辑)。
  • 第三方库适配 :当依赖的第三方类未实现接口,但需要增强其方法时(如对第三方 HTTP 客户端添加超时重试逻辑)。
  • Spring 框架补充:Spring 中对未实现接口的 Bean 默认使用 CGLIB 代理。
  • ORM 框架:如 MyBatis 中对 Mapper 接口的代理(虽然 Mapper 是接口,但底层结合 CGLIB 实现动态 SQL 执行逻辑)。

大功告成!

相关推荐
柯南二号2 小时前
【AI】【Java后端】RAG 实战示例:SpringBoot + 向量检索 + LLM 问答系统
java·人工智能·spring boot
Mr.Pascal2 小时前
后端直接返回错误信息的Map 和 抛出异常(异常机制)优劣势对比
java·springboot
南囝coding2 小时前
Vercel 发布 AI Gateway 神器!可一键访问数百个模型,助力零门槛开发 AI 应用
前端·后端
耀耀_很无聊2 小时前
14_Spring Boot 跨域(CORS)处理指南
spring boot·后端
他日若遂凌云志3 小时前
深入拆解 Windows Socket 五种 I/O 模型:核心机制、Linux 差异与场景适配
后端
小码编匠3 小时前
开箱即用!集成 YOLO+OpenCV+OCR 的 WebAI 平台(支持RTSP/RTMP视频流识别与自训练)
spring boot·后端·opencv
zcychong3 小时前
如何让A、B、C三个线程按严格顺序执行(附十一种解)?
java·面试
文心快码BaiduComate3 小时前
再获殊荣!文心快码荣膺2025年度优秀软件产品!
前端·后端·代码规范
步行cgn3 小时前
HttpSessionBindingListener
java·开发语言·数据仓库·servlet