首页 维修案例文章正文

INAIR AI助手 手把手教你搞懂Java动态代理:静态代理 vs 动态代理

维修案例 2026年04月28日 14:00 5 小编

2026年4月8日 星期三 | 知识科普 · 底层原理 · 高频考点

开篇引入:动态代理是Java开发中不可或缺的核心技术,它不仅是Spring AOP的底层基石,更是各大厂面试中的必考知识点。许多开发者虽然每天都在使用Spring框架,却对“AOP到底是怎么实现方法拦截的”一头雾水——会用但不懂原理,是学习路上的常见痛点。本文将用通俗的语言、清晰的代码示例,从静态代理的局限讲起,逐步深入JDK动态代理与CGLIB的实现原理,最后梳理面试高频考题,帮你打通“理解-应用-应试”的完整知识链路。

一、痛点切入:为什么需要动态代理?

在日常开发中,我们经常遇到这样的场景:想给Service层的每个业务方法加上日志打印,或者给支付接口加上权限校验,却不想在每个方法里重复编写相同的代码。

用原始方式实现日志记录:

java
复制
下载
public class UserServiceImpl implements UserService {
    @Override
    public void addUser(String username) {
        // 日志记录——重复代码
        System.out.println("〖日志〗开始执行addUser,参数:" + username);
        // 核心业务逻辑
        System.out.println("数据库新增用户:" + username);
        // 日志记录——重复代码
        System.out.println("〖日志〗addUser执行完毕");
    }
    
    @Override
    public void deleteUser(String username) {
        // 同样的日志代码又要写一遍……
        System.out.println("〖日志〗开始执行deleteUser,参数:" + username);
        System.out.println("数据库删除用户:" + username);
        System.out.println("〖日志〗deleteUser执行完毕");
    }
}

这种实现方式存在明显缺点:代码冗余严重、扩展性差、维护成本高——每个方法都要重复编写日志逻辑,一旦日志格式需要变更,所有方法都要逐一修改,极易出错-2

代理模式正是为了解决这一矛盾而诞生的。它通过引入代理对象作为目标对象的“中间层”,在调用目标方法前后插入附加逻辑,实现核心业务与横切关注点的解耦,符合设计模式的开闭原则-21

二、静态代理:最基础的实现方式

2.1 什么是静态代理?

静态代理(Static Proxy)是最基础的代理实现方式。其核心特点是代理类在编译期就已确定,与目标类一一对应——就像为某个明星配备的“专属经纪人”,只服务这一个对象-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. 静态代理类:为UserServiceImpl附加日志功能
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执行完毕");
    }
}

// 使用方式
public class Client {
    public static void main(String[] args) {
        UserService target = new UserServiceImpl();
        UserService proxy = new UserServiceProxy(target);
        proxy.addUser("张三");
    }
}

2.3 静态代理的缺点

静态代理虽然解决了核心业务与增强逻辑的分离问题,但存在明显局限:一个代理类只能服务一个目标类。假设项目中有100个Service类需要添加日志功能,就需要编写100个代理类,代码量爆炸式增长,维护成本极高-39

三、动态代理:运行时生成的“万能代理”

3.1 什么是动态代理?

动态代理(Dynamic Proxy)是一种在运行时动态创建代理对象的机制。与静态代理不同,它无需为每个目标类手动编写代理类,而是通过反射机制在程序运行期间自动生成代理类的字节码,一个动态代理类可以为任意多个真实类提供代理服务-39-6

核心关键词拆解:

  • 动态:代理类的生成时机在“运行时”而非“编译期”

  • 代理:充当目标对象的“中间人”,控制方法访问

  • 反射:底层依赖Java反射机制,实现运行时类型信息获取和方法调用

3.2 生活化类比

打个比方,你要租房——自己找房源既麻烦又耗时,这时候找个房产中介(相当于代理),中介统一帮你处理找房、看房、签约等事宜。Java动态代理就是这样一个“万能中介”:它不只为某一个租客服务,而是可以在运行时为任意租客动态创建代理对象-6

3.3 JDK动态代理的实现方式

JDK动态代理是Java标准库自带的功能,要求目标类必须实现一个或多个接口。核心依赖两个关键类:java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler-11

JDK动态代理实现示例:

java
复制
下载
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 1. 定义业务接口
public interface UserService {
    void addUser(String username);
}

// 2. 目标类实现接口
public class UserServiceImpl implements UserService {
    @Override
    public void addUser(String username) {
        System.out.println("数据库新增用户:" + username);
    }
}

// 3. 实现InvocationHandler接口,定义增强逻辑
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() + ",参数:" + args[0]);
        // 通过反射调用目标方法
        Object result = method.invoke(target, args);
        // 后置增强
        System.out.println("〖日志〗" + method.getName() + "执行完毕");
        return result;
    }
}

// 4. 使用动态代理
public class DynamicProxyDemo {
    public static void main(String[] args) {
        UserService target = new UserServiceImpl();
        
        UserService proxy = (UserService) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),  // 类加载器
            target.getClass().getInterfaces(),   // 目标类实现的接口
            new LogInvocationHandler(target)     // 调用处理器
        );
        
        proxy.addUser("张三");
    }
}

执行流程解析:

  1. 调用 Proxy.newProxyInstance() 方法,传入类加载器、接口数组和 InvocationHandler

  2. JVM在内存中动态生成代理类的字节码(类名通常为 $Proxy0),该代理类实现了指定的接口-6

  3. 代理对象的方法被调用时,实际执行的是 InvocationHandler.invoke() 方法中的逻辑

  4. invoke() 中,通过反射调用目标对象的原始方法,并在调用前后插入增强逻辑

四、CGLIB动态代理:无接口的解决方案

4.1 什么是CGLIB?

CGLIB(Code Generation Library)是一个高性能的字节码生成库,它通过继承目标类的方式生成代理子类,因此不需要目标类实现接口,但无法代理 final 类或 final 方法-11

4.2 JDK动态代理 vs CGLIB

对比维度JDK动态代理CGLIB动态代理
代理方式基于接口基于继承(子类)
目标类要求必须实现接口无需接口,但不能是final类
底层技术反射 + ProxyASM字节码增强
生成速度较快较慢
执行性能JDK8之前较慢,JDK8之后差距缩小更快(直接调用 vs 反射调用)
适用场景接口编程场景无接口类代理

JDK动态代理基于接口,通过 Proxy.newProxyInstance() 生成代理类;CGLIB基于ASM字节码生成工具,通过继承目标类生成代理子类-16。Spring AOP正是根据目标类是否实现了接口来自动选择代理方式的:有接口则优先使用JDK动态代理,否则使用CGLIB-11

CGLIB实现示例:

java
复制
下载
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sg.cglib.proxy.MethodProxy;

// 目标类:无需实现接口
public class UserService {
    public void addUser(String username) {
        System.out.println("数据库新增用户:" + username);
    }
}

// 实现MethodInterceptor接口
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("〖日志〗" + method.getName() + "执行完毕");
        return result;
    }
}

// 使用CGLIB动态代理
public class CglibDemo {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserService.class);
        enhancer.setCallback(new LogMethodInterceptor());
        UserService proxy = (UserService) enhancer.create();
        proxy.addUser("张三");
    }
}

五、概念关系总结:一张图理解代理体系

text
复制
下载
代理模式(设计思想)

    ├── 静态代理(编译期实现)
    │     └── 为每个目标类手动编写代理类 → 代码冗余、扩展性差

    └── 动态代理(运行时实现)
            ├── JDK动态代理:基于接口 + 反射
            └── CGLIB:基于继承 + 字节码增强

一句话概括:静态代理是“写死的中间人”,动态代理是“自动生成的万能中间人” -4

六、底层原理:反射与字节码

JDK动态代理的底层核心是 Java反射机制。当调用 Proxy.newProxyInstance() 时,JVM做了三件事:

  1. 生成代理类字节码:根据传入的接口信息,在内存中动态生成代理类的字节码

  2. 加载代理类:通过类加载器将生成的字节码加载到JVM中

  3. 创建代理对象:通过反射创建代理对象的实例-6

反射机制使得Java程序可以在运行时获取类的信息并动态操作对象,这正是动态代理能够“动态”生成代理类的底层支撑-30

CGLIB则更进一步,使用ASM框架直接操作字节码生成目标类的子类,实现了无需接口的代理能力-4

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

面试题1:静态代理和动态代理有什么区别?

参考答案:

  1. 生成时机不同:静态代理的代理类在编译期就已确定,动态代理的代理类在运行时动态生成

  2. 代码量不同:静态代理需要为每个目标类手动编写代理类,动态代理只需一套增强逻辑即可为任意目标类生成代理

  3. 灵活性不同:静态代理扩展性差,接口新增方法需同步修改代理类;动态代理更灵活,可动态改变代理行为-39

面试题2:JDK动态代理和CGLIB有什么区别?Spring AOP默认使用哪种?

参考答案:

区别点JDK动态代理CGLIB
实现原理基于接口,通过反射基于继承,通过ASM字节码生成
目标类要求必须实现接口无需接口,但不能是final类
执行性能JDK8后与CGLIB差距缩小执行速度更快
生成速度较快较慢

Spring AOP默认根据目标类是否实现接口自动选择:有接口则使用JDK动态代理,无接口则使用CGLIB-11。可通过配置强制使用CGLIB。

面试题3:动态代理的“动态”体现在哪里?AOP底层是如何实现的?

参考答案:

“动态”体现在:代理类在运行时生成,而非编译期硬编码。AOP的底层实现依赖动态代理,核心流程是:定义横切逻辑(InvocationHandler/MethodInterceptor),运行时通过Proxy/CGLIB动态生成代理对象,方法调用时被拦截并执行增强逻辑-37

面试题4:CGLIB为什么不能代理final类?

参考答案:

CGLIB通过继承目标类来生成代理子类。如果目标类是 final 类,Java不允许被继承,因此CGLIB无法为其生成代理子类。同理,final 方法无法被子类重写,也无法被代理-11

八、结尾总结

本文核心知识点回顾:

知识点核心结论
静态代理编译期确定,一对一服务,代码冗余但实现简单
JDK动态代理运行时生成,基于接口+反射,需实现InvocationHandler
CGLIB运行时生成,基于继承+字节码,可代理无接口类
核心底层反射 + 字节码增强
应用场景Spring AOP、事务管理、日志记录、权限控制

易错提醒:

  • 不要混淆JDK和CGLIB的适用场景——JDK要求目标类必须有接口

  • CGLIB无法代理 final 类和 final 方法

  • 动态代理的性能开销主要集中在代理生成阶段,对单例对象影响较小

预告:

下一期我们将深入 Spring AOP源码剖析,从 JdkDynamicAopProxyCglibAopProxy 的源码层面,彻底搞清楚框架是如何自动化完成代理创建的,敬请期待!

上海羊羽卓进出口贸易有限公司 备案号:沪ICP备2024077106号