Java的动态代理
1. 为什么需要动态代理?
动态代理的核心目的是在不修改原始代码的前提下,增强或控制对象的行为,解决以下问题:
- 代码解耦:将通用逻辑(如日志、事务、权限校验)与业务逻辑分离,避免代码重复和侵入性修改。
- 灵活性:运行时动态创建代理对象,适应不同场景的需求(如接口代理、类增强)。
- 简化开发:通过统一拦截方法调用,减少重复代码,提升可维护性(例如 Spring AOP 的实现基础)。
2. 如何实现动态代理?
Java 动态代理的两种主要方式:
JDK 动态代理(基于接口)
// 1. 定义接口
public interface UserService {void save();
}// 2. 实现接口
public class UserServiceImpl implements UserService {@Overridepublic void save() { System.out.println("保存用户"); }
}// 3. 实现 InvocationHandler(增强逻辑)
public class LogHandler implements InvocationHandler {private final Object target;public LogHandler(Object target) { this.target = target; }@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("方法调用前记录日志");Object result = method.invoke(target, args); // 调用原始方法System.out.println("方法调用后记录日志");return result;}
}// 4. 创建代理对象
public static void main(String[] args) {UserService target = new UserServiceImpl();UserService proxy = (UserService) Proxy.newProxyInstance(target.getClass().getClassLoader(), // 类加载器target.getClass().getInterfaces(), // 代理接口new LogHandler(target) // InvocationHandler 实现);proxy.save(); // 调用代理方法
}
CGLIB 动态代理(基于类)
适用于无接口的类,通过继承生成子类代理:
// 1. 定义真实类(无需接口)
public class UserService {public void save() { System.out.println("保存用户"); }
}// 2. 实现 MethodInterceptor(增强逻辑)
public class LogInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("方法调用前记录日志");Object result = proxy.invokeSuper(obj, args); // 调用父类方法System.out.println("方法调用后记录日志");return result;}
}// 3. 创建代理对象
public static void main(String[] args) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(UserService.class); // 设置父类enhancer.setCallback(new LogInterceptor()); // 设置回调逻辑UserService proxy = (UserService) enhancer.create();proxy.save(); // 调用代理方法
}
3. 动态代理是什么?
定义:动态代理是一种在运行时动态生成代理对象的技术,通过拦截方法调用,在目标方法前后插入额外逻辑。其核心机制包括:
- 代理模式:通过代理对象间接访问真实对象,实现增强功能。
- 反射机制:动态调用目标方法(如
method.invoke()
)。 - 字节码生成:JDK Proxy 或 CGLIB 在内存中生成代理类的字节码并加载。
特点:
- JDK 动态代理:依赖接口,通过
Proxy
和InvocationHandler
实现。 - CGLIB 动态代理:通过继承生成子类代理,无法代理
final
类或方法。
4. CGLIB 和 JDK 动态代理的区别
以下为 CGLIB 和 JDK 动态代理的核心区别对比表格:
对比维度 | JDK 动态代理 | CGLIB 动态代理 |
---|---|---|
实现原理 | 基于接口,通过 Proxy 类生成代理对象 | 基于继承,通过 Enhancer 生成目标类的子类代理 |
接口依赖 | 目标类必须实现接口 | 目标类无需实现接口,可直接代理普通类 |
继承限制 | 无限制(代理接口) | 无法代理 final 类或 final 方法 |
性能特点 | JDK 8+ 反射调用优化后性能接近 CGLIB | 通过 FastClass 机制跳过反射,方法调用更快 |
生成方式 | 运行时动态生成接口实现类字节码 | 运行时动态生成目标类的子类字节码 |
方法调用 | 通过反射 method.invoke() 调用目标方法 | 通过 MethodProxy.invokeSuper() 直接调用父类方法 |
适用场景 | Spring AOP 默认代理有接口的类(JDK ≥ 8 前) | Spring Boot 2.x+ 默认代理方式(无接口也可用) |
依赖库 | JDK 原生支持(java.lang.reflect 包) | 需引入 CGLIB 库(如 cglib-core 和 cglib-asm ) |
补充说明
- 性能差异:
- JDK 动态代理:早期版本(JDK 6)反射性能较差,但 JDK 8+ 对反射调用进行了优化(如
MethodHandle
),性能差距显著缩小。 - CGLIB:通过
FastClass
机制为代理类的方法建立索引,直接调用目标方法,避免了反射开销,但生成代理类的过程较慢。
- JDK 动态代理:早期版本(JDK 6)反射性能较差,但 JDK 8+ 对反射调用进行了优化(如
- 代理限制:
- CGLIB 无法代理
final
方法(子类不能重写),但 JDK 动态代理无此问题(代理接口方法)。 - JDK 动态代理的接口方法不能为
static
或final
(接口方法默认public abstract
)。
- CGLIB 无法代理
- 框架选择:
- Spring 5.x / Spring Boot 2.x+:默认优先使用 CGLIB(即使目标类实现了接口)。
- 旧版 Spring:若目标类有接口,默认使用 JDK 动态代理;否则使用 CGLIB。
总结
动态代理通过解耦增强逻辑与业务代码,结合反射和字节码技术,为框架设计(如 Spring AOP)提供了灵活、非侵入式的解决方案。选择 JDK 或 CGLIB 取决于目标类是否有接口,以及对性能的要求。