本文在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,如:
<bean id="someAction" class="com.tin.action.SomeAction" parent="basicActionWithAuthtication" singleton="false">
<property name="someDAO">
<ref bean="someDAO" />
</property>
</bean>
我指的prototype方式就是在xwork中這樣配置:
<action name="someAction" class="someAction">
而autowire方式就是指在xwork中這樣配置:
<action name="someAction" class="com.tin.action.SomeAction">
看起來相同,但其實不同(我以前發過帖子,其中說這幾種方法都可,但是其實它們的機制是不同的。
4、Portotye和autowire在XWork的SpringObjectFactory中是如何運作的:
我們先看一下代碼,就能明白兩者的區別了:

public Object buildBean(String beanName, Map extraContext) throws Exception
{

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的理論效率:

public class testSpringObjectFactory extends TestCase
{
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 < 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));
}


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://m.tkk7.com/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