違反ClassLoader雙親委派機(jī)制三部曲第二部——Tomcat類加載機(jī)制

前言:
本文是基于 ClassLoader雙親委派機(jī)制源碼分析 了解過(guò)正統(tǒng)JDK類加載機(jī)制及其實(shí)現(xiàn)原理的基礎(chǔ)上磁椒,進(jìn)而分析這種思想如何應(yīng)用到Tomcat這個(gè)web容器中郊供,從源碼的角度對(duì) 違反ClassLoader雙親委派機(jī)制三部曲之首部——JDBC驅(qū)動(dòng)加載 中提出的Tomcat是如何完成多個(gè)web應(yīng)用之間相互隔離,又如何保證多個(gè)web應(yīng)用都能加載到基礎(chǔ)類庫(kù)的問(wèn)題進(jìn)行了解答削饵,我們按如下的思路布局整篇文章:

  • 先給出Tomcat整體的類加載體系結(jié)構(gòu)
  • 通過(guò)查看源碼驗(yàn)證該類加載體系的正確性
  • 總結(jié)Tomcat如何設(shè)計(jì)保證多應(yīng)用隔離
    另外本文是基于Tomcat7的源碼進(jìn)行分析的加叁,因此讀者最好先搭建一套基于Tomcat7的環(huán)境扮休,以便查閱源碼以及運(yùn)行調(diào)試锣枝,可以按照該文章的方式進(jìn)行搭建:Tomcat源碼導(dǎo)入Idea

Tomcat類加載體系結(jié)構(gòu)

圖1. Tomcat整體類加載體系結(jié)構(gòu)

Tomcat本身也是一個(gè)java項(xiàng)目厢拭,因此其也需要被JDK的類加載機(jī)制加載,也就必然存在引導(dǎo)類加載器惊橱、擴(kuò)展類加載器和應(yīng)用(系統(tǒng))類加載器蚪腐。Tomcat自身定義的類加載器主要由圖中下半部分組成,Common ClassLoader作為Catalina ClassLoaderShared ClassLoader的parent税朴,而Shared ClassLoader又可能存在多個(gè)children類加載器WebApp ClassLoader,一個(gè)WebApp ClassLoader實(shí)際上就對(duì)應(yīng)一個(gè)Web應(yīng)用家制,那Web應(yīng)用就有可能存在Jsp頁(yè)面正林,這些Jsp頁(yè)面最終會(huì)轉(zhuǎn)成class類被加載,因此也需要一個(gè)Jsp的類加載器颤殴,就是圖中的JasperLoder
需要注意的是觅廓,在代碼層面Catalina ClassLoaderShared ClassLoader涵但、Common ClassLoader對(duì)應(yīng)的實(shí)體類實(shí)際上都是URLClassLoader或者SecureClassLoader杈绸,一般我們只是根據(jù)加載內(nèi)容的不同和加載父子順序的關(guān)系,在邏輯上劃分為這三個(gè)類加載器矮瘟;而WebApp ClassLoaderJasperLoader都是存在對(duì)應(yīng)的類加載器類的
下面我們從源碼設(shè)計(jì)的角度驗(yàn)證圖中類加載器的設(shè)計(jì)

源碼分析Tomcat類加載機(jī)制

Tomcat的啟動(dòng)入口在Bootstrap.class

圖2. Tomcat啟動(dòng)入口

其中初始化類加載器的流程在bootstrap.init();中瞳脓,如下“代碼清單1

public void init()
        throws Exception
    {

        // Set Catalina path
        setCatalinaHome();
        setCatalinaBase();

        // (1)   初始化 classLoader
        initClassLoaders();
        Thread.currentThread().setContextClassLoader(catalinaLoader);

        SecurityClassLoad.securityClassLoad(catalinaLoader);

        // Load our startup class and call its process() method
        if (log.isDebugEnabled())
            log.debug("Loading startup class");

        //加載 org.apache.catalina.startup.Catalina class
        Class<?> startupClass =
            catalinaLoader.loadClass
            ("org.apache.catalina.startup.Catalina");

        // (2)  實(shí)例化 Catalina 實(shí)例
        Object startupInstance = startupClass.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;
        Method method =
            startupInstance.getClass().getMethod(methodName, paramTypes);
        method.invoke(startupInstance, paramValues);

        catalinaDaemon = startupInstance;

    }

