动态代理
目前主流的动态代理方案(共 5 种)
- JDK 动态代理(只能代理接口)
- CGLIB(继承类做代理)
- ByteBuddy(现代最强大、Spring 已替换 CGLIB)
- Javassist(JBoss、MyBatis 常用)
- ASM(最底层、性能最高,框架底层用)
目前主流的动态代理方案(共 5 种)
- JDK 动态代理(只能代理接口)
- CGLIB(继承类做代理)
- ByteBuddy(现代最强大、Spring 已替换 CGLIB)
- Javassist(JBoss、MyBatis 常用)
- ASM(最底层、性能最高,框架底层用)
总览(先记这张表)
| 方案 | 原理 | 代理目标 | 性能 | 难度 | 核心用途 |
|---|---|---|---|---|---|
| JDK 动态代理 | 实现接口 + 继承 Proxy | 只能接口 | 高 | 极低 | Spring AOP(默认) |
| CGLIB | 继承目标类 + ASM | 类/接口 | 高 | 中 | 旧版 Spring |
| ByteBuddy | 继承/实现接口 + 底层ASM | 类/接口 | 极高 | 低 | Spring Boot 2.x+ 默认 |
| Javassist | 字符串拼接代码 | 类/接口 | 中 | 极低 | MyBatis Mapper 代理 |
| ASM | 直接操作字节码指令 | 任意 | 最高 | 地狱 | 框架底层、中间件 |
1. JDK 动态代理
核心原理
- 动态生成类:
$Proxy0 extends Proxy implements 目标接口 - 所有方法通过反射转发到
InvocationHandler - 只能代理接口
适用场景
- 目标类有接口
- Spring AOP 默认方案
完整代码
接口
public interface UserService {
void addUser(String name);
}
实现类
public class UserServiceImpl implements UserService {
@Override
public void addUser(String name) {
System.out.println("添加用户:" + name);
}
}
代理处理器
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置增强");
Object result = method.invoke(target, args); // 反射调用
System.out.println("后置增强");
return result;
}
}
测试
import java.lang.reflect.Proxy;
public class JdkProxyTest {
public static void main(String[] args) {
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new MyInvocationHandler(target)
);
proxy.addUser("JDK");
}
}
优点
- JDK 原生,无需依赖
- 简单稳定
- 高版本 JDK 性能优秀
缺点
- 只能代理接口
2. CGLIB
核心原理
- 生成目标类的子类
- 重写非 final 方法
- 用 FastClass 避免反射
适用场景
- 目标类没有接口
- 旧版 Spring AOP
依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
代码
import net.sf.cglib.proxy.*;
public class CglibTest {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserServiceImpl.class); // 继承目标类
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
System.out.println("前置");
Object res = proxy.invokeSuper(obj, args);
System.out.println("后置");
return res;
});
UserServiceImpl proxy = (UserServiceImpl) enhancer.create();
proxy.addUser("CGLIB");
}
}
优点
- 可代理普通类
- 性能不错
缺点
- 不能代理 final 类/方法
- 已被 ByteBuddy 替代
3. ByteBuddy(🔥 现代王者)
核心原理
- 底层 ASM
- 支持继承/实现接口
- API 优雅,性能接近 ASM
- Spring 默认
适用场景
- Spring AOP
- 动态代理、类增强
- 现代框架首选
依赖
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.14.10</version>
</dependency>
代码
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.implementation.MethodDelegation;
import static net.bytebuddy.matcher.ElementMatchers.*;
public class ByteBuddyTest {
public static void main(String[] args) throws Exception {
// 生成 UserService 子类
Class<? extends UserServiceImpl> proxyClass = new ByteBuddy()
.subclass(UserServiceImpl.class)
.method(named("addUser"))
.intercept(MethodDelegation.to(new MyInterceptor()))
.make()
.load(UserServiceImpl.class.getClassLoader())
.getLoaded();
UserServiceImpl proxy = proxyClass.newInstance();
proxy.addUser("ByteBuddy");
}
}
// 拦截器
class MyInterceptor {
public void intercept() {
System.out.println("前置增强");
// 调用原方法
System.out.println("后置增强");
}
}
优点
- 性能极高
- API 优雅
- 可代理类/接口
- Spring 官方推荐
缺点
- 依赖第三方包
4. Javassist
核心原理
- 用字符串写 Java 代码
- 动态编译成字节码
- 简单到极致
适用场景
- MyBatis Mapper 代理
- 动态生成简单类
依赖
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.30.2-GA</version>
</dependency>
代码
import javassist.util.proxy.*;
public class JavassistTest {
public static void main(String[] args) throws Exception {
ProxyFactory factory = new ProxyFactory();
factory.setSuperclass(UserServiceImpl.class);
MethodHandler handler = (self, thisMethod, proceed, args) -> {
System.out.println("前置");
Object res = proceed.invoke(self, args);
System.out.println("后置");
return res;
};
UserServiceImpl proxy = (UserServiceImpl) factory.createClass().newInstance();
((ProxyObject) proxy).setHandler(handler);
proxy.addUser("Javassist");
}
}
优点
- 最简单
- 字符串拼代码即可
缺点
- 性能略低
- 复杂场景难维护
5. ASM(最底层)
核心原理
- 直接操作 JVM 字节码指令
- 性能天花板
- 难度极高
适用场景
- 框架底层
- CGLIB / ByteBuddy 底层
- APM、热部署
优点
- 性能最高
- 最灵活
缺点
- 必须懂字节码
- 开发效率极低
五大方案 终极对比(面试满分)
1. 代理能力
- JDK:只能接口
- CGLIB:类(不能final)
- ByteBuddy:类/接口(全能)
- Javassist:类/接口
- ASM:任意
2. 性能
ASM > ByteBuddy > CGLIB >= JDK > Javassist
3. 编码难度
ASM >>> CGLIB > ByteBuddy > JDK > Javassist
4. 现代企业选择
- 有接口:JDK
- 无接口:ByteBuddy(Spring 默认)
- 快速开发:Javassist
- 框架底层:ASM
- CGLIB 已淘汰
一句话总结(背会直接封神)
- JDK:只能代理接口,原生简单。
- CGLIB:继承类代理,已过时。
- ByteBuddy:现代最强,Spring 默认,性能+易用完美。
- Javassist:最简单,字符串拼代码,MyBatis 在用。
- ASM:最底层、最快、最难,框架基石。
Java五大动态代理面试10题
一、基础必考题
Java动态代理有哪些主流方案?请简要说明。
共5种,核心3种常用。① JDK动态代理(原生,只能代理接口);② CGLIB(继承目标类,已被淘汰);③ ByteBuddy(现代首选,Spring Boot 2.x+默认);④ Javassist(简单,MyBatis在用);⑤ ASM(最底层,框架底层使用)。
JDK动态代理为什么只能代理接口?
核心是Java单继承限制。JDK动态代理运行时生成的代理类,会强制继承Proxy类,一个类只能有一个直接父类,无法再继承目标业务类,因此只能通过实现接口的方式完成代理。
CGLIB动态代理的原理是什么?有什么限制?
原理是通过ASM生成目标类的子类,重写非final方法,通过FastClass机制(方法索引)直接调用目标方法,避免反射。限制:不能代理final类、final方法,也不能代理static方法。
ByteBuddy相比CGLIB有什么优势?为什么Spring替换了CGLIB?
优势有3点:① 性能更优,接近ASM;② API更优雅,支持链式编程,开发效率高;③ 功能更强大,支持继承/实现接口两种代理方式,兼容性更好。Spring替换CGLIB,是因为ByteBuddy在性能和易用性上实现了完美平衡,且更适配现代框架开发。
面试官:Javassist的核心特点是什么?主要用在什么场景?
核心特点是简单易用,支持通过字符串拼接Java代码,动态编译成字节码,无需懂字节码指令。主要场景:MyBatis的Mapper代理、快速开发简单的动态类增强需求。
二、进阶高频题
面试官:JDK动态代理和CGLIB的核心区别(至少3点)?
① 代理原理:JDK是实现接口+继承Proxy,CGLIB是继承目标类;② 代理目标:JDK只能代理接口,CGLIB可代理普通类;③ 调用方式:JDK用反射调用,CGLIB用FastClass直接调用;④ 限制:JDK依赖接口,CGLIB不能代理final类/方法。
面试官:ASM为什么不适合业务开发?它的核心用途是什么?
因为ASM直接操作JVM字节码指令,开发难度极高,需要熟练掌握字节码规范,开发效率极低,容易出错,因此不适合业务开发。核心用途:作为所有动态代理工具的底层引擎(如CGLIB、ByteBuddy),以及框架底层、APM监控、热部署等场景。
面试官:实际开发中,如何选择动态代理方案?
优先遵循Spring的选择:① 目标类有接口:用JDK动态代理(原生、稳定);② 目标类无接口:用ByteBuddy(Spring默认,性能优);③ 快速开发简单需求:用Javassist;④ 框架/中间件开发:用ASM(追求极致性能);⑤ 不推荐用CGLIB(已被ByteBuddy替代)。
三、深度拓展题
面试官:JDK动态代理的调用链路是什么?
调用链路:代理对象调用方法 → 动态生成的$Proxy0类的对应方法 → 转发到InvocationHandler的invoke方法 → 执行增强逻辑 + 反射调用目标对象的方法 → 返回结果。
面试官:ByteBuddy、CGLIB、ASM的性能排序,以及为什么?
性能排序:ASM > ByteBuddy > CGLIB。原因:① ASM直接操作字节码,无任何额外封装,性能最高;② ByteBuddy底层基于ASM,封装了优雅的API,但几乎没有性能损耗,接近ASM;③ CGLIB同样基于ASM,但封装层次更多,且FastClass机制的性能略逊于ByteBuddy的优化逻辑。