Java 代理模式全链路解析与大厂面试避坑指南
第一部分:代理模式的哲学——为什么要“多此一举”?
你必须理解代理模式的核心思想:控制访问与功能增强。
1. 核心定义
代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。
-
真实对象 (Real Subject):最终要执行业务逻辑的对象。
-
代理对象 (Proxy):持有了真实对象的引用,负责预处理消息、过滤消息、把消息转发给真实对象,以及事后处理消息。
2. 为什么要用代理?
在分布式或复杂业务系统中,直接访问真实对象会有风险或不便。代理可以实现:
-
解耦:客户端不直接依赖真实对象。
-
开闭原则 (OCP):无需修改原始代码,即可增加日志、事务、限流、权限校验等功能。
第二部分:静态代理——虽笨重但清晰
静态代理是由程序员创建或工具生成源代码,再进行编译。在程序运行前,代理类的 .class 文件就已经存在了。
1. 代码实战
Java
// 1. 定义接口
public interface SmsService {
String send(String message);
}
// 2. 真实实现类
public class SmsServiceImpl implements SmsService {
public String send(String message) {
System.out.println("发送短信:" + message);
return message;
}
}
// 3. 代理类
public class SmsProxy implements SmsService {
private final SmsService smsService; // 持有真实对象
public SmsProxy(SmsService smsService) {
this.smsService = smsService;
}
@Override
public String send(String message) {
System.out.println("方法执行前:记录日志");
smsService.send(message); // 调用真实对象
System.out.println("方法执行后:清理现场");
return message;
}
}
2. 局限性
-
类爆炸:一个接口就要对应一个代理类,成百上千个接口会疯掉。
-
维护难:接口一旦增加方法,真实类和代理类都得改。
第三部分:JDK 动态代理——运行时的黑魔法
面试官:“JDK 动态代理为什么必须要求实现接口?”
深度回答:因为 JDK 生成的代理类(如 $Proxy0)已经继承了 java.lang.reflect.Proxy。Java 是单继承的,所以它只能通过实现接口来完成代理。
1. 核心机制
它利用 java.lang.reflect 包下的 Proxy 类和 InvocationHandler 接口。
2. 代码实战
Java
public class DebugInvocationHandler implements InvocationHandler {
private final Object target; // 被代理的目标对象
public DebugInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method: " + method.getName());
Object result = method.invoke(target, args); // 反射调用
System.out.println("After method: " + method.getName());
return result;
}
}
// 使用方式
SmsService smsService = (SmsService) Proxy.newProxyInstance(
SmsServiceImpl.class.getClassLoader(),
new Class[]{SmsService.class},
new DebugInvocationHandler(new SmsServiceImpl())
);
第四部分:CGLIB 动态代理——暴力美学
面试官:“如果类没有实现接口,还能代理吗?”
回答:当然,用 CGLIB。它的原理是动态生成一个子类去继承被代理类。
1. 核心特点
-
原理:通过
ASM字节码操作框架生成子类。 -
限制:不能代理
final类和final/private方法(因为无法被子类重写)。
2. 代码实战
Java
public class DebugMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("CGLIB 增强前...");
Object result = proxy.invokeSuper(obj, args); // 调用父类(真实对象)方法
System.out.println("CGLIB 增强后...");
return result;
}
}
第五部分:面试复盘脑图
为了帮你将两万字的知识点内化,我整理了这张核心脑图:
Code snippet
mindmap root((Java 代理模式)) 核心价值 解耦与扩展 无侵入式增强 (日志/事务/权限) Spring AOP 的基石 静态代理 编译期确定 类爆炸问题, 维护成本高 JDK 动态代理 运行时生成字节码 依赖 java.lang.reflect.Proxy 必须实现接口 (单继承限制) 反射调用, 效率随版本提升已接近原生 CGLIB 动态代理 继承机制: 生成子类 不要求接口 无法代理 final 类/方法 字节码技术实现 (ASM) 实战对比 JDK 更稳定, 官方支持 CGLIB 更灵活, 适合无接口场景 Spring AOP: 默认有接口用JDK, 无接口用CGLIB
第六部分:大厂面试官的“深度思考题”
-
Spring AOP 默认使用的是哪种代理?
-
回答要点:如果目标对象实现了接口,默认用 JDK;如果没有实现接口,强制用 CGLIB。当然,你也可以通过配置
proxy-target-class=true强制全量使用 CGLIB。
-
-
动态代理的性能真的比静态代理差吗?
-
回答要点:早期 JDK 确实差。但从 JDK 8 开始,JDK 动态代理的效率有了质的飞跃。在现代 JVM 中,反射调用的开销非常小,且由于减少了大量的类加载,动态代理在整体系统内存占用上更具优势。
-
-
代理模式和装饰器模式(Decorator)有什么区别?
-
回答要点:
-
装饰器模式:侧重于“增强”,你明确知道你在装饰谁。
-
代理模式:侧重于“控制”,你可能根本不知道后台真实的执行者是谁(如远程代理 RPC)。
-
-
结语:从“理解代码”到“理解框架”
代理模式是 Java 框架“灵活性”的源泉。 掌握了代理,你就能读懂 Spring 的事务回滚逻辑,就能理解 MyBatis 是如何把一个接口变成 SQL 执行引擎的。
这篇文章梳理了代理模式的所有命门。如果你在面试中能手写出 JDK 动态代理的流程并分析出它和 CGLIB 的字节码差异,大厂的入场券已经在你手里了。
更多推荐




所有评论(0)