程式者的胡言亂語
從Struts無法啟動的怪事到DTD的載入
前一陣子我們遇到了一個很怪的問題,在開發端一切都正常,搬到測試環境時,發現連Struts都無法啟動,從stack trace看來,問題出在org.apache.struts.action.ActionServlet,它有一個init(),裡頭會做initModuleConfig(),在initModuleConfig()中,會去讀/WEB-INF/struts-config.xml這個檔案。異常就是從這個function被丟出來的,第一個直覺反應就是這個XML格式有問題,但同一個XML為什麼在開發環境可以正常運作,但在測試環境就出問題,這真是令人百思不得其解。
後來我們把log4j對struts的logging打開,發現從這個method丟出來的異常竟然是java.net.UnknownHostException。這明顯的跟網路活動有關,載入XML會需要什麼網路活動?載入DTD-這是很立即的反應。原來我們的測試環境是在一個private network裡,在這個網路中,必須透過proxy才能夠連線出去,由於DTD的位置指向http://jakarta.apache.org/struts/dtds/struts-config_1_2.dtd,所以在剖析這個XML時,引發了到這個網址下載DTD的活動,但又由於private network的關係,使得它根本連不上這個網址。在當時,治標不治本的方法,就是在啟動JVM時,加上-Dhttp.proxyHost以及-Dhttp.proxyPort的選項,指定該環境下可以對外連線的proxy位址,果然解決Struts不能啟動的問題。
不過有趣的是,我們在該環境中也早就部署了另一個應用系統,為什麼它的Struts就運作的一切正常,沒有遇到這個問題呢?再來,倘若每個DTD的載入都會引發網路活動,那麼勢必會遇到效能上的問題,影響剖析XML時的速度,事實肯定不是這樣子的,而且在離線的情況下,豈不是就不能執行Struts了呢?Struts一定會從某個本地端位置載入本地端版本的DTD,為什麼它最終引發到網路上取得DTD,一定是因為本地端版本的DTD找不到。好了,問題來了,Struts是怎麼載入DTD的?或更廣泛一點,其他同樣需要剖析XML的應用程式,是怎麼處理DTD載入的活動的?
其實Struts把struts-config_*dtd放在struts.jar的org\apache\struts\resources中,這也就是它的DTD本地端版本的所在。在我們遇到的情況中,我們使用的是struts 1.1版,但剛好有人放進了struts 1.2版的設定,而在struts 1.1版中,是沒有1.2版設定相對應的DTD。在開發環境由於沒有網路的限制,所以運作的還正常,因為即使找不到本地端版本,還是會到網路上去下載。但是在測試環境就不行了。根本的解決之道,當然就是將設定改回1.1版即可。
我花了點時間追溯了一下Struts載入設定檔的方式,以我下載的struts 1.3.5的source code來看,在org.apache.struts.action.ActionServlet中定義了字串陣列:
protected String[] registrations =
{
"-//Apache Software Foundation//DTD Struts Configuration 1.0//EN",
"/org/apache/struts/resources/struts-config_1_0.dtd",
"-//Apache Software Foundation//DTD Struts Configuration 1.1//EN",
"/org/apache/struts/resources/struts-config_1_1.dtd",
"-//Apache Software Foundation//DTD Struts Configuration 1.2//EN",
"/org/apache/struts/resources/struts-config_1_2.dtd",
"-//Apache Software Foundation//DTD Struts Configuration 1.3//EN",
"/org/apache/struts/resources/struts-config_1_3.dtd",
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN",
"/org/apache/struts/resources/web-app_2_3.dtd"
};
位於偶數索引值的是PUBLIC ID,而位於奇數索引值位置的就是該PUBLIC ID對應到的DTD檔在classpath上的位置。這個ActionServlet用來向另一個class-org.apache.commons.digester.Digester註冊用的表格。在ActionServlet中會在initConfigDigester()裡產生Digester的instance,並且根據registrations表的內容,透過呼叫Digester的register()來進行註冊的動作。而在Digester的register()的註解說明中,它是這麼說的:
Digeste contains an internal EntityResolver implementation. This maps PUBLICID's to URLs (from which the resource will be loaded). A common use case for this method is to register local URLs (possibly computed at runtime by a classloader) for DTDs. This allows the performance advantage of using a local version without having to ensure every SYSTEM URI on every processed xml document is local. This implementation provides only basic functionality. If more sophisticated features are required, using setEntityResolver to set a custom resolver is recommended.
顯然答案就在這裡囉。用Digester來做XML的剖析時,別忘了第一次呼叫parse()前要先做該做的register()動作呀。
Posted at 11:36上午 九月 08, 2006 by Chien-Hsing Wang in Java |
星期五 九月 08, 2006
