什么是代理模式?应用场景有哪些?

一则或许对你有用的小广告

欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于 Spring AI + Spring Boot3.x + JDK 21...点击查看;
  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot3.x + JDK 17...点击查看项目介绍; 演示链接: http://116.62.199.48:7070/;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/

面试考察点

面试官提出这个问题,主要想考察以下几个层面:

  1. 对基础设计模式的掌握程度:是否理解代理模式作为一种经典结构型模式的核心思想与定义。
  2. 对其实现原理的深入理解:能否清晰阐述静态代理与动态代理(特别是 JDK 动态代理)的区别、实现机制及底层原理。
  3. 理论联系实际的能力:不仅仅是记住概念,更是想知道你能否识别出哪些真实开发场景适合应用代理模式,这反映了你的架构思维和实战经验。
  4. 对主流框架的认知深度:许多主流框架(如 Spring)的核心功能都重度依赖代理模式,理解代理模式是理解这些框架背后工作机制的钥匙。

核心答案

代理模式是一种结构型设计模式,其核心是为一个对象提供一个代理对象,并由代理对象控制对原对象的访问。客户端不直接与真实对象交互,而是通过代理对象,从而可以在不改变真实对象代码的前提下,增加额外的功能(如日志、鉴权、事务控制)或控制对真实对象的访问。

主要应用场景包括:

  • 访问控制与安全代理:控制客户端对真实对象的权限。
  • 延迟初始化(虚拟代理):在对象创建开销大时,代理可以延迟其创建,直到真正需要时。
  • 增强功能(日志、监控、事务):为原始方法添加统一的横切关注点逻辑。
  • 远程代理:在 RPC 或网络通信中,本地代理负责网络通信,隐藏远程调用的复杂性。
  • 智能引用:在访问真实对象前后执行额外操作,如引用计数、资源释放检查。

在 Java 生态中,Spring AOPMyBatis 的 Mapper 接口实现 是动态代理最经典的落地应用。

深度解析

原理与机制

代理模式的核心角色有三个:

  1. Subject(抽象主题):声明了真实对象和代理对象的共同接口,使代理可以替代真实对象。
  2. RealSubject(真实主题):实现了 Subject 接口,是真正执行业务逻辑的对象。
  3. Proxy(代理):也实现了 Subject 接口,内部持有对 RealSubject 的引用。在调用客户端请求时,代理可以在转发请求给真实对象之前或之后,执行额外的操作。

代理主要分为两大类:

  • 静态代理:在编译期就确定了代理类,代理类和被代理类实现同一接口。一个代理类通常只服务一个接口,代码冗余。
  • 动态代理:在运行时动态生成代理类。Java 原生支持基于接口的 JDK 动态代理,其核心是利用 java.lang.reflect.Proxy 类和 InvocationHandler 接口。第三方库(如 CGLIB)则支持通过继承实现基于类的动态代理。

代码示例:静态代理 vs JDK 动态代理

1. 静态代理示例:

// 1. 抽象主题
interface UserService {
    void saveUser();
}

// 2. 真实主题
class UserServiceImpl implements UserService {
    @Override
    public void saveUser() {
        System.out.println("核心业务:保存用户");
    }
}

// 3. 静态代理类
class UserServiceStaticProxy implements UserService {
    private UserService target; // 持有真实对象的引用

    public UserServiceStaticProxy(UserService target) {
        this.target = target;
    }

    @Override
    public void saveUser() {
        System.out.println("【静态代理】操作前记录日志...");
        target.saveUser(); // 调用真实对象的方法
        System.out.println("【静态代理】操作后记录日志...");
    }
}

// 客户端调用
public class StaticProxyDemo {
    public static void main(String[] args) {
        UserService realService = new UserServiceImpl();
        UserService proxy = new UserServiceStaticProxy(realService);
        proxy.saveUser(); // 客户端通过代理调用
    }
}

2. JDK 动态代理示例:

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

// 动态代理处理器
class LogInvocationHandler implements InvocationHandler {
    private final Object target; // 目标对象,可以是任何实现了接口的对象

    public LogInvocationHandler(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 DynamicProxyDemo {
    public static void main(String[] args) {
        UserService realService = new UserServiceImpl();

        // 动态创建代理对象
        UserService dynamicProxy = (UserService) Proxy.newProxyInstance(
                realService.getClass().getClassLoader(), // 类加载器
                realService.getClass().getInterfaces(),  // 实现的接口数组
                new LogInvocationHandler(realService)    // 调用处理器
        );

        dynamicProxy.saveUser(); // 调用代理方法
    }
}

对比分析与最佳实践

特性静态代理JDK 动态代理CGLIB 动态代理
实现方式显式编写代理类运行时通过 Proxy 类生成运行时通过继承被代理类生成子类
代理目标接口只能代理接口可以代理 (final 类/方法除外)
性能编译时确定,调用快反射调用,略慢于静态代理早期生成 FastClass,调用效率通常高于 JDK 代理
灵活性低,一个代理类对应一个目标类高,一个处理器可代理多个接口高,但无法代理 final 方法/类
依赖JDK 原生支持需要引入 CGLIB 库

最佳实践与常见误区

  1. 框架首选:在 Spring 中,如果目标对象实现了接口,默认使用 JDK 动态代理;如果没有实现接口,则使用 CGLIB。可以通过配置强制使用 CGLIB。
  2. 明确场景不要为了用模式而用模式。代理模式适用于需要非侵入式增强的场景。如果增强逻辑与业务强相关,或许直接写在业务类里更清晰。
  3. 性能考量:动态代理的反射调用会带来微小的性能开销,但在绝大多数应用场景中,这种开销与它带来的解耦和灵活性的收益相比是微不足道的。不应过早优化。
  4. 常见误区:认为动态代理可以代理任何类。务必记住 JDK 动态代理只能基于接口,这是其最根本的限制。如果要对未实现接口的普通类进行代理,需要借助 CGLIB 等字节码增强库。

总结

代理模式的本质是通过一个中间层来控制和管理对目标对象的访问,实现了调用者与目标对象的解耦,是实现面向切面编程(AOP)等高级特性的基石。掌握其静态与动态的实现方式,并理解其在 Spring 等框架中的应用,是 Java 开发者深入技术栈的必备技能。