Tomcat Catalina為什麼不new出來原理解析

一、Catalina為什麼不new出來?

掌握瞭Java的類加載器和雙親委派機制,現在我們就可以回答正題上來瞭,Tomcat的類加載器是怎麼設計的?

1.Web容器的特性

Web容器有其自身的特殊性,所以在類加載器這塊是不能完全使用JVM的類加載器的雙親委派機制的。在Web容器中我們應該要滿足如下的特性:

隔離性

部署在同一個Web容器上的兩個Web應用程序所使用的Java類庫可以實現相互隔離。設想一下,兩個Web應用,一個使用瞭Spring3.0,另一個使用瞭新的的5.0,應用服務器使用一個類加載器,Web應用將會因為jar包覆蓋而無法啟動。

靈活性:

Web應用之間的類加載器相互獨立,那麼就能針對一個Web應用進行重新部署,此時Web應用的類加載器會被重建,而且不會影響其他的Web應用。如果采用一個類加載器,類之間的依賴是雜亂復雜的,無法完全移出某個應用的類。

性能:

性能也是一個比較重要的點。部署在同一個Web容器上的兩個Web應用程序所使用的Java類庫可以互相共享。這個需求也很常見,例如,用戶可能有10個使用Spring框架的應用程序部署在同一臺服務器上,如果把10份Spring分別存放在各個應用程序的隔離目錄中,將會是很大的資源浪費——這主要倒不是浪費磁盤空間的問題,而是指類庫在使用時都要被加載到Web容器的內存,如果類庫不能共享,虛擬機的方法區就會很容易出現過度膨脹的風險。

2.Tomcat類加載器結構

明白瞭Web容器的類加載器有多個,再來看tomcat的類加載器結構。

首先上張圖,整體看下tomcat的類加載器:

可以看到在原先的java類加載器基礎上,tomcat新增瞭幾個類加載器,包括3個基礎類加載器和每個Web應用的類加載器,其中3個基礎類加載器可在conf/catalina.properties中配置,具體介紹下:

Common

以應用類加載器為父類,是tomcat頂層的公用類加載器,其路徑由conf/catalina.properties中的common.loader指定,默認指向${catalina.base}/lib下的包。

Catalina

以Common類加載器為父類,是用於加載Tomcat應用服務器的類加載器,其路徑由server.loader指定,默認為空,此時tomcat使用Common類加載器加載應用服務器。

Shared

以Common類加載器為父類,是所有Web應用的父類加載器,其路徑由shared.loader指定,默認為空,此時tomcat使用Common類加載器作為Web應用的父加載器。

Web應用

以Shared類加載器為父類,加載/WEB-INF/classes目錄下的未壓縮的Class和資源文件以及/WEB-INF/lib目錄下的jar包,該類加載器隻對當前Web應用可見,對其他Web應用均不可見。

默認情況下,Common、Catalina、Shared類加載器是同一個,但可以配置3個不同的類加載器,使他們各司其職。

首先,Common類加載器復雜加載Tomcat應用服務器內部和Web應用均可見的類,如Servlet規范相關包和一些通用工具包。

其次,Catalina類加載器負責隻有Tomcat應用服務器內部可見的類,這些類對Web應用不可見。比如,想實現自己的會話存儲方案,而且該方案依賴瞭一些第三方包,當然是不希望這些包對Web應用可見,這時可以配置server.load,創建獨立的Catalina類加載器。

再次,Shared類復雜加載Web應用共享類,這些類tomcat服務器不會依賴

3.Tomcat源碼分析

3.1 CatalinClassLoader

首先來看看Tomcat的類加載器的繼承結構

可以看到繼承的結構和我們上面所寫的類加載器的結構不同。

大傢需要註意雙親委派機制並不是通過繼承來實現的,而是相互之間組合而形成的。

所以AppClassLoader沒有繼承自 ExtClassLoader,WebappClassLoader也沒有繼承自AppClassLoader。

至於Common ClassLoader ,Shared ClassLoader,Catalina ClassLoader則是在啟動時初始化的三個不同名字的URLClassLoader。

先來看看Bootstrap#init()方法。init方法會調用initClassLoaders,同樣也會將Catalina ClassLoader設置到當前線程設置到當前線程,進入initClassLoaders來看看。

    private void initClassLoaders() {
        try {
            // 創建 commonLoader  catalinaLoader sharedLoader
            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();
            }
            // 默認情況下 server.loader 和 shared.loader 都為空則會返回 commonLoader 類加載器
            catalinaLoader = createClassLoader("server", commonLoader);
            sharedLoader = createClassLoader("shared", commonLoader);
        } catch (Throwable t) {
            handleThrowable(t);
            log.error("Class loader creation threw exception", t);
            System.exit(1);
        }
    }

我們可以看到在initClassLoaders()方法中完成瞭CommonClassLoader, CatalinaClassLoader,和SharedClassLoader的創建,而且進入到createClassLoader方法中。

可以看到這三個基礎類加載器所加載的資源剛好對應conf/catalina.properties中的common.loader,server.loader,shared.loader

3.2 層次結構

Common/Catalina/Shared ClassLoader的創建好瞭之後就會維護相互之間的組合關系

其實也就是設置瞭父加載器

3.3 具體的加載過程

源碼比較長,直接進入到 WebappClassLoaderBase中的 LoadClass方法

