Java 代理模式:从生活中的"中介"到代码中的"代理人"

🏠 一、从生活中的"中介"说起

在生活中,我们经常遇到"代理"的场景:

  • 买房租房:你不会直接找房东,而是通过房产中介
  • 明星代言:品牌方不会直接联系明星,而是通过经纪人
  • 网购下单:你买书时,可能有"打折"和"送代金券"等附加服务

这些场景有一个共同点:真正做事的人(房东、明星、出版社)并不直接面对客户,而是通过一个"代理人"来处理事务。

这个代理人不仅能完成核心任务,还能在前后做很多额外的事情------比如中介带你参观房源、经纪人谈判价格、书店搞促销活动。

在软件世界中,代理模式就是这种思想的体现。


🎯 二、什么是代理模式?

定义

代理模式(Proxy Pattern):为其他对象提供一种代理以控制对这个对象的访问。

核心价值

  1. 控制访问:客户端不能直接访问真实对象,必须通过代理
  2. 增强功能:代理可以在调用真实对象前后添加额外操作
  3. 解耦职责:将核心业务逻辑与辅助逻辑分离

生活类比

现实场景 真实对象 代理对象 代理做的事
租房 房东 房产中介 带看房源、签合同、收租金
代言 明星 经纪人 谈价格、安排档期、接电话
买书 出版社 书店 打折、送代金券、卖书

👥 三、代理模式的三种角色

代理模式包含三个核心角色,它们各司其职:

scss 复制代码
┌─────────────┐
│   Subject   │ (抽象主题角色)
│  <<interface>> │
└─────────────┘
       △
       │ 实现
       │
   ┌───┴─────────────────┐
   │                     │
┌───▼───────┐      ┌────▼─────────┐
│RealSubject│      │    Proxy     │
│(真实主题) │      │  (代理主题)   │
└───────────┘      └──────────────┘

1. Subject(抽象主题角色)

  • 是什么:真实主题与代理主题的共同接口
  • 作用:定义了真实对象和代理对象的公共方法
  • 好处:客户端面向接口编程,不关心具体是真实对象还是代理对象

2. RealSubject(真实主题角色)

  • 是什么:定义了代理所代表的真实对象
  • 作用:执行真正的业务逻辑
  • 特点:实现了 Subject 接口

3. Proxy(代理主题角色)

  • 是什么:持有对真实主题的引用
  • 作用:在调用真实对象前后可以执行额外操作
  • 特点:同样实现 Subject 接口,控制对真实对象的访问

🔧 四、静态代理实战:书店卖书案例

场景描述

假设有一个"卖书"的业务:

  • RealSubject:出版社直接卖书
  • Proxy:书店作为代理,在卖书前"打折",卖书后"送代金券"

代码实现

Step 1:定义抽象主题接口

java 复制代码
package com.renxin.proxy;

/**
 * 抽象主题角色 - 定义卖书的公共接口
 *
 * @author xuzhennan
 * @since 2026-03-22
 */
public interface Subject {
    /**
     * 卖书方法
     */
    void sailBook();
}

Step 2:实现真实主题

java 复制代码
package com.renxin.proxy;

/**
 * 真实主题角色 - 出版社直接卖书
 *
 * @author xuzhennan
 * @since 2026-03-22
 */
public class RealSubject implements Subject {

    @Override
    public void sailBook() {
        System.out.println("卖书");
    }
}

Step 3:实现代理主题

java 复制代码
package com.renxin.proxy;

/**
 * 代理主题角色 - 书店代理卖书
 * 在卖书前后增加打折和送代金券的逻辑
 *
 * @author xuzhennan
 * @since 2026-03-22
 */
public class ProxySubject implements Subject {

    private RealSubject realSubject;

    public RealSubject getRealSubject() {
        return realSubject;
    }

    public void setRealSubject(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public void sailBook() {
        // 前置增强:打折
        dazhe();

        // 调用真实对象的方法
        this.realSubject.sailBook();

        // 后置增强:赠送代金券
        give();
    }

    /**
     * 打折促销
     */
    private void dazhe() {
        System.out.println("打折");
    }

    /**
     * 赠送代金券
     */
    private void give() {
        System.out.println("赠送代金券");
    }
}

Step 4:客户端测试

java 复制代码
package com.renxin.proxy;

/**
 * 测试类 - 验证代理模式
 *
 * @author xuzhennan
 * @since 2026-03-22
 */
public class MainClass {
    public static void main(String[] args) {
        // 创建真实对象
        RealSubject realSubject = new RealSubject();

        // 创建代理对象,并注入真实对象
        ProxySubject proxySubject = new ProxySubject();
        proxySubject.setRealSubject(realSubject);

        // 通过代理调用方法
        proxySubject.sailBook();
    }
}

运行结果

复制代码
打折
卖书
赠送代金券

代码执行流程图

scss 复制代码
客户端
  │
  ▼
ProxySubject.sailBook()
  │
  ├─► dazhe()          【前置增强】
  │   └─► "打折"
  │
  ├─► realSubject.sailBook()  【调用真实对象】
  │   └─► "卖书"
  │
  └─► give()           【后置增强】
      └─► "赠送代金券"

🚀 五、动态代理实战:InvocationHandler 的魔法

静态代理的局限性

上面的静态代理虽然实现了功能,但有一个明显的问题:

一个代理类只能服务于一个接口

如果你有 10 个接口需要代理,就要写 10 个代理类------这显然太笨重了。

动态代理的解决方案

Java 提供了 动态代理 机制,可以在运行时动态生成代理类,无需手动编写。

核心三要素

  1. InvocationHandler 接口:定义了代理对象的调用逻辑
  2. invoke 方法:代理对象的所有方法调用都会转到这里
  3. Proxy.newProxyInstance():动态生成代理对象

代码实现

Step 1:复用 Subject 和 RealSubject

java 复制代码
// 与静态代理相同
public interface Subject {
    void sailBook();
}

public class RealSubject implements Subject {
    public void sailBook() {
        System.out.println("卖书");
    }
}

Step 2:创建调用处理器

java 复制代码
package com.renxin.news;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * 动态代理调用处理器
 * 所有通过代理对象的方法调用都会被转发到 invoke 方法
 *
 * @author xuzhennan
 * @since 2026-03-22
 */
public class MyHandler implements InvocationHandler {

    private RealSubject realSubject;

