Tomcat9中類加載體系是怎么樣的

小編給大家分享一下Tomcat9中類加載體系是怎么樣的,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

創(chuàng)新互聯(lián)擁有一支富有激情的企業(yè)網(wǎng)站制作團隊,在互聯(lián)網(wǎng)網(wǎng)站建設(shè)行業(yè)深耕十年,專業(yè)且經(jīng)驗豐富。十年網(wǎng)站優(yōu)化營銷經(jīng)驗,我們已為成百上千中小企業(yè)提供了成都網(wǎng)站制作、網(wǎng)站設(shè)計解決方案,按需開發(fā)網(wǎng)站,設(shè)計滿意,售后服務(wù)無憂。所有客戶皆提供一年免費網(wǎng)站維護!

1.概要

Tomcat作為Web容器,支持多個應(yīng)用程序的部署運行,所以會涉及應(yīng)用程序間類庫的隔離和共享,例如:不同應(yīng)用程序既可以使用某個工具類庫的不同版本但互不影響,亦可以共享使用某個類庫。

2.JVM雙親委派模型

在JVM中有3種系統(tǒng)提供的類加載器:

  • 啟動類加載器Bootstrap ClassLoader:這負載加載存放在

    /lib目錄中的類庫,啟動類加載器無法被Java程序直接引用
  • 擴展類加載器Extension ClassLoader:負載加載

    /lib/ext目錄中的類庫
  • 應(yīng)用程序類加載器 Application ClassLoader:負載加載用戶類路徑CLASSPATH上所指定的類庫,ClassLoader.getSysteClassLoader()方法的返回值就是此加載器,一般情況下這個就是程序中默認的類加載器

我們的應(yīng)用程序都是由這3種類加載器互相配合進行加載的,還可以加入自己定義的類加載器,他們之間的關(guān)系如圖所示:
Tomcat9中類加載體系是怎么樣的
圖中展示的類加載器之間的層次關(guān)系,成為類加載器的雙親委派模型(Parents Delegation Model),雙親委派模型要求除了頂層的啟動類加載器之外,其余的類加載器都應(yīng)該有父類加載器。這里類加載器之間的父子關(guān)系一般不會繼承(Inheritance)的強耦合關(guān)系來實現(xiàn),而是使用組合(Composition)的弱耦合關(guān)系來復(fù)用父加載器的代碼。

類加載器的雙親委派模型在JDK1.2期間被引入并被廣泛應(yīng)用于之后的所有Java程序中,但它并不是一個強制的約束模型,而是Java設(shè)計者推薦給開發(fā)者的一種類加載器實現(xiàn)方式,在之后章節(jié)中會看到,在Apache Tomcat中就打破了此模型。

雙親委派模型的工作過程是:如果一個類加載器收到加載類的請求,他首先不會自己去加載這個類,而是把這個請求委派給父加載器去完成,每個層次的類加載器都是如此處理,所有的加載請求最終都會傳送到頂層的啟動類加載器中,只有當(dāng)父加載器反饋自己無法完成這個加載請求時(沒有找類),子加載器才會嘗試自己加載。

雙親委派模型本質(zhì)上就是委托代理,雖然簡單,卻使Java類隨著類加載器一起具備了優(yōu)先級層次,例如類java.lang.Object,它存放在rt.jar之中,無論哪一個類加載器加載這個類,最終都是委派給啟動類加載器進行加載,最終Object類在程序中的各種類加載器環(huán)境中都是同一個類。也就是說,如果你嘗試編寫一個與rJDK基礎(chǔ)類庫中已有類重名的Java類,類似java.lang.Object,你會發(fā)現(xiàn),雖然可以正常編譯,但永遠無法被加載運行。

3.Tomcat9類加載體系

功能健全的Web容器的類加載器需要支持以下幾點:

  • 服務(wù)器的類庫要與Web應(yīng)用程序類庫互相隔離,互不影響

  • 同一個服務(wù)器上兩個Web程序所使用的Java類庫實現(xiàn)互相獨立隔離

  • 同一個服務(wù)器上了兩個Web應(yīng)用程序所使用的Java類庫可以被共享

  • 支持JSP的熱替換(HotSwap),修改JSP文件不需要重啟服務(wù)器

