Spring AOP底層的動態代理實現有兩種方式:一種是JDK動態代理,另一種是CGLib動態代理。
JDK動態代理
JDK 1.3版本以后提供了動態代理,允許開發者在運行期創建接口的代理實例,而且只能為接口創建代理實例。
如果被代理目標沒有接口那么Spring也無能為力,Spring通過Java的反射機制生成被代理接口的新的匿名實現類。
JDK動態代理具體實現原理:
-
通過實現
InvocationHandlet
接口創建自己的調用處理器; -
通過為Proxy類指定ClassLoader對象和一組interface來創建動態代理;
-
通過反射機制獲取動態代理類的構造函數,其唯一參數類型就是調用處理器接口類型;
-
通過構造函數創建動態代理類實例,構造時調用處理器對象作為參數參入;
CGLib動態代理
CGLib 全稱 Code Generation Library
,是一個強大的高性能字節碼生成類庫,可以實現運行期動態擴展Java類。
Spring在運行期采用CGLib的字節碼技術為類創建一個子類,並在子類中攔截所有父類方法的調用,織入橫切邏輯實現AOP面向切面編程。
注意事項
如果被代理的對象實現了接口,那么Spring默認會使用JDK動態代理,否則會強制使用CGLib實現動態代理(如果被代理的類被final關鍵字所修飾,那么代理會失敗)
關於兩者的性能,JDK動態代理所創建的代理對象,在1.8以前的版本中性能並不高,最新版本中性能得到了很大的提升,和CGLib相差不大。
Spring Boot中無法正常啟用JDK動態代理的問題
關於Spring的默認動態代理模式,官方文檔中顯示是JDK動態代理,但Spring Boot 2.2中發現即使強制指定@EnableAspectJAutoProxy(proxyTargetClass = false)
,生成的代理類依然顯示$EnhancerBySpringCGLIB。
在DefaultAopProxyFactory
中發現isProxyTargetClass
被指定為強制代理目標類,所以會采用ObjenesisCglibAopProxy
創建代理。
最后跟蹤到ValidationAutoConfiguration
中的一個Bean方法中,主動讀取了環境變量spring.aop.proxy-target-class
,而且默認值是true。
問題點算是找到了,不過這里只是一個函數校驗的處理器,竟然會強制讀取魔法配置,有些莫名其妙...手工添加配置后JDK代理恢復正常。
@Bean
@ConditionalOnMissingBean
public static MethodValidationPostProcessor methodValidationPostProcessor(Environment environment, @Lazy Validator validator) {
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
boolean proxyTargetClass = environment.getProperty("spring.aop.proxy-target-class", Boolean.class, true);
processor.setProxyTargetClass(proxyTargetClass);
processor.setValidator(validator);
return processor;
}