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

摘要:本文介绍设计模式中的代理模式,核心是通过代理在不修改真实对象代码的前提下实现功能增强。含**静态代理、动态代理(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 执行逻辑)。

大功告成!

相关推荐
言慢行善16 小时前
sqlserver模糊查询问题
java·数据库·sqlserver
专吃海绵宝宝菠萝屋的派大星17 小时前
使用Dify对接自己开发的mcp
java·服务器·前端
大数据新鸟17 小时前
操作系统之虚拟内存
java·服务器·网络
Tong Z17 小时前
常见的限流算法和实现原理
java·开发语言
凭君语未可17 小时前
Java 中的实现类是什么
java·开发语言
He少年17 小时前
【基础知识、Skill、Rules和MCP案例介绍】
java·前端·python
cTz6FE7gA17 小时前
数据结构与算法:大厂面试高频题的深度解析
intellij-idea·perl
克里斯蒂亚诺更新17 小时前
myeclipse的pojie
java·ide·myeclipse
迷藏49417 小时前
**eBPF实战进阶:从零构建网络流量监控与过滤系统**在现代云原生架构中,**网络可观测性**和**安全隔离**已成为
java·网络·python·云原生·架构