一個功能健全的Web服務器,要解決如下幾個問題:
- 部署在同一個服務器上的兩個Web應用程序使用的Java 類庫可以實現相互隔離。不能要求一個類庫在一個服務器中只有一份,服務器應當保證兩個應用程序的類庫可以互相獨立使用。
- 部署在同一個服務器上的兩個Web應用程序所使用的Java類庫可以互相共享,如果Java類庫不能共享使用,虛擬機的方法區很容易出現過度膨脹的風險,比如一台服務器上部署了10個使用Spring的程序。
- 服務器需要盡可能保證自身安全不受部署的Web應用程序影響。服務器所使用的類庫應該與應用程序使用的類庫互相獨立。
- 支持JSP的服務器,大部分都需要支持HotSwap功能(熱交換功能)
Tomcat目錄結構中,有三組目錄(“/common/*”,“/server/*”和“shared/*”)可以存放公用Java類庫,此外還有第四組Web應用程序自身的目錄“/WEB-INF/*”,把java類庫放置在這些目錄中的含義分別是:
- 放置在common目錄中:類庫可被Tomcat和所有的Web應用程序共同使用。
- 放置在server目錄中:類庫可被Tomcat使用,但對所有的Web應用程序都不可見。
- 放置在shared目錄中:類庫可被所有的Web應用程序共同使用,但對Tomcat自己不可見。
- 放置在/WebApp/WEB-INF目錄中:類庫僅僅可以被此Web應用程序使用,對Tomcat和其他Web應用程序都不可見。
為了支持這套目錄結構,並對目錄里面的類庫進行加載和隔離,Tomcat自定義了多個類加載器,這些類加載器按照經典的雙親委派模型來實現,如下圖所示 :
Spring加載問題:
Tomcat 加載器的實現清晰易懂,並且采用了官方推薦的“正統”的使用類加載器的方式。這時作者提一個問題:如果有 10 個 Web 應用程序都用到了spring的話,可以把Spring的jar包放到 common 或 shared 目錄下讓這些程序共享。Spring 的作用是管理每個web應用程序的bean,getBean時自然要能訪問到應用程序的類,而用戶的程序顯然是放在 /WebApp/WEB-INF 目錄中的(由 WebAppClassLoader 加載),那么在 CommonClassLoader 或 SharedClassLoader 中的 Spring 容器如何去加載並不在其加載范圍的用戶程序(/WebApp/WEB-INF/)中的Class呢?
解答:
spring根本不會去管自己被放在哪里,它統統使用線程上下文加載器來加載類,而線程上下文加載器默認設置為了WebAppClassLoader,也就是說哪個WebApp應用調用了spring,spring就去取該應用自己的WebAppClassLoader來加載bean。
線程上下文類加載器的適用場景:
1. 當高層提供了統一接口讓低層去實現,同時又要是在高層加載(或實例化)低層的類時,必須通過線程上下文類加載器來幫助高層的ClassLoader找到並加載該類。
2. 當使用本類托管類加載,然而加載本類的ClassLoader未知時,為了隔離不同的調用者,可以取調用者各自的線程上下文類加載器代為托管。
簡而言之就是ContextClassLoader
默認存放了AppClassLoader
的引用,由於它是在運行時被放在了線程中,所以不管當前程序處於何處(BootstrapClassLoader或是ExtClassLoader等),在任何需要的時候都可以用Thread.currentThread().getContextClassLoader()
取出應用程序類加載器來完成需要的操作。