深入理解Tomcat(五)類加載機(jī)制

前言

我們知道,Java默認(rèn)的類加載機(jī)制是通過雙親委派模型來(lái)實(shí)現(xiàn)的。而Tomcat實(shí)現(xiàn)的方式又和雙親委派模型有所區(qū)別。原因在于一個(gè)Tomcat容器允許同時(shí)運(yùn)行多個(gè)Web程序,每個(gè)Web程序依賴的類又必須是相互隔離的。因此,如果Tomcat使用雙親委派模式來(lái)加載類的話,將導(dǎo)致Web程序依賴的類變?yōu)楣蚕淼摹?/p>

舉個(gè)例子,假如我們有兩個(gè)Web程序,一個(gè)依賴A庫(kù)的1.0版本,另一個(gè)依賴A庫(kù)的2.0版本,他們都使用了類xxx.xx.Clazz,其實(shí)現(xiàn)的邏輯因類庫(kù)版本的不同而結(jié)構(gòu)完全不同。那么這兩個(gè)Web程序的其中一個(gè)必然因?yàn)榧虞d的Clazz不是所使用的Clazz而出現(xiàn)問題!而這對(duì)于開發(fā)來(lái)說是非常致命的!

怎么解決呢?這將是這篇文章所要探討的問題!

雙親委派模式

Java是一門面向?qū)ο?/code>的語(yǔ)言,而對(duì)象又必然依托于要運(yùn)行,必須首先被加載到內(nèi)存。我們可以簡(jiǎn)單地把類分為幾類:

  1. Java自帶的核心類
  2. Java支持的可擴(kuò)展類
  3. 我們自己編寫的類

如果所有的類都使用一個(gè)類加載器來(lái)加載,會(huì)出現(xiàn)什么問題呢?

假如我們自己編寫一個(gè)類java.util.Object,它的實(shí)現(xiàn)可能有一定的危險(xiǎn)性或者隱藏的bug。而我們知道Java自帶的核心類里面也有java.util.Object,如果JVM啟動(dòng)的時(shí)候先行加載的是我們自己編寫的java.util.Object,那么就有可能出現(xiàn)安全問題!

所以,Sun(后被Oracle收購(gòu))采用了另外一種方式來(lái)保證最基本的、也是最核心的功能不會(huì)被破壞。你猜的沒錯(cuò),那就是雙親委派模式

雙親委派模式對(duì)類加載器定義了層級(jí),每個(gè)類加載器都有一個(gè)父類加載器。在一個(gè)類需要加載的時(shí)候,首先委派給父類加載器來(lái)加載,而父類加載器又委派給祖父類加載器來(lái)加載,以此類推。如果父類及上面的類加載器都加載不了,那么由當(dāng)前類加載器來(lái)加載,并將被加載的類緩存起來(lái)。

我們畫一個(gè)圖來(lái)輔助我們理解!

類加載模型
  1. Java自帶的核心類 -- 由啟動(dòng)類加載器加載
  2. Java支持的可擴(kuò)展類 -- 由擴(kuò)展類加載器加載
  3. 我們自己編寫的類 -- 默認(rèn)由應(yīng)用程序類加載器或其子類加載

雙親委派模型解決了類錯(cuò)亂加載的問題,也設(shè)計(jì)得非常精妙。但它也不是萬(wàn)能的,在有些場(chǎng)景也會(huì)遇到它解決不了的問題。哪些場(chǎng)景呢?我們舉一個(gè)例子來(lái)看看。

在Java核心類里面有SPI(Service Provider Interface),它由Sun編寫規(guī)范,第三方來(lái)負(fù)責(zé)實(shí)現(xiàn)。SPI需要用到第三方實(shí)現(xiàn)類。如果使用雙親委派模型,那么第三方實(shí)現(xiàn)類也需要放在Java核心類里面才可以,不然的話第三方實(shí)現(xiàn)類將不能被加載使用。但是這顯然是不合理的!怎么辦呢?ContextClassLoader(上下文類加載器)就來(lái)解圍了。

java.lang.Thread里面有兩個(gè)方法,get/set上下文類加載器

  1. public void setContextClassLoader(ClassLoader cl)
  2. public ClassLoader getContextClassLoader()

