(轉(zhuǎn))Spring5 AOP 默認(rèn)使用Cglib? 從現(xiàn)象到源碼深度分析

原文地址

Spring5 AOP 默認(rèn)使用 Cglib 了?我第一次聽到這個說法是在一個微信群里:

群聊天

真的假的?查閱文檔

剛看到這個說法的時候,我是保持懷疑態(tài)度的。

大家都知道 Spring5 之前的版本 AOP 在默認(rèn)情況下是使用 JDK 動態(tài)代理的,那是不是 Spring5 版本真的做了修改呢?于是我打開 Spring Framework 5.x 文檔,再次確認(rèn)了一下:

文檔地址:https://docs.spring.io/spring/docs/5.2.0.RELEASE/spring-framework-reference/core.html#aop

Spring Framework 5.x 文檔

簡單翻譯一下。Spring AOP 默認(rèn)使用 JDK 動態(tài)代理,如果對象沒有實現(xiàn)接口,則使用 CGLIB 代理。當(dāng)然,也可以強(qiáng)制使用 CGLIB 代理。

什么?文檔寫錯了?!

當(dāng)我把官方文檔發(fā)到群里之后,又收到了這位同學(xué)的回復(fù):

文檔寫錯了?!

SpringBoot 2.x 代碼示例

為了證明文檔寫錯了,這位同學(xué)還寫了一個 DEMO。下面,就由我來重現(xiàn)一下這個 DEMO 程序:

運(yùn)行環(huán)境:SpringBoot 2.2.0.RELEASE 版本,內(nèi)置 Spring Framework 版本為 5.2.0.RELEASE 版本。同時添加 spring-boot-starter-aop 依賴,自動裝配 Spring AOP。

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.....()");
    }
}

默認(rèn)使用Cglib代理了?

UserServiceImpl實現(xiàn)了UserService接口,同時使用UserServiceAspectUserService#work方法進(jìn)行前置增強(qiáng)攔截。

從運(yùn)行結(jié)果來看,這里的確使用了 CGLIB 代理而不是 JDK 動態(tài)代理。

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

@EnableAspectJAutoProxy 源碼注釋

在 Spring Framework 中,是使用@EnableAspectJAutoProxy注解來開啟 Spring AOP 相關(guān)功能的。

Spring Framework 5.2.0.RELEASE 版本@EnableAspectJAutoProxy注解源碼如下:

@EnableAspectJAutoProxy源碼

通過源碼注釋我們可以了解到:在 Spring Framework 5.2.0.RELEASE 版本中,proxyTargetClass的默認(rèn)取值依舊是false,默認(rèn)還是使用 JDK 動態(tài)代理。

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

@EnableAspectJAutoProxy 的 proxyTargetClass 無效了?

接下來,我嘗試使用@EnableAspectJAutoProxy來強(qiáng)制使用 JDK 動態(tài)代理。

運(yùn)行環(huán)境:SpringBoot 2.2.0.RELEASE 版本,內(nèi)置 Spring Framework 版本為 5.2.0.RELEASE 版本。

proxyTargetClass設(shè)置無效了?

通過運(yùn)行發(fā)現(xiàn),還是使用了 CGLIB 代理。難道@EnableAspectJAutoProxyproxyTargetClass設(shè)置無效了?

Spring Framework 5.x

整理一下思路

  1. 有人說 Spring5 開始 AOP 默認(rèn)使用 CGLIB 了
  2. Spring Framework 5.x 文檔和 @EnableAspectJAutoProxy源碼注釋都說了默認(rèn)是使用 JDK 動態(tài)代理
  3. 程序運(yùn)行結(jié)果說明,即使繼承了接口,設(shè)置proxyTargetClassfalse,程序依舊使用 CGLIB 代理

等一下,我們是不是遺漏了什么?

示例程序是使用 SpringBoot 來運(yùn)行的,那如果不用 SpringBoot,只用 Spring Framework 會怎么樣呢?

運(yùn)行環(huán)境:Spring Framework 5.2.0.RELEASE 版本。
UserServiceImpl 和 UserServiceAspect 類和上文一樣,這里不在贅述。

Spring Framework 5.x
Spring Framework 5.x使用CGLIB

運(yùn)行結(jié)果表明: 在 Spring Framework 5.x 版本中,如果類實現(xiàn)了接口,AOP 默認(rèn)還是使用 JDK 動態(tài)代理。

