大家都知道,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 {@Overridepublic void hello() {}}
方法不用實現。
再來一個簡單的切面:
@Aspect
@Component
@EnableAspectJAutoProxy
public class LogAspect {@Before("execution(* org.lzx.demo.UserServiceImpl.*(..))")public void before(JoinPoint jp) {System.out.println("jp.getSignature().getName() = " + jp.getSignature().getName());}}
最后再來一個簡單的測試方法,注入 IUserService 實例:
@RestController
public class HelloController {@Autowiredprivate 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 動態代理,需要自己手動配置。