基于以上需求,下圖為Tomcat類加載器的委派關(guān)系:Tomcat9中類加載體系是怎么樣的

  • Common ClassLoader:Tomcat的基本類加載器,加載路徑中的class可以被Tomcat容器本身以及各個Webapp應(yīng)用程序訪問

  • Catalina ClassLoader:Tomcat容器私有的類加載器,加載路徑中的class僅對Tomcat容器本身可見,Webapp應(yīng)用程序不可訪問

  • Shared ClassLoader:各個Webapp應(yīng)用程序共享的類加載器,加載路徑中的class對于所有的webapp可見

  • Webapp ClassLoader:各個 Webapp私有的類加載器,加載路徑中的class僅對當(dāng)前webapp可見

  • JasperLoader:記載方位僅為JSP文件所編譯出的一個.class文件,當(dāng)容器檢測到JSP文件被修改時,互替換掉目前的JasperLoader實例,通過新建一個JSP類加載器來實現(xiàn)JSP文件的HotSwap功能

4.源碼分析

org.apache.catalina.startup.Bootstrap.init()
   /**
     * Initialize daemon.
     * @throws Exception Fatal initialization error
     */
    public void init() throws Exception {
        initClassLoaders(); //初始化類加載器
        Thread.currentThread().setContextClassLoader(catalinaLoader); //設(shè)置當(dāng)前線程類加載器為catalinaLoader
        SecurityClassLoad.securityClassLoad(catalinaLoader); //使用Java SecurityManager初始化
        // Load our startup class and call its process() method
        if (log.isDebugEnabled())
            log.debug("Loading startup class");
        Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
        Object startupInstance = startupClass.getConstructor().newInstance();
        // Set the shared extensions class loader
        if (log.isDebugEnabled())
            log.debug("Setting startup class properties");
        String methodName = "setParentClassLoader";
        Class<?> paramTypes[] = new Class[1];
        paramTypes[0] = Class.forName("java.lang.ClassLoader");
        Object paramValues[] = new Object[1];
        paramValues[0] = sharedLoader; //WebappLoader的parentLoader
        Method method =
            startupInstance.getClass().getMethod(methodName, paramTypes);
        method.invoke(startupInstance, paramValues);
        catalinaDaemon = startupInstance;
    }

commLoader、catalinaLoader和sharedLoader的初始化是在Tomcat容器運行的最初進行的,調(diào)用的方法為org.apache.catalina.startup.Bootstrap.init()

  • initClassLoaders初始化commLoader、catalinaLoader和sharedLoader

  • 設(shè)置當(dāng)前線程的類加載器為catalinaLoader,實現(xiàn)catalinaLoader為容器自身的私有類加載的目的

  • 當(dāng)使用Java SecurityManager時進行一些初始化工作

  • 將sharedLoader向后傳遞,以作為webappLoader的parent

org.apache.catalina.startup.Bootstrap.initClassLoaders()
 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) {
            handleThrowable(t);
            log.error("Class loader creation threw exception", t);
            System.exit(1);
        }
    }

initClassLoaders方法中,對commLoader、catalinaLoader和sharedLoader進行初始化

  • commLoader、catalinaLoader和sharedLoader均通過createClassLoader創(chuàng)建

  • createClassLoader方法的第二參數(shù)為父類加載器,所以catalinaLoader、sharedLoader的父類加載器為commLoader