我們可以通過在SPI類里面調(diào)用getContextClassLoader來(lái)獲取第三方實(shí)現(xiàn)類的類加載器。由第三方實(shí)現(xiàn)類通過調(diào)用setContextClassLoader來(lái)傳入自己實(shí)現(xiàn)的類加載器。這樣就變相地解決了雙親委派模式遇到的問題。但是很顯然,這種機(jī)制破壞了雙親委派模式

Tomcat類加載機(jī)制

既然Tomcat的類加載機(jī)器不同于雙親委派模式,那么它又是一種怎樣的模式呢?官網(wǎng)鏈接-ClassLoading有對(duì)此進(jìn)行描述。

另外,我們還是先來(lái)看看Tomcat整體的類加載圖是怎樣的吧~

Tomcat類加載圖

我們?cè)谶@張圖中看到很多類加載器,除了Jdk自帶的類加載器,我們尤其關(guān)心Tomcat自身持有的類加載器。仔細(xì)一點(diǎn)我們很容易發(fā)現(xiàn):Catalina類加載器和Shared類加載器,他們并不是父子關(guān)系,而是兄弟關(guān)系。為啥這樣設(shè)計(jì),我們得分析一下每個(gè)類加載器的用途,才能知曉。

  1. Common類加載器,負(fù)責(zé)加載Tomcat和Web應(yīng)用都復(fù)用的類
  2. Catalina類加載器,負(fù)責(zé)加載Tomcat專用的類,而這些被加載的類在Web應(yīng)用中將不可見
  3. Shared類加載器,負(fù)責(zé)加載Tomcat下所有的Web應(yīng)用程序都復(fù)用的類,而這些被加載的類在Tomcat中將不可見
  4. WebApp類加載器,負(fù)責(zé)加載具體的某個(gè)Web應(yīng)用程序所使用到的類,而這些被加載的類在Tomcat和其他的Web應(yīng)用程序都將不可見
  5. Jsp類加載器,每個(gè)jsp頁(yè)面一個(gè)類加載器,不同的jsp頁(yè)面有不同的類加載器,方便實(shí)現(xiàn)jsp頁(yè)面的熱插拔

CATALINA_HOME和CATALINA_BASE

在tomcat官網(wǎng)上有對(duì)他們進(jìn)行描述,官網(wǎng)鏈接-CATALINA_HOME and CATALINA_BASE

官網(wǎng)說明

簡(jiǎn)單地說,CATALINA_HOME指的是安裝目錄,CATALINA_BASE指的是工作目錄。在一臺(tái)物理機(jī)上面,可以只有一個(gè)安裝目錄,但是允許有多個(gè)工作目錄。

File的路徑方法

java.io.File有3個(gè)方法可以獲取路徑,為了避免在閱讀Tomcat源碼的時(shí)候出現(xiàn)理解問題,這兒寫出demo來(lái)說明其含義。

  1. getPath()
  2. getAbsolutePath()
  3. getCanonicalPath()
public class Main {
    public static void main(String[] args) throws Exception {
        System.out.println(System.getProperty("user.dir"));

        System.out.println("-----默認(rèn)相對(duì)路徑:取得路徑不同------");
        File file1 = new File("../src/test1.txt");
        System.out.println(file1.getPath());
        System.out.println(file1.getAbsolutePath());
        System.out.println(file1.getCanonicalPath());

        System.out.println("-----默認(rèn)相對(duì)路徑:取得路徑不同------");
        File file = new File("./test1.txt");
        System.out.println(file.getPath());
        System.out.println(file.getAbsolutePath());
        System.out.println(file.getCanonicalPath());

        System.out.println("-----默認(rèn)絕對(duì)路徑:取得路徑相同------");
        File file2 = new File("/Users/pro/ws/learn/learn-javaagent/test1.txt");
        System.out.println(file2.getPath());
        System.out.println(file2.getAbsolutePath());
        System.out.println(file2.getCanonicalPath());
    }
}

輸出結(jié)果如下:

