梦幻AI助手|Java代理模式从入门到面试,一篇文章全搞定(2026年4月9日)

小编头像

小编

管理员

发布于:2026年05月09日

4 阅读 · 0 评论

梦幻AI助手为技术学习者提供全方位的编程知识支持。本文由梦幻AI助手整理完成,带你彻底搞懂Java代理模式——从“只会用”到“懂原理能面试”。

做Java开发时,你是否经常遇到这样的场景:想给Service层的每个业务方法都加上日志打印,结果每个方法里都塞满了log.info(...);想给支付接口做权限校验,又怕直接修改原有逻辑引入Bug;甚至想在不改动老代码的前提下,偷偷给旧系统加个性能监控——这些“在不入侵原有代码的前提下给业务逻辑附加额外功能”的需求,其实都在呼唤同一个解决方案:代理模式

代理模式是Java开发中必学必会的核心知识点,也是Spring AOP、MyBatis Mapper、Dubbo RPC等主流框架的底层基石。然而很多开发者在实际工作中虽然天天用,却说不出其本质原理,面试时更是“话到嘴边说不出”。本文将从实际问题出发,由浅入深带你搞懂代理模式,掌握静态代理、JDK动态代理、CGLIB动态代理三大实现方式的原理、差异和适用场景。


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

先看一个典型场景。假设你有一个用户服务,需要在方法执行前后打印日志:

java
复制
下载
// 目标类:专注核心业务
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);
    }
}

传统做法——直接在业务方法内部混入日志代码:

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执行完成");
    }
}

这么做的问题很明显

  • 代码冗余:每个方法都要重复写日志代码

  • 耦合度高:日志逻辑与业务逻辑强耦合,修改日志格式需要改动所有方法

  • 维护困难:如果需要切换日志框架,改动量巨大

  • 违背开闭原则:对扩展开放,但对修改没有封闭

代理模式正是为解决这一矛盾而生的设计模式-1。它通过在客户端和目标对象之间引入一个“中间人”——代理对象,在不修改目标类代码的前提下,为方法调用附加额外逻辑。


二、核心概念讲解:静态代理

什么是静态代理?

静态代理(Static Proxy) :代理类的代码在项目编译阶段就已确定,与目标类的字节码一同存在于最终产物中,需为每个被代理类手动编写对应的代理类-

生活化类比

静态代理就像明星的“专属经纪人” 。一个经纪人只服务一个明星,负责处理行程安排、对外沟通等事务,明星只需专注于唱歌演戏本身-

静态代理实现步骤

静态代理的核心是代理类和目标类实现相同的接口,代理类持有目标类的引用,在调用方法时插入增强逻辑-1

步骤1:定义业务接口(目标类与代理类的“契约”)

java
复制
下载
public interface UserService {
    void addUser(String username);
    void deleteUser(String username);
}

步骤2:实现目标类(真正“干活”的对象)

java
复制
下载
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:实现代理类(附加功能的“中间人”)

java
复制
下载
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:客户端使用

java
复制
下载
public class Client {
    public static void main(String[] args) {
        UserService target = new UserServiceImpl();
        UserService proxy = new UserServiceProxy(target);
        proxy.addUser("张三");  // 实际调用的是代理对象的方法
    }
}

运行结果

text
复制
下载
〖日志〗开始执行addUser,参数:张三
数据库新增用户:张三
〖日志〗addUser执行完成

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

什么是动态代理?

动态代理(Dynamic Proxy) :代理类并非提前编译好,而是在Java程序运行过程中,根据实际业务需求动态创建并加载,无需手动编写代理类代码-

动态代理通过在运行时动态生成代理类,极大提升了灵活性。一句话总结:静态代理是“编译期绑定”,动态代理是“运行期生成”。

生活化类比

动态代理就像一个“万能中介平台” (比如链家)。你不需要为每个房源单独写一套代理代码,平台会根据你的需求动态匹配房源并处理所有交易流程。

Java动态代理的两种实现方式

1. JDK动态代理

JDK动态代理:基于Java原生的java.lang.reflect.Proxy类和InvocationHandler接口实现,要求目标类必须实现至少一个接口-

JDK动态代理核心三要素
  • 接口:目标对象必须实现的接口,定义了可被代理的方法

  • InvocationHandler:方法调用处理器,拦截所有代理方法调用,在其中实现增强逻辑

  • Proxy:JDK提供的工具类,通过newProxyInstance()方法生成代理对象-12

JDK动态代理代码示例

目标接口和实现类

java
复制
下载
public interface UserService {
    void addUser(String username);
    void deleteUser(String username);
}

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);
    }
}

实现InvocationHandler(自定义调用处理器)

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

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() + ",参数:" + java.util.Arrays.toString(args));
        // 通过反射调用目标对象的方法
        Object result = method.invoke(target, args);
        // 后置增强
        System.out.println("〖日志〗方法执行完成,返回值:" + result);
        return result;
    }
}

生成代理对象并调用

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

