用get ai助手辅助搜索资料,代理模式从入门到面试通关(2026-04-08)

小编头像

小编

管理员

发布于:2026年05月10日

2 阅读 · 0 评论

本文通过

get ai助手 高效检索技术资料,整合零散信息,系统梳理代理模式的完整知识链路。

前言

代理模式是GoF 23种设计模式中的结构型设计模式,英文全称Proxy Pattern。它的官方定义为:为其他对象提供一种代理,以控制对这个对象的访问

这套定义听起来有点抽象,但回到日常场景就很好理解了:租房时你找中介而不是直接联系房东,签约时明星的经纪人在前面替你谈条件,海淘时代购帮你搞定一切手续。这些“中间人”本质上就是代理——你只和代理打交道,代理负责与真实对象交互,并在过程中承担额外的职责。

很多学习者在接触代理模式时会遇到类似的困惑:静态代理和动态代理傻傻分不清,知道AOP用到了代理却说不出底层原理,面试被问到JDK与CGLIB的区别只能支支吾吾。 这篇文章将带你从概念到原理、从代码到面试,打通代理模式的完整知识链路。


一、痛点切入:为什么需要代理模式

在理解代理模式之前,先看看不使用时的问题。

假设有一个发送短信的业务接口和它的实现类:

java
复制
下载
public interface SmsService {
    void send(String message);
}

public class SmsServiceImpl implements SmsService {
    @Override
    public void send(String message) {
        System.out.println("发送消息:" + message);
    }
}

现在需要在发送短信前后分别记录日志。最直接的做法是修改 send() 方法,在首尾添加日志输出。但问题随之而来:假如有10个不同业务都需要加日志,就要修改10处代码,并且一旦日志逻辑需要调整,10处都要改动。

如果业务和增强逻辑混在一起,会出现以下问题:

  • 代码冗余:相同的增强逻辑在多个方法中重复编写

  • 耦合度高:业务代码混杂非业务逻辑,违背单一职责原则

  • 扩展性差:新增增强功能需修改原有代码,违背开闭原则

  • 维护困难:修改一处增强逻辑需同步修改所有调用处

这正是代理模式要解决的问题——在不修改原始类的前提下,为目标对象提供功能增强和访问控制


二、核心概念讲解:代理模式

标准定义

代理模式(Proxy Pattern) 是一种结构型设计模式,它引入一个代理对象来代表真实对象,客户端只与代理交互,代理决定何时、如何、是否将请求转发给真实对象-1

生活化类比

代理模式的运作机制,用“中介租房”类比最直观:

角色代理模式角色中介场景对应
定义公共行为抽象主题(Subject)租房接口(带看、签约)
真正执行业务真实主题(RealSubject)房东(拥有房子)
控制访问代理主题(Proxy)中介(持有房东信息)
发起请求客户端(Client)租客

房东只负责收租和交房,不处理带看、签约、押金这些琐事;租客只和中介沟通,不直接联系房东。中介在带看前后可以添加“筛选房源”“记录看房日志”等操作,这就是“增强”。代理对象不创造核心能力,最终的核心业务还是由真实对象完成 -1

核心价值

引入代理层带来三点核心价值:

  • 透明性:客户端无需感知代理的存在,调用方式与直接调用真实对象一致

  • 控制性:代理可在请求前后插入逻辑(权限校验、日志记录、缓存等)

  • 解耦性:将横切关注点(安全、日志)从业务逻辑中剥离,符合单一职责原则-4


三、关联概念讲解:静态代理 vs 动态代理

代理模式根据代理类的创建时机和方式,分为静态代理动态代理两大类。

3.1 静态代理

静态代理是指代理类的代码在编译时就已经确定,需要开发人员手动编写代理类。代理类和目标类都实现同一个接口,代理类持有目标对象的引用,在调用目标方法前后添加增强逻辑-2

示例:为SmsService添加日志增强