再整理思路

  1. Spring5 AOP 默認(rèn)依舊使用 JDK 動態(tài)代理,官方文檔和源碼注釋沒有錯。
  2. SpringBoot 2.x 版本中,AOP 默認(rèn)使用 cglib,且無法通過proxyTargetClass進(jìn)行修改。
  3. 那是不是 SpringBoot 2.x 版本做了一些改動呢?

再探 SpringBoot 2.x

結(jié)果上面的分析,很有可能是 SpringBoot2.x 版本中,修改了 Spring AOP 的相關(guān)配置。那就來一波源碼分析,看一下內(nèi)部到底做了什么。

源碼分析

源碼分析,找對入口很重要。那這次的入口在哪里呢?

@SpringBootApplication是一個組合注解,該注解中使用@EnableAutoConfiguration實現(xiàn)了大量的自動裝配。

EnableAutoConfiguration也是一個組合注解,在該注解上被標(biāo)志了@Import。關(guān)于@Import注解的詳細(xì)用法,可以參看筆者之前的文章:https://mp.weixin.qq.com/s/7arh4sVH1mlHE0GVVbZ84Q

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

AutoConfigurationImportSelector實現(xiàn)了DeferredImportSelector接口。

在 Spring Framework 4.x 版本中,這是一個空接口,它僅僅是繼承了ImportSelector接口而已。而在 5.x 版本中拓展了DeferredImportSelector接口,增加了一個getImportGroup方法:

AutoConfigurationImportSelector#getImportGroup

在這個方法中返回了AutoConfigurationGroup類。這是AutoConfigurationImportSelector中的一個內(nèi)部類,他實現(xiàn)了DeferredImportSelector.Group接口。

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

導(dǎo)入配置類

通過斷點調(diào)試可以看到,和 AOP 相關(guān)的自動配置是通過org.springframework.boot.autoconfigure.aop.AopAutoConfiguration來進(jìn)行配置的。

AopAutoConfiguration源碼

真相大白

看到這里,可以說是真相大白了。在 SpringBoot2.x 版本中,通過AopAutoConfiguration來自動裝配 AOP。

默認(rèn)情況下,是肯定沒有spring.aop.proxy-target-class這個配置項的。而此時,在 SpringBoot 2.x 版本中會默認(rèn)使用 Cglib 來實現(xiàn)。

SpringBoot 2.x 中如何修改 AOP 實現(xiàn)

通過源碼我們也就可以知道,在 SpringBoot 2.x 中如果需要修改 AOP 的實現(xiàn),需要通過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 版本中,默認(rèn)還是使用 JDK 動態(tài)代理的。

SpringBoot 2.x 為何默認(rèn)使用 Cglib

SpringBoot 2.x 版本為什么要默認(rèn)使用 Cglib 來實現(xiàn) AOP 呢?這么做的好處又是什么呢?筆者從網(wǎng)上找到了一些資料,先來看一個 issue。

Spring Boot issue #5423

Use @EnableTransactionManagement(proxyTargetClass = true) #5423

https://github.com/spring-projects/spring-boot/issues/5423

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

issue

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

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

討厭的代理問題

假設(shè),我們有一個UserServiceImplUserService類,此時需要在UserContoller中使用UserService。在 Spring 中通常都習(xí)慣這樣寫代碼:

@Autowired
UserService userService;

在這種情況下,無論是使用 JDK 動態(tài)代理,還是 CGLIB 都不會出現(xiàn)問題。

但是,如果你的代碼是這樣的呢:

@Autowired
UserServiceImpl userService;

這個時候,如果我們是使用 JDK 動態(tài)代理,那在啟動時就會報錯:

啟動報錯

因為 JDK 動態(tài)代理是基于接口的,代理生成的對象只能賦值給接口變量。

而 CGLIB 就不存在這個問題。因為 CGLIB 是通過生成子類來實現(xiàn)的,代理對象無論是賦值給接口還是實現(xiàn)類這兩者都是代理對象的父類。

SpringBoot 正是出于這種考慮,于是在 2.x 版本中,將 AOP 默認(rèn)實現(xiàn)改為了 CGLIB。

更多的細(xì)節(jié)信息,讀者可以自己查閱上述 issue。

總結(jié)

  1. Spring 5.x 中 AOP 默認(rèn)依舊使用 JDK 動態(tài)代理。
  2. SpringBoot 2.x 開始,為了解決使用 JDK 動態(tài)代理可能導(dǎo)致的類型轉(zhuǎn)化異常而默認(rèn)使用 CGLIB。
  3. 在 SpringBoot 2.x 中,如果需要默認(rèn)使用 JDK 動態(tài)代理可以通過配置項spring.aop.proxy-target-class=false來進(jìn)行修改,proxyTargetClass配置已無效。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容