口語化回答
好的,面試官,動態常見的兩種,一種是 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(); // 不走代理!
}