【Java 开发日记】我们来说一说动态代理
动态代理是 Java 中非常核心且高频出现的技术之一,几乎所有现代框架(Spring、MyBatis、Dubbo、Shiro、Hibernate 等)的很多“魔法”功能都依赖它。
今天我们把动态代理从原理到实战、从底层到常见误区完整梳理一遍。
1. 什么是动态代理?它和静态代理的区别
静态代理
在编译期就写好代理类,代理类和目标类实现同一个接口,代理类内部持有一个目标对象引用。
// 接口
interface UserService {
void save();
}
// 目标类
class UserServiceImpl implements UserService {
public void save() {
System.out.println("保存用户");
}
}
// 静态代理类(提前写好)
class UserServiceProxy implements UserService {
private UserService target;
public UserServiceProxy(UserService target) {
this.target = target;
}
public void save() {
System.out.println("【静态代理】开启事务");
target.save();
System.out.println("【静态代理】提交事务");
}
}
缺点:
- 每个接口都要写一个代理类
- 如果接口方法增加,代理类也要改
- 代码重复严重
动态代理
在运行时生成代理类,不需要提前写代理类代码。
Java 中主要有两种实现方式:
- JDK 动态代理(基于接口)
- CGLIB 动态代理(基于继承)
2. JDK 动态代理(最经典、最基础)
核心类:java.lang.reflect.Proxy + InvocationHandler
三个关键要素:
- 接口(被代理对象必须实现接口)
- InvocationHandler(真正决定“代理要做什么”的地方)
- Proxy.newProxyInstance()(运行时生成代理对象)
完整代码示例:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 接口
interface UserService {
void saveUser(String name);
String getUserName(int id);
}
// 目标实现
class UserServiceImpl implements UserService {
@Override
public void saveUser(String name) {
System.out.println("保存用户:" + name);
}
@Override
public String getUserName(int id) {
return "用户" + id;
}
}
// 自定义 InvocationHandler(核心:在这里写增强逻辑)
class LoggingHandler implements InvocationHandler {
// 真正的目标对象
private final Object target;
public LoggingHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置增强
System.out.println("【JDK动态代理】方法开始:" + method.getName());
long start = System.currentTimeMillis();
// 执行目标方法
Object result = method.invoke(target, args);
// 后置增强
long end = System.currentTimeMillis();
System.out.println("【JDK动态代理】方法结束:" + method.getName() + ",耗时:" + (end - start) + "ms");
return result;
}
}
// 测试
public class JdkProxyDemo {
public static void main(String[] args) {
UserService target = new UserServiceImpl();
// 生成代理对象(注意:返回类型是接口)
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 类加载器
target.getClass().getInterfaces(), // 被代理的接口
new LoggingHandler(target) // 调用处理器
);
proxy.saveUser("张三");
System.out.println(proxy.getUserName(100));
}
}
输出示例:
【JDK动态代理】方法开始:saveUser
保存用户:张三
【JDK动态代理】方法结束:saveUser,耗时:0ms
【JDK动态代理】方法开始:getUserName
用户100
【JDK动态代理】方法结束:getUserName,耗时:0ms
3. CGLIB 动态代理(无接口也能代理)
核心思想:通过字节码生成技术,运行时生成目标类的子类,然后重写方法,在子类方法中插入增强逻辑。
优点:
- 不需要目标类实现接口
- 可以代理普通类(只要不是 final 类)
缺点:
- final 类、final 方法无法代理
- 性能首次生成稍慢(但后续缓存后很快)
现代 Spring Boot 默认使用 CGLIB(即使有接口也优先 CGLIB)
简单代码示例(使用 cglib 库):
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
class OrderService {
public void createOrder(String orderNo) {
System.out.println("创建订单:" + orderNo);
}
}
class LogInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("【CGLIB】开始:" + method.getName());
Object result = proxy.invokeSuper(obj, args); // 调用父类方法
System.out.println("【CGLIB】结束:" + method.getName());
return result;
}
}
public class CglibDemo {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OrderService.class);
enhancer.setCallback(new LogInterceptor());
OrderService proxy = (OrderService) enhancer.create();
proxy.createOrder("ORD-20250211");
}
}
4. Spring AOP 中的动态代理选择(2025–2026 现状)
| 条件 | Spring AOP 默认使用的代理方式 | 说明 |
|---|---|---|
| 目标类实现了接口 | JDK 动态代理(历史默认) | 早期默认 |
| 目标类没有接口 | CGLIB | 必须 |
| Spring Boot 2.x+ 默认配置 | CGLIB | spring.aop.proxy-target-class=true |
| 强制使用 JDK 代理 | 设置 proxy-target-class=false | 必须有接口 |
现代推荐:保持默认 CGLIB,行为更一致,兼容性更好。
5. 动态代理最常见的面试/实战问题
- JDK 动态代理和 CGLIB 的区别?
接口 vs 继承、能否代理无接口类、final 方法、性能等 - 为什么 Spring Boot 默认改成 CGLIB?
避免接口代理导致的类型转换问题、@Configuration 类必须子类化 - this 调用方法为什么不走代理?(自调用失效)
因为 this 是原始对象,不是代理对象 解决办法:
- 注入自己(
@Autowired UserService self) - 使用
AopContext.currentProxy()(需开启 expose-proxy)
- 动态代理生成的类长什么样?
JDK:$Proxy0、$Proxy1…
CGLIB:UserService$$EnhancerBySpringCGLIB$$xxxx - 动态代理能代理 static、private、final 方法吗?
都不能(static 是类方法,private/final 不能被重写/覆盖)
6. 一句话总结动态代理
动态代理就是在运行时生成一个代理对象,这个代理对象要么实现目标接口(JDK),要么继承目标类(CGLIB),拦截方法调用,在调用前后插入我们自定义的增强逻辑。
希望这篇日记把动态代理的来龙去脉讲清楚了。
如果你想继续聊下面任一方向,随时告诉我:
- Spring AOP 动态代理完整源码流程
- InvocationHandler 的 invoke 方法底层原理
- 手写一个简单的 Spring 风格 AOP
- 动态代理在 MyBatis、Dubbo 中的应用
- 性能对比(JDK vs CGLIB vs ByteBuddy vs Javassist)
继续写日记~