代理模式:定义、结构、实现、优缺点、应用场景

代理模式,说白了就是「找人办事」。你想想看,有时候你不想直接面对某个对象,或者直接调用它太麻烦、太危险,那就找个代理来替你搞定。我在项目中遇到过不少这样的场景——比如远程调用、权限控制、延迟加载,代理模式都是个很优雅的解决方案。

一、定义与核心思想

代理模式(Proxy Pattern)属于结构型设计模式。它提供了一个替身,来控制对另一个对象的访问。这个替身就是代理对象,它和被代理对象实现相同的接口,客户端通过代理来间接访问真实对象。

嗯,这里要注意:代理不是装饰器。装饰器是增强功能,代理是控制访问。我刚开始学的时候也容易搞混,后来在项目里踩过坑才真正分清楚。

核心思想:用一个代理对象来控制对真实对象的访问,可以在不修改真实对象的前提下,增加额外的控制逻辑。

二、结构组成

代理模式的结构其实挺简单的,就三个角色:

  • 抽象主题(Subject):定义真实对象和代理对象的共同接口。这样客户端才能统一对待它们。
  • 真实主题(RealSubject):真正干活的业务对象。代理对象背后就是它。
  • 代理(Proxy):持有真实对象的引用,控制对它的访问。可以在调用前后加一些额外操作。

我习惯用一张图来理解它们的关系,你看下面这个:

抽象主题 (Subject) + request() 真实主题 (RealSubject) 代理 (Proxy) + request() 实现 实现 持有引用 客户端 (Client) 调用

三、实现方式:静态代理 vs 动态代理

代理模式有两种实现方式。静态代理是手写代理类,动态代理是运行时生成代理类。我个人更推荐动态代理,因为它更灵活。但静态代理在某些简单场景下反而更直观。

1. 静态代理

静态代理就是手动创建一个代理类,让它实现和真实对象相同的接口。每个真实对象都需要一个对应的代理类。代码写起来比较啰嗦,但好处是简单易懂。

// 抽象主题
interface Image {
    void display();
}

// 真实主题
class RealImage implements Image {
    private String filename;

    public RealImage(String filename) {
        this.filename = filename;
        loadFromDisk();  // 构造时就加载,比较重
    }

    private void loadFromDisk() {
        System.out.println("加载图片: " + filename);
    }

    @Override
    public void display() {
        System.out.println("显示图片: " + filename);
    }
}

// 静态代理
class ImageProxy implements Image {
    private RealImage realImage;
    private String filename;

    public ImageProxy(String filename) {
        this.filename = filename;
    }

    @Override
    public void display() {
        // 延迟加载:第一次显示时才真正创建真实对象
        if (realImage == null) {
            realImage = new RealImage(filename);
        }
        realImage.display();
    }
}

// 客户端
public class Client {
    public static void main(String[] args) {
        Image image = new ImageProxy("photo.jpg");
        // 此时还没加载图片
        image.display();  // 第一次调用,才真正加载
        image.display();  // 第二次调用,直接显示
    }
}

你看,静态代理的代码很直白。但问题来了——如果我有几十个类都需要代理,每个都手写一个代理类,那工作量就大了。我曾经在一个老项目里见过这种情况,光代理类就写了上百个,维护起来真是头疼。

2. 动态代理

动态代理就不一样了。它利用 Java 的反射机制,在运行时动态生成代理类。你只需要写一个 InvocationHandler,就能代理任意接口。这比静态代理灵活太多了。

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

// 通用的 InvocationHandler
class LogHandler implements InvocationHandler {
    private Object target;

    public LogHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("[日志] 调用方法: " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("[日志] 方法结束: " + method.getName());
        return result;
    }
}

// 客户端使用动态代理
public class DynamicProxyClient {
    public static void main(String[] args) {
        Image realImage = new RealImage("photo.jpg");
        Image proxy = (Image) Proxy.newProxyInstance(
            realImage.getClass().getClassLoader(),
            realImage.getClass().getInterfaces(),
            new LogHandler(realImage)
        );
        proxy.display();
    }
}

小提示:动态代理只能代理接口,不能代理类。如果你需要代理类,可以考虑 CGLIB 或 ByteBuddy 这类库。我在做 Spring AOP 相关项目时,就经常用 CGLIB 来处理没有接口的类。

四、优缺点分析

优点 缺点
  • 职责清晰:代理对象负责控制访问,真实对象只关心业务
  • 扩展性好:可以在不修改真实对象的前提下增加功能
  • 保护真实对象:比如权限校验、访问控制
  • 延迟加载:减少系统启动时的资源消耗
  • 增加系统复杂度:多了一层代理,调试起来更麻烦
  • 响应可能变慢:每次调用都经过代理,有额外开销
  • 静态代理类数量膨胀:每个真实对象都要配一个代理类

避坑指南:我曾经在一个项目中过度使用代理模式,给每个 Service 都加了代理层。结果代码变得非常绕,出了问题要跳好几层才能找到真实逻辑。后来我反思了一下——代理模式要用在真正需要控制访问的地方,不要为了用模式而用模式。

五、应用场景

代理模式在实际项目中应用非常广泛。我挑几个最常见的场景说说:

  1. 远程代理(RPC):比如 Dubbo、Feign 这些框架,客户端调用的其实是一个本地代理对象,代理负责把请求通过网络发送到远程服务器。你想想看,如果没有代理,客户端就得自己处理网络通信、序列化、异常重试这些事,那代码得多复杂。
  2. 虚拟代理(延迟加载):像上面图片加载的例子,大对象在真正需要时才创建。我在做报表系统时就用过这个思路——报表数据量很大,用户不点开查看就不加载,节省了不少内存。
  3. 保护代理(权限控制):在调用真实对象之前,先检查调用方有没有权限。比如 Spring Security 里的 @PreAuthorize 注解,底层就是通过 AOP 代理来实现的。
  4. 智能引用(引用计数):当对象被多个客户端引用时,代理可以记录引用次数,在引用数为零时释放资源。我记得在做一个缓存框架时就用到了这个机制。
  5. 日志记录与监控:通过代理在方法调用前后记录日志、统计耗时。Spring AOP 的 @Around 通知就是典型的代理应用。

说白了,代理模式的核心价值就是「控制访问」。它让你在不改变原有代码的情况下,给对象调用加上一层额外的逻辑。这个思想在很多框架中都有体现——Spring AOP、MyBatis 的 Mapper 代理、Hibernate 的懒加载代理,背后都是代理模式。

嗯,最后说一句:代理模式虽然好用,但别滥用。我见过有人给每个类都配一个代理,结果代码变得又臭又长。记住,模式是工具,不是目的。用对地方才是高手。


公众号:蓝海资料掘金营,微信deep3321