AOP 代理机制对性能的影响案例分析
在讨论动态代理机制时,一个不可避免的话题是性能。无论采用 JDK 动态代理还是 CGLIB 动态代理,本质上都是在原有目标对象上进行了封装和转换,这个过程需要消耗资源和性能。而 JDK 动态代理和 CGLIB 动态代理的内部实现过程本身也存在很大的差异性。本节将讨论这两种动态代理机制对系统运行性能所带来的影响。
测试案例设计
为了量化不同动态代理机制对性能的影响程度,我们将设计一个案例,在该案例中同样使用前面介绍的 AccountService 接口以及它的实现类 AccountServiceImpl。我们将通过创建一定数量的 AccountServiceImpl 实例并调用它的 doAccountTransaction()方法,触发动态代理机制。
为了能够基于不同的代理机制来创建代理对象,需要引入 Spring 中一个非常有用的注解,即 @Scope 注解。我们已经在第 2 章中了解到该注解可以用来设置 Bean 的作用域。其实,@Scope 注解还可以用来指定代理模式 ScopedProxyMode。在 Spring 中,Scoped-ProxyMode 是一个枚举,代码如下所示:
public enum ScopedProxyMode {
DEFAULT,NO,INTERFACES,TARGET_CLASS;
}
复制代码
请注意,ScopedProxyMode 中的 INTERFACES 代表的就是 JDK 动态代理,而 TARGET_CLASS 使用的则是 CGLIB 动态代理。
现在,让我们创建两个配置类 JDKProxyConfig 和 CGLIBProxyConfig,分别针对 AccountServiceImpl 指定两种不同的代理机制。其中,JDKProxyConfig 代码如下所示:
@Configuration
@EnableAspectJAutoProxy
public class JDKProxyConfig {
@Bean
@Scope(proxyMode=ScopedProxyMode.INTERFACES)
public AccountService accountService(){
return new AccountServiceImpl();
}
}
复制代码
而 CGLIBProxyConfig 则如下所示:
@Configuration
@EnableAspectJAutoProxy
public class CGLIBProxyAppConfig {
@Bean
@Scope(proxyMode=ScopedProxyMode.TARGET_CLASS)
public AccountService accountService(){
return new AccountServiceImpl();
}
}
复制代码
借助于这两个配置文件,我们就可以通过 AnnotationConfigApplicationContext 这个基于注解配置的应用上下文对象来获取添加了不同代理机制的 AccountServiceImpl 对象,实现方式如下所示:
//基于JDKProxyConfig获取AccountServiceImpl对象
AccountService accountService = new AnnotationConfigApplicationContext(JDKProxyConfig.class).getBean(AccountService.class);
//基于CGLIBProxyConfig获取AccountServiceImpl对象
AccountService accountService = new AnnotationConfigApplicationContext(CGLIBProxyConfig.class).getBean(AccountService.class);
复制代码
现在,准备工作已经完成,让我们编写一个测试用例来对不同代理机制的性能进行量化。测试用例如下所示:
@Test
public void testAopProxyPerformance() {
int countofObjects = 5000;
AccountServiceImpl[] unproxiedClasses = new AccountServiceImpl[countofObjects];
for (int i = 0; i < countofObjects; i++) {
unproxiedClasses[i] = new AccountServiceImpl();
}
AccountService[] cglibProxyClasses = new AccountService[countofObjects];
AccountService accountService = null;
for (int i = 0; i < countofObjects; i++) {
accountService = new AnnotationConfigApplicationContext(CGLIBProxyAppConfig.class).getBean(AccountService.class);
cglibProxyClasses[i] = accountService;
}
AccountService[] jdkProxyClasses = new AccountService[countofObjects];
for (int i = 0; i < countofObjects; i++) {
accountService = new AnnotationConfigApplicationContext(JDKProxyAppConfig.class).getBean(AccountService.class);
jdkProxyClasses[i] = accountService;
}
long timeTookForUnproxiedObjects = invokeTargetObjects(countofObjects, unproxiedClasses);
displayResults("NOProxy", timeTookForUnproxiedObjects);
long timeTookForJdkProxiedObjects = invokeTargetObjects(countofObjects, jdkProxyClasses);
displayResults("JDKProxy", timeTookForJdkProxiedObjects);
long timeTookForCglibProxiedObjects = invokeTargetObjects(countofObjects, cglibProxyClasses);
displayResults("CGLIBProxy", timeTookForCglibProxiedObjects);
}
复制代码
可以看到,我们分别针对不使用代理、使用 JDK 代理以及使用 CGLIB 代理的场景,创建了 5000 个 AccountServiceImpl 对象实例,并记录它们的创建时间。
完整的代码可以参考:https://github.com/tianminzheng/springboot-examples/tree/main/SpringAopProxyExample
案例结果分析
现在,让我们执行这个测试用例,得到的结果如下所示:
NOProxy: 562900(ns) 0(ms)
JDKProxy: 39113600(ns) 39(ms)
CGLIBProxy: 46222000(ns) 46(ms)
复制代码
以上量化结果取决于不同的机器配置,但不影响我们得出结论。从结果中不难看出,JDK 动态代理在性能上优于 CGLIB 动态代理,但相差并不大。事实上,通常情况下,我们不需要对上述结果有太多的担忧,因为相比代理机制带来的优势,添加代理的时间往往可以忽略不计。
评论