public class JdkProxyDemo {
    public static void main(String[] args) {
        // 1. 创建真实目标对象
        UserService target = new UserServiceImpl();
        
        // 2. 创建InvocationHandler
        LogInvocationHandler handler = new LogInvocationHandler(target);
        
        // 3. 通过Proxy生成代理对象
        // 参数说明:
        // - loader:类加载器,使用目标类的类加载器
        // - interfaces:目标类实现的接口列表
        // - h:InvocationHandler实例
        UserService proxy = (UserService) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            handler
        );
        
        // 4. 调用代理对象的方法
        proxy.addUser("李四");
        proxy.deleteUser("王五");
    }
}

运行结果

text
复制
下载
〖日志〗调用方法:addUser,参数:[李四]
数据库新增用户:李四
〖日志〗方法执行完成,返回值:null
〖日志〗调用方法:deleteUser,参数:[王五]
数据库删除用户:王五
〖日志〗方法执行完成,返回值:null

2. CGLIB动态代理

CGLIB动态代理:通过第三方CGLIB库生成字节码,在内存中动态生成目标类的子类作为代理类,无需目标类实现接口,可以代理普通类-

📌 注意:CGLIB无法代理被final修饰的类和方法(因为无法继承重写)。

CGLIB动态代理核心原理
  • 通过ASM字节码操作框架,在运行时动态生成目标类的子类

  • 在子类中重写所有非final的方法,将调用委托给MethodInterceptor拦截器

  • 在拦截器的intercept()方法中插入增强逻辑-

CGLIB动态代理代码示例

目标类(无需实现接口)

java
复制
下载
public class UserService {
    public void addUser(String username) {
        System.out.println("数据库新增用户:" + username);
    }
    
    public void deleteUser(String username) {
        System.out.println("数据库删除用户:" + username);
    }
}

实现MethodInterceptor

java
复制
下载
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() + ",参数:" + java.util.Arrays.toString(args));
        // 调用父类(目标类)的原始方法
        Object result = proxy.invokeSuper(obj, args);
        // 后置增强
        System.out.println("〖日志〗方法执行完成,返回值:" + result);
        return result;
    }
}

生成CGLIB代理对象

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

public class CglibProxyDemo {
    public static void main(String[] args) {
        // 1. 创建Enhancer(CGLIB核心类)
        Enhancer enhancer = new Enhancer();
        // 2. 设置父类(即目标类)
        enhancer.setSuperclass(UserService.class);
        // 3. 设置回调(MethodInterceptor)
        enhancer.setCallback(new LogMethodInterceptor());
        // 4. 创建代理对象
        UserService proxy = (UserService) enhancer.create();
        
        // 5. 调用代理对象的方法
        proxy.addUser("赵六");
        proxy.deleteUser("钱七");
    }
}

四、概念关系与区别总结

静态代理 vs 动态代理

对比维度静态代理动态代理
生成时机编译期手动编写,生成.class文件运行期动态生成,无物理.class文件-
代码量每个目标类需要一个代理类,代码冗余一个代理类可服务多个目标类,代码复用性强-3
灵活性接口变更需同步修改代理类,扩展性差无需手动修改,自动适配接口变更-
性能直接调用,性能略优有反射/字节码操作开销,但JDK8+已大幅优化-39
适用场景目标对象少、代理逻辑固定、对性能要求极高目标对象多、需通用增强逻辑(日志/事务/权限)、框架底层

JDK动态代理 vs CGLIB动态代理

对比维度JDK动态代理CGLIB动态代理
代理方式基于接口基于继承(生成子类)-31
目标类要求必须实现接口无需接口,但不能是final类
底层技术反射 + ProxyASM字节码增强-31
方法限制只能代理接口中定义的方法无法代理final和static方法-31
性能表现JDK8+优化后性能优于CGLIBJDK8以下版本性能略优-39
依赖JDK原生,无需额外依赖需要引入CGLIB库

🎯 一句话记忆:静态代理是“一对一”提前写好,动态代理是“一对多”运行时生成;JDK代理针对接口,CGLIB代理针对普通类。


五、底层原理探析

JDK动态代理底层原理

JDK动态代理的核心是反射机制 + 字节码动态生成。当调用Proxy.newProxyInstance()时,JVM会执行以下步骤-27

  1. 动态生成字节码:根据传入的接口数组,在内存中动态生成一个实现这些接口的代理类(通常命名为$Proxy0$Proxy1等)

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

  3. 实例化代理对象:通过反射获取代理类的构造方法,传入InvocationHandler实例,创建代理对象

生成的代理类大致如下所示:

java
复制
下载
public final class $Proxy0 extends Proxy implements UserService {
    private static Method m1;
    
    public $Proxy0(InvocationHandler h) {
        super(h);  // 将InvocationHandler存入父类Proxy中
    }
    
    @Override
    public void addUser(String username) {
        try {
            // 调用InvocationHandler的invoke方法
            super.h.invoke(this, m1, new Object[]{username});
        } catch (RuntimeException | Error e) {
            throw e;
        } catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }
}