(1)處注釋的代碼主要進(jìn)行類加載的初始化以及形成類加載器的關(guān)系初始化,繼續(xù)跟進(jìn)

圖3. initClassLoaders()方法

這里紅線處的代碼實(shí)際上創(chuàng)建了三個(gè)ClassLoader對(duì)象澈侠,其名稱和Tomcat類加載關(guān)系圖中的類加載器高度一致劫侧,那么我們猜測(cè)createClassLoader(String,ClassLoader)方法可能就是創(chuàng)建Tomcat自定義類加載器的方法之一,繼續(xù)往下看 “ 代碼清單2

private ClassLoader createClassLoader(String name, ClassLoader parent)
        throws Exception {
        // (1) 根據(jù)名稱查找特定的配置
        String value = CatalinaProperties.getProperty(name + ".loader");
        if ((value == null) || (value.equals("")))
            return parent;

        value = replace(value);

        List<Repository> repositories = new ArrayList<Repository>();

        StringTokenizer tokenizer = new StringTokenizer(value, ",");
        while (tokenizer.hasMoreElements()) {
            String repository = tokenizer.nextToken().trim();
            if (repository.length() == 0) {
                continue;
            }

            // 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));
            }
        }
        // (2) 類加載器工廠創(chuàng)建特定類加載器
        return ClassLoaderFactory.createClassLoader(repositories, parent);
    }

