驚人!Spring5 AOP 默認使用Cglib ?從現象到源碼深度分析

Spring5 AOP 默認使用 Cglib 了?我第一次聽到這個說法是在一個微信羣裏:html

羣聊天

真的假的?查閱文檔

剛看到這個說法的時候,我是保持懷疑態度的。java

你們都知道 Spring5 以前的版本 AOP 在默認狀況下是使用 JDK 動態代理的,那是否是 Spring5 版本真的作了修改呢?因而我打開 Spring Framework 5.x 文檔,再次確認了一下:git

文檔地址:docs.spring.io/spring/docs…github

Spring Framework 5.x 文檔

簡單翻譯一下。Spring AOP 默認使用 JDK 動態代理,若是對象沒有實現接口,則使用 CGLIB 代理。固然,也能夠強制使用 CGLIB 代理。spring

什麼?文檔寫錯了?!

當我把官方文檔發到羣裏以後,又收到了這位同窗的回覆:json

文檔寫錯了?!

SpringBoot 2.x 代碼示例

爲了證實文檔寫錯了,這位同窗還寫了一個 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.....()");
    }
}
複製代碼

默認使用Cglib代理了?

UserServiceImpl實現了UserService接口,同時使用UserServiceAspectUserService#work方法進行前置加強攔截。框架

從運行結果來看,這裏的確使用了 CGLIB 代理而不是 JDK 動態代理。ide

難道真的是文檔寫錯了?!

@EnableAspectJAutoProxy 源碼註釋

在 Spring Framework 中,是使用@EnableAspectJAutoProxy註解來開啓 Spring AOP 相關功能的。

Spring Framework 5.2.0.RELEASE 版本@EnableAspectJAutoProxy註解源碼以下:

@EnableAspectJAutoProxy源碼

經過源碼註釋咱們能夠了解到:在 Spring Framework 5.2.0.RELEASE 版本中,proxyTargetClass的默認取值依舊是false,默認仍是使用 JDK 動態代理。

難道文檔和源碼註釋都寫錯了?!

@EnableAspectJAutoProxy 的 proxyTargetClass 無效了?

接下來,我嘗試使用@EnableAspectJAutoProxy來強制使用 JDK 動態代理。

運行環境:SpringBoot 2.2.0.RELEASE 版本,內置 Spring Framework 版本爲 5.2.0.RELEASE 版本。

proxyTargetClass設置無效了?

經過運行發現,仍是使用了 CGLIB 代理。難道@EnableAspectJAutoProxyproxyTargetClass設置無效了?

Spring Framework 5.x

整理一下思路

  1. 有人說 Spring5 開始 AOP 默認使用 CGLIB 了
  2. Spring Framework 5.x 文檔和 @EnableAspectJAutoProxy源碼註釋都說了默認是使用 JDK 動態代理
  3. 程序運行結果說明,即便繼承了接口,設置proxyTargetClassfalse,程序依舊使用 CGLIB 代理

等一下,咱們是否是遺漏了什麼?

示例程序是使用 SpringBoot 來運行的,那若是不用 SpringBoot,只用 Spring Framework 會怎麼樣呢?

運行環境:Spring Framework 5.2.0.RELEASE 版本。 UserServiceImpl 和 UserServiceAspect 類和上文同樣,這裏不在贅述。

Spring Framework 5.x

Spring Framework 5.x使用CGLIB

運行結果代表: 在 Spring Framework 5.x 版本中,若是類實現了接口,AOP 默認仍是使用 JDK 動態代理。

再整理思路

  1. Spring5 AOP 默認依舊使用 JDK 動態代理,官方文檔和源碼註釋沒有錯。
  2. SpringBoot 2.x 版本中,AOP 默認使用 cglib,且沒法經過proxyTargetClass進行修改。
  3. 那是否是 SpringBoot 2.x 版本作了一些改動呢?

再探 SpringBoot 2.x

結果上面的分析,頗有多是 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方法:

AutoConfigurationImportSelector#getImportGroup

在這個方法中返回了AutoConfigurationGroup類。這是AutoConfigurationImportSelector中的一個內部類,他實現了DeferredImportSelector.Group接口。

在 SpringBoot 2.x 版本中,就是經過AutoConfigurationImportSelector.AutoConfigurationGroup#process方法來導入自動配置類的。

導入配置類

經過斷點調試能夠看到,和 AOP 相關的自動配置是經過org.springframework.boot.autoconfigure.aop.AopAutoConfiguration來進行配置的。

AopAutoConfiguration源碼

真相大白

看到這裏,能夠說是真相大白了。在 SpringBoot2.x 版本中,經過AopAutoConfiguration來自動裝配 AOP。

默認狀況下,是確定沒有spring.aop.proxy-target-class這個配置項的。而此時,在 SpringBoot 2.x 版本中會默認使用 Cglib 來實現。

SpringBoot 2.x 中如何修改 AOP 實現

經過源碼咱們也就能夠知道,在 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

這裏也提一下spring-configuration-metadata.json文件的做用:在使用application.propertiesapplication.yml文件時,IDEA 就是經過讀取這些文件信息來提供代碼提示的,SpringBoot 框架本身是不會來讀取這個配置文件的。

SringBoot 1.5.x 又是怎麼樣的

SringBoot 1.5.x

能夠看到,在 SpringBoot 1.5.x 版本中,默認仍是使用 JDK 動態代理的。

SpringBoot 2.x 爲什麼默認使用 Cglib

SpringBoot 2.x 版本爲何要默認使用 Cglib 來實現 AOP 呢?這麼作的好處又是什麼呢?筆者從網上找到了一些資料,先來看一個 issue。

Spring Boot issue #5423

Use @EnableTransactionManagement(proxyTargetClass = true) #5423

github.com/spring-proj…

在這個 issue 中,拋出了這樣一個問題:

image.png

翻譯一下:咱們應該使用@EnableTransactionManagement(proxyTargetClass = true)來防止人們不使用接口時出現討厭的代理問題。

這個"不使用接口時出現討厭的代理問題"是什麼呢?思考一分鐘。

討厭的代理問題

假設,咱們有一個UserServiceImplUserService類,此時須要在UserContoller中使用UserService。在 Spring 中一般都習慣這樣寫代碼:

@Autowired
UserService userService;
複製代碼

在這種狀況下,不管是使用 JDK 動態代理,仍是 CGLIB 都不會出現問題。

可是,若是你的代碼是這樣的呢:

@Autowired
UserServiceImpl userService;
複製代碼

這個時候,若是咱們是使用 JDK 動態代理,那在啓動時就會報錯:

啓動報錯

由於 JDK 動態代理是基於接口的,代理生成的對象只能賦值給接口變量。

而 CGLIB 就不存在這個問題。由於 CGLIB 是經過生成子類來實現的,代理對象不管是賦值給接口仍是實現類這二者都是代理對象的父類。

SpringBoot 正是出於這種考慮,因而在 2.x 版本中,將 AOP 默認實現改成了 CGLIB。

更多的細節信息,讀者能夠本身查閱上述 issue。

總結

  1. Spring 5.x 中 AOP 默認依舊使用 JDK 動態代理。
  2. SpringBoot 2.x 開始,爲了解決使用 JDK 動態代理可能致使的類型轉化異常而默認使用 CGLIB。
  3. 在 SpringBoot 2.x 中,若是須要默認使用 JDK 動態代理能夠經過配置項spring.aop.proxy-target-class=false來進行修改,proxyTargetClass配置已無效。

延伸閱讀

issue:Default CGLib proxy setting default cannot be overridden by using core framework annotations (@EnableTransactionManagement, @EnableAspectJAutoProxy) #12194

github.com/spring-proj…

這個 issue 也聊到了關於proxyTargetClass設置失效的問題,討論內容包括:@EnableAspectJAutoProxy@EnableCaching@EnableTransactionManagement。感興趣的讀者能夠自行查閱該 issue內容。


歡迎關注我的公衆號,一塊兒學習成長:

Coder小黑
相關文章
相關標籤/搜索