    public void setRealSubject(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    /**
     * 代理对象的所有方法调用都会经过这个方法
     *
     * @param proxy  动态生成的代理对象
     * @param method 被调用的方法
     * @param args   方法参数
     * @return 方法执行结果
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = null;

        // 前置增强
        dazhe();

        // 调用真实对象的方法
        result = method.invoke(realSubject, args);

        // 后置增强
        give();

        return result;
    }

    private void dazhe() {
        System.out.println("打折!");
    }

    private void give() {
        System.out.println("赠送代金券!");
    }
}

Step 3:客户端使用动态代理

java 复制代码
package com.renxin.news;

import java.lang.reflect.Proxy;

/**
 * 动态代理测试类
 *
 * @author xuzhennan
 * @since 2026-03-22
 */
public class MainClass {
    public static void main(String[] args) {
        // 创建真实对象
        RealSubject realSubject = new RealSubject();

        // 创建调用处理器
        MyHandler myHandler = new MyHandler();
        myHandler.setRealSubject(realSubject);

        // 动态生成代理对象
        Subject proxySubject = (Subject) Proxy.newProxyInstance(
            RealSubject.class.getClassLoader(),      // 类加载器
            realSubject.getClass().getInterfaces(), // 实现的接口
            myHandler                               // 调用处理器
        );

        // 调用代理对象的方法
        proxySubject.sailBook();
    }
}

运行结果

复制代码
打折!
卖书
赠送代金券!

动态代理原理图

scss 复制代码
┌─────────────────────────────────────────────────────┐
│ 客户端调用 proxySubject.sailBook()                    │
└─────────────────────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────┐
│ Proxy.newProxyInstance() 动态生成的代理对象           │
│ (实现了 Subject 接口)                                 │
└─────────────────────────────────────────────────────┘
                         │
                         ▼ 所有方法调用
┌─────────────────────────────────────────────────────┐
│ MyHandler.invoke(proxy, method, args)                │
│                                                      │
│  1. dazhe()           ──► "打折!"                   │
│  2. method.invoke()   ──► "卖书"                     │
│  3. give()            ──► "赠送代金券!"              │
└─────────────────────────────────────────────────────┘

⚖️ 六、静态代理 vs 动态代理

对比维度 静态代理 动态代理
代码量 每个接口需要写一个代理类 一个 InvocationHandler 处理所有接口
灵活性 编译期确定,不够灵活 运行期动态生成,非常灵活
维护成本 新增接口需新增代理类 无需修改,自动适配
性能 直接调用,性能高 反射调用,有轻微性能损耗
适用场景 接口少、逻辑固定 接口多、逻辑统一
实现方式 手写代理类 JDK 动态代理 / CGLIB

选择建议

kotlin 复制代码
项目需求 ──► 代理的接口数量少? ──► 是 ──► 静态代理
    │
    └──► 否 ──► 需要统一处理所有方法? ──► 是 ──► 动态代理
                  │
                  └──► 否 ──► 考虑 CGLIB/Byte Buddy 等字节码增强库

💼 七、代理模式的实际应用场景

1. Spring AOP(面向切面编程)

最经典的应用! Spring 通过动态代理实现 AOP:

java 复制代码
// 事务管理
@Transactional
public void saveUser(User user) {
    userDao.save(user);
}

// Spring 会动态生成代理,在方法前后开启/提交事务

原理

  • 前置增强:开启事务 beginTransaction()
  • 执行方法:saveUser()
  • 后置增强:提交事务 commit()

2. RPC 远程调用

Dubbo、gRPC 等框架通过代理屏蔽网络通信细节:

java 复制代码
// 客户端就像调用本地方法一样
UserService userService = rpcProxy.create(UserService.class);
User user = userService.getUserById(123);  // 实际是远程调用

3. 延迟加载(懒加载)

Hibernate 中的实体关联加载:

java 复制代码
User user = session.get(User.class, 1L);
// 此时 orders 是代理对象,不会查询数据库
List<Order> orders = user.getOrders();

// 只有真正访问时才查询数据库
orders.size();

4. 权限控制

java 复制代码
public class AdminProxy implements AdminService {
    private AdminService realAdmin;
    private User currentUser;

    public void deleteUser(Long userId) {
        if (!currentUser.isAdmin()) {
            throw new AccessDeniedException("无权操作");
        }
        realAdmin.deleteUser(userId);
    }
}

5. 缓存代理

java 复制代码
public class CacheProxy implements DataService {
    private DataService realService;
    private Map<String, Object> cache = new HashMap<>();

    public Data getData(String key) {
        if (cache.containsKey(key)) {
            return cache.get(key);  // 命中缓存
        }
        Data data = realService.getData(key);
        cache.put(key, data);        // 写入缓存
        return data;
    }
}

🤔 八、总结与思考

代理模式的核心价值

  1. 单一职责:真实对象只关注核心业务,代理对象处理辅助逻辑
  2. 开闭原则:新增代理类不会修改原有代码
  3. 控制访问:代理可以决定是否允许访问真实对象
相关推荐
野犬寒鸦1 小时前
JVM垃圾回收机制深度解析(G1篇)(垃圾回收过程及专业名词详解)(补充)
java·服务器·开发语言·jvm·后端·面试
白宇横流学长1 小时前
基于SpringBoot实现的信息技术知识赛系统设计与实现【源码+文档】
java·spring boot·后端
砍光二叉树1 小时前
【设计模式】结构型-适配器模式
设计模式·适配器模式
yhyyht1 小时前
Maven命令学习记录(一)
后端
Soofjan2 小时前
Go channel源码
后端
Soofjan2 小时前
channel
后端
SimonKing2 小时前
OpenClaw,再见!
java·后端·程序员
大阿明2 小时前
Spring BOOT 启动参数
java·spring boot·后端
hutengyi2 小时前
Spring Boot 实战篇(四):实现用户登录与注册功能
java·spring boot·后端