AI信息助手深度解析:Java代理模式(静态代理vs动态代理)

小编头像

小编

管理员

发布于:2026年04月21日

3 阅读 · 0 评论

更新时间:北京时间2026年4月10日

在日常开发中,你是否经常需要在核心业务方法前后加入日志打印、性能监控或权限校验?如果直接写在方法里,代码会迅速膨胀;如果使用Java代理模式这一经典设计思想,你就能在完全不修改原有代码的前提下优雅实现功能增强。本文将由AI信息助手带你从零理解代理模式的核心概念、静态与动态代理的区别,并通过代码示例和面试题,帮助你建立完整知识链路。

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

假设你有一个支付服务类,现在要为它添加方法耗时监控和操作日志。最直接的做法是在每个方法里塞进这些逻辑:

java
复制
下载
public class PaymentService {
    public void pay() {
        // 监控、限流、审计...(占比80%)
        long start = System.nanoTime();
        System.out.println("〖日志〗开始执行支付...");
        
        realPay();  // 核心业务(占比20%)
        
        System.out.println("〖日志〗支付完成");
        System.out.println("耗时:" + (System.nanoTime() - start) + " ns");
        // 事务提交、日志回收...(占比80%)
    }
    private void realPay() { / 核心支付逻辑 / }
}

这种方式存在三大问题:

  • 违背开闭原则:每次修改增强逻辑,都要改动业务代码,容易引入Bug-50

  • 代码重复度高:多个Service类都需要日志、监控时,每个类都要重复编写相同逻辑

  • 类膨胀严重:如果有100个Service接口,为了实现日志增强,你需要手写100个代理类-50

代理模式正是为解决这一问题而生的设计模式——它通过引入代理对象作为中间层,在不修改目标类代码的前提下,为核心业务添加增强功能-61

二、静态代理:编译期确定的“专属经纪人”

2.1 标准定义

静态代理(Static Proxy) 是指在编译期就已确定代理类与被代理类关系的代理实现方式。代理类和目标类需要实现相同的接口,代理类内部持有目标对象的引用,在接口方法中调用目标对象的相应方法-1

生活化类比:静态代理就像明星的 “专属经纪人” ,一个经纪人只服务一位明星,一对一绑定。客户找明星谈合作时,必须先通过经纪人——经纪人会处理合同、排期等事务,最后才把核心业务(演出)交给明星本人-2

2.2 代码示例

java
复制
下载
// 1. 定义业务接口(代理的契约)
public interface UserService {
    void addUser(String username);
    void deleteUser(String username);
}

// 2. 目标类:只负责核心业务
public class UserServiceImpl implements UserService {
    @Override
    public void addUser(String username) {
        System.out.println("数据库新增用户:" + username);
    }
    @Override
    public void deleteUser(String username) {
        System.out.println("数据库删除用户:" + username);
    }
}

// 3. 静态代理类:增强逻辑 + 委派调用
public class UserServiceProxy implements UserService {
    private final UserService target;  // 持有目标对象引用
    
    public UserServiceProxy(UserService target) {
        this.target = target;
    }
    
    @Override
    public void addUser(String username) {
        // 前置增强:日志记录
        System.out.println("〖日志〗开始执行addUser,参数:" + username);
        // 调用目标对象的核心方法
        target.addUser(username);
        // 后置增强
        System.out.println("〖日志〗addUser执行完成");
    }
    
    @Override
    public void deleteUser(String username) {
        System.out.println("〖日志〗开始执行deleteUser,参数:" + username);
        target.deleteUser(username);
        System.out.println("〖日志〗deleteUser执行完成");
    }
}

// 4. 使用
UserService service = new UserServiceProxy(new UserServiceImpl());
service.addUser("张三");

2.3 静态代理的致命缺陷

静态代理实现简单,但存在三大“死穴”-50-

缺陷说明
类膨胀每个目标类都需要一个专属代理类,100个Service就要写100个代理类
维护噩梦接口新增方法,所有实现类和代理类都要同步修改
重复劳动日志、监控等增强逻辑在每个代理类中重复编写

