my助手ai深度拆解:一文吃透Java代理模式(2026.04.09)

小编头像

小编

管理员

发布于:2026年04月28日

16 阅读 · 0 评论

在日常开发中,你是否遇到过这样的场景:想给Service层的核心业务方法加日志,却不想在每个方法里重复写log.info(...);想给支付接口加权限校验,又怕修改原有逻辑引入bug;甚至想在不改动老代码的前提下,悄悄给旧系统加个性能监控?这些“不侵入原有代码的前提下给业务附加功能”的需求,其实都在呼唤同一个解决方案——代理模式(Proxy Pattern) 。本文将通过 my助手ai 的视角,从痛点切入到原理剖析,再到代码实战和面试要点,帮你彻底吃透这个面试必考、开发必用的核心知识点。


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

先看一个传统写法。假设要给TaskService的方法加执行时间统计:

java
复制
下载
public class TaskService {

public void dealTask(String taskName) { long startTime = System.currentTimeMillis(); // 统计逻辑 System.out.println("执行任务:" + taskName); // 核心业务 long cost = System.currentTimeMillis() - startTime; // 统计逻辑 System.out.println("耗时:" + cost + "ms"); } }

痛点一目了然:

  • 耦合严重:统计逻辑与业务逻辑混在一起,修改统计规则就要改业务代码-64

  • 代码冗余:每个需要统计的方法都要重复写时间记录代码

  • 职责混乱:一个方法同时负责“业务执行”和“性能统计”,违背单一职责原则

代理模式的解决方案就是引入“中间人”——代理对象负责附加功能,目标对象专注核心业务。这样一来,附加功能与业务逻辑彻底解耦-64


二、核心概念:什么是代理模式?

代理模式(Proxy Pattern) 是一种结构型设计模式,通过引入一个代理对象来控制对原始对象的访问,在不修改原始类代码的前提下增强其功能-11

生活化类比:明星和经纪人的关系。明星只负责演戏(核心业务),经纪人负责接洽、合同、公关等杂事(附加功能)。外部只接触经纪人,不直接接触明星。

代理模式的三要素

角色说明
抽象主题(Subject)接口,定义目标对象和代理对象的共同行为
真实主题(RealSubject)目标对象,实现核心业务逻辑
代理类(Proxy)持有目标对象引用,负责附加功能

三、静态代理:最简单的“专属中间人”

标准定义

静态代理(Static Proxy) 的代理类代码在项目编译阶段就已生成,与目标类的字节码一同存在于最终产物中,需手动编写代理类-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执行完毕");
    }
}

优缺点分析

优点缺点
符合开闭原则,不修改目标对象类爆炸:每个目标类都需要对应代理类-11
编译期类型安全接口耦合:接口新增方法,代理类必须同步修改
职责清晰,辅助逻辑集中在代理类代码冗余,维护成本高

一句话总结:静态代理简单直接,但“一个萝卜一个坑”,代码量翻倍。


四、动态代理:一劳永逸的“万能中间人”

标准定义

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

概念对比

对比维度静态代理动态代理
生成时机编译期运行期
是否需要手写代理类需要不需要
代码量每个目标类一个代理类一个代理工厂服务所有类
灵活性

一句话概括两者关系:静态代理是“思想”(谁来代理),动态代理是“实现手段”(如何生成代理)。静态代理手动编码实现代理模式的思想;动态代理在运行期自动生成代理类,是实现代理模式的技术手段-1


五、JDK动态代理(基于接口)

核心机制

JDK动态代理基于Java反射机制,核心依赖java.lang.reflect.Proxy类和InvocationHandler接口-22。运行时通过Proxy.newProxyInstance()动态生成代理类,代理类实现传入的接口,并将方法调用统一转发给InvocationHandlerinvoke()方法-22

代码示例

java
复制
下载
// 1. 业务接口(必须!JDK动态代理要求目标类实现接口)
public interface UserService {
    void addUser(String username);
}

public class UserServiceImpl implements UserService {
    @Override
    public void addUser(String username) {
        System.out.println("新增用户:" + username);
    }
}

// 2. 实现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() + "方法开始执行");  // 前置增强
        Object result = method.invoke(target, args);                      // 反射调用
        System.out.println("日志:" + method.getName() + "方法执行结束");  // 后置增强
        return result;
    }
}

// 3. 动态生成代理对象
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
    target.getClass().getClassLoader(),
    target.getClass().getInterfaces(),
    new LogInvocationHandler(target)
);
proxy.addUser("张三");

底层支撑

JDK动态代理的底层依赖 Java反射机制method.invoke(target, args)本质上是反射调用目标方法,这也是其性能损耗的主要来源-


六、CGLIB动态代理(基于继承)

核心机制

CGLIB(Code Generation Library) 通过ASM字节码操作框架,在运行时动态生成目标类的子类,并覆盖所有非final方法,将方法调用拦截到MethodInterceptor.intercept()方法-29它不需要目标类实现任何接口

代码示例

java
复制
下载
// 1. 目标类:普通类,无需实现接口
public class UserService {
    public void addUser(String username) {
        System.out.println("新增用户:" + username);
    }
}

// 2. 实现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;
    }
}

// 3. 通过Enhancer生成代理对象
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback(new LogMethodInterceptor());
UserService proxy = (UserService) enhancer.create();
proxy.addUser("张三");

底层支撑

CGLIB底层依赖 ASM字节码操作框架,在运行时动态生成字节码创建子类。与JDK动态代理不同,CGLIB通过FastClass机制避免了反射调用,早期版本性能优势明显-32


七、JDK vs CGLIB:一张表吃透区别

对比维度JDK动态代理CGLIB动态代理
代理方式基于接口基于继承(生成子类)
目标类要求必须实现接口无需接口,但不能是final
底层技术反射 + ProxyASM字节码增强
能否代理final方法不涉及不能(final方法无法被子类覆盖)
性能(JDK 8以后)反射优化后差距缩小调用效率略高
依赖Java标准库,无需额外引入需引入CGLIB库(Spring已内置)
典型场景Spring AOP代理有接口的BeanSpring AOP代理无接口的Bean、Hibernate懒加载

性能小贴士:JDK 6及以前,CGLIB调用性能明显优于JDK;JDK 7开始JDK动态代理性能大幅提升,与CGLIB差距缩小;JDK 8以后两者差距已不显著-47


八、高频面试题

Q1:代理模式和装饰器模式有什么区别?

答题要点:区分设计目的增强方式

  • 设计目的不同:代理模式重在控制访问(如权限校验、懒加载),装饰器模式重在动态扩展功能(如IO流嵌套包装)-38

  • 创建时机不同:代理模式通常由框架在运行时创建,装饰器模式由客户端主动包装


Q2:静态代理和动态代理有什么区别?为什么实际开发更倾向用动态代理?

答题要点

  • 生成时机:静态代理编译期确定,动态代理运行期生成-1

  • 代码量:静态代理需为每个目标类手写代理类,动态代理一个代理工厂服务所有

  • 维护性:接口新增方法时,静态代理的代理类必须同步修改;动态代理完全不需要改动

  • 实际开发选择动态代理的原因:避免类爆炸、降低维护成本、提高灵活性


Q3:JDK动态代理和CGLIB动态代理如何选择?

答题要点(建议按优先级排序回答):

  1. 优先JDK动态代理:若目标类实现了接口,优先使用JDK,无需引入第三方依赖

  2. 选择CGLIB:若目标类未实现接口,只能用CGLIB

  3. 两者都能用时:JDK 8以后性能差距不大,选JDK更轻量;需注意CGLIB无法代理final方法

加分点:Spring AOP默认对实现了接口的类使用JDK动态代理,对没有实现接口的类自动切换到CGLIB-47


Q4:CGLIB为什么无法代理final类或final方法?

答题要点:CGLIB通过生成目标类的子类来实现代理,子类必须能够覆盖父类方法。final类不能被继承,final方法不能被覆盖,因此无法代理-47


Q5:动态代理的底层依赖什么技术?有什么性能影响?

答题要点

  • JDK动态代理依赖反射机制,每次方法调用都有反射开销

  • CGLIB动态代理依赖ASM字节码操作,通过生成子类避免反射,但生成代理对象时开销较大-32

  • 实际项目中通常采用单例模式复用代理对象,减少频繁创建的性能损耗


九、总结

本文围绕代理模式,从静态代理到动态代理,从JDK实现到CGLIB实现,梳理了以下核心知识点:

核心要点一句话记忆
代理模式价值不修改原有代码,实现功能增强,业务与增强解耦
静态代理 vs 动态代理静态“一对一”手写,动态“一对多”运行时生成
JDK动态代理基于接口+反射,需实现接口,无需额外依赖
CGLIB动态代理基于继承+字节码,无接口限制,不能代理final类
动态代理底层JDK依赖反射,CGLIB依赖ASM字节码生成
面试避坑分清“思想 vs 实现”、“接口 vs 继承”

下一期预告:从代理模式到Spring AOP源码,带你追踪动态代理在Spring框架中的落地实现。敬请期待!

标签:

相关阅读