java
复制
下载
// 代理类:实现同一接口,持有真实对象引用
public class SmsProxy implements SmsService {
    private final SmsService smsService;
    
    public SmsProxy(SmsService smsService) {
        this.smsService = smsService;
    }
    
    @Override
    public void send(String message) {
        // 前置增强:日志记录
        System.out.println("【前置】发送消息开始:" + message);
        
        // 调用真实对象的核心方法
        smsService.send(message);
        
        // 后置增强:日志记录
        System.out.println("【后置】消息发送成功");
    }
}

// 使用方式
SmsService realService = new SmsServiceImpl();
SmsService proxy = new SmsProxy(realService);
proxy.send("Hello");  // 调用代理对象,代理内部调用真实对象

缺点:如果被代理类增加了新的方法,代理类需要同步增加,违背开闭原则;且每个目标类都需要单独写一个代理类,代码量大、维护成本高-12

3.2 动态代理

动态代理在运行时动态生成代理类的字节码,无需手动编写代理类。代理类的创建逻辑被集中封装,大幅减少代码冗余-

Java平台提供两种主流动态代理实现:

JDK动态代理

原理:基于接口实现,通过 java.lang.reflect.Proxy 类和 InvocationHandler 接口,在运行时生成实现指定接口的代理类-15

java
复制
下载
public 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("【后置】方法执行完成");
        return result;
    }
}

// 使用方式
SmsService realService = new SmsServiceImpl();
InvocationHandler handler = new LogInvocationHandler(realService);
SmsService proxy = (SmsService) Proxy.newProxyInstance(
    realService.getClass().getClassLoader(),
    realService.getClass().getInterfaces(),
    handler
);
proxy.send("Hello");

限制目标类必须实现至少一个接口,代理对象的类型由接口列表决定-15

CGLIB动态代理

原理:通过字节码生成技术,在运行时动态生成目标类的子类来创建代理对象。CGLIB底层采用ASM字节码框架,对目标类的非final方法进行覆写增强-20

java
复制
下载
public class LogMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, 
                           MethodProxy proxy) throws Throwable {
        System.out.println("【前置】调用方法:" + method.getName());
        Object result = proxy.invokeSuper(obj, args);  // 调用父类原始方法
        System.out.println("【后置】方法执行完成");
        return result;
    }
}

// 使用方式(需引入cglib依赖)
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SmsServiceImpl.class);
enhancer.setCallback(new LogMethodInterceptor());
SmsServiceImpl proxy = (SmsServiceImpl) enhancer.create();
proxy.send("Hello");

限制:无法代理 final 类,也无法代理 finalprivate 方法-20

3.3 JDK动态代理 vs CGLIB动态代理:全维度对比

对比维度JDK动态代理CGLIB动态代理
实现方式接口代理子类代理
是否依赖接口必须有接口不需要接口
代理类生成ProxyGenerator.generateProxyClass()ASM字节码框架动态生成子类
类名格式$Proxy0$Proxy1类名$$EnhancerByCGLIB$$xxxx
性能特点调用成本低,生成成本低生成类成本高,调用快
final类/方法不可代理不可代理
Spring默认策略有接口时优先使用无接口时fallback使用

一句话总结:JDK动态代理是“对接口编程”的代理,CGLIB动态代理是“对类继承”的代理-34


四、概念关系与区别总结

理清以下三层关系,代理模式就彻底通透了:

第一层:代理模式 vs 静态/动态代理

  • 代理模式是设计思想(概念层)

  • 静态代理和动态代理是实现方式(技术层)

  • 静态代理在编译期确定代理关系,动态代理在运行期动态生成-2

第二层:静态代理的特点

  • 手动编写代理类,实现简单,代码清晰

  • 每个目标类需对应一个代理类 → 类数量膨胀

  • 接口变更时代理类需同步修改 → 违背开闭原则

  • 适用场景:目标类少、需求固定、性能要求高-72