org.apache.catalina.startup.Bootstrap.createClassLoader()
private ClassLoader createClassLoader(String name, ClassLoader parent)
        throws Exception {
        String value = CatalinaProperties.getProperty(name + ".loader");
        if ((value == null) || (value.equals("")))
            return parent;
        value = replace(value);
        List<Repository> repositories = new ArrayList<>();
        String[] repositoryPaths = getPaths(value);
        for (String repository : repositoryPaths) {
            // Check for a JAR URL repository
            try {
                @SuppressWarnings("unused")
                URL url = new URL(repository);
                repositories.add(new Repository(repository, RepositoryType.URL));
                continue;
            } catch (MalformedURLException e) {
                // Ignore
            }
            // Local repository
            if (repository.endsWith("*.jar")) {
                repository = repository.substring
                    (0, repository.length() - "*.jar".length());
                repositories.add(new Repository(repository, RepositoryType.GLOB));
            } else if (repository.endsWith(".jar")) {
                repositories.add(new Repository(repository, RepositoryType.JAR));
            } else {
                repositories.add(new Repository(repository, RepositoryType.DIR));
            }
        }
        return ClassLoaderFactory.createClassLoader(repositories, parent);
    }
  • 獲取類加載器相應(yīng)的加載路徑,路徑的配置信息默認在conf/catalina.properties中,也可以通過環(huán)境變量catalina.config指定配置文件

  • 類加載路徑支持:.jar、*.jar和目錄三種形式

  • 當(dāng)沒有配置類加載器對應(yīng)的加載路徑時,則直接返回父類加載器。Tomcat9中catalinaLoader和sharedLoader均沒有進行配置,故這兩個加載器均為commonLoader
    以下為Tomat9的conf/catalina.properties默認的加載路徑配置:

    common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
    server.loader=
    shared.loader=
    org.apache.catalina.core.StandardContext.startInternal()
/**
     * Start this component and implement the requirements
     * of {@link org.apache.catalina.util.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);
        }
        ...省略其他代碼...
    }

webappLoader的初始化在StandardContext中進行,這里創(chuàng)建WebappLoader

org.apache.catalina.loader.WebappLoader
    //重新實現(xiàn)ClassLoader
    private String loaderClass = ParallelWebappClassLoader.class.getName();
    @Override
    protected void startInternal() throws LifecycleException {
        ...省略其他代碼...
        // Construct a class loader based on our current repositories list
        try {
            classLoader = createClassLoader(); //創(chuàng)建webappLoader
            classLoader.setResources(context.getResources());
            classLoader.setDelegate(this.delegate); //是否雙親委派
         ...省略其他代碼...
    }
    /**
     * Create associated classLoader.
     */
    private WebappClassLoaderBase createClassLoader()
        throws Exception {
        Class<?> clazz = Class.forName(loaderClass);
        WebappClassLoaderBase classLoader = null;
        if (parentClassLoader == null) {
            parentClassLoader = context.getParentClassLoader();
        }
        Class<?>[] argTypes = { ClassLoader.class };
        Object[] args = { parentClassLoader };
        Constructor<?> constr = clazz.getConstructor(argTypes);
        classLoader = (WebappClassLoaderBase) constr.newInstance(args); //反射創(chuàng)建類加載器
        return classLoader;
    }
  • 在WebappLoader的startInternal()方法調(diào)用createClassLoader()創(chuàng)建webappLoader

  • createClassLoader()通過反射創(chuàng)建ParallelWebappClassLoader