@Override
    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            if (log.isDebugEnabled()) {
                log.debug("loadClass(" + name + ", " + resolve + ")");
            }
            Class<?> clazz = null;
            // Log access to stopped class loader
            checkStateForClassLoading(name);
            // (0) Check our previously loaded local class cache
            // 檢查WebappClassLoader中是否加載過此類
            clazz = findLoadedClass0(name);
            if (clazz != null) {
                if (log.isDebugEnabled()) {
                    log.debug("  Returning class from cache");
                }
                if (resolve) {
                    resolveClass(clazz);
                }
                return clazz;
            }
            // (0.1) Check our previously loaded class cache
            // 如果第一步沒有找到,則繼續檢查JVM虛擬機中是否加載過該類
            clazz = findLoadedClass(name);
            if (clazz != null) {
                if (log.isDebugEnabled()) {
                    log.debug("  Returning class from cache");
                }
                if (resolve) {
                    resolveClass(clazz);
                }
                return clazz;
            }
            // (0.2) Try loading the class with the bootstrap class loader, to prevent
            //       the webapp from overriding Java SE classes. This implements
            //       SRV.10.7.2
            // 如果前兩步都沒有找到,則使用系統類加載該類(也就是當前JVM的ClassPath)。
            // 為瞭防止覆蓋基礎類實現,這裡會判斷class是不是JVMSE中的基礎類庫中類。
            String resourceName = binaryNameToPath(name, false);
            ClassLoader javaseLoader = getJavaseClassLoader();
            boolean tryLoadingFromJavaseLoader;
            try {
                // Use getResource as it won't trigger an expensive
                // ClassNotFoundException if the resource is not available from
                // the Java SE class loader. However (see
                // https://bz.apache.org/bugzilla/show_bug.cgi?id=58125 for
                // details) when running under a security manager in rare cases
                // this call may trigger a ClassCircularityError.
                // See https://bz.apache.org/bugzilla/show_bug.cgi?id=61424 for
                // details of how this may trigger a StackOverflowError
                // Given these reported errors, catch Throwable to ensure any
                // other edge cases are also caught
                URL url;
                if (securityManager != null) {
                    PrivilegedAction<URL> dp = new PrivilegedJavaseGetResource(resourceName);
                    url = AccessController.doPrivileged(dp);
                } else {
                    url = javaseLoader.getResource(resourceName);
                }
                tryLoadingFromJavaseLoader = (url != null);
            } catch (Throwable t) {
                // Swallow all exceptions apart from those that must be re-thrown
                ExceptionUtils.handleThrowable(t);
                // The getResource() trick won't work for this class. We have to
                // try loading it directly and accept that we might get a
                // ClassNotFoundException.
                tryLoadingFromJavaseLoader = true;
            }
            if (tryLoadingFromJavaseLoader) {
                try {
                    clazz = javaseLoader.loadClass(name);
                    if (clazz != null) {
                        if (resolve) {
                            resolveClass(clazz);
                        }
                        return clazz;
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }
            // (0.5) Permission to access this class when using a SecurityManager
            if (securityManager != null) {
                int i = name.lastIndexOf('.');
                if (i >= 0) {
                    try {
                        securityManager.checkPackageAccess(name.substring(0,i));
                    } catch (SecurityException se) {
                        String error = sm.getString("webappClassLoader.restrictedPackage", name);
                        log.info(error, se);
                        throw new ClassNotFoundException(error, se);
                    }
                }
            }
            // 檢查是否 設置瞭delegate屬性,設置為true,那麼就會完全按照JVM的"雙親委托"機制流程加載類。
            boolean delegateLoad = delegate || filter(name, true);
            // (1) Delegate to our parent if requested
            if (delegateLoad) {
                if (log.isDebugEnabled()) {
                    log.debug("  Delegating to parent classloader1 " + parent);
                }
                try {
                    clazz = Class.forName(name, false, parent);
                    if (clazz != null) {
                        if (log.isDebugEnabled()) {
                            log.debug("  Loading class from parent");
                        }
                        if (resolve) {
                            resolveClass(clazz);
                        }
                        return clazz;
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }
            // (2) Search local repositories
            // 若是沒有委托,則默認會首次使用WebappClassLoader來加載類。通過自定義findClass定義處理類加載規則。
            // findClass()會去Web-INF/classes 目錄下查找類。
            if (log.isDebugEnabled()) {
                log.debug("  Searching local repositories");
            }
            try {
                clazz = findClass(name);
                if (clazz != null) {
                    if (log.isDebugEnabled()) {
                        log.debug("  Loading class from local repository");
                    }
                    if (resolve) {
                        resolveClass(clazz);
                    }
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
            // (3) Delegate to parent unconditionally
            // 若是WebappClassLoader在/WEB-INF/classes、/WEB-INF/lib下還是查找不到class,
            // 那麼無條件強制委托給System、Common類加載器去查找該類。
            if (!delegateLoad) {
                if (log.isDebugEnabled()) {
                    log.debug("  Delegating to parent classloader at end: " + parent);
                }
                try {
                    clazz = Class.forName(name, false, parent);
                    if (clazz != null) {
                        if (log.isDebugEnabled()) {
                            log.debug("  Loading class from parent");
                        }
                        if (resolve) {
                            resolveClass(clazz);
                        }
                        return clazz;
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }
        }
        throw new ClassNotFoundException(name);
    }

Web應用類加載器默認的加載順序是:

  • (1).先從緩存中加載;
  • (2).如果沒有,則從JVM的Bootstrap類加載器加載;
  • (3).如果沒有,則從當前類加載器加載(按照WEB-INF/classes、WEB-INF/lib的順序);
  • (4).如果沒有,則從父類加載器加載,由於父類加載器采用默認的委派模式,所以加載順序是AppClassLoader、Common、Shared。

tomcat提供瞭delegate屬性用於控制是否啟用java委派模式,默認false(不啟用),當設置為true時,tomcat將使用java的默認委派模式,這時加載順序如下:

  • (1).先從緩存中加載;
  • (2).如果沒有,則從JVM的Bootstrap類加載器加載;
  • (3).如果沒有,則從父類加載器加載,加載順序是AppClassLoader、Common、Shared。
  • (4).如果沒有,則從當前類加載器加載(按照WEB-INF/classes、WEB-INF/lib的順序)

以上就是Tomcat Catalina為什麼不new出來原理解析的詳細內容,更多關於Tomcat Catalina原理的資料請關註WalkonNet其它相關文章!

推薦閱讀: