1、整體分析
主流的Java Web服務器,如Tomcat、Jetty、WebLogic、WebSphere等都實現了本身定義的類加載器(通常都不止一個)。由於一個功能健全的Web服務器,須要解決以下的幾個問題:java
- 部署在同一個服務器上的兩個Web應用程序使用的Java 類庫能夠實現相互隔離,這是最基本的要求.兩個不一樣應用程序可能會依賴同一個第三方類庫的不一樣版本的,不能要求一個類庫在一個服務器中只有一份,服務器應當保證兩個應用程序的類庫能夠互相獨立使用
- 部署在同一個服務器上的兩個Web應用程序所使用的Java類庫能夠互相共享,這個需求也很常見,若是Java類庫不能共享使用,虛擬機的方法區很容易出現過分膨脹的風險
- 服務器須要儘量保證自身安全不受部署的Web應用程序影響.目前有許多主流的Java Web服務器都使用Java語言開發,所以服務器自己也有類庫依賴的問題,通常來講,基於安全的考慮,服務器所使用的類庫應該與應用程序使用的類庫互相獨立
- 支持JSP的服務器,大部分都須要支持HotSwap功能(熱交換功能)
本文基於Tomcat7.0.69的Java源碼,對其類加載體系進行分析。web
因爲上述的種種問題,在部署Web應用的時候若是隻使用一個單獨的ClassPath是沒法知足需求的,因此各類Web服務器都不約而同的提供了多個ClassPath路徑供用戶存在第三方類庫,這些路徑通常都以lib,classes命名,被放置到不一樣路徑的類庫,具有不一樣的訪問範圍和服務對象.tomcat服務器劃分用戶類庫結構和類加載描述以下,而後用一張圖片來展現Tomcat的類加載體系:各個類加載器之間不是繼承關係,而是一種委派關係。apache
![](http://static.javashuo.com/static/loading.gif)
這裏結合以前對雙親委派模式的類加載過程的描述,對上圖所示類加載體系進行介紹:
ClassLoader:Java提供的類加載器抽象類,用戶自定義的類加載器須要繼承實現
commonLoader:Tomcat最基本的類加載器,加載路徑中的class能夠被Tomcat容器自己以及各個Webapp訪問;
catalinaLoader:Tomcat容器私有的類加載器,加載路徑中的class對於Webapp不可見;
sharedLoader:各個Webapp共享的類加載器,加載路徑中的class對於全部Webapp可見,可是對於Tomcat容器不可見;緩存
WebappClassLoader:各個Webapp私有的類加載器,加載路徑中的class只對當前Webapp可見;tomcat
經過下面類關係圖以及邏輯關係圖,同時對比上文內容梳理這些類加載器之間的關係。安全
一、類關係圖
![classloader-3](http://static.javashuo.com/static/loading.gif)
從圖中看到了Common,Catalina,Shared類加載器是URLClassLoader類的一個實例,只是它們的類加載路徑不同,在tomcat/conf/catalina.properties配置文件中配置(common.loader,server.loader,shared.loader).WebAppClassLoader繼承自WebAppClassLoaderBase,基本全部邏輯都在WebAppClassLoaderBase爲中實現了,能夠看出tomcat的全部類加載器都是以URLClassLoader爲基礎進行擴展。服務器
二、邏輯關係圖
![classloader-4](http://static.javashuo.com/static/loading.gif)
上面說到Common,Catalina,Shared類加載器是URLClassLoader類的一個實例,在默認的配置中,它們其實都是同一個對象,即commonLoader,結合初始化時的代碼(只保留關鍵代碼):session
private void initClassLoaders() {
commonLoader = createClassLoader("common", null); // commonLoader的加載路徑爲common.loader
if( commonLoader == null ) {
commonLoader=this.getClass().getClassLoader();
}
catalinaLoader = createClassLoader("server", commonLoader); // 加載路徑爲server.loader,默認爲空,父類加載器爲commonLoader
sharedLoader = createClassLoader("shared", commonLoader); // 加載路徑爲shared.loader,默認爲空,父類加載器爲commonLoader
}
private ClassLoader createClassLoader(String name, ClassLoader parent) throws Exception {
String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals("")))
return parent; // catalinaLoader與sharedLoader的加載路徑均爲空,因此直接返回commonLoader對象,默認3者爲同一個對象
}
在上面的代碼初始化時很明確是指出了,catalina與shared類加載器的父類加載器爲common類加載器,而初始化commonClassLoader時父類加載器設置爲null,最終會調到createClassLoader
靜態方法:數據結構
public static ClassLoader createClassLoader(List<Repository> repositories,
final ClassLoader parent)
throws Exception {
.....
return AccessController.doPrivileged(
new PrivilegedAction<URLClassLoader>() {
@Override
public URLClassLoader run() {
if (parent == null)
return new URLClassLoader(array); //該構造方法默認獲取系統類加載器爲父類加載器,即AppClassLoader
else
return new URLClassLoader(array, parent);
}
});
}
在createClassLoader
中指定參數parent==null
時,最終會以系統類加載器(AppClassLoader)做爲父類加載器,這解釋了爲何commonClassLoader的父類加載器是AppClassLoader.app
一個web應用對應着一個StandardContext
實例,每一個web應用都擁有獨立web應用類加載器(WebClassLoader),這個類加載器在StandardContext.startInternal()
中被構造了出來:
if (getLoader() == null) {
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
}
這裏getParentClassLoader()
會獲取父容器StandarHost.parentClassLoader
對象屬性,而這個對象屬性是在Catalina$SetParentClassLoaderRule.begin()
初始化,初始化的值其實就是Catalina.parentClassLoader
對象屬性,再來跟蹤一下Catalina.parentClassLoader
,在Bootstrap.init()
時經過反射調用了Catalina.setParentClassLoader()
,將Bootstrap.sharedLoader
屬性設置爲Catalina.parentClassLoader
,因此WebClassLoader的父類加載器是Shared ClassLoader.
三、類加載邏輯
Tomcat的類加載機制是違反了雙親委託原則的,對於一些未加載的非基礎類(Object,String等),各個web應用本身的類加載器(WebAppClassLoader)會優先加載,加載不到時再交給commonClassLoader走雙親委託。具體的加載邏輯位於WebAppClassLoaderBase.loadClass()
方法中,代碼篇幅長,這裏以文字描述加載一個類過程:
- 先在本地緩存中查找是否已經加載過該類(對於一些已經加載了的類,會被緩存在
resourceEntries
這個數據結構中),若是已經加載即返回,不然 繼續下一步。
- 讓系統類加載器(AppClassLoader)嘗試加載該類,主要是爲了防止一些基礎類會被web中的類覆蓋,若是加載到即返回,返回繼續。
- 前兩步均沒加載到目標類,那麼web應用的類加載器將自行加載,若是加載到則返回,不然繼續下一步。
- 最後仍是加載不到的話,則委託父類加載器(Common ClassLoader)去加載。
第3第4兩個步驟的順序已經違反了雙親委託機制,除了tomcat以外,JDBC,JNDI,Thread.currentThread().setContextClassLoader();
等不少地方都同樣是違反了雙親委託。
2、源碼分析
commonLoader、catalinaLoader和sharedLoader在Tomcat容器初始化的一開始,即調用Bootstrap的init方法時建立。catalinaLoader會被設置爲Tomcat主線程的線程上下文類加載器,而且使用catalinaLoader加載Tomcat容器自身容器下的class。Bootstrap的init方法的部分代碼見代碼清單1。
代碼清單1 Bootstrap的init方法的部分實現
[java] view plain copy
- /**
- * Initialize daemon.
- */
- public void init()
- throws Exception
- {
-
- // Set Catalina path
- setCatalinaHome();
- setCatalinaBase();
-
- initClassLoaders();
-
- Thread.currentThread().setContextClassLoader(catalinaLoader);
-
- SecurityClassLoad.securityClassLoad(catalinaLoader);
- // 省略後邊的代碼
代碼清單1中,咱們首先關注initClassLoaders方法的實現,見代碼清單2.initClassLoaders方法用來初始化commonLoader、catalinaLoader、sharedLoader。
代碼清單2 initClassLoaders方法的實現
[java] view plain copy
- private void initClassLoaders() {
- try {
- commonLoader = createClassLoader("common", null);
- if( commonLoader == null ) {
- // no config file, default to this loader - we might be in a 'single' env.
- commonLoader=this.getClass().getClassLoader();
- }
- catalinaLoader = createClassLoader("server", commonLoader);
- sharedLoader = createClassLoader("shared", commonLoader);
- } catch (Throwable t) {
- log.error("Class loader creation threw exception", t);
- System.exit(1);
- }
- }
從代碼清單2中看到建立類加載器是經過調用createClassLoader方法實現的,createClassLoader的實現見代碼清單3.
代碼清單3 createClassLoader方法的實現
[java] view plain copy
- private ClassLoader createClassLoader(String name, ClassLoader parent)
- throws Exception {
-
- String value = CatalinaProperties.getProperty(name + ".loader");
- if ((value == null) || (value.equals("")))
- return parent;
-
- ArrayList<String> repositoryLocations = new ArrayList<String>();
- ArrayList<Integer> repositoryTypes = new ArrayList<Integer>();
- int i;
-
- StringTokenizer tokenizer = new StringTokenizer(value, ",");
- while (tokenizer.hasMoreElements()) {
- String repository = tokenizer.nextToken();
-
- // Local repository
- boolean replace = false;
- String before = repository;
- while ((i=repository.indexOf(CATALINA_HOME_TOKEN))>=0) {
- replace=true;
- if (i>0) {
- repository = repository.substring(0,i) + getCatalinaHome()
- + repository.substring(i+CATALINA_HOME_TOKEN.length());
- } else {
- repository = getCatalinaHome()
- + repository.substring(CATALINA_HOME_TOKEN.length());
- }
- }
- while ((i=repository.indexOf(CATALINA_BASE_TOKEN))>=0) {
- replace=true;
- if (i>0) {
- repository = repository.substring(0,i) + getCatalinaBase()
- + repository.substring(i+CATALINA_BASE_TOKEN.length());
- } else {
- repository = getCatalinaBase()
- + repository.substring(CATALINA_BASE_TOKEN.length());
- }
- }
- if (replace && log.isDebugEnabled())
- log.debug("Expanded " + before + " to " + repository);
-
- // Check for a JAR URL repository
- try {
- new URL(repository);
- repositoryLocations.add(repository);
- repositoryTypes.add(ClassLoaderFactory.IS_URL);
- continue;
- } catch (MalformedURLException e) {
- // Ignore
- }
-
- if (repository.endsWith("*.jar")) {
- repository = repository.substring
- (0, repository.length() - "*.jar".length());
- repositoryLocations.add(repository);
- repositoryTypes.add(ClassLoaderFactory.IS_GLOB);
- } else if (repository.endsWith(".jar")) {
- repositoryLocations.add(repository);
- repositoryTypes.add(ClassLoaderFactory.IS_JAR);
- } else {
- repositoryLocations.add(repository);
- repositoryTypes.add(ClassLoaderFactory.IS_DIR);
- }
- }
-
- String[] locations = repositoryLocations.toArray(new String[0]);
- Integer[] types = repositoryTypes.toArray(new Integer[0]);
-
- ClassLoader classLoader = ClassLoaderFactory.createClassLoader
- (locations, types, parent);
-
- // Retrieving MBean server
- MBeanServer mBeanServer = null;
- if (MBeanServerFactory.findMBeanServer(null).size() > 0) {
- mBeanServer = MBeanServerFactory.findMBeanServer(null).get(0);
- } else {
- mBeanServer = ManagementFactory.getPlatformMBeanServer();
- }
-
- // Register the server classloader
- ObjectName objectName =
- new ObjectName("Catalina:type=ServerClassLoader,name=" + name);
- mBeanServer.registerMBean(classLoader, objectName);
-
- return classLoader;
-
- }
createClassLoader方法的執行步驟以下:
- 獲取各個類加載器相應的資源配置文件(分別爲common.loader、server.loader、shared.loader),從中獲取類資源路徑的配置信息;
- 解析類資源路徑下的各個資源位置和類型,也包括對jar資源的檢查;
- 調用ClassLoaderFactory.createClassLoader(locations, types, parent)方法建立ClassLoader;
- 將ClassLoader註冊到JMX服務中,有個JMX的內容能夠參照《Tomcat7.0源碼分析——生命週期管理 》一文中的相關介紹。
咱們回頭看看代碼清單1中的SecurityClassLoad.securityClassLoad(catalinaLoader)的實現,見代碼清單4.這說明加載Tomcat容器自己的類資源的確是使用catalinaLoader來完成的。
代碼清單4 securityClassLoad的實現
[java] view plain copy
- public static void securityClassLoad(ClassLoader loader)
- throws Exception {
-
- if( System.getSecurityManager() == null ){
- return;
- }
-
- loadCorePackage(loader);
- loadLoaderPackage(loader);
- loadSessionPackage(loader);
- loadUtilPackage(loader);
- loadJavaxPackage(loader);
- loadCoyotePackage(loader);
- loadTomcatPackage(loader);
- }
securityClassLoad方法主要加載Tomcat容器所需的class,包括:
- Tomcat核心class,即org.apache.catalina.core路徑下的class;
- org.apache.catalina.loader.WebappClassLoader$PrivilegedFindResourceByName;
- Tomcat有關session的class,即org.apache.catalina.session路徑下的class;
- Tomcat工具類的class,即org.apache.catalina.util路徑下的class;
- javax.servlet.http.Cookie;
- Tomcat處理請求的class,即org.apache.catalina.connector路徑下的class;
- Tomcat其它工具類的class,也是org.apache.catalina.util路徑下的class;
咱們以加載Tomcat核心class的loadCorePackage方法爲例,其實現見代碼清單5所示。
代碼清單5 loadCorePackage的實現
[java] view plain copy
- private final static void loadCorePackage(ClassLoader loader)
- throws Exception {
- String basePackage = "org.apache.catalina.";
- loader.loadClass
- (basePackage +
- "core.ApplicationContextFacade$1");
- loader.loadClass
- (basePackage +
- "core.ApplicationDispatcher$PrivilegedForward");
- loader.loadClass
- (basePackage +
- "core.ApplicationDispatcher$PrivilegedInclude");
- loader.loadClass
- (basePackage +
- "core.AsyncContextImpl");
- loader.loadClass
- (basePackage +
- "core.AsyncContextImpl$AsyncState");
- loader.loadClass
- (basePackage +
- "core.AsyncContextImpl$DebugException");
- loader.loadClass
- (basePackage +
- "core.AsyncContextImpl$1");
- loader.loadClass
- (basePackage +
- "core.AsyncContextImpl$2");
- loader.loadClass
- (basePackage +
- "core.AsyncListenerWrapper");
- loader.loadClass
- (basePackage +
- "core.ContainerBase$PrivilegedAddChild");
- loader.loadClass
- (basePackage +
- "core.DefaultInstanceManager$1");
- loader.loadClass
- (basePackage +
- "core.DefaultInstanceManager$2");
- loader.loadClass
- (basePackage +
- "core.DefaultInstanceManager$3");
- loader.loadClass
- (basePackage +
- "core.DefaultInstanceManager$4");
- loader.loadClass
- (basePackage +
- "core.DefaultInstanceManager$5");
- loader.loadClass
- (basePackage +
- "core.ApplicationHttpRequest$AttributeNamesEnumerator");
- }
至此,有關commonLoader、catalinaLoader和sharedLoader三個類加載器的初始化以及使用catalinaLoader加載Tomcat容器自身類資源的內容已經介紹完了,可是咱們尚未看到WebappClassLoader。啓動StandardContext的時候會建立WebappLoader,根據《Tomcat7.0源碼分析——生命週期管理 》一文的內容,咱們知道啓動StandardContext時會最終調用其startInternal方法,其實現見代碼清單6.
代碼清單6 StandardContext的startInternal方法
[java] view plain copy
- /**
- * Start this component and implement the requirements
- * of {@link LifecycleBase#startInternal()}.
- *
- * @exception LifecycleException if this component detects a fatal error
- * that prevents this component from being used
- */
- @Override
- protected synchronized void startInternal() throws LifecycleException {
-
- // 省略前邊的代碼
-
- if (getLoader() == null) {
- WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
- webappLoader.setDelegate(getDelegate());
- setLoader(webappLoader);
- }
- // 省略中間的代碼
- // Start our subordinate components, if any
- if ((loader != null) && (loader instanceof Lifecycle))
- ((Lifecycle) loader).start();
- // 省略後邊的代碼
- }
從代碼清單6看到首先建立WebappLoader實例,而後調用WebappLoader的start方法,start又調用了startInternal方法,WebappLoader的startInternal的實現見代碼清單7.
代碼清單7 WebappLoader的startInternal實現
[java] view plain copy
- /**
- * Start associated {@link ClassLoader} and implement the requirements
- * of {@link LifecycleBase#startInternal()}.
- *
- * @exception LifecycleException if this component detects a fatal error
- * that prevents this component from being used
- */
- @Override
- protected void startInternal() throws LifecycleException {
-
- // Register a stream handler factory for the JNDI protocol
- URLStreamHandlerFactory streamHandlerFactory =
- new DirContextURLStreamHandlerFactory();
- if (first) {
- first = false;
- try {
- URL.setURLStreamHandlerFactory(streamHandlerFactory);
- } catch (Exception e) {
- // Log and continue anyway, this is not critical
- log.error("Error registering jndi stream handler", e);
- } catch (Throwable t) {
- // This is likely a dual registration
- log.info("Dual registration of jndi stream handler: "
- + t.getMessage());
- }
- }
-
- // Construct a class loader based on our current repositories list
- try {
-
- classLoader = createClassLoader();
- classLoader.setResources(container.getResources());
- classLoader.setDelegate(this.delegate);
- classLoader.setSearchExternalFirst(searchExternalFirst);
- if (container instanceof StandardContext) {
- classLoader.setAntiJARLocking(
- ((StandardContext) container).getAntiJARLocking());
- classLoader.setClearReferencesStatic(
- ((StandardContext) container).getClearReferencesStatic());
- classLoader.setClearReferencesStopThreads(
- ((StandardContext) container).getClearReferencesStopThreads());
- classLoader.setClearReferencesStopTimerThreads(
- ((StandardContext) container).getClearReferencesStopTimerThreads());
- classLoader.setClearReferencesThreadLocals(
- ((StandardContext) container).getClearReferencesThreadLocals());
- }
-
- for (int i = 0; i < repositories.length; i++) {
- classLoader.addRepository(repositories[i]);
- }
咱們看到代碼清單7中經過調用createClassLoader來建立類加載器,而且設置其資源路徑爲當前Webapp下某個context的類資源。最後咱們看看createClassLoader的實現,見代碼清單8.
代碼清單8 createClassLoader的實現
[java] view plain copy
- /**
- * Create associated classLoader.
- */
- private WebappClassLoader createClassLoader()
- throws Exception {
-
- //loaderClass即字符串org.apache.catalina.loader.WebappClassLoader
- Class<?> clazz = Class.forName(loaderClass);
- WebappClassLoader classLoader = null;
-
- if (parentClassLoader == null) {
- parentClassLoader = container.getParentClassLoader();
- }
- Class<?>[] argTypes = { ClassLoader.class };
- Object[] args = { parentClassLoader };
- Constructor<?> constr = clazz.getConstructor(argTypes);
- classLoader = (WebappClassLoader) constr.newInstance(args);
-
- return classLoader;
-
- }
這裏loaderClass的值是字符串org.apache.catalina.loader.WebappClassLoader,經過反射來實例化WebappClassLoader。因爲每一個Webapp下的類資源由不一樣的WebappClassLoader負責加載,所以Webapp下各個Context的類資源是獨立的。至此,整個Tomcat的類加載體系構建完畢。
此外每一個jsp爲了實現熱替換,會有專門的類加載器負責加載。
參考文檔:http://blog.csdn.net/beliefer/article/details/50995516
《深刻理解Java虛擬機》
http://blog.csdn.net/czmacd/article/details/54017027