org.apache.catalina.loader.ParallelWebappClassLoader
public class ParallelWebappClassLoader extends WebappClassLoaderBase{
    ...省略其他代碼...
}
public abstract class WebappClassLoaderBase extends URLClassLoader
        implements Lifecycle, InstrumentableClassLoader, WebappProperties, PermissionCheck {
    ...省略其他代碼...
    @Override
    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (JreCompat.isGraalAvailable() ? this : getClassLoadingLock(name)) {
            if (log.isDebugEnabled())
                log.debug("loadClass(" + name + ", " + resolve + ")");
            Class<?> clazz = null;
            // Log access to stopped class loader
            checkStateForClassLoading(name);
            // (0)檢查之前加載過的本地緩存,如果緩存存在,則取緩存中的類
            clazz = findLoadedClass0(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("  Returning class from cache");
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
            // (0.1)檢查之前加載過的本地緩存,如果緩存存在,則取緩存中的類
            // GraalVM直接返回null,查詢緩存時,會校驗name的合法性
            clazz = JreCompat.isGraalAvailable() ? null : findLoadedClass(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("  Returning class from cache");
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
            //(0.2) 通過系統(tǒng)加載器加載類,放置webapp中覆寫Java SE的基礎(chǔ)類
            //此處的類加載器應(yīng)為Bootstrap ClassLoader
            String resourceName = binaryNameToPath(name, false);
            ClassLoader javaseLoader = getJavaseClassLoader();
            boolean tryLoadingFromJavaseLoader;
            try {
                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) 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);
                    }
                }
            }
            boolean delegateLoad = delegate || filter(name, true);
            // (1) 當(dāng)設(shè)置雙親委派或者加載類的名稱為Tomcat容器內(nèi)部的類,則委托給父類加載器加載
            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) 在應(yīng)用類加載路徑進行加載
            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) 委托父類加載器加載
            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);
    }
    ...省略其他代碼...
}
  • ParallelWebappClassLoader繼承了WebappClassLoaderBase,而WebappClassLoaderBase繼承了URLClassLoader,并覆寫了loadClass()方法,相當(dāng)于實現(xiàn)了自己的類加載器

  • loadClass()方法顯示,Tomcat類加載體系打破了雙親委派模型,其加載過程為

    • 從緩存中查詢,如果已加載過,則直接返回緩存中的class

    • 通過系統(tǒng)類加載器加載(Bootstrap ClassLoader),已避免應(yīng)用程序中覆寫JavaSE基礎(chǔ)類

    • 判斷是否設(shè)置為雙親委派,或者classname為特定路徑下的,則委托給父類加載器夾雜

    • 通過應(yīng)用程序類加載路徑加載,加載通過WebResourceRoot進行

    • 當(dāng)雙親委派標記為假時,最終委托給父類加載器加載