底层支撑是Java的反射机制——Method.invoke()通过JVM内部的Klass元数据结构找到目标方法的入口地址并执行-27

CGLIB动态代理底层原理

CGLIB的底层是ASM字节码操作框架。核心步骤-32

  1. 生成子类:通过ASM在内存中动态生成目标类的子类

  2. 重写方法:在子类中重写所有非final的方法

  3. 拦截调用:被重写的方法内部调用MethodInterceptor.intercept(),执行增强逻辑

  4. FastClass优化:CGLIB生成FastClass类,通过方法索引直接调用目标方法,避免反射带来的性能损耗-34


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

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

参考答案

代理模式是一种结构型设计模式,通过引入一个代理对象来控制对目标对象的访问,在不修改目标对象源码的前提下为其添加额外功能-38

静态代理与动态代理的核心区别:

  • 生成时机:静态代理在编译期手动编写代理类;动态代理在运行期动态生成代理类

  • 代码量:静态代理需为每个目标类编写代理类,代码冗余;动态代理一个代理类可服务多个目标类

  • 灵活性:静态代理灵活性差,接口变更需同步修改;动态代理灵活性强,自动适配变更-

踩分点:说出“编译期 vs 运行期”、“手动编写 vs 动态生成”、“一对一 vs 一对多”即可得高分。

面试题2:JDK动态代理和CGLIB动态代理有什么区别?Spring AOP如何选择?

参考答案

区别维度JDK动态代理CGLIB动态代理
代理方式基于接口基于继承(生成子类)
目标类要求必须实现接口无需接口,但不能是final类
底层技术反射 + ProxyASM字节码增强
性能JDK8+优化后性能更好JDK8以下版本性能更优

Spring AOP的选择策略:默认情况下,如果目标类实现了接口,则使用JDK动态代理;如果目标类没有实现接口,则自动切换为CGLIB动态代理。也可通过<aop:config proxy-target-class="true">强制使用CGLIB-39

踩分点:说清“接口 vs 继承”的核心差异,以及Spring的“自动选择”策略。

面试题3:动态代理在Spring框架中有哪些实际应用?

参考答案

动态代理是Spring AOP的底层核心,主要应用场景包括:

  1. 声明式事务管理:在业务方法执行前自动开启事务,执行后提交事务,出现异常时回滚——开发者无需编写任何事务控制代码-39

  2. 统一日志记录:通过代理在方法执行前后记录入参、耗时、返回值,避免在每个业务方法中重复编写日志代码-39

  3. 权限校验拦截:在核心业务方法执行前校验用户权限,无权限则直接拒绝执行-39

  4. 性能监控:统计方法执行时间,为性能优化提供数据支撑

踩分点:举例说明即可,答出“事务管理”和“日志记录”两个典型场景基本过关。

面试题4:动态代理的底层实现原理是什么?

参考答案

  • JDK动态代理:基于反射机制,通过Proxy.newProxyInstance()在运行时动态生成实现指定接口的代理类字节码,所有方法调用都被转发给InvocationHandler.invoke(),通过Method.invoke()反射调用目标方法-27

  • CGLIB动态代理:基于ASM字节码增强,在运行时生成目标类的子类,重写所有非final方法,方法调用被MethodInterceptor.intercept()拦截,通过MethodProxy.invokeSuper()调用父类原始方法-32

踩分点:说出“反射 + Proxy生成”和“ASM + 继承子类”两个核心原理即可。

面试题5:为什么JDK动态代理只能代理接口?

参考答案

JDK动态代理生成的代理类$Proxy0已经继承了java.lang.reflect.Proxy。由于Java是单继承的,代理类无法再继承其他类,因此只能通过实现接口的方式来代理目标对象。如果要代理普通的类(没有实现接口),就需要使用CGLIB这种基于继承的方案-38

踩分点:点出“单继承”和“已继承Proxy类”两个关键词。


七、结尾总结

本文从代码痛点出发,系统梳理了Java代理模式的完整知识体系:

  • 静态代理:编译期确定,代码简单但冗余,适合目标对象少的场景

  • JDK动态代理:运行期生成,基于接口+反射,Spring AOP的默认选择

  • CGLIB动态代理:运行期生成,基于继承+字节码增强,可代理普通类

  • 底层原理:JDK依赖反射,CGLIB依赖ASM字节码增强

  • 面试要点:掌握三种代理的对比、底层原理、Spring中的应用场景

💡 学习建议:理论理解后,建议手动敲一遍本文的代码示例,亲自感受静态代理和动态代理的运行差异。只有代码跑起来了,面试时才不会“话到嘴边说不出”。

📚 下一期预告:深入Spring AOP源码,看代理模式如何实现“一键增强”,敬请期待!


本文由梦幻AI助手整理撰写,致力于为技术学习者提供高质量的知识服务。如果你觉得有帮助,欢迎点赞收藏,也欢迎在评论区留言讨论!

标签:

相关阅读