动态代理常用的两种方式?
口语化回答
好的,面试官,动态常见的两种,一种是 jdk 动态代理,一种是 cglib 动态代理,两者的最主要区别是 jdk 动态代理主要是依赖于接口创建代理对象,cglib 是通过生成子类的方式,不需要接口,两种经常会在一起配合,假设类没有接口的时候,就可以通过 cglib 来弥补不足。从性能上来看,因为 jdk 使用反射机制,他的性能,相比 cglib 稍有逊色。cglib 会更占用内存一些。两者都可以满足各种需求,按照有没有接口的原则进行选择。
题目解析
常考题,两种方式非常清晰,大家要记住的是两种所适应的场景,有没有接口的一个区别,这道题后面,经常就会引入出来,spring 用的是什么。
面试得分点
jdk 动态代理,cglib,有无接口,子类
题目详细答案
动态代理常用的两种方式是基于接口的动态代理(JDK 动态代理)和基于类的动态代理(CGLIB 动态代理)。
JDK 动态代理
JDK 动态代理是 Java 标准库提供的一种动态代理机制,它依赖于接口来创建代理对象。JDK 动态代理通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口实现。当你有一个接口并希望为该接口的实现类创建代理时,可以使用 JDK 动态代理。
CGLIB 动态代理
CGLIB(Code Generation Library)是一个强大的高性能代码生成库,它通过生成子类的方式来为目标类创建代理对象。CGLIB 动态代理不需要接口,可以直接代理类。当你没有接口,只有具体类时,可以使用 CGLIB 动态代理。
比较
依赖性:
JDK 动态代理依赖于接口,如果没有接口就无法使用。
CGLIB 动态代理可以直接代理类,不需要接口。
性能:
JDK 动态代理由于需要通过反射调用方法,性能可能会有所影响。
CGLIB 动态代理通过生成字节码来创建代理类,性能通常比 JDK 动态代理更高,但生成字节码的过程会稍微多占用一些内存。
使用场景:
JDK 动态代理适用于有接口的情况,适用于大多数常见的业务场景。
CGLIB 动态代理适用于没有接口的情况,适用于需要代理大量具体类的场景。
动态代理两种实现方式的通俗解释
面试官您好,关于JDK动态代理和CGLIB动态代理的区别,我用一个生活中的例子来简单说明:
核心区别(用租房中介类比)
- JDK动态代理 - 像"正规房屋中介"
- 必须通过租房平台(接口)才能代理
- 租客只能通过平台联系房东(必须实现接口)
- 流程规范但灵活性较低
- CGLIB动态代理 - 像"熟人介绍的二房东"
- 直接和房东打交道(不需要接口)
- 可以改造房屋(增强原有类方法)
- 更灵活但需要更多手续(生成子类)
具体差异对比
对比项 | JDK动态代理 | CGLIB动态代理 |
代理原理 | 实现接口 | 继承目标类生成子类 |
接口要求 | 必须实现至少一个接口 | 不需要接口 |
性能 | 反射调用稍慢 | 直接方法调用更快 |
内存占用 | 较小 | 较大(需要生成子类字节码) |
适用场景 | Spring默认对接口的代理 | Spring对普通类的代理 |
限制 | 无法代理无接口类 | 无法代理final类/方法 |
实际项目中的选择
在我们项目中:
- 如果服务类有接口定义,Spring默认用JDK代理(如Service层接口)
public interface UserService {void addUser();
}@Service
public class UserServiceImpl implements UserService {...}
- 如果没有接口(如Controller),Spring自动切换CGLIB
@Controller // 没有实现接口
public class UserController {...}
性能优化的经验
- JDK代理优化:
- 减少反射调用(如缓存Method对象)
- 适合代理方法较少的接口
- CGLIB优化:
- 使用Spring Boot 2.x+默认CGLIB
- 避免代理final方法
- 大项目要监控PermGen/Metaspace
Spring中的特殊处理
Spring其实很智能:
- 如果类实现了接口,优先用JDK代理
- 如果没有接口,自动用CGLIB
- 可以通过配置强制使用CGLIB:
@EnableAspectJAutoProxy(proxyTargetClass = true)
常见误区提醒
- 不是CGLIB一定比JDK快:
- 简单场景差异不大
- CGLIB初始化成本更高
- 代理嵌套问题:
@Transactional
@Cacheable
public void foo() {} // 两种代理可能产生顺序问题
- this调用失效:
public void bar() {this.foo(); // 不走代理!
}