Spring5 AOP 默認使用 Cglib 了?我第一次聽到這個說法是在一個微信羣裏:html
剛看到這個說法的時候,我是保持懷疑態度的。java
你們都知道 Spring5 以前的版本 AOP 在默認狀況下是使用 JDK 動態代理的,那是否是 Spring5 版本真的作了修改呢?因而我打開 Spring Framework 5.x 文檔,再次確認了一下:git
文檔地址:docs.spring.io/spring/docs…github
簡單翻譯一下。Spring AOP 默認使用 JDK 動態代理,若是對象沒有實現接口,則使用 CGLIB 代理。固然,也能夠強制使用 CGLIB 代理。spring
當我把官方文檔發到羣裏以後,又收到了這位同窗的回覆:json
爲了證實文檔寫錯了,這位同窗還寫了一個 DEMO。下面,就由我來重現一下這個 DEMO 程序:微信
運行環境:SpringBoot 2.2.0.RELEASE 版本,內置 Spring Framework 版本爲 5.2.0.RELEASE 版本。同時添加 spring-boot-starter-aop 依賴,自動裝配 Spring AOP。app
public interface UserService {
void work();
}
@Service
public class UserServiceImpl implements UserService {
@Override
public void work() {
System.out.println("開始幹活...coding...");
}
}
複製代碼
@Component
@Aspect
public class UserServiceAspect {
@Before("execution(* com.me.aop.UserService.work(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("UserServiceAspect.....()");
}
}
複製代碼
UserServiceImpl
實現了UserService
接口,同時使用UserServiceAspect
對UserService#work
方法進行前置加強攔截。框架
從運行結果來看,這裏的確使用了 CGLIB 代理而不是 JDK 動態代理。ide
難道真的是文檔寫錯了?!
在 Spring Framework 中,是使用@EnableAspectJAutoProxy
註解來開啓 Spring AOP 相關功能的。
Spring Framework 5.2.0.RELEASE 版本@EnableAspectJAutoProxy
註解源碼以下:
經過源碼註釋咱們能夠了解到:在 Spring Framework 5.2.0.RELEASE 版本中,proxyTargetClass
的默認取值依舊是false
,默認仍是使用 JDK 動態代理。
難道文檔和源碼註釋都寫錯了?!
接下來,我嘗試使用@EnableAspectJAutoProxy
來強制使用 JDK 動態代理。
運行環境:SpringBoot 2.2.0.RELEASE 版本,內置 Spring Framework 版本爲 5.2.0.RELEASE 版本。
經過運行發現,仍是使用了 CGLIB 代理。難道@EnableAspectJAutoProxy
的 proxyTargetClass
設置無效了?
@EnableAspectJAutoProxy
源碼註釋都說了默認是使用 JDK 動態代理proxyTargetClass
爲false
,程序依舊使用 CGLIB 代理等一下,咱們是否是遺漏了什麼?
示例程序是使用 SpringBoot 來運行的,那若是不用 SpringBoot,只用 Spring Framework 會怎麼樣呢?
運行環境:Spring Framework 5.2.0.RELEASE 版本。 UserServiceImpl 和 UserServiceAspect 類和上文同樣,這裏不在贅述。
運行結果代表: 在 Spring Framework 5.x 版本中,若是類實現了接口,AOP 默認仍是使用 JDK 動態代理。
proxyTargetClass
進行修改。結果上面的分析,頗有多是 SpringBoot2.x 版本中,修改了 Spring AOP 的相關配置。那就來一波源碼分析,看一下內部到底作了什麼。
源碼分析,找對入口很重要。那此次的入口在哪裏呢?
@SpringBootApplication
是一個組合註解,該註解中使用@EnableAutoConfiguration
實現了大量的自動裝配。
EnableAutoConfiguration
也是一個組合註解,在該註解上被標誌了@Import
。關於@Import
註解的詳細用法,能夠參看筆者以前的文章:mp.weixin.qq.com/s/7arh4sVH1…
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
複製代碼
AutoConfigurationImportSelector
實現了DeferredImportSelector
接口。
在 Spring Framework 4.x 版本中,這是一個空接口,它僅僅是繼承了ImportSelector
接口而已。而在 5.x 版本中拓展了DeferredImportSelector
接口,增長了一個getImportGroup
方法:
在這個方法中返回了AutoConfigurationGroup
類。這是AutoConfigurationImportSelector
中的一個內部類,他實現了DeferredImportSelector.Group
接口。
在 SpringBoot 2.x 版本中,就是經過AutoConfigurationImportSelector.AutoConfigurationGroup#process
方法來導入自動配置類的。
經過斷點調試能夠看到,和 AOP 相關的自動配置是經過org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
來進行配置的。
看到這裏,能夠說是真相大白了。在 SpringBoot2.x 版本中,經過AopAutoConfiguration
來自動裝配 AOP。
默認狀況下,是確定沒有spring.aop.proxy-target-class
這個配置項的。而此時,在 SpringBoot 2.x 版本中會默認使用 Cglib 來實現。
經過源碼咱們也就能夠知道,在 SpringBoot 2.x 中若是須要修改 AOP 的實現,須要經過spring.aop.proxy-target-class
這個配置項來修改。
#在application.properties文件中經過spring.aop.proxy-target-class來配置
spring.aop.proxy-target-class=false
複製代碼
這裏也提一下spring-configuration-metadata.json
文件的做用:在使用application.properties
或application.yml
文件時,IDEA 就是經過讀取這些文件信息來提供代碼提示的,SpringBoot 框架本身是不會來讀取這個配置文件的。
能夠看到,在 SpringBoot 1.5.x 版本中,默認仍是使用 JDK 動態代理的。
SpringBoot 2.x 版本爲何要默認使用 Cglib 來實現 AOP 呢?這麼作的好處又是什麼呢?筆者從網上找到了一些資料,先來看一個 issue。
Use @EnableTransactionManagement(proxyTargetClass = true) #5423
在這個 issue 中,拋出了這樣一個問題:
翻譯一下:咱們應該使用@EnableTransactionManagement(proxyTargetClass = true)來防止人們不使用接口時出現討厭的代理問題。
這個"不使用接口時出現討厭的代理問題"是什麼呢?思考一分鐘。
假設,咱們有一個UserServiceImpl
和UserService
類,此時須要在UserContoller
中使用UserService
。在 Spring 中一般都習慣這樣寫代碼:
@Autowired
UserService userService;
複製代碼
在這種狀況下,不管是使用 JDK 動態代理,仍是 CGLIB 都不會出現問題。
可是,若是你的代碼是這樣的呢:
@Autowired
UserServiceImpl userService;
複製代碼
這個時候,若是咱們是使用 JDK 動態代理,那在啓動時就會報錯:
由於 JDK 動態代理是基於接口的,代理生成的對象只能賦值給接口變量。
而 CGLIB 就不存在這個問題。由於 CGLIB 是經過生成子類來實現的,代理對象不管是賦值給接口仍是實現類這二者都是代理對象的父類。
SpringBoot 正是出於這種考慮,因而在 2.x 版本中,將 AOP 默認實現改成了 CGLIB。
更多的細節信息,讀者能夠本身查閱上述 issue。
spring.aop.proxy-target-class=false
來進行修改,proxyTargetClass
配置已無效。issue:Default CGLib proxy setting default cannot be overridden by using core framework annotations (@EnableTransactionManagement, @EnableAspectJAutoProxy) #12194
這個 issue 也聊到了關於proxyTargetClass
設置失效的問題,討論內容包括:@EnableAspectJAutoProxy
、@EnableCaching
和 @EnableTransactionManagement
。感興趣的讀者能夠自行查閱該 issue內容。
歡迎關注我的公衆號,一塊兒學習成長: