Spring Boot 中的 AOP,到底是 JDK 動態代理還是 Cglib 動態代理?

語言: CN / TW / HK

好啦,開始今天的正文。

大家都知道,AOP 底層是動態代理,而 Java 中的動態代理有兩種實現方式:

  • 基於 JDK 的動態代理
  • 基於 Cglib 的動態代理

這兩者最大的區別在於基於 JDK 的動態代理需要被代理的物件有介面,而基於 Cglib 的動態代理並不需要被代理物件有介面。

那麼小夥伴們不禁要問,Spring 中的 AOP 是怎麼實現的?是基於 JDK 的動態代理還是基於 Cglib 的動態代理?

1. Spring

先來說結論,Spring 中的動態代理,具體用哪種,分情況:

如果代理物件有介面,就用 JDK 動態代理,否則就是 Cglib 動態代理。

如果代理物件沒有介面,那麼就直接是 Cglib 動態代理。

來看看這段來自官方文件的說辭:

可以看到,即使在最新版的 Spring 中,依然是如上策略不變。即能用 JDK 做動態代理就用 JDK,不能用 JDK 做動態代理就用 Cglib,即首選 JDK 做動態代理。

2. Spring Boot

Spring Boot 和 Spring 一脈相承,那麼在動態代理這個問題上是否也是相同的策略呢?抱歉,這個還真不一樣。

Spring Boot 中對這個問題的處理,以 Spring Boot2.0 為節點,前後不一樣。

在 Spring Boot2.0 之前,關於 Aop 的自動化配置程式碼是這樣的(Spring Boot 1.5.22.RELEASE):

@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {

 @Configuration
 @EnableAspectJAutoProxy(proxyTargetClass = false)
 @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
   matchIfMissing = true)
 public static class JdkDynamicAutoProxyConfiguration {

 }

 @Configuration
 @EnableAspectJAutoProxy(proxyTargetClass = true)
 @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
   matchIfMissing = false)
 public static class CglibAutoProxyConfiguration {

 }

}

可以看到,這個自動化配置主要是在討論 application.properties 配置檔案中的 spring.aop.proxy-target-class 屬性的值。

具體起作用的是 @ConditionalOnProperty 註解,關於這個註解中的幾個屬性,鬆哥也來稍微說下:

  • prefix:配置檔案的字首。
  • name:配置檔案的名字,和 prefix 共同組成配置的 key。
  • having:期待配置的值,如果實際的配置和 having 的值相同,則這個配置就會生效,否則不生效。
  • matchIfMissing:如果開發者沒有在 application.properties 中進行配置,那麼這個配置類是否生效。

基於如上介紹,我們很容易看出:

  • 如果開發者設定了spring.aop.proxy-target-class 為 false,則使用 JDK 代理。
  • 如果開發者設定了spring.aop.proxy-target-class 為 true,則使用 Cglib 代理。
  • 如果開發者一開始就沒配置spring.aop.proxy-target-class 屬性,則使用 JDK 代理。
  • 這是 Spring Boot 2.0 之前的情況。

再來看看 Spring Boot 2.0(含)之後的情況(Spring Boot 2.0.0.RELEASE):

@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class,
  AnnotatedElement.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {

 @Configuration
 @EnableAspectJAutoProxy(proxyTargetClass = false)
 @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false)
 public static class JdkDynamicAutoProxyConfiguration {

 }

 @Configuration
 @EnableAspectJAutoProxy(proxyTargetClass = true)
 @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
 public static class CglibAutoProxyConfiguration {

 }

}

可以看到,大部分配置都是一樣的,有一個地方不太相同,那就是 matchIfMissing 屬性的值。可以看到,從 Spring Boot2.0 開始,如果使用者什麼都沒有配置,那麼預設情況下使用的是 Cglib 代理。

3. 實踐

最後我們寫一個簡單的例子驗證一下我們的想法。

首先建立一個 Spring Boot 專案(本案例使用最新版 Spring Boot,即預設使用 Cglib 代理),加入三個依賴即可,如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

接下來我們建立一個 IUserService 介面,如下:

public interface IUserService {
    void hello();
}

然後我們再來建立一個該介面的實現類:

@Service
public class UserServiceImpl implements IUserService {
    @Override
    public void hello() {

    }
}

方法不用實現。

再來一個簡單的切面:

@EnableAspectJAutoProxy
@Aspect
@Component
public class LogAspect {
    @Before("execution(* org.javaboy.demo.UserServiceImpl.*(..))")
    public void before(JoinPoint jp) {
        System.out.println("jp.getSignature().getName() = " + jp.getSignature().getName());
    }
}

最後再來一個簡單的測試方法,注入 IUserService 例項:

@RestController
public class HelloController {
    @Autowired
    IUserService iUserService;
    @GetMapping("/hello")
    public void hello() {
        iUserService.hello();
    }
}

DBUEG 執行一下,就可以看到 IUserService 是通過 Cglib 來代理的。

如果我們想用 JDK 來代理,那麼只需要在 application.properties 中新增如下配置即可:

spring.aop.proxy-target-class=false

新增完成後,重新 DEBUG,如下圖:

可以看到,已經使用了 JDK 動態代理了。

如果用的是 Spring Boot 1.5.22.RELEASE 這個版本,那麼即使不在 application.properties 中新增配置,預設也是 JDK 代理,這個我就不測試了,小夥伴們可以自己來試試。

4. 小結

總結一下:

  • Spring 中的 AOP,有介面就用 JDK 動態代理,沒有介面就用 Cglib 動態代理。
  • Spring Boot 中的 AOP,2.0 之前和 Spring 一樣;2.0 之後首選 Cglib 動態代理,如果使用者想要使用 JDK 動態代理,需要自己手動配置。

just this。