静态代理就像为每个工人配备专属监工——项目规模扩大后,监工比工人还多。这正是动态代理登场的直接原因。

三、JDK动态代理:运行时生成的“中介公司”

3.1 标准定义

JDK动态代理(JDK Dynamic Proxy) 是Java原生支持的动态代理技术,它利用反射机制在运行时动态生成代理类的字节码。代理类会继承java.lang.reflect.Proxy类并实现目标对象的所有接口,所有方法调用都会被转发到InvocationHandlerinvoke()方法中进行处理-16-

生活化类比:JDK动态代理就像一家 “正规中介公司” 。明星不需要提前安排专属经纪人,中介公司持有明星的“营业执照”(接口),能根据客户需求动态匹配,统一处理合同、排期等事务-32

3.2 核心组件

JDK动态代理依赖两个关键类-

  • InvocationHandler(调用处理器) :定义了代理对象被调用时的处理逻辑,核心方法是invoke()

  • Proxy(代理类) :提供newProxyInstance()静态方法,用于动态生成代理对象

3.3 代码示例

java
复制
下载
// 业务接口(JDK代理强制要求)
public interface OrderService {
    void createOrder(String orderId);
    void payOrder(String orderId);
}

// 目标类:实现接口,专注核心业务
public class OrderServiceImpl implements OrderService {
    @Override
    public void createOrder(String orderId) {
        System.out.println("创建订单:" + orderId);
    }
    @Override
    public void payOrder(String orderId) {
        System.out.println("支付订单:" + orderId);
    }
}

// 动态代理的调用处理器
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

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() + "] 开始执行");
        long start = System.nanoTime();
        
        // 通过反射调用目标对象的真实方法
        Object result = method.invoke(target, args);
        
        // 后置增强:性能统计
        System.out.println("〖日志〗方法执行耗时:" + (System.nanoTime() - start) + " ns");
        return result;
    }
}

// 使用动态代理
public class Main {
    public static void main(String[] args) {
        OrderService target = new OrderServiceImpl();
        
        OrderService proxy = (OrderService) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),      // 类加载器
            target.getClass().getInterfaces(),       // 要实现的接口数组
            new LogInvocationHandler(target)         // 调用处理器
        );
        
        // 通过代理对象调用方法 → 自动触发invoke()
        proxy.createOrder("ORD-001");
        proxy.payOrder("ORD-001");
    }
}

关键执行流程

  1. Proxy.newProxyInstance()在运行时动态生成代理类(类名如$Proxy0)的字节码并加载到JVM

  2. 调用代理对象的方法(如createOrder())时,会被内部统一转发给InvocationHandler.invoke()

  3. invoke()中可以通过method.invoke(target, args)反射调用目标对象的真实方法-14

3.4 为什么只能代理接口?

JDK动态代理只能代理实现了接口的类。原因在于:Proxy.newProxyInstance()生成的代理类会继承Proxy并实现指定的接口。由于Java不支持多重继承,代理类无法再继承其他类,因此目标类必须通过接口来定义行为-14

四、CGLIB动态代理:基于继承的“克隆人工厂”

4.1 标准定义

CGLIB(Code Generation Library) 是一个基于Java的开源代码生成库,它通过动态生成目标类的子类来实现代理。CGLIB底层依赖ASM字节码框架,能够在运行时为目标类生成一个子类,并重写其中的非final方法,在方法调用时进行拦截-24-

生活化类比:CGLIB就像一家 “高科技克隆人工厂” 。不管明星有没有营业执照,工厂直接提取明星的“DNA”(类结构),瞬间克隆出一个长得一模一样的“子类”。这个克隆人继承了明星的所有能力,并在做事前后自动加上日志、监控-32

4.2 核心组件

  • Enhancer:CGLIB的核心入口类,用于配置和生成代理对象

  • MethodInterceptor:方法拦截器接口,定义了拦截逻辑