其中必須通過父類加載加載的類名稱通過filter()方法判斷:

 protected boolean filter(String name, boolean isClassName) {
        if (name == null)
            return false;
        char ch;
        if (name.startsWith("javax")) {
            /* 5 == length("javax") */
            if (name.length() == 5) {
                return false;
            }
            ch = name.charAt(5);
            if (isClassName && ch == '.') {
                /* 6 == length("javax.") */
                if (name.startsWith("servlet.jsp.jstl.", 6)) {
                    return false;
                }
                if (name.startsWith("el.", 6) ||
                    name.startsWith("servlet.", 6) ||
                    name.startsWith("websocket.", 6) ||
                    name.startsWith("security.auth.message.", 6)) {
                    return true;
                }
            } else if (!isClassName && ch == '/') {
                /* 6 == length("javax/") */
                if (name.startsWith("servlet/jsp/jstl/", 6)) {
                    return false;
                }
                if (name.startsWith("el/", 6) ||
                    name.startsWith("servlet/", 6) ||
                    name.startsWith("websocket/", 6) ||
                    name.startsWith("security/auth/message/", 6)) {
                    return true;
                }
            }
        } else if (name.startsWith("org")) {
            /* 3 == length("org") */
            if (name.length() == 3) {
                return false;
            }
            ch = name.charAt(3);
            if (isClassName && ch == '.') {
                /* 4 == length("org.") */
                if (name.startsWith("apache.", 4)) {
                    /* 11 == length("org.apache.") */
                    if (name.startsWith("tomcat.jdbc.", 11)) {
                        return false;
                    }
                    if (name.startsWith("el.", 11) ||
                        name.startsWith("catalina.", 11) ||
                        name.startsWith("jasper.", 11) ||
                        name.startsWith("juli.", 11) ||
                        name.startsWith("tomcat.", 11) ||
                        name.startsWith("naming.", 11) ||
                        name.startsWith("coyote.", 11)) {
                        return true;
                    }
                }
            } else if (!isClassName && ch == '/') {
                /* 4 == length("org/") */
                if (name.startsWith("apache/", 4)) {
                    /* 11 == length("org/apache/") */
                    if (name.startsWith("tomcat/jdbc/", 11)) {
                        return false;
                    }
                    if (name.startsWith("el/", 11) ||
                        name.startsWith("catalina/", 11) ||
                        name.startsWith("jasper/", 11) ||
                        name.startsWith("juli/", 11) ||
                        name.startsWith("tomcat/", 11) ||
                        name.startsWith("naming/", 11) ||
                        name.startsWith("coyote/", 11)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }
org.apache.catalina.webresources.StandardRoot
public class StandardRoot extends LifecycleMBeanBase implements WebResourceRoot {
    ...省略其他代碼...
    private final List<WebResourceSet> preResources = new ArrayList<>();
    private WebResourceSet main;
    private final List<WebResourceSet> classResources = new ArrayList<>();
    private final List<WebResourceSet> jarResources = new ArrayList<>(); //對應(yīng)WEB-INF/lib
    private final List<WebResourceSet> postResources = new ArrayList<>();
    private final List<WebResourceSet> mainResources = new ArrayList<>(); //對應(yīng)應(yīng)用webapp路徑
    private final List<List<WebResourceSet>> allResources = new ArrayList<>();
    {
        allResources.add(preResources);
        allResources.add(mainResources);
        allResources.add(classResources);
        allResources.add(jarResources);
        allResources.add(postResources);
    }
    ...省略其他代碼...
}

StandardRoot 在容器創(chuàng)建時進行初始化,其實現(xiàn)了接口WebResourceRoot ,用于加載Webapp類庫。
以上代碼可以看出,在Tomcat中Webapp類加載順序為:

preResources->mainResources->classResources->jarResources->postResources

其中主要用到的資源路徑為:

  • mainResources:對應(yīng)應(yīng)用目錄下WEB-INF/classes

  • classResources:對應(yīng)應(yīng)用目錄下WEB-INF/lib

所以在開發(fā)時,可以在src目錄下覆蓋lib包中的類,因為WEB-INF/classes會有限WEB-INF/lib進行加載。通常我們可以將依賴的第三方類庫的源代碼復(fù)制到src目錄經(jīng),通過Tomcat運行進行debug,這對于排查第三方類庫的問題很有幫助。

org.apache.jasper.JspCompilationContext
public ClassLoader getJspLoader() {
        if( jspLoader == null ) {
            jspLoader = new JasperLoader
                    (new URL[] {baseUrl},
                            getClassLoader(),
                            rctxt.getPermissionCollection());
        }
        return jspLoader;
    }
public void clearJspLoader() {
        jspLoader = null;
    }
  • 在Tomcat的conf/web.xml中,指定了處理JSP的Servlet:org.apache.jasper.servlet.JspServlet,在處理JSP頁面的請求時,Tomcat會檢測相應(yīng)的JSP文件是否發(fā)生的修改,如果修改則會清理JSP的編譯文件,然后先生成java文件,在編譯為class文件

  • jspLoader為加載JSP的轉(zhuǎn)化成的Servlet的類加載器,在Tomcat檢測到j(luò)sp文件發(fā)生變化時都會重新生成

  • jspLoader的父類加載器為webapp的classloader,父類加載器的初始在org.apache.jasper.compiler.JspRuntimeContext的構(gòu)造方法中,詳見如下代碼

    public JspRuntimeContext(ServletContext context, Options options) {
          ..省略其他代碼
          // Get the parent class loader
          ClassLoader loader = Thread.currentThread().getContextClassLoader();
          if (loader == null) {
              loader = this.getClass().getClassLoader();
          }
          parentClassLoader =  loader;
            ...省略其他代碼...
      }

以上是“Tomcat9中類加載體系是怎么樣的”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!

當(dāng)前標題:Tomcat9中類加載體系是怎么樣的
當(dāng)前鏈接:http://muchs.cn/article22/pidjjc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站建設(shè)企業(yè)建站、電子商務(wù)動態(tài)網(wǎng)站、App設(shè)計、網(wǎng)站設(shè)計公司

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

營銷型網(wǎng)站建設(shè)