-----默認(rèn)相對(duì)路徑:取得路徑不同------
../src/test1.txt
/Users/pro/ws/learn/learn-javaagent/../src/test1.txt
/Users/pro/ws/learn/src/test1.txt
-----默認(rèn)相對(duì)路徑:取得路徑不同------
./test1.txt
/Users/pro/ws/learn/learn-javaagent/./test1.txt
/Users/pro/ws/learn/learn-javaagent/test1.txt
-----默認(rèn)絕對(duì)路徑:取得路徑相同------
/Users/pro/ws/learn/learn-javaagent/test1.txt
/Users/pro/ws/learn/learn-javaagent/test1.txt
/Users/pro/ws/learn/learn-javaagent/test1.txt

源碼閱讀

Tomcat啟動(dòng)的入口在Bootstrap的main()方法main()方法執(zhí)行前,必然先執(zhí)行其static{}塊。所以我們首先分析static{}塊,然后分析main()方法

Bootstrap.static{}

static {
    // 獲取用戶目錄
    // Will always be non-null
    String userDir = System.getProperty("user.dir");

    // 第一步,從環(huán)境變量中獲取catalina.home,在沒有獲取到的時(shí)候?qū)?zhí)行后面的獲取操作
    // Home first
    String home = System.getProperty(Globals.CATALINA_HOME_PROP);
    File homeFile = null;

    if (home != null) {
        File f = new File(home);
        try {
            homeFile = f.getCanonicalFile();
        } catch (IOException ioe) {
            homeFile = f.getAbsoluteFile();
        }
    }

    // 第二步,在第一步?jīng)]獲取的時(shí)候,從bootstrap.jar所在目錄的上一級(jí)目錄獲取
    if (homeFile == null) {
        // First fall-back. See if current directory is a bin directory
        // in a normal Tomcat install
        File bootstrapJar = new File(userDir, "bootstrap.jar");

        if (bootstrapJar.exists()) {
            File f = new File(userDir, "..");
            try {
                homeFile = f.getCanonicalFile();
            } catch (IOException ioe) {
                homeFile = f.getAbsoluteFile();
            }
        }
    }

    // 第三步,第二步中的bootstrap.jar可能不存在,這時(shí)我們直接把user.dir作為我們的home目錄
    if (homeFile == null) {
        // Second fall-back. Use current directory
        File f = new File(userDir);
        try {
            homeFile = f.getCanonicalFile();
        } catch (IOException ioe) {
            homeFile = f.getAbsoluteFile();
        }
    }

    // 重新設(shè)置catalinaHome屬性
    catalinaHomeFile = homeFile;
    System.setProperty(
            Globals.CATALINA_HOME_PROP, catalinaHomeFile.getPath());

    // 接下來(lái)獲取CATALINA_BASE(從系統(tǒng)變量中獲取),若不存在,則將CATALINA_BASE保持和CATALINA_HOME相同
    // Then base
    String base = System.getProperty(Globals.CATALINA_BASE_PROP);
    if (base == null) {
        catalinaBaseFile = catalinaHomeFile;
    } else {
        File baseFile = new File(base);
        try {
            baseFile = baseFile.getCanonicalFile();
        } catch (IOException ioe) {
            baseFile = baseFile.getAbsoluteFile();
        }
        catalinaBaseFile = baseFile;
    }
   // 重新設(shè)置catalinaBase屬性
    System.setProperty(
            Globals.CATALINA_BASE_PROP, catalinaBaseFile.getPath());
}

我們把代碼中的注釋搬下來(lái)總結(jié)一下:

  1. 獲取用戶目錄
  2. 第一步,從環(huán)境變量中獲取catalina.home,在沒有獲取到的時(shí)候?qū)?zhí)行后面的獲取操作
  3. 第二步,在第一步?jīng)]獲取的時(shí)候,從bootstrap.jar所在目錄的上一級(jí)目錄獲取
  4. 第三步,第二步中的bootstrap.jar可能不存在,這時(shí)我們直接把user.dir作為我們的home目錄
  5. 重新設(shè)置catalinaHome屬性
  6. 接下來(lái)獲取CATALINA_BASE(從系統(tǒng)變量中獲取),若不存在,則將CATALINA_BASE保持和CATALINA_HOME相同
  7. 重新設(shè)置catalinaBase屬性