代碼清單中(1)處注釋是根據(jù)上圖中傳遞的“名稱”加上后綴.loader去某個(gè)配置文件加載文件哨啃,為了突出重點(diǎn)烧栋,這里直接給出結(jié)論,其加載的內(nèi)容為/org/apache/catalina/startup/catalina.properties拳球,比如要加載 common.loader對(duì)應(yīng)的value审姓,其在文件中的值為${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar,也就是說(shuō)Common ClassLoader要加載的路徑是這些祝峻,是Tomcat運(yùn)行要使用的公共組件魔吐,比如servlet-api.jar次坡、catalina.jar等;而我們發(fā)現(xiàn)當(dāng)要加載server.loadershared.loader時(shí)画畅,其key在配置文件中的value為空砸琅,也就是說(shuō),默認(rèn)情況下Catalina ClassLoader和Shared ClassLoader(Tomcat整體類加載體系結(jié)構(gòu)圖中紅色虛線內(nèi))都不存在轴踱,只有Common ClassLoader
方法中的第二個(gè)參數(shù)表示創(chuàng)建類加載器的父類加載器是哪個(gè)症脂,再看initClassLoaders()方法圖中代碼,在創(chuàng)建catalinaLoadersharedLoader時(shí)淫僻,父類加載器傳入的實(shí)際上就是commonLoader诱篷,以此可以驗(yàn)證圖1中Catalina ClassLoaderShared ClassLoaderCommon ClassLoader的父子關(guān)系雳灵。而common ClassLoader的父類加載器參數(shù)傳遞的為null棕所,為什么null就會(huì)導(dǎo)致該類加載器的父類加載器為System ClassLoader呢?我們需要進(jìn)入代碼清單2中看注釋(2)處標(biāo)識(shí)的代碼 代碼清單3

    public static ClassLoader createClassLoader(List<Repository> repositories,
                                                final ClassLoader parent)
        throws Exception {

        if (log.isDebugEnabled())
            log.debug("Creating new class loader");

        // Construct the "class path" for this class loader
        Set<URL> set = new LinkedHashSet<URL>();
        // 加載指定路徑下的資源對(duì)象
        if (repositories != null) {
            for (Repository repository : repositories)  {
                if (repository.getType() == RepositoryType.URL) {
                    URL url = buildClassLoaderUrl(repository.getLocation());
                    if (log.isDebugEnabled())
                        log.debug("  Including URL " + url);
                    set.add(url);
                } else if (repository.getType() == RepositoryType.DIR) {
                    File directory = new File(repository.getLocation());
                    directory = directory.getCanonicalFile();
                    if (!validateFile(directory, RepositoryType.DIR)) {
                        continue;
                    }
                    URL url = buildClassLoaderUrl(directory);
                    if (log.isDebugEnabled())
                        log.debug("  Including directory " + url);
                    set.add(url);
                } else if (repository.getType() == RepositoryType.JAR) {
                    File file=new File(repository.getLocation());
                    file = file.getCanonicalFile();
                    if (!validateFile(file, RepositoryType.JAR)) {
                        continue;
                    }
                    URL url = buildClassLoaderUrl(file);
                    if (log.isDebugEnabled())
                        log.debug("  Including jar file " + url);
                    set.add(url);
                } else if (repository.getType() == RepositoryType.GLOB) {
                    File directory=new File(repository.getLocation());
                    directory = directory.getCanonicalFile();
                    if (!validateFile(directory, RepositoryType.GLOB)) {
                        continue;
                    }
                    if (log.isDebugEnabled())
                        log.debug("  Including directory glob "
                            + directory.getAbsolutePath());
                    String filenames[] = directory.list();
                    if (filenames == null) {
                        continue;
                    }
                    for (int j = 0; j < filenames.length; j++) {
                        String filename = filenames[j].toLowerCase(Locale.ENGLISH);
                        if (!filename.endsWith(".jar"))
                            continue;
                        File file = new File(directory, filenames[j]);
                        file = file.getCanonicalFile();
                        if (!validateFile(file, RepositoryType.JAR)) {
                            continue;
                        }
                        if (log.isDebugEnabled())
                            log.debug("    Including glob jar file "
                                + file.getAbsolutePath());
                        URL url = buildClassLoaderUrl(file);
                        set.add(url);
                    }
                }
            }
        }

        // Construct the class loader itself
        final URL[] array = set.toArray(new URL[set.size()]);
        if (log.isDebugEnabled())
            for (int i = 0; i < array.length; i++) {
                log.debug("  location " + i + " is " + array[i]);
            }
        //  返回創(chuàng)建的類加載器
        return AccessController.doPrivileged(
                new PrivilegedAction<URLClassLoader>() {
                    @Override
                    public URLClassLoader run() {
                        if (parent == null)
                            return new URLClassLoader(array);
                        else
                            return new URLClassLoader(array, parent);
                    }
                });
    }

大塊的if中的代碼實(shí)際上是對(duì)資源進(jìn)行轉(zhuǎn)化加載的過(guò)程悯辙,而return部分才是返回類加載器的部分琳省,代碼根據(jù)是否有parent調(diào)用了URLClassLoader不同的構(gòu)造器,Common ClassLoader調(diào)用的是沒(méi)有parent的構(gòu)造器

圖4. Common ClassLoader的parent創(chuàng)建的底層關(guān)鍵代碼

按紅線所畫Common ClassLoader的parent實(shí)際上是JDK中sun.misc.Launcher.class類的loader成員變量躲撰,而在上一篇文章中已經(jīng)知道該loader的值就是應(yīng)用類加載器(系統(tǒng)類加載器)System ClassLoader针贬。至此Tomcat中類加載機(jī)制和JDK的類加載機(jī)制也建立上了聯(lián)系
現(xiàn)在Tomcat的類加載機(jī)制已完成了一大半,剩下用于加載每個(gè)web應(yīng)用的類加載器WebApp ClassLoader的分析拢蛋,這個(gè)時(shí)候需要重新回到代碼清單1中看注釋(2)以下的部分桦他,其主要做的事情是通過(guò)反射創(chuàng)建了org.apache.catalina.startup.Catalina類的實(shí)例,然后調(diào)用了簽名為void setParentClassLoader(ClassLoader parentClassLoader)的方法谆棱,并傳入了Shared ClassLoader快压,上面我們說(shuō)過(guò)默認(rèn)情況下Shared ClassLoader就是Common ClassLoader,因此其傳入的參數(shù)實(shí)際上是Common ClassLoader
我們思考既然有保存parent的方法垃瞧,必定使用時(shí)會(huì)調(diào)用獲得parent方法蔫劣,那么我們需要查看Catalina類中ClassLoader getParentClassLoader()方法的調(diào)用棧(層級(jí)關(guān)系比較復(fù)雜,要緊跟主線不要迷失)皆警,最終定位到StandardContext中的synchronized void startInternal() throws LifecycleException方法(其中涉及到Tomcat的各種組件關(guān)系拦宣,生命周期管理等內(nèi)容,將在下次分析Tomcat組件文章中詳細(xì)介紹)信姓,下面是只保留核心邏輯的startInternal()方法 代碼清單4

    protected synchronized void startInternal() throws LifecycleException {
        // 其他邏輯略......

        // Add missing components as necessary
        if (webappResources == null) {   // (1) Required by Loader
            if (log.isDebugEnabled())
                log.debug("Configuring default Resources");
            try {
                String docBase = getDocBase();
                if (docBase == null) {
                    setResources(new EmptyDirContext());
                } else if (docBase.endsWith(".war")
                        && !(new File(getBasePath())).isDirectory()) {
                    setResources(new WARDirContext());
                } else {
                    setResources(new FileDirContext());
                }
            } catch (IllegalArgumentException e) {
                log.error(sm.getString("standardContext.resourcesInit"), e);
                ok = false;
            }
        }
        if (ok) {
            if (!resourcesStart()) {
                throw new LifecycleException("Error in resourceStart()");
            }
        }

        // (1)  為每一個(gè)web應(yīng)用創(chuàng)建一個(gè)WebappLoader
        if (getLoader() == null) {
            WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
            webappLoader.setDelegate(getDelegate());
            setLoader(webappLoader);
        }

      // 其他邏輯略......

        try {

            if (ok) {
                // (2)  調(diào)用WebappLoader的start
                // Start our subordinate components, if any
                if ((loader != null) && (loader instanceof Lifecycle))
                    ((Lifecycle) loader).start();
                }

        // 其他邏輯省略......

        } finally {
            // Unbinding thread
            unbindThread(oldCCL);
        }
    }

(1)處注釋下的代碼邏輯就是為每一個(gè)web應(yīng)用創(chuàng)建一個(gè)類加載器鸵隧,該類加載器的父類加載器就是通過(guò)getParentClassLoader()得到的Shared ClassLoader(Common ClassLoader),(2)處代碼調(diào)用了WebappLoaderstart方法意推,繼續(xù)跟進(jìn)

    protected void startInternal() throws LifecycleException {
        // 其他邏輯省略.....
        try {
            //創(chuàng)建類加載器關(guān)鍵方法
            classLoader = createClassLoader();
            classLoader.setResources(container.getResources());
            classLoader.setDelegate(this.delegate);
            classLoader.setSearchExternalFirst(searchExternalFirst);
            if (container instanceof StandardContext) {
                classLoader.setAntiJARLocking(
                        ((StandardContext) container).getAntiJARLocking());
                classLoader.setClearReferencesRmiTargets(
                        ((StandardContext) container).getClearReferencesRmiTargets());
                classLoader.setClearReferencesStatic(
                        ((StandardContext) container).getClearReferencesStatic());
                classLoader.setClearReferencesStopThreads(
                        ((StandardContext) container).getClearReferencesStopThreads());
                classLoader.setClearReferencesStopTimerThreads(
                        ((StandardContext) container).getClearReferencesStopTimerThreads());
                classLoader.setClearReferencesHttpClientKeepAliveThread(
                        ((StandardContext) container).getClearReferencesHttpClientKeepAliveThread());
            }

        // 其他邏輯省略.....
    }

由于Tomcat的設(shè)計(jì)豆瘫,WebappLoaderstart方法實(shí)際上調(diào)用的是父類的模板,而模板中的startinternal方法由各個(gè)子類具體實(shí)現(xiàn)菊值,其中最關(guān)鍵的方法為createClassLoader()

圖5. WebappLoader中createClassLoader方法

上圖中的loadClass成員變量的值為org.apache.catalina.loader.WebappClassLoader外驱,所以育灸,實(shí)際上該類為每一個(gè)web應(yīng)用創(chuàng)建了一個(gè)WebappClassLoader的實(shí)例,該實(shí)例的parent就是Shared ClassLoader或者Common ClassLoader昵宇,至此WebApp ClassLoader在圖1中的位置也得以驗(yàn)證磅崭。
從理論上分析來(lái)看,由于類加載的“雙親委派”機(jī)制瓦哎,一個(gè)類加載器只能加載本加載器指定的目錄以及使用有“繼承”關(guān)系的父類加載器加載過(guò)的類砸喻,而Tomcat為每一個(gè)Web應(yīng)用創(chuàng)建了一個(gè)WebappClassLoader,不同的WebappClassLoader是同級(jí)關(guān)系蒋譬,不會(huì)存在交叉訪問(wèn)的問(wèn)題割岛,從而達(dá)到web應(yīng)用相互隔離的目的。
那Tomcat是否沒(méi)有"破壞"雙親委派機(jī)制呢犯助?我們通過(guò)查看WebappClassLoader及其父類WebappClassLoaderBaseloadClass()findClass()分析一下Tomcat加載web應(yīng)用相關(guān)類的策略

public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {

        synchronized (getClassLoadingLockInternal(name)) {
            if (log.isDebugEnabled())
                log.debug("loadClass(" + name + ", " + resolve + ")");
            Class<?> clazz = null;

            // Log access to stopped classloader
            if (!started) {
                try {
                    throw new IllegalStateException();
                } catch (IllegalStateException e) {
                    log.info(sm.getString("webappClassLoader.stopped", name), e);
                }
            }
            //                (1)          
            // Check our previously loaded local class cache
            clazz = findLoadedClass0(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("  Returning class from cache");
                if (resolve)
                    resolveClass(clazz);
                return (clazz);
            }

            //              (2)
            //  Check our previously loaded class cache
            clazz = findLoadedClass(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("  Returning class from cache");
                if (resolve)
                    resolveClass(clazz);
                return (clazz);
            }

            //                (3)
            //  Try loading the class with the system class loader, to prevent
            //       the webapp from overriding J2SE classes
            try {
                clazz = j2seClassLoader.loadClass(name);
                if (clazz != null) {
                    if (resolve)
                        resolveClass(clazz);
                    return (clazz);
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }

            //  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 = "Security Violation, attempt to use " +
                            "Restricted Class: " + name;
                        if (name.endsWith("BeanInfo")) {
                            // BZ 57906: suppress logging for calls from
                            // java.beans.Introspector.findExplicitBeanInfo()
                            log.debug(error, se);
                        } else {
                            log.info(error, se);
                        }
                        throw new ClassNotFoundException(error, se);
                    }
                }
            }

            //              (4)
            boolean delegateLoad = delegate || filter(name);

            //              (5)
            //  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
                }
            }

            //            (6)
            //  Search local repositories
            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
            }

            // Delegate to parent unconditionally
            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);
    }

