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

0 阅读7分钟

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

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

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

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

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

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


🎯 二、什么是代理模式?

定义

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

核心价值

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

生活类比

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

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

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

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

1. Subject(抽象主题角色)

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

2. RealSubject(真实主题角色)

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

3. Proxy(代理主题角色)

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

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

场景描述

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

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

代码实现

Step 1:定义抽象主题接口

package com.renxin.proxy;

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

Step 2:实现真实主题

package com.renxin.proxy;

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

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

Step 3:实现代理主题

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:客户端测试

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();
    }
}

运行结果

打折
卖书
赠送代金券

代码执行流程图

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

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

静态代理的局限性

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

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

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

动态代理的解决方案

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

核心三要素

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

代码实现

Step 1:复用 Subject 和 RealSubject

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

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

Step 2:创建调用处理器

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:客户端使用动态代理

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();
    }
}

运行结果

打折!
卖书
赠送代金券!

动态代理原理图

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

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

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

选择建议

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

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

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

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

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

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

原理

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

2. RPC 远程调用

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

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

3. 延迟加载(懒加载)

Hibernate 中的实体关联加载:

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

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

4. 权限控制

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. 缓存代理

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. 控制访问:代理可以决定是否允许访问真实对象