java代理
1. Java 中的代理分类
| 类型 | 描述 | 例子 |
|---|---|---|
| 静态代理 | 编写代理类,手动调用目标方法 | 自己写的代理类 |
| 动态代理 | 运行时生成代理类 | JDK 动态代理、CGLIB 代理 |
2. 静态代理
特点
- 代理类和被代理类都需要实现相同的接口。
- 需要手动编写代理类。
- 扩展性差,如果接口方法多,需要重复工作。
示例
public interface Service {
void doSomething();
}
public class RealService implements Service {
public void doSomething() {
System.out.println("Real service is doing something");
}
}
public class ServiceProxy implements Service {
private RealService realService = new RealService();
public void doSomething() {
System.out.println("Before");
realService.doSomething();
System.out.println("After");
}
}
3. JDK 动态代理
特点
- 只能代理接口(
interface)。 - 运行时动态生成代理对象。
- 通过
Proxy类和InvocationHandler接口实现。
核心类
Proxy:生成代理对象。InvocationHandler:处理方法调用的接口。
示例代码
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(proxyObj, method, args) -> {
System.out.println("Before");
Object result = method.invoke(target, args);
System.out.println("After");
return result;
}
);
proxy.sayHello();
面试重点
为什么 JDK 动态代理只能代理接口? 因为 Java 的 Proxy 类生成的代理类是通过
implements 接口来创建的,不是继承类。JDK 动态代理的底层实现? 通过运行时生成一个新类(继承
Proxy类并实现接口),字节码生成后加载到 JVM 中。一个类多个接口,怎么办
在 JDK 动态代理 中,一个类可以实现多个接口,代理时是可以同时代理多个接口的 —— 本质上,代理对象实现的是这些接口的组合,并不会代理具体类本身(因为 JDK Proxy 只能基于接口)。
如何保存生成的代理类? 设置系统属性:
System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
4. CGLIB 动态代理
特点
- 可以代理普通类(没有接口也能代理)。
- 通过继承的方式生成子类代理。
- CGLIB(Code Generation Library)是开源项目,Spring 默认也集成了。
核心类
Enhancer:生成代理对象。MethodInterceptor:方法拦截器。
示例代码
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
System.out.println("Before");
Object result = proxy.invokeSuper(obj, args);
System.out.println("After");
return result;
});
UserService proxy = (UserService) enhancer.create();
proxy.sayHello();
面试重点
- 为什么 final 类不能被 CGLIB 代理? 因为 CGLIB 是通过继承来生成代理子类的,final 类不能被继承。
- CGLIB 与 JDK 动态代理的区别?
| JDK 动态代理 | CGLIB 动态代理 | |
|---|---|---|
| 是否需要接口 | 是 | 否 |
| 实现方式 | 实现接口 | 继承类 |
| 代理限制 | 只能代理接口 | final类不能代理,final方法不会被代理 |
| 性能 | 接口少时性能好 | 类复杂时性能好 |
- CGLIB 代理的原理? 基于 ASM 框架,在运行时生成字节码文件,创建子类并重写方法。ASM 是一个 Java 字节码操控库,允许你:
- 动态生成类(不写 .java,只构建字节码)
- 修改已有的类(AOP 增强、插桩、监控)
- 分析
.class文件结构(如反编译工具)
5. 动态代理在 Spring AOP 中的应用
Spring 本身支持两种代理:
| 代理方式 | 用途 |
|---|---|
| JDK 动态代理 | 接口类的 AOP 代理(默认首选) |
| CGLIB 代理 | 非接口类(纯类)或强制使用 CGLIB 的情况 |
Spring 代理选择策略
有接口:默认用 JDK 动态代理。
没有接口:使用 CGLIB 动态代理。
可以通过配置强制使用 CGLIB:
spring: aop: proxy-target-class: true或者在 JavaConfig 里:
@EnableAspectJAutoProxy(proxyTargetClass = true)
6. QA
✅ 什么是动态代理? 动态代理是运行时生成代理对象,拦截并增强方法调用的一种机制。
✅ JDK 动态代理和 CGLIB 的区别? JDK 动态代理基于接口,CGLIB 基于继承(子类化目标类)。
✅ JDK 动态代理的底层原理? 通过 Proxy 类+InvocationHandler 动态生成实现接口的类(字节码生成+反射调用)。
✅ CGLIB 动态代理能代理 final 类吗? 不能,CGLIB 通过继承生成代理类,final 类无法继承。final方法也不能代理
✅ Spring AOP 是如何选择代理方式的? 默认有接口用 JDK 动态代理,没接口用 CGLIB;可以配置 proxy-target-class=true 强制使用 CGLIB。
✅ 如何保存 JDK 生成的代理类字节码? 设置系统属性:
System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
✅ 动态代理的优缺点?
| 优点 | 缺点 |
|---|---|
| 1. 解耦增强逻辑与业务逻辑 2. 运行时灵活增强 | 1. 性能稍有开销 2. JDK Proxy必须有接口 3. CGLIB无法代理final类或方法 |
ByteBuddy介绍
CGLIB的替代品,mockito 2已经从cglib使用了byteBuddy。
ByteBuddy 相较于 CGLIB 在多个方面实现了 更现代、更强大、更易用 的字节码生成与修改能力。它已经成为 Mockito、Spring、Hibernate 等框架的主流选择(尤其是 Mockito 2+ 完全弃用了 CGLIB,转向 ByteBuddy)。api简单,jdk兼容好,社区维护好
为什么Spring需要 CGLIB 或 ByteBuddy?
🔹 框架解耦:Spring 不自己造轮子,而是集成稳定成熟的类库。
- CGLIB 是一个强大的字节码增强工具,可以生成任意类的子类。
- 后来 Spring 和 Mockito 都逐渐转向了 ByteBuddy,因为它功能更强,API 更简洁。
ByteBuddy生成类过程
- 在内存中构造新的类(或子类)
- 使用
ByteBuddy().subclass(...)等 API 定义结构
- 使用
- 动态生成字节码
- 内部使用 ASM 框架将类定义转换为
.class字节码
- 内部使用 ASM 框架将类定义转换为
- 通过
ClassLoader加载到 JVM 中- 通常使用
ClassLoadingStrategy.Default.INJECTION或自定义加载方式
- 通常使用
- 返回这个“新类”的实例
- 可直接用反射创建,也可以作为 mock 对象返回
动态代理使用场景
- AOP(面向切面编程):可以通过代理来做日志记录、权限检查、事务管理等横切关注点。
- Mock 框架:例如,Mockito 使用动态代理来模拟接口和类的行为,帮助进行单元测试。
- 性能优化:动态代理可以在运行时为方法加上缓存等性能增强逻辑。