第三层:动态代理的特点

  • 运行时动态生成代理类 → 代码复用率高、扩展性强

  • JDK代理要求接口,CGLIB代理无此限制

  • 动态代理广泛应用于AOP、过滤器、拦截器等框架设计-69

一句话高度概括:静态代理是为每个目标类手写一个“助理”,动态代理是通过反射/字节码技术自动生成“助理”;JDK是“按接口合同”生成助理,CGLIB是“认目标类当爹”生成助理。


五、代码示例完整演示

以“订单下单”业务场景为例,完整演示从静态代理到JDK动态代理的演进过程。

5.1 业务接口与实现

java
复制
下载
// 抽象主题:订单服务接口
public interface OrderService {
    void placeOrder(String userId, String productId);
}

// 真实主题:核心业务实现
public class OrderServiceImpl implements OrderService {
    @Override
    public void placeOrder(String userId, String productId) {
        System.out.println("核心业务:用户 " + userId + " 下单商品 " + productId);
    }
}

5.2 静态代理实现

java
复制
下载
public class OrderServiceStaticProxy implements OrderService {
    private final OrderService target;
    
    public OrderServiceStaticProxy(OrderService target) {
        this.target = target;
    }
    
    @Override
    public void placeOrder(String userId, String productId) {
        // 前置增强:权限校验 + 日志记录
        System.out.println("【静态代理前置】校验用户权限:" + userId);
        System.out.println("【静态代理前置】记录请求日志");
        
        target.placeOrder(userId, productId);  // 调用真实业务
        
        // 后置增强:发送通知
        System.out.println("【静态代理后置】发送下单成功通知");
    }
}

5.3 JDK动态代理实现

java
复制
下载
// 统一的增强处理器
public class LogAndAuthInvocationHandler implements InvocationHandler {
    private final Object target;
    
    public LogAndAuthInvocationHandler(Object target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("【动态代理前置】方法:" + method.getName());
        System.out.println("【动态代理前置】参数:" + Arrays.toString(args));
        
        Object result = method.invoke(target, args);
        
        System.out.println("【动态代理后置】执行完成");
        return result;
    }
}

// 使用方式
OrderService realService = new OrderServiceImpl();
OrderService proxy = (OrderService) Proxy.newProxyInstance(
    realService.getClass().getClassLoader(),
    realService.getClass().getInterfaces(),
    new LogAndAuthInvocationHandler(realService)
);
proxy.placeOrder("user001", "productA");

输出结果:

text
复制
下载
【动态代理前置】方法:placeOrder
【动态代理前置】参数:[user001, productA]
核心业务:用户 user001 下单商品 productA
【动态代理后置】执行完成

5.4 对比效果

对比项静态代理动态代理
代理类编写每个业务类单独写一个代理类一个InvocationHandler覆盖所有业务
接口变更代理类必须同步修改无需改动,InvocationHandler自动适配
代码量随业务增长线性增加固定不变
灵活度
性能直接调用反射调用略有开销

六、底层原理与技术支撑

6.1 JDK动态代理底层

JDK动态代理的核心在 Proxy.newProxyInstance() 方法。底层调用 ProxyGenerator.generateProxyClass(),在运行时动态生成标准Java字节码(符合JVM规范),输出实现指定接口的代理类,类名格式为 $Proxy0$Proxy1-20

代理类内部会为接口的每个方法生成对应实现,方法体中调用 InvocationHandler.invoke(),实现统一的拦截入口。所有被代理的方法调用最终都被“拦截”到 invoke 方法中。

6.2 CGLIB动态代理底层

CGLIB底层采用ASM字节码框架,直接在内存中逐指令构建类文件。它复制目标类的非final方法字节码,在开头和结尾插入拦截逻辑,并重写构造器,第一行强制调用父类构造器后将 Callback 实例塞进字段-20

