Spring Boot 中的 AOP,到底是 JDK 動(dòng)態(tài)代理還是 Cglib 動(dòng)態(tài)代理?
好啦,開(kāi)始今天的正文。
大家都知道,AOP 底層是動(dòng)態(tài)代理,而 Java 中的動(dòng)態(tài)代理有兩種實(shí)現(xiàn)方式:
- 基于 JDK 的動(dòng)態(tài)代理
- 基于 Cglib 的動(dòng)態(tài)代理
這兩者最大的區(qū)別在于基于 JDK 的動(dòng)態(tài)代理需要被代理的對(duì)象有接口,而基于 Cglib 的動(dòng)態(tài)代理并不需要被代理對(duì)象有接口。
那么小伙伴們不禁要問(wèn),Spring 中的 AOP 是怎么實(shí)現(xiàn)的?是基于 JDK 的動(dòng)態(tài)代理還是基于 Cglib 的動(dòng)態(tài)代理?
1. Spring
先來(lái)說(shuō)結(jié)論,Spring 中的動(dòng)態(tài)代理,具體用哪種,分情況:
如果代理對(duì)象有接口,就用 JDK 動(dòng)態(tài)代理,否則就是 Cglib 動(dòng)態(tài)代理。
如果代理對(duì)象沒(méi)有接口,那么就直接是 Cglib 動(dòng)態(tài)代理。
來(lái)看看這段來(lái)自官方文檔的說(shuō)辭:
可以看到,即使在最新版的 Spring 中,依然是如上策略不變。即能用 JDK 做動(dòng)態(tài)代理就用 JDK,不能用 JDK 做動(dòng)態(tài)代理就用 Cglib,即首選 JDK 做動(dòng)態(tài)代理。
2. Spring Boot
Spring Boot 和 Spring 一脈相承,那么在動(dòng)態(tài)代理這個(gè)問(wèn)題上是否也是相同的策略呢?抱歉,這個(gè)還真不一樣。
Spring Boot 中對(duì)這個(gè)問(wèn)題的處理,以 Spring Boot2.0 為節(jié)點(diǎn),前后不一樣。
在 Spring Boot2.0 之前,關(guān)于 Aop 的自動(dòng)化配置代碼是這樣的(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 {
}
}
可以看到,這個(gè)自動(dòng)化配置主要是在討論 application.properties 配置文件中的 spring.aop.proxy-target-class 屬性的值。
具體起作用的是 @ConditionalOnProperty 注解,關(guān)于這個(gè)注解中的幾個(gè)屬性,松哥也來(lái)稍微說(shuō)下:
- prefix:配置文件的前綴。
- name:配置文件的名字,和 prefix 共同組成配置的 key。
- having:期待配置的值,如果實(shí)際的配置和 having 的值相同,則這個(gè)配置就會(huì)生效,否則不生效。
- matchIfMissing:如果開(kāi)發(fā)者沒(méi)有在 application.properties 中進(jìn)行配置,那么這個(gè)配置類是否生效。
基于如上介紹,我們很容易看出:
- 如果開(kāi)發(fā)者設(shè)置了spring.aop.proxy-target-class 為 false,則使用 JDK 代理。
- 如果開(kāi)發(fā)者設(shè)置了spring.aop.proxy-target-class 為 true,則使用 Cglib 代理。
- 如果開(kāi)發(fā)者一開(kāi)始就沒(méi)配置spring.aop.proxy-target-class 屬性,則使用 JDK 代理。
- 這是 Spring Boot 2.0 之前的情況。
再來(lái)看看 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 {
}
}
可以看到,大部分配置都是一樣的,有一個(gè)地方不太相同,那就是 matchIfMissing 屬性的值。可以看到,從 Spring Boot2.0 開(kāi)始,如果用戶什么都沒(méi)有配置,那么默認(rèn)情況下使用的是 Cglib 代理。
3. 實(shí)踐
最后我們寫(xiě)一個(gè)簡(jiǎn)單的例子驗(yàn)證一下我們的想法。
首先創(chuàng)建一個(gè) Spring Boot 項(xiàng)目(本案例使用最新版 Spring Boot,即默認(rèn)使用 Cglib 代理),加入三個(gè)依賴即可,如下:
<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>
接下來(lái)我們創(chuàng)建一個(gè) IUserService 接口,如下:
public interface IUserService {
void hello();
}
然后我們?cè)賮?lái)創(chuàng)建一個(gè)該接口的實(shí)現(xiàn)類:
@Service
public class UserServiceImpl implements IUserService {
@Override
public void hello() {
}
}
方法不用實(shí)現(xiàn)。
再來(lái)一個(gè)簡(jiǎn)單的切面:
@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());
}
}
最后再來(lái)一個(gè)簡(jiǎn)單的測(cè)試方法,注入 IUserService 實(shí)例:
@RestController
public class HelloController {
@Autowired
IUserService iUserService;
@GetMapping("/hello")
public void hello() {
iUserService.hello();
}
}
DBUEG 運(yùn)行一下,就可以看到 IUserService 是通過(guò) Cglib 來(lái)代理的。
如果我們想用 JDK 來(lái)代理,那么只需要在 application.properties 中添加如下配置即可:
spring.aop.proxy-target-class=false
添加完成后,重新 DEBUG,如下圖:
可以看到,已經(jīng)使用了 JDK 動(dòng)態(tài)代理了。
如果用的是 Spring Boot 1.5.22.RELEASE 這個(gè)版本,那么即使不在 application.properties 中添加配置,默認(rèn)也是 JDK 代理,這個(gè)我就不測(cè)試了,小伙伴們可以自己來(lái)試試。
4. 小結(jié)
總結(jié)一下:
- Spring 中的 AOP,有接口就用 JDK 動(dòng)態(tài)代理,沒(méi)有接口就用 Cglib 動(dòng)態(tài)代理。
- Spring Boot 中的 AOP,2.0 之前和 Spring 一樣;2.0 之后首選 Cglib 動(dòng)態(tài)代理,如果用戶想要使用 JDK 動(dòng)態(tài)代理,需要自己手動(dòng)配置。
just this。