問題現(xiàn)象:在做web應(yīng)用時會碰到這種情況,某些地方無法通過web當(dāng)中的ApplicationContext來獲得springIOC容器提供的bean,比如提供給外界的webservice接口,這個時候就需要手工通過ClassPathXmlApplicationContext等方式來獲取ApplicationContext,代碼如下:
ApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext-*.xml");
IXXXService xxxservice = (IXXXService ) context
.getBean("xxxservice ");
這是一段很典型的加載。
然而,正是這種看似到處都是的加載卻為后面的BUG埋下伏筆。
xxxservice是具體的業(yè)務(wù)類,它向下與DAO依賴并控制著事務(wù),這里代表了一個經(jīng)典而且簡單的service,具體配置略去,值得一提的是scope,這里沒有指定,默認的是單例。
一切都是那么順利,像這樣的service代碼寫的應(yīng)該不下幾百個,可能諸位寫的更多,過程依然很陶醉,修改完畢。測試,再測試。什么?ORA-12519錯誤!見鬼,我打造的這套號稱簡易快速的SSH2框架已經(jīng)在多個項目好評無數(shù)久經(jīng)考驗了,寫了不下幾百次的service居然報ORA-12519錯誤。
迅速打開PLSQL,檢查數(shù)據(jù)庫session,Select Count(1) From v$session t Where t.SCHEMANAME='XXX';
隨著service的執(zhí)行,session數(shù)在增加,沒有減少的意思。是的,當(dāng)時就是這樣。
解決思路:這種錯誤出現(xiàn)在久經(jīng)考驗的框架當(dāng)中,我心里是相當(dāng)不安的,居然會有這種低級趣味的錯誤。整理思路開始分析:這段代碼唯一與以前不同的地方就是,我們在web應(yīng)用中,是通過容器加載提供bean的,只有容器啟動的時候才會加載xml。那么重點就應(yīng)該是關(guān)注XML的加載方式了。
在這里我們用的是ApplicationContext接口。注意看spring文檔3.5.1.2.2 在非web應(yīng)用中優(yōu)雅地關(guān)閉springioc容器。它這里用到的是AbstractApplicationContext,在取得bean后,再執(zhí)行一個context.registerShutdownHook();
這里實驗一把,將ApplicationContext改成AbstractApplicationContext,執(zhí)行context.close()。結(jié)果出來了,session已被正?;厥眨嫦酀u漸浮出水面。
結(jié)論:每次加載context的做法相當(dāng)于每次都生成了一次新的spring容器,在默認單例的情況下,如果不及時關(guān)閉context。service所依賴的DAO當(dāng)中創(chuàng)建的dataSource也一直存在(包括所有的單例情況下所生成的類),從日志看,service事務(wù)管轄中的session確實已經(jīng)關(guān)閉,但SessionFactory還是存在的。只有在容器關(guān)閉的情況下,并指定了dataSource實例配置中的destroy-method="close",dataSource單例才會被釋放。
spring文檔當(dāng)中對生命周期也描述的很清楚。通過DisposableBean或者指定destroy-method都能很好的釋放單例對象。而prototype類型的對象需要客戶端顯式的指定釋放,釋放對象完全是客戶端控制,spring不負責(zé)釋放。
所以,要改善context的加載方式,盡量的少多次去加載,實在沒辦法的情況下,一定要記得關(guān)閉。
最后,寫代碼的隨意性,圖省事,不經(jīng)思考,是造成這種BUG的罪惡根源。