既然你在复习 Spring Cloud 和 Spring Boot,那 Java 动态代理 绝对是绕不开的灵魂考点。因为无论是 Spring AOP、声明式事务 (@Transactional) 还是 OpenFeign,底层全靠它。
面试时,建议按照 “静态代理的痛点 -> 两种实现方式 -> 核心区别” 的逻辑来回答。
1. 为什么要用动态代理?(解决什么问题)
-
静态代理的痛点: 每一个目标类都要手动写一个代理类,如果有一百个 Service 要加日志,你就得写一百个代理,代码冗余,扩展性极差。
-
动态代理的优势: 代理类是在程序运行期间根据反射动态生成的。你只需要写一个通用的代理逻辑,就能代理各种各样的类。
2. Java 动态代理的两大主流实现
A. JDK 动态代理(Java 原生)
-
核心: 基于接口。
-
关键类:
java.lang.reflect.Proxy和InvocationHandler。 -
原理: 利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用
InvokeHandler来处理。 -
要求: 目标类必须实现至少一个接口。
B. CGLIB 动态代理(第三方库)
-
核心: 基于继承。
-
关键类:
MethodInterceptor和Enhancer。 -
原理: 通过 ASM 字节码操作框架,动态生成目标类的一个子类,并覆盖其中的方法。
-
要求: 目标类不能是
final的,目标方法也不能是final的(因为子类没法重写)。
3. 面试必问:JDK vs CGLIB 怎么选?
| 维度 | JDK 动态代理 | CGLIB 动态代理 |
| 实现原理 | Java 反射机制 | ASM 字节码技术(直接修改字节码) |
| 目标类限制 | 必须实现接口 | 只要不是 final 类即可 |
| 性能 | 较旧版本性能低,但 Java 8+ 已经优化得极好 | 老版本性能高,但创建代理对象较慢 |
| 归属 | JDK 原生自带 | 第三方库(Spring 已内置) |
Spring AOP 的策略:
如果目标对象实现了接口,默认采用 JDK 动态代理。
如果没有实现接口,则采用 CGLIB。
你也可以在配置中强制使用 CGLIB (
proxy-target-class="true")。
4. 核心代码演示(找回肌肉记忆)
如果你面试时需要手写或者描述流程,记住这几行核心:
JDK 代理核心:
// 1. 实现 InvocationHandler 接口
public class MyHandler implements InvocationHandler {
private Object target; // 目标对象
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置增强 (比如开启事务)
Object result = method.invoke(target, args); // 执行原方法
// 后置增强 (比如提交事务)
return result;
}
}
// 2. 生成代理对象
Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
handler);
5. 常见面试连环炮
-
Q:为什么 JDK 代理一定要有接口?
-
A: 因为生成的代理类默认已经继承了
Proxy类(Java 是单继承),所以它只能通过实现接口的方式来扩展功能。
-
-
Q:动态代理和装饰器模式有什么区别?
-
A: 装饰器模式侧重于静态地给对象增强功能,通常要手动编写;动态代理侧重于批量、动态地拦截方法,对开发者更透明。
-
-
Q:Spring 事务失效跟动态代理有关系吗?
-
A: 有!如果在同一个类中 A 方法调用 B 方法(B 带有
@Transactional),事务会失效。因为这属于自调用,没有经过代理对象,拦截逻辑就没生效。
-
小建议:
既然你之前在做 PancakeBot 的自动化交易和高级架构,可以跟面试官提一句:在处理交易信号的埋点监控或者性能统计时,你会考虑用 AOP(即动态代理)来做非侵入式的开发,这样显得你对技术的应用非常有落地感。