本文在060216进行了修改,因为发现了测试中的错误!注意5.5和7的内容。
1、引子:
其实是ajoo的这篇“Nuts和Spring 1.2.6 效率对比”和“IoC容器的prototype性能测试 ”,他们在Javaeye上详细讨论了Spring的prototype的缺陷。
Spring的prototype指的就是singleton="false"的bean,具体可以看Spring参考手册“3.2.5. To singleton or not to singleton”介绍。
2、Webwork 2.2的Spring结合问题:
Webwork 2.2已经抛弃自己的IoC,默认使用Spring的IoC。
上在OpenSymphony的官方Wiki,和jscud后来的几篇文章中没有特别提出prototype的问题。但是托他们的福,我们已经顺利的使Spring和Webwork良好的协同工作起来了。
可是而后的一些问题却把prototype的问题搞得神秘起来……
ajoo的测试中指出Spring的prototype性能很差,参见后面参考中的一篇文章和Javaeye的讨论。
而后又发现robbin在Javaeye的Wiki上面的“集成webwork和spring”中的最后注到:
“注意:目前并不推荐使用Spring来管理Webwork Action,因为对于prototype类型的bean来说,Spring创建bean和调用bean的效率是很低的!更进一步信息请看IoC容器的prototype性能测试”
这就使我们常用的Spring+Webwork2.2的连接中使用的prototype的问题被摆出来了。
我现在的项目中使用了prototype的方式将Webwork Action使用Spring进行显示的装配,我担心这个性能的问题会很严重,所以今天花了半天时间具体测试了一下。
3、Prototype VS autowire的解释:
我不知道怎么命名两种方式好,所以这里先做个解释:
spring的配置中Action会有个id,如:
<property name="someDAO">
<ref bean="someDAO" />
</property>
</bean>
我指的prototype方式就是在xwork中这样配置:
而autowire方式就是指在xwork中这样配置:
看起来相同,但其实不同(我以前发过帖子,其中说这几种方法都可,但是其实它们的机制是不同的。
4、Portotye和autowire在XWork的SpringObjectFactory中是如何运作的:
我们先看一下代码,就能明白两者的区别了:
try {
return appContext.getBean(beanName);
} catch (NoSuchBeanDefinitionException e) {
Class beanClazz = getClassInstance(beanName);
return buildBean(beanClazz, extraContext);
}
}
public Object buildBean(Class clazz, Map extraContext) throws Exception {
Object bean;
try {
bean = autoWiringFactory.autowire(clazz, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false);
} catch (UnsatisfiedDependencyException e) {
// Fall back
bean = super.buildBean(clazz, extraContext);
}
bean = autoWiringFactory.applyBeanPostProcessorsBeforeInitialization(bean, bean.getClass().getName());
// We don’t need to call the init-method since one won’t be registered.
bean = autoWiringFactory.applyBeanPostProcessorsAfterInitialization(bean, bean.getClass().getName());
return autoWireBean(bean, autoWiringFactory);
}
public Object autoWireBean(Object bean) {
return autoWireBean(bean, autoWiringFactory);
}
如果按照autowire配置会使用第二个buildBean方法,而prototype会使用第一个buildBean方法。
5、我的测试,首先测试SpringObjectFactory的理论效率:
protected FileSystemXmlApplicationContext appContext;
protected SpringObjectFactory sof = null;
protected Map map = null;
final String[] paths = {
"WebRoot/WEB-INF/applicationContext.xml",
"WebRoot/WEB-INF/spring-daos.xml",
"WebRoot/WEB-INF/spring-actions.xml"
};
protected void setUp() throws Exception {
super.setUp();
appContext = new FileSystemXmlApplicationContext(paths);
sof = new SpringObjectFactory();
sof.setApplicationContext(appContext);
sof.setAutowireStrategy(AutowireCapableBeanFactory.AUTOWIRE_BY_NAME);
map = new HashMap();
}
public void testSpringObjectFacotyWithAutowire() {
long begin = System.currentTimeMillis();
try {
for (int i = 0; i <
sof.buildBean("com.wqh.action.XinfangNewsAction", map);
}
} catch (Exception e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("**************************Used time:" +
(begin – end));
}
public void testSpringObjectFacotyWithPrototype() {
long begin = System.currentTimeMillis();
try {
for (int i = 0; i < 100000; i++) {
sof.buildBean("xinfangNewsAction", map);
}
} catch (Exception e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("**************************Used time:" +
(begin – end));
}
public void testSpringObjectFacotyWithSpringProxyableObjectFactory() {
sof = new SpringProxyableObjectFactory();
sof.setApplicationContext(appContext);
sof.setAutowireStrategy(AutowireCapableBeanFactory.AUTOWIRE_BY_NAME);
long begin = System.currentTimeMillis();
try {
for (int i = 0; i < 100000; i++) {
sof.buildBean("com.wqh.action.XinfangNewsAction", map);
}
} catch (Exception e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("**************************Used time:" +
(begin – end));
}
}
重要的是测试结果:
**************************Used time:-16875
**************************Used time:-80500
**************************Used time:-12703(使用SpringProxyableObjectFactory()这个实现)
prototype是autowire运行时间的4.77X倍,十分可观。
5.5 巨大的反差,原来是我搞错了配置,发现了幕后黑手:
第二天,我又重新运行了5里面的测试。但是结果令人吃惊,运行了十多次,结果于昨天反差巨大,prototype方式获得的bean反而性能最快!
摘要两次测量结果
**************************Autowire Used time:-17578
**************************Prototype Used time:-7609
**************************Proxy Used time:-13063
———————————————–
**************************Autowire Used time:-17047
**************************Prototype Used time:-7609
**************************Proxy Used time:-12797
这是为什么呢?我百思不得其解,问题出在哪里呢?后来经过跟踪svn里面的提交纪录。我发现,我在昨天测试以后,把spring配置文件中的<beans default-autowire="autodetect">变成了<beans>。也就是没有打开自动检测的autowire!
而后就真相大白了。我有配置上default-autowire="autodetect"进行测试,结果:
**************************Autowire Used time:-16937
**************************Prototype Used time:-79750
**************************Proxy Used time:-12578
这和昨天的测试结果完全相同。也就是说我昨天写的4.77x的结果其实没有实际意义。倒是说明了Spring和Webwork集成的文章上面说的default-autowire="autodetect"是很坏的实践,即失去了name的灵活性也带来了巨大的性能损失。
而如果使用默认的Spring autowire配置下,prototype的性能已经很好了,实际上它工作起来应该是最快的。
6、在实际的Web项目中的性能对比:
我使用了我的一个小项目,就是反复调用一个action获取一个页面,其中有一个DAO注入。使用了JMeter进行了一个测试:2个线程,间隔0.5秒,循环50次,对比“据和报告中的”Throughput,单位/sec。
使用autowire方式:Avg. 148.34(吞吐量越高越好)
使用prototype方式:Avg. 138.5
也就是说在实际应用中两者也是有性能差距的,后者大约是前者性能的93%。
具体代码我不放出了,因为意义不大,大家也可以自己动手试验一下。
补充说明:
首先注意这个测试是在default-autowire="autodetect"下进行的。
测试的这个Action其实是一个空Action,它没有调用service和DAO,只是直接return SUCCESS,然后dispatcher到一个静态内容的jsp页面。我的本意是为了能够在获取Action占据的时间比例比较高的情况下分析性能区别。但是实际上却间接的夸大了在真正的实际应用中的性能差距。实际应用中如果加上service、DAO等逻辑的执行时间、模板View的渲染时间还有广域网上的网络传输时间,那么获取Action实例的时间差距可能就微乎其微了。
7、后续:
经过今天的思考,可以说完全改变了想法,重新汇总一下:
a、在不使用default-autowire="autodetect"时,Webwork 2.2的xwork中的action class使用spring的bean id配置的理论性能最好。而且,我认为如果不是为了追求配置上的简单,严重推荐关闭spring的default-autowire。
b、在使用default-autowire="autodetect、name、class"时,需要考虑你的需求。如果不使用Spring AOP提供的功能则在Webwork 2.2的xwork中的action class使用class全名比较好。如果使用Spring AOP的功能,则还是使用bean id。
c、在Spring中是否使用default-autowire是个需要慎重考虑的问题。autowire如果打开,命名会受到限制(class则更不推荐,受限更大,参考相关文档),它所带来的配置简化我认为只算是小小的语法糖果,背后却是吃掉它所埋下的隐患。
d、6中的测试还是有些说明意义的。7%的性能差距是在使用了default-autowire的方式下得出的,其中测试的那个action其实没有执行什么逻辑,而是一个直接dispatcher到success view的action,如果有商业逻辑包装,则性能差据估计会更小。因为实际上Action的执行过程、service、DAO等逻辑的执行过程和模板View的渲染过程(网络延迟)才是耗时大户。所以,关于性能应该下的结论是,prototype与否,在实际应用中性能差距是很小的,基本可以忽略不计。我们讨论的更多是编码的更好的实践。
e、autowire不使用Spring AOP相对还是trade off,因为虽然配置简单一点,但是对于使用Spring的声明性事务等内容会带来麻烦。虽然XML不那么好,但是显示配置带来的好处还是很多的。
f、谢谢robbin的提示。关于事务我也是无奈,放弃Action事务后难道给DAO多封装一层事务?如何没有事务依然使用HibernateDAOSurpport?Acegi的确不适合Web,使用WW的Inteceptor可以实现更舒适的解决方案。
g、SpringProxyableObjectFactory的问题……使用上难道只能改代码?找了半天没有这个东西的介绍。看来还是需要看看代码。不过发现现在Webwork和Xwork的代码又变动了很多……
h、我的测试是在Webwork2.2+Spring 1.2.6环境下测试的
8、参考资源:
Nuts和Spring 1.2.6 效率对比
http://www.javaeye.com/pages/viewpage.action?pageId=786
IoC容器的prototype性能测试
http://forum.javaeye.com/viewtopic.php?t=17622&postdays=0&postorder=asc&start=0
JavaEye的Wiki:集成webwork和spring
http://www.javaeye.com/pages/viewpage.action?pageId=860
WebWork – Spring官方Wiki
http://www.opensymphony.com/webwork/wikidocs/Spring.html
webwork2 + spring 结合的几种方法的小结
http://forum.javaeye.com/viewtopic.php?t=9990
WebWork2.2中结合Spring:"新的方式"
http://www.blogjava.net/scud/archive/2005/09/21/13667.html
我为什么不推荐对Action进行事务控制
http://www.javaeye.com/pages/viewpage.action?pageId=1205
我为什么不推荐使用Acegi
http://www.javaeye.com/pages/viewpage.action?pageId=1199
One thought on “Webwork 2.2的Action是否使用Spring的prototype获取的性能对比”