1 概述
在讨论动态代理机制时,一个不可避免的话题是性能。无论采用JDK动态代理还是CGLIB动态代理,本质上都是在原有目标对象上进行了封装和转换,这个过程需要消耗资源和性能。而JDK和CGLIB动态代理的内部实现过程本身也存在很大差异。下面将讨论两种动态代理机制对系统运行性能所带来的影响。
2 测试案例设计
为了量化不同动态代理对性能的影响程度,将设计一个案例,在该案例中同样使用前面介绍的AccountService接口以及它的实现类AccountServiceImpl。将通过创建一定数量的AccountServiceImpl实例并调用它的doAccountTransaction()方法,触发动态代理机制。
为了能够基于不同的代理机制来创建对象,需要引入Spring中一个非常有用的注解,即@Scope。这个注解可以用来设置Bean的作用域,还可以用来指定代理模式ScopedProxyMode,在Spring中,ScopedProxyMode是一个枚举,代码如下:
java
public enum ScopedProxyMode {
DEFAULT,
NO,
INTERFACES,
TARGET_CLASS;
private ScopedProxyMode() {
}
}
这里的INTERFACES代表的就是JDK动态代理,而TARGET_CLASS使用的则是CGLIB动态代理。现在,创建两个配置类JDKProxyConfig和CGLIBProxyConfig,分别针对AccountServiceImpl指定两种不同的代理机制。JDKProxyConfig代码如下:
java
@Configuration
@EnableAspectJAutoProxy
public class JDKProxyConfig {
@Bean
@Scope(proxyMode = ScopedProxyMode.INTERFACES)
public AccountService accountService(){
return new AccountServiceImpl();
}
}
CGLIBProxyConfig代码如下:
java
@EnableAspectJAutoProxy
@Configuration
public class CGLIBProxyConfig {
@Bean
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
public AccountService accountService(){
return new AccountServiceImpl();
}
}
借助于这两个配置类,就可以通过AnnotationConfigApplicationContext这个基于注解配置的应用上下文对象来获取添加了不同代理机制的AccountServiceImpl对象。准备工作已经完成,编写一个测试用例来对不同代理机制的性能进行量化。代码如下:
java
public class ProxyTest {
private static final Logger LOGGER = Logger.getLogger(ProxyTest.class);
@Test
public void testAopProxyPerformance() throws MinimumAccountException {
int countObjects = 5000;
AccountServiceImpl[] unproxiedClasses = new AccountServiceImpl[countObjects];
for (int i = 0; i < countObjects; i++) {
unproxiedClasses[i] = new AccountServiceImpl();
}
AccountService[] cglibProxyClasses = new AccountService[countObjects];
AccountService accountService = null;
for (int i = 0; i < countObjects; i++) {
accountService = new AnnotationConfigApplicationContext(CGLIBProxyConfig.class).getBean(AccountService.class);
cglibProxyClasses[i] = accountService;
}
AccountService[] jdkProxyClasses = new AccountService[countObjects];
for (int i = 0; i < countObjects; i++) {
accountService = new AnnotationConfigApplicationContext(JDKProxyConfig.class).getBean(AccountService.class);
jdkProxyClasses[i] = accountService;
}
long noProxy = invokeTargetObjects(countObjects, unproxiedClasses);
displayResults("NOProxy",noProxy);
long jdkProxy = invokeTargetObjects(countObjects, jdkProxyClasses);
displayResults("JDKProxy",jdkProxy);
long cglibProxy = invokeTargetObjects(countObjects, cglibProxyClasses);
displayResults("cglibProxy",cglibProxy);
}
private void displayResults(String label, long timeTook) {
LOGGER.info(label + ": " + timeTook + "(ns) " + (timeTook / 1000000) + "(ms)");
}
private long invokeTargetObjects(int countObjects,AccountService[] classes) throws MinimumAccountException {
long start = System.nanoTime();
Account source = new Account(101,"Account1");
Account dest = new Account(102,"Account2");
for (int i = 0; i < countObjects; i++) {
classes[i].doAccountTransaction(source, dest, 100);
}
long end = System.nanoTime();
return end - start;
}
}
运行结果:
可以看到,分别针对不使用代理、使用JDK代理和CGLIB代理的场景,创建了5000个AccountServiceImpl对象实例,并记录它们的创建时间。以上量化结果取决于不同的机器配置,但不影响得出结论。从结果中不难看出,JDK动态代理在性能上优于CGLIB动态代理,但相差并不大。
其他类的代码如下:
java
public class Account {
private String accountName;
private Integer accountNumber;
public String getAccountName() {
return accountName;
}
public void setAccountName(String accountName) {
this.accountName = accountName;
}
public Integer getAccountNumber() {
return accountNumber;
}
public void setAccountNumber(Integer accountNumber) {
this.accountNumber = accountNumber;
}
public Account(Integer accountNumber, String accountName) {
this.accountNumber = accountNumber;
this.accountName = accountName;
}
}
java
public interface AccountService {
boolean doAccountTransaction(Account source, Account dest, int amount);
}
java
public class AccountServiceImpl implements AccountService {
@Override
public boolean doAccountTransaction(Account source, Account dest, int amount) {
return true;
}
}