我們首先定位到WebappClassLoaderBaseloadClass方法癣漆,(1)處首先看name對(duì)應(yīng)的類是否存在緩存中,緩存是一個(gè)ConcurrentHashMap<String, ResourceEntry>的集合剂买,如果沒(méi)有緩存執(zhí)行(2)處邏輯惠爽,從JVM中查找是否曾今加載過(guò)該類,(3)中的代碼保證自定義類不會(huì)覆蓋java基礎(chǔ)類庫(kù)中的類雷恃,(4)的邏輯就是是否進(jìn)行雙親委派的分叉口疆股,其中delegate默認(rèn)為false,那么就要看filter(String)方法倒槐,該方法的內(nèi)部實(shí)際上將待加載類的全路徑名稱和一個(gè)成員變量protected static final String[] packageTriggers中的類名進(jìn)行比較,如果待加載的類名和packageTriggers數(shù)組中的內(nèi)容前綴匹配附井,則需要委派父類加載讨越,即執(zhí)行(5)處代碼,否則執(zhí)行(6)永毅,調(diào)用重寫的findClass(String)方法加載該類

public Class<?> findClass(String name) throws ClassNotFoundException {
        // 其他代碼略去.....
       
        // Ask our superclass to locate this class, if possible
        // (throws ClassNotFoundException if it is not found)
        Class<?> clazz = null;
        try {
            if (log.isTraceEnabled())
                log.trace("      findClassInternal(" + name + ")");
            //        (1)
            if (hasExternalRepositories && searchExternalFirst) {
                try {
                    clazz = super.findClass(name);
                } catch(ClassNotFoundException cnfe) {
                    // Ignore - will search internal repositories next
                } catch(AccessControlException ace) {
                    log.warn("WebappClassLoaderBase.findClassInternal(" + name
                            + ") security exception: " + ace.getMessage(), ace);
                    throw new ClassNotFoundException(name, ace);
                } catch (RuntimeException e) {
                    if (log.isTraceEnabled())
                        log.trace("      -->RuntimeException Rethrown", e);
                    throw e;
                }
            }
            //            (2)
            if ((clazz == null)) {
                try {
                    clazz = findClassInternal(name);
                } catch(ClassNotFoundException cnfe) {
                    if (!hasExternalRepositories || searchExternalFirst) {
                        throw cnfe;
                    }
                } catch(AccessControlException ace) {
                    log.warn("WebappClassLoaderBase.findClassInternal(" + name
                            + ") security exception: " + ace.getMessage(), ace);
                    throw new ClassNotFoundException(name, ace);
                } catch (RuntimeException e) {
                    if (log.isTraceEnabled())
                        log.trace("      -->RuntimeException Rethrown", e);
                    throw e;
                }
            }
            
      //其他代碼略去........
        return (clazz);

    }

(1)處由于hasExternalRepositoriessearchExternalFirst默認(rèn)為false把跨,因此執(zhí)行(2)處邏輯,調(diào)用findClassInternal(String)方法

圖6. WebappClassLoader類的findClassInternal方法

其主要的思想是根據(jù)待加載類的全路徑讀取該類的二進(jìn)制數(shù)據(jù)沼死,進(jìn)而進(jìn)行類的預(yù)定義着逐、class source的解析等,將該類加載到JVM中
綜上所述意蛀,我認(rèn)為Tomcat的類加載機(jī)制不能算完全“正統(tǒng)”的雙親委派耸别,WebappClassLoader內(nèi)部重寫了loadClassfindClass方法,實(shí)現(xiàn)了繞過(guò)“雙親委派”直接加載web應(yīng)用內(nèi)部的資源县钥,當(dāng)然可以通過(guò)在Context.xml文件中加上<Loader delegate = "true">開啟正統(tǒng)的“雙親委派”加載機(jī)制

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末秀姐,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子若贮,更是在濱河造成了極大的恐慌省有,老刑警劉巖痒留,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異蠢沿,居然都是意外死亡伸头,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門舷蟀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)恤磷,“玉大人雪侥,你說(shuō)我怎么就攤上這事速缨。” “怎么了吭历?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵通贞,是天一觀的道長(zhǎng)哭懈。 經(jīng)常有香客問(wèn)我锄贷,道長(zhǎng)琉预,這世上最難降的妖魔是什么卒暂? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任诈嘿,我火速辦了婚禮析砸,結(jié)果婚禮上作郭,老公的妹妹穿的比我還像新娘。我一直安慰自己咏尝,他們只是感情好伺糠,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布酣倾。 她就那樣靜靜地躺著午绳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上据忘,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音钥屈,去河邊找鬼射亏。 笑死,一個(gè)胖子當(dāng)著我的面吹牛竭业,可吹牛的內(nèi)容都是我干的智润。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼未辆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼窟绷!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起咐柜,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤兼蜈,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后拙友,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體为狸,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年遗契,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了辐棒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖漾根,靈堂內(nèi)的尸體忽然破棺而出泰涂,到底是詐尸還是另有隱情,我是刑警寧澤立叛,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布负敏,位于F島的核電站,受9級(jí)特大地震影響秘蛇,放射性物質(zhì)發(fā)生泄漏其做。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一赁还、第九天 我趴在偏房一處隱蔽的房頂上張望妖泄。 院中可真熱鬧,春花似錦艘策、人聲如沸蹈胡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)罚渐。三九已至,卻和暖如春驯妄,著一層夾襖步出監(jiān)牢的瞬間荷并,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工青扔, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留源织,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓微猖,卻偏偏與公主長(zhǎng)得像谈息,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子凛剥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容