簡(jiǎn)單總結(jié)一下,就是加載并設(shè)置catalinaHome和catalinaBase相關(guān)的信息,以備后續(xù)使用。

main()

main方法大體分成兩塊,一塊為init,另一塊為load+start。

public static void main(String args[]) {
    // 第一塊,main方法第一次執(zhí)行的時(shí)候,daemon肯定為null,所以直接new了一個(gè)Bootstrap對(duì)象,然后執(zhí)行其init()方法
    if (daemon == null) {
        // Don't set daemon until init() has completed
        Bootstrap bootstrap = new Bootstrap();
        try {
            bootstrap.init();
        } catch (Throwable t) {
            handleThrowable(t);
            t.printStackTrace();
            return;
        }
        // daemon守護(hù)對(duì)象設(shè)置為bootstrap
        daemon = bootstrap;
    } else {
        // When running as a service the call to stop will be on a new
        // thread so make sure the correct class loader is used to prevent
        // a range of class not found exceptions.
        Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
    }

    // 第二塊,執(zhí)行守護(hù)對(duì)象的load方法和start方法
    try {
        String command = "start";
        if (args.length > 0) {
            command = args[args.length - 1];
        }

        if (command.equals("startd")) {
            args[args.length - 1] = "start";
            daemon.load(args);
            daemon.start();
        } else if (command.equals("stopd")) {
            args[args.length - 1] = "stop";
            daemon.stop();
        } else if (command.equals("start")) {
            daemon.setAwait(true);
            daemon.load(args);
            daemon.start();
            if (null == daemon.getServer()) {
                System.exit(1);
            }
        } else if (command.equals("stop")) {
            daemon.stopServer(args);
        } else if (command.equals("configtest")) {
            daemon.load(args);
            if (null == daemon.getServer()) {
                System.exit(1);
            }
            System.exit(0);
        } else {
            log.warn("Bootstrap: command \"" + command + "\" does not exist.");
        }
    } catch (Throwable t) {
        // Unwrap the Exception for clearer error reporting
        if (t instanceof InvocationTargetException &&
                t.getCause() != null) {
            t = t.getCause();
        }
        handleThrowable(t);
        t.printStackTrace();
        System.exit(1);
    }
}

我們點(diǎn)到init()里面去看看~

public void init() throws Exception {
    // 非常關(guān)鍵的地方,初始化類加載器s,后面我們會(huì)詳細(xì)具體地分析這個(gè)方法
    initClassLoaders();

    // 設(shè)置上下文類加載器為catalinaLoader,這個(gè)類加載器負(fù)責(zé)加載Tomcat專用的類
    Thread.currentThread().setContextClassLoader(catalinaLoader);
    // 暫時(shí)略過,后面會(huì)講
    SecurityClassLoad.securityClassLoad(catalinaLoader);

    // 使用catalinaLoader加載我們的Catalina類
    // 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();

    // 設(shè)置Catalina類的parentClassLoader屬性為sharedLoader
    // 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);

    // catalina守護(hù)對(duì)象為剛才使用catalinaLoader加載類、并初始化出來(lái)的Catalina對(duì)象
    catalinaDaemon = startupInstance;
}

關(guān)鍵的方法initClassLoaders,這個(gè)方法負(fù)責(zé)初始化Tomcat的類加載器。通過這個(gè)方法,我們很容易驗(yàn)證我們上一小節(jié)提到的Tomcat類加載圖。

private void initClassLoaders() {
    try {
        // 創(chuàng)建commonLoader,如果未創(chuàng)建成果的話,則使用應(yīng)用程序類加載器作為commonLoader
        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();
        }
        // 創(chuàng)建catalinaLoader,父類加載器為commonLoader
        catalinaLoader = createClassLoader("server", commonLoader);
        // 創(chuàng)建sharedLoader,父類加載器為commonLoader
        sharedLoader = createClassLoader("shared", commonLoader);
    } catch (Throwable t) {
        // 如果創(chuàng)建的過程中出現(xiàn)異常了,日志記錄完成之后直接系統(tǒng)退出
        handleThrowable(t);
        log.error("Class loader creation threw exception", t);
        System.exit(1);
    }
}

所有的類加載器的創(chuàng)建都使用到了方法createClassLoader,所以,我們進(jìn)一步分析一下這個(gè)方法。createClassLoader用到了CatalinaProperties.getProperty("xxx")方法,這個(gè)方法用于從conf/catalina.properties文件獲取屬性值。

private ClassLoader createClassLoader(String name, ClassLoader parent)
    throws Exception {
    // 獲取類加載器待加載的位置,如果為空,則不需要加載特定的位置,使用父類加載返回回去。
    String value = CatalinaProperties.getProperty(name + ".loader");
    if ((value == null) || (value.equals("")))
        return parent;
    // 替換屬性變量,比如:${catalina.base}、${catalina.home}
    value = replace(value);

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

   // 解析屬性路徑變量為倉(cāng)庫(kù)路徑數(shù)組
    String[] repositoryPaths = getPaths(value);

    // 對(duì)每個(gè)倉(cāng)庫(kù)路徑進(jìn)行repositories設(shè)置。我們可以把repositories看成一個(gè)個(gè)待加載的位置對(duì)象,可以是一個(gè)classes目錄,一個(gè)jar文件目錄等等
    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));
        }
    }
    // 使用類加載器工廠創(chuàng)建一個(gè)類加載器
    return ClassLoaderFactory.createClassLoader(repositories, parent);
}

createClassLoader方法里面有幾個(gè)關(guān)鍵的信息,一個(gè)為Repository,另一個(gè)為ClassLoaderFactory.createClassLoader。我們逐一來(lái)分析一下。

首先來(lái)看Repository,非常簡(jiǎn)單,就是一個(gè)POJO,里面有一個(gè)location表示位置,type表示類型。type的枚舉有4種:目錄、jar集合、jar和url路徑。

public enum RepositoryType {
        DIR,
        GLOB,
        JAR,
        URL
    }

    public static class Repository {
        private final String location;
        private final RepositoryType type;

        public Repository(String location, RepositoryType type) {
            this.location = location;
            this.type = type;
        }

        public String getLocation() {
            return location;
        }

        public RepositoryType getType() {
            return type;
        }
    }

其次,我們來(lái)分析一下ClassLoaderFactory.createClassLoader--類加載器工廠創(chuàng)建類加載器。這個(gè)方法可謂是非常的長(zhǎng)!前方高能~

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<>();
    // 遍歷repositories,對(duì)每個(gè)repository進(jìn)行類型判斷,并生成URL,每個(gè)URL我們都要校驗(yàn)其有效性,有效的URL我們會(huì)放到URL集合中
    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]);
        }

    // 從這兒看,最終所有的類加載器都是URLClassLoader的對(duì)象~~
    return AccessController.doPrivileged(
            new PrivilegedAction<URLClassLoader>() {
                @Override
                public URLClassLoader run() {
                    if (parent == null)
                        return new URLClassLoader(array);
                    else
                        return new URLClassLoader(array, parent);
                }
            });
}

類加載器工廠方法中有用到validateFile方法,這個(gè)方法是干嘛的呢?我們分析一下。

private static boolean validateFile(File file,
        RepositoryType type) throws IOException {
    // 對(duì)于目錄類型或者jar集合目錄類型,我們會(huì)校驗(yàn)是否為目錄和是否可讀
    if (RepositoryType.DIR == type || RepositoryType.GLOB == type) {
        if (!file.isDirectory() || !file.canRead()) {
            String msg = "Problem with directory [" + file +
                    "], exists: [" + file.exists() +
                    "], isDirectory: [" + file.isDirectory() +
                    "], canRead: [" + file.canRead() + "]";

            File home = new File (Bootstrap.getCatalinaHome());
            home = home.getCanonicalFile();
            File base = new File (Bootstrap.getCatalinaBase());
            base = base.getCanonicalFile();
            File defaultValue = new File(base, "lib");

            // Existence of ${catalina.base}/lib directory is optional.
            // Hide the warning if Tomcat runs with separate catalina.home
            // and catalina.base and that directory is absent.
            if (!home.getPath().equals(base.getPath())
                    && file.getPath().equals(defaultValue.getPath())
                    && !file.exists()) {
                log.debug(msg);
            } else {
                log.warn(msg);
            }
            return false;
        }
    }
    // 對(duì)于JAR,我們會(huì)校驗(yàn)文件是否可讀
    else if (RepositoryType.JAR == type) {
        if (!file.canRead()) {
            log.warn("Problem with JAR file [" + file +
                    "], exists: [" + file.exists() +
                    "], canRead: [" + file.canRead() + "]");
            return false;
        }
    }
    return true;
}
  1. 對(duì)于目錄類型或者jar集合目錄類型,我們會(huì)校驗(yàn)是否為目錄和是否可讀
  2. 對(duì)于JAR,我們會(huì)校驗(yàn)文件是否可讀