生成的代理类是目标类的子类,类名包含 $$EnhancerByCGLIB$$ 标识。这也是为什么CGLIB无法代理 final 类的原因——Java的 final 类不允许被继承。

6.3 与主流框架的关联

代理模式是Spring AOP(Aspect Oriented Programming,面向切面编程)的底层核心支柱。Spring在容器初始化Bean时,判断是否需要AOP增强,若需要则通过动态代理生成代理对象替换原始Bean-34

  • 目标类实现了接口 → Spring默认使用JDK动态代理

  • 目标类没有实现接口 → Spring fallback到CGLIB代理

这也是面试中高频出现的考点——理解代理模式,就等于理解了Spring AOP的一半。


七、高频面试题与参考答案

Q1:静态代理和动态代理的区别是什么?

参考答案

  1. 生成时机不同:静态代理在编译期确定,代理类字节码在运行前就已存在;动态代理在运行期动态生成,编译后没有实际class文件-

  2. 代码量不同:静态代理需为每个目标类手动编写代理类;动态代理只需编写一个InvocationHandler即可代理任意目标对象

  3. 扩展性不同:静态代理接口变更时代理类需同步修改,违背开闭原则;动态代理自动适配,扩展性强

  4. 性能差异:静态代理性能更高(直接调用);动态代理涉及反射调用,有轻微性能损耗

Q2:JDK动态代理和CGLIB动态代理有什么区别?

参考答案

对比点JDK动态代理CGLIB动态代理
实现原理基于接口,生成实现接口的代理类基于继承,生成目标类的子类
接口要求目标类必须实现接口不要求接口
final类/方法不可代理不可代理
底层技术ProxyGenerator生成字节码ASM字节码框架动态生成子类
Spring默认有接口时优先无接口时fallback

Q3:为什么JDK动态代理只能代理接口?

参考答案
JDK动态代理基于 Proxy.newProxyInstance() 方法创建代理对象,该方法需要传入接口列表来定义代理对象的类型。代理对象内部的方法调用通过反射转发到 InvocationHandler.invoke(),这要求目标类必须有接口约束。如果要代理一个普通类,只能使用CGLIB这种基于继承的方案-15

Q4:Spring AOP默认使用哪种代理方式?

参考答案
Spring AOP根据目标类是否实现接口来决定:

  • 目标类实现了接口 → 默认使用JDK动态代理

  • 目标类没有实现接口 → 使用CGLIB代理
    通过配置 @EnableAspectJAutoProxy(proxyTargetClass = true) 可强制使用CGLIB代理-34

Q5:CGLIB能代理final类吗?为什么?

参考答案
不能。CGLIB通过生成目标类的子类来实现代理,而Java中的 final 类不允许被继承,因此CGLIB无法代理final类。同样,finalprivate 方法也无法被代理,因为子类无法覆写它们-20


八、结尾总结

回顾全文核心知识点:

  1. 代理模式定义:为其他对象提供一种代理,以控制对这个对象的访问

  2. 四大角色:Subject、RealSubject、Proxy、Client

  3. 静态代理:编译期确定,手动编写,简单但类膨胀

  4. JDK动态代理:运行期生成,基于接口,统一拦截入口

  5. CGLIB动态代理:运行期生成,基于继承,无接口要求

  6. 底层原理:JDK依赖 ProxyGenerator 生成字节码,CGLIB依赖ASM框架生成子类

  7. 框架应用:Spring AOP底层核心支撑,理解代理即理解AOP的一半

重点记忆:静态代理是“手动挡”,JDK动态代理是“接口适配自动挡”,CGLIB动态代理是“继承式自动挡”。面试中能清晰区分三者的原理和适用场景,并能说明Spring AOP的选择策略,就足以应对大部分代理模式相关提问。


下篇预告:本文将深入剖析代理模式与装饰器模式、适配器模式的区别——这三个结构型设计模式常被混淆,下一期将用对比表格和代码实例帮你彻底区分它们。

标签:

相关阅读