4.3 代码示例

java
复制
下载
// 目标类:无需实现任何接口(但不能是final类)
public class PaymentService {
    public void pay(double amount) {
        System.out.println("执行支付:" + amount + "元");
    }
}

// 方法拦截器
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class LogMethodInterceptor implements MethodInterceptor {
    
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) 
            throws Throwable {
        // 前置增强
        System.out.println("〖日志〗方法 [" + method.getName() + "] 开始执行");
        long start = System.nanoTime();
        
        // 调用父类的原始方法(注意:是invokeSuper,不是method.invoke)
        Object result = proxy.invokeSuper(obj, args);
        
        // 后置增强
        System.out.println("〖日志〗执行耗时:" + (System.nanoTime() - start) + " ns");
        return result;
    }
}

// 使用CGLIB代理
public class Main {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(PaymentService.class);      // 设置目标类作为父类
        enhancer.setCallback(new LogMethodInterceptor());  // 设置回调拦截器
        
        PaymentService proxy = (PaymentService) enhancer.create();  // 生成代理对象
        proxy.pay(100.0);
    }
}

关键执行流程

  1. Enhancer.create()使用ASM在运行时动态生成目标类的子类(类名如PaymentService$$EnhancerByCGLIB$$xxx

  2. 该子类重写所有非final方法,在方法体中调用MethodInterceptor.intercept()

  3. intercept()中通过MethodProxy.invokeSuper()调用父类的原始方法-22

五、概念关系与核心区别总结

5.1 逻辑关系一句话概括

代理模式是“思想”,静态代理是“手工实现”,动态代理是“自动化实现”;JDK代理是“接口驱动的动态代理”,CGLIB是“继承驱动的动态代理” -61

5.2 三种代理方式对比表

对比维度静态代理JDK动态代理CGLIB动态代理
代理类生成时机编译期运行时运行时
依赖条件目标类需实现接口目标类必须实现接口目标类不能是final类
核心原理手动编写代理类反射 + ProxyASM字节码生成子类
代码侵入需为每个类写代理无侵入无侵入
代理类数量N个目标类 → N个代理类1个Handler → N个代理对象1个Interceptor → N个代理对象
方法调用性能直接调用(最快)反射调用(略慢)字节码调用(快)
代理类创建速度编译时完成快(无需生成字节码)慢(需ASM生成字节码)
能否代理final方法N/A(接口无final方法)否,且final类完全无法代理
能否代理无接口类是(需实现同一接口)
额外依赖需cglib依赖(Spring已内置)

5.3 性能差异说明

JDK动态代理在代理对象创建时较快(无需生成字节码),但方法调用时通过反射执行,有一定性能开销;CGLIB在代理对象创建时较慢(需ASM生成字节码并加载),但方法调用时直接操作字节码,执行效率更高-31。不过在JDK 8及以上版本中,两种方式的性能差距已显著缩小-

六、底层原理与技术支撑

6.1 JDK动态代理的底层依赖

JDK动态代理的核心底层依赖是 Java反射机制(Reflection) -

  • Proxy.newProxyInstance()通过ProxyGenerator.generateProxyClass()在运行时拼装代理类的字节码

  • 生成的代理类(如$Proxy0)会继承Proxy类,并实现指定的接口

  • 代理类中的每个方法体都是一个固定模板:super.h.invoke(this, mXX, args)——所有调用统一转发到InvocationHandler.invoke()

  • method.invoke(target, args)则利用反射在运行时动态调用目标对象的真实方法-15

动态代理的“动态”本质:代理类不是在编译期写死的,而是由JVM在运行时根据接口定义动态生成字节码并加载,因此一个InvocationHandler可以为任意多个目标类提供服务。

6.2 CGLIB的底层原理

CGLIB底层依赖 ASM字节码操作框架,直接操作JVM的字节码指令-24

  • 通过ASM为目标类动态生成一个子类,并重写所有非final方法

  • 在重写的方法体中,插入对MethodInterceptor.intercept()的调用逻辑

  • CGLIB通过 FastClass机制 使用方法索引替代反射调用,进一步提升运行时性能-24

  • 生成代理类时会包含三份字节码文件:代理类本身、FastClass索引类等

提示:底层原理的深入理解是面试中的加分项,本文作为入门教程不展开源码细节,后续进阶内容会深入分析字节码生成流程和JVM类加载机制。

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

面试题1:请简述静态代理和动态代理的区别?

参考答案

  • 静态代理:代理类在编译期就已确定,需要手动编写,代理类与目标类一一对应。优点是实现简单,缺点是类膨胀严重、维护成本高-

  • 动态代理:代理类在运行期由JVM动态生成,无需手动编写代理类代码。一个动态代理处理器可以为任意多个目标类提供代理服务-49

  • 一句话概括:静态代理是“手工打造”,动态代理是“3D打印”

面试题2:JDK动态代理和CGLIB有什么区别?各自适用于什么场景?

参考答案

维度JDK动态代理CGLIB
原理基于接口,代理类实现目标接口基于继承,代理类是目标类的子类
依赖目标类必须实现接口目标类不能是final类
创建速度慢(需ASM生成字节码)
调用性能反射调用,略慢字节码调用,较快

适用场景

  • 目标类已实现接口 → 优先用JDK代理(更简洁,符合面向接口编程)

  • 目标类无接口或需要代理内部方法 → 用CGLIB(功能更强大)-31-

面试题3:Spring AOP默认使用哪种动态代理?为什么?

参考答案

  • Spring Framework(Spring 3.2+) :默认优先使用JDK动态代理。当目标对象实现了接口时用JDK代理,没有实现接口时自动切换为CGLIB-

  • Spring Boot 2.x+ :将默认值改为了CGLIB-

  • 选择原因:JDK代理是Java原生支持、无额外依赖;CGLIB可以代理无接口的类,覆盖更广的使用场景

面试题4:JDK动态代理为什么只能代理有接口的类?

参考答案
因为Proxy.newProxyInstance()生成的代理类必须继承java.lang.reflect.Proxy类。由于Java是单继承的,代理类无法再继承其他类。因此目标类必须通过接口来定义行为,代理类通过实现相同接口来替代目标对象-14。如果传入一个没有实现接口的类,会直接抛出IllegalArgumentException

面试题5:CGLIB能代理final类或final方法吗?为什么?

参考答案
不能。 CGLIB基于继承实现代理——它会动态生成目标类的子类,通过重写非final方法来添加增强逻辑。

  • 如果目标类是final类,无法被继承,会抛出IllegalArgumentException: Cannot subclass final class

  • 如果方法是final方法,子类无法重写,因此无法被拦截和增强-22-31

八、结尾总结

本文围绕Java代理模式这一核心知识点,按“问题驱动 → 概念讲解 → 代码演示 → 原理分析 → 面试考点”的递进逻辑,系统梳理了:

知识点核心要点
静态代理编译期确定,一对一的“专属经纪人”,简单但类膨胀严重
JDK动态代理运行时生成,依赖接口,基于反射,1个Handler服务N个类
CGLIB动态代理运行时生成,依赖继承,基于ASM字节码,无法代理final类/方法
面试考点三种方式的区别、适用场景、Spring AOP的默认策略

一句话考点记忆

  • 静态代理 = 手写代理类,一对一的“专属经纪人”

  • JDK代理 = 反射+接口,一对多的“中介公司”

  • CGLIB代理 = ASM+继承,无接口也能干的“克隆人工厂”

下一篇预告:本文重点讲解了代理模式的概念、区别和代码示例。下一篇文章将深入代理模式的底层实现——从ProxyGenerator的字节码生成原理到CGLIB的ASM源码剖析,带你彻底搞懂“动态”二字背后的JVM机制。欢迎持续关注AI信息助手的系列教程!

标签:

相关阅读