理解Tomcat的Classpath-常見問題以及如何解決

 

理解Tomcat的Classpath-常見問題以及如何解決

在很多Apache Tomcat用戶論壇,一個問題經常被提出,那就是如何配置Tomcat的classpath,使得一個web應用程序能夠找到類或者jar文件,從而可以正常工作。就像許多困擾Tomcat新用戶的問題一樣,這個問題也很容易解決。在這篇文章中,我們將會介紹Tomcat是如何產生和利用classpath的,然後一個一個解決大多數常見的與classpath相關的問題。

爲什麼Classpaths給Tomcat用戶帶來了麻煩

一個classpath就是一個參數,來告訴java虛擬機在哪裏可以找到類和包去運行一個程序。classpath總是在程序源碼外設置的,將其同程序分開可以允許java代碼以一種抽象的方式來引用類和包,允許程序可以在任何系統上被配置。爲什麼那些很有經驗的java用戶,他們已經非常清楚classpath是什麼了,但是在Tomcat運行程序的時候,還是會遇到這樣那樣的問題呢?

可能有以下三個原因:
1、Tomcat處理classpaths的方式與其他java程序是不同的
2、Tomcat的版本不同,其處理classpaths的方式也可能不同
3、Tomcat的文檔和默認的配置要求以一種特定的方式來完成某些事情。如果你不遵循這種方式,那麼你就會陷入麻煩之中。關於如何解決常見的classpath問題,沒有信息可以提供,比如外部依賴,共享依賴或者多版本的相同依賴。

Tomcat的Classpath如何不同於標準的Classpath

Apache Tomcat目的是儘可能的獨立,直觀和自動化,爲了有效的管理,標準化web應用程序的配置和部署過程,爲了安全和命名控件的考慮,限制對不同庫的訪問。這就是爲什麼不使用java classpath環境變量的原因了,java的classpath是聲明依賴類庫默認的地方,Tomcat的start腳本忽略了這個變量,在創建Tomcat系統classloader的時候產生了自己的classpaths。

想要理解Tomcat如何處理classpath的,看看以下Tomcat6的啓動過程:
1、java虛擬機的bootstrap loader加載java的核心類庫。java虛擬機使用JAVA_HOME環境變量來定位核心庫的位置。
2、Startup.sh,使用start參數調用Catalina.sh,重寫系統的classpath並加載bootstrap.jar和tomcat-juli.jar。這些資源僅對Tomcat可見。
3、爲每一個部署的Context創建Class loader,加載位於每個web應用程序WEB-INF/classes和WEB-INF/lib目錄下的所有類和jar文件。每個web應用程序僅僅可見自己目錄下的資源。
4、Common class loader加載位於$CATALINA_HOME/lib目錄下的所有類和jar文件,這些資源對所有應用程序和Tomcat可見。
Tomcat的類加載機制是如何在不同版本之間變化的

之前的Tomcat版本,其類加載機制有一些不同

Tomcat 4.x以及更早的版本,一個server loader負責加載Catalina類,現在由commons loader負責了。
Tomcat 5.x,一個shared loader負責加載在應用程序間共享的類,這些類位於$CATALINA_HOME/shared/lib。在Tomcat6中,這種方式被取消了。
Tomcat 5.x也包括了一個Catalina loader,加載所有的Catalina組件,現在也被Common loader取代了。

當你不能按照Tomcat要求的方式做事的時候,怎麼辦

如果你使用Tomcat文檔推薦的方式做事,你不應該有關於classpath的問題。你的wars包含了所有庫和包的複本,你沒有任何理由去在多個應用程序間共享一個jar文件,你不需要調用任何外在的資源,你也將不會遇到複雜的情況,例如一個web應用程序運行的時候需要一個jar文件的多個版本。但是如果你確實不能按照推薦的方式來做的時候,一個文件可以解決你所有的問題:catalina.properties。

使用catalina.properties來配置Tomcat Classpath

對於那些不想使用默認來加載方式的用戶來說,幸運的是,Tomcat的classpath選項不是硬編碼的,它們是從$CATALINA_HOME/conf/catalina.properties文件中讀取的。

這個文件包含了除bootstrap和system loader之外的所有其他的loaders,檢查這個文件,你會有一些新發現:
1、Server以及Shared loader還沒有被刪除,如果它們的屬性沒有定義,Commons loader負責處理。
2、被各種loaders加載的類和jar文件不會被自動加載,它們只是用一個簡單的通配符語法指定爲一組。
3、這裏沒有說你不能指定一個額外的倉庫,事實上就是說你是可以的。

server loader不應該改動,但是shared loader還是有許多有用的應用。(shared loader將會在啓動過程的最後階段加載它的類,在Commons loader之後)。現在讓我們看看一些常見的問題以及如何解決。

問題、解決方案和最佳實踐

問題:我的應用程序依賴一個外部的倉庫,我不能引用它。
讓Tomcat知道一個外部的倉庫,在catalina.properties文件的shared loader位置,使用正確的語法,聲明這個倉庫。語法基於你要配置的文件或倉庫的類型:
1、增加一個文件夾作爲類倉庫,使用“path/to/foldername”
2、增加一個文件夾下的所有jar文件作爲類倉庫,使用"path/to/foldername/*.jar"
3、增加單個jar文件作爲類倉庫,使用"file://path/to/foldername/jarname.jar"
4、調用環境變量,使用${}格式,例如${VARIABLE_NAME}
5、聲明多個資源,用逗號分隔開
6、所有的路徑相對於CATALINA_BASE或CATALINA_HOME,或者是絕對路徑

問題:我想多個應用程序共享一個jar文件,這個jar文件在Tomcat裏面。
除了一些常見的第三方庫(比如JDBC drivers),最好不要在$CATALINA_HOME/lib目錄下包含額外的庫,即使這樣在一些情況下是可行的。應該重新創建比如/shared/lib和/shared/classes目錄,然後在catalina.properties配置shared.loader屬性:
"shared/classes,shared/lib/*.jar"

問題:除了另一個框架,我在一個應用中使用了一個嵌入式Tomcat server,當我訪問框架組件的時候遇到classpath errors。
這個問題好像超出了這篇文章的範疇,但是作爲一個常見的classpath相關的問題,這裏對如何引起你的錯誤作一個簡單的介紹。
當嵌入到包含另外核心框架(Wicket或者Spring)的應用中時,Tomcat將在啓動框架的時候,使用System classloader加載核心類,而不是從應用的WEB-INF/lib目錄下加載。
java的類加載是懶加載,就是說請求某個類的第一個加載器擁有這個類剩下的生命週期。如果System classloader,它的類對web應用是不可見的,首先加載了框架相關的類,java虛擬機將會阻止來的其他實例被創建,這樣就引起了classpath錯誤。
解決這個問題的一種方式就是增加一個自定義的bootstrap classloader,使得這個classloader加載合適的庫,然後對程序剩下部分的啓動正常對待。

問題:我使用一個標準的應用程序,程序的war包含了依賴的所有包,但是我仍然遇到類定義錯誤。
這個問題可能是由許多事情引起的,包括編譯或部署過程不是很正確,但是最有可能是web應用程序的目錄結構不對造成的。
java命名轉換要求類名映射到存放這個類的目錄結構。例如,一個類com.mycompany.mygreat.class需要被存放到目錄WEB-INF/classes/com/mycompany/。
經常代碼中丟失一個點號就會引起classpath相關的問題。

問題:我的web應用程序的不同模塊需要使用同一個jar包的兩個不同版本。
這種情況常常出現在一個應用程序中使用多個web框架,這些web框架依賴一個庫的不同版本。
有幾種解決方案,但是它們都和classpath不相關。我們在這裏說這個問題,是因爲一些用戶試圖在框架的jar文件中的Manifest文件中指定依賴的庫的不同版本的classpath,來解決這個問題。
這種方式在一些web應用服務器中是支持的,所以一些用戶想要在Tomcat中也使用這種方式。但是在Manifest文件中指定classpath在Tomcat中是不支持的,這也不是Servlet說明的一部分。
有以下四種方式來解決這個問題:
1、你可以更新框架的版本,如果這裏能夠使得與其他框架依賴相同的版本。
2、你可以創建兩個或更多的自定義classloaders,每個jar文件一個,在WEB/INF/context.xml文件中配置,創建你所需要的兩個不同版本的類的實例。
3、你可以使用jarjar工具將框架和它依賴的庫打包成一個jar文件,那麼它們會一起被加載。
4、如果你發現你每隔一天就要處理這種情況,你應該考慮使用OSGI框架,這個框架有許多方法專門就是用來處理這種一個類的多個版本需要運行在同一個java虛擬機裏的情況的。

最佳實踐
1、避免在Tomcat裏使用Commons loader加載不屬於Tomcat標準發佈的庫和包,這可能會引起兼容錯誤。如果你需要在多個應用程序間共享一個庫或包,創建shared/lib和shared/classes目錄,然後配置到catalina.properties文件的Shared loader。
2、以上規則的一個例外就是常用的第三方共享庫,例如JDBC driver。這些應該放到$CATALINA_HOME/lib目錄下。
3、可能的話,儘可能按照Tomcat的開發者推薦的方式使用Tomcat,如果你發現你不得不頻繁配置classpath,你可能需要重新考慮你的開發過程了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章