我們已經(jīng)對(duì)initClassLoaders分析完了,接下來(lái)分析SecurityClassLoad.securityClassLoad,我們看看里面做了什么事情

public static void securityClassLoad(ClassLoader loader) throws Exception {
    securityClassLoad(loader, true);
}

static void securityClassLoad(ClassLoader loader, boolean requireSecurityManager) throws Exception {

    if (requireSecurityManager && System.getSecurityManager() == null) {
        return;
    }

    loadCorePackage(loader);
    loadCoyotePackage(loader);
    loadLoaderPackage(loader);
    loadRealmPackage(loader);
    loadServletsPackage(loader);
    loadSessionPackage(loader);
    loadUtilPackage(loader);
    loadValvesPackage(loader);
    loadJavaxPackage(loader);
    loadConnectorPackage(loader);
    loadTomcatPackage(loader);
}

 private static final void loadCorePackage(ClassLoader loader) throws Exception {
    final String basePackage = "org.apache.catalina.core.";
    loader.loadClass(basePackage + "AccessLogAdapter");
    loader.loadClass(basePackage + "ApplicationContextFacade$PrivilegedExecuteMethod");
    loader.loadClass(basePackage + "ApplicationDispatcher$PrivilegedForward");
    loader.loadClass(basePackage + "ApplicationDispatcher$PrivilegedInclude");
    loader.loadClass(basePackage + "ApplicationPushBuilder");
    loader.loadClass(basePackage + "AsyncContextImpl");
    loader.loadClass(basePackage + "AsyncContextImpl$AsyncRunnable");
    loader.loadClass(basePackage + "AsyncContextImpl$DebugException");
    loader.loadClass(basePackage + "AsyncListenerWrapper");
    loader.loadClass(basePackage + "ContainerBase$PrivilegedAddChild");
    loadAnonymousInnerClasses(loader, basePackage + "DefaultInstanceManager");
    loader.loadClass(basePackage + "DefaultInstanceManager$AnnotationCacheEntry");
    loader.loadClass(basePackage + "DefaultInstanceManager$AnnotationCacheEntryType");
    loader.loadClass(basePackage + "ApplicationHttpRequest$AttributeNamesEnumerator");
}

這兒其實(shí)就是使用catalinaLoader加載tomcat源代碼里面的各個(gè)專用類。我們大致羅列一下待加載的類所在的package:

  1. org.apache.catalina.core.*
  2. org.apache.coyote.*
  3. org.apache.catalina.loader.*
  4. org.apache.catalina.realm.*
  5. org.apache.catalina.servlets.*
  6. org.apache.catalina.session.*
  7. org.apache.catalina.util.*
  8. org.apache.catalina.valves.*
  9. javax.servlet.http.Cookie
  10. org.apache.catalina.connector.*
  11. org.apache.tomcat.*

好了,至此我們已經(jīng)分析完了init里面涉及到的幾個(gè)關(guān)鍵方法,真不容易呀~

load()和start()

我們上面提到main()方法除了初始化方法init,還有l(wèi)oad和start。下面我們逐一來(lái)分析他們。

private void load(String[] arguments)
    throws Exception {

    // Call the load() method
    String methodName = "load";
    Object param[];
    Class<?> paramTypes[];
    if (arguments==null || arguments.length==0) {
        paramTypes = null;
        param = null;
    } else {
        paramTypes = new Class[1];
        paramTypes[0] = arguments.getClass();
        param = new Object[1];
        param[0] = arguments;
    }
    Method method =
        catalinaDaemon.getClass().getMethod(methodName, paramTypes);
    if (log.isDebugEnabled())
        log.debug("Calling startup class " + method);
    method.invoke(catalinaDaemon, param);
}

load()方法只是調(diào)用了Catalina的load()方法,只是會(huì)根據(jù)main方法傳入的參數(shù)來(lái)判斷是調(diào)用public void load()還是public void load(String args[])

public void start() throws Exception {
    if( catalinaDaemon==null ) init();

    Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
    method.invoke(catalinaDaemon, (Object [])null);
}

如果Catalina對(duì)象不為null,則調(diào)用其init()方法來(lái)初始化,初始化完成之后,調(diào)用Catalina對(duì)象的start方法

WebApp類加載器

到這兒,我們隱隱感覺到少分析了點(diǎn)什么!沒錯(cuò),就是WebApp類加載器。整個(gè)啟動(dòng)過程分析下來(lái),我們?nèi)匀粵]有看到這個(gè)類加載器。它又是在哪兒出現(xiàn)的呢?

我們知道WebApp類加載器是Web應(yīng)用私有的,而每個(gè)Web應(yīng)用其實(shí)算是一個(gè)Context,那么我們通過Context的實(shí)現(xiàn)類應(yīng)該可以發(fā)現(xiàn)。在Tomcat中,Context的默認(rèn)實(shí)現(xiàn)為StandardContext,我們看看這個(gè)類的startInternal()方法,在這兒我們發(fā)現(xiàn)了我們感興趣的WebApp類加載器。

protected synchronized void startInternal() throws LifecycleException {
    if (getLoader() == null) {
        WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
        webappLoader.setDelegate(getDelegate());
        setLoader(webappLoader);
    }
}

入口代碼非常簡(jiǎn)單,就是webappLoader不存在的時(shí)候創(chuàng)建一個(gè),并調(diào)用setLoader方法。我們接著分析setLoader

public void setLoader(Loader loader) {

    Lock writeLock = loaderLock.writeLock();
    writeLock.lock();
    Loader oldLoader = null;
    try {
        // Change components if necessary
        oldLoader = this.loader;
        if (oldLoader == loader)
            return;
        this.loader = loader;

        // Stop the old component if necessary
        if (getState().isAvailable() && (oldLoader != null) &&
            (oldLoader instanceof Lifecycle)) {
            try {
                ((Lifecycle) oldLoader).stop();
            } catch (LifecycleException e) {
                log.error("StandardContext.setLoader: stop: ", e);
            }
        }

        // Start the new component if necessary
        if (loader != null)
            loader.setContext(this);
        if (getState().isAvailable() && (loader != null) &&
            (loader instanceof Lifecycle)) {
            try {
                ((Lifecycle) loader).start();
            } catch (LifecycleException e) {
                log.error("StandardContext.setLoader: start: ", e);
            }
        }
    } finally {
        writeLock.unlock();
    }

    // Report this property change to interested listeners
    support.firePropertyChange("loader", oldLoader, loader);
}

這兒,我們感興趣的就兩行代碼:

  1. ((Lifecycle) oldLoader).stop(); // 舊的加載器停止
  2. ((Lifecycle) loader).start(); // 新的加載器啟動(dòng)

總結(jié)

我們終于完整地分析完了Tomcat的整個(gè)啟動(dòng)過程+類加載過程。也了解并學(xué)習(xí)了Tomcat不同的類加載機(jī)制是為什么要這樣設(shè)計(jì),帶來(lái)的附加作用又是怎樣的。

從最后分析的load和start來(lái)看,第二入口在Catalina這個(gè)類,其對(duì)應(yīng)的方法為load()start()。后續(xù)分析源碼,我們會(huì)從這兩個(gè)方法入手。

除了Catalina之后,我們還要逐一分析巨多的tomcat組件,希望每個(gè)組件的分析都能帶給我們不一樣的閱讀體驗(yàn)和設(shè)計(jì)方式~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,382評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,038評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,616評(píng)論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,112評(píng)論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,355評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,869評(píng)論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,727評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,928評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,165評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評(píng)論 1 282
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,585評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,892評(píng)論 2 372

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