Tomcat源碼解析系列(一)Bootstrap 啓動

前言java


Tomcat是後端服務最多見的web容器,對於後端程序員來講,瞭解它對我的自身技術能力以及業務開發能力都是必要的。程序員

tomcat 版本:9.0.16web


1. main 方法apache

Tomcat是能夠獨立啓動的,java程序啓動是須要main方法的,所以讀tomcat源碼就從它的main方法開始。Tomcat的main方法在org.apache.catalina.startup.Bootstrap 裏bootstrap

public final class Bootstrap {
    ……
    
    /**
     * Daemon object used by main.
     */
    private static final Object daemonLock = new Object();
    
    ……
    
    
   /**
     * Main method and entry point when starting Tomcat via the provided
     * scripts.
     *
     * @param args Command line arguments to be processed
     */
    public static void main(String args[]) {

        synchronized (daemonLock) {
            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 = 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);
            }
        }

        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);
        }

    }
    
    ……
}

上面代碼邏輯很簡單,就是建立一個 Bootstrap 對象,調用它的 init 方法初始化,而後根據啓動參數,分別調用 Bootstrap 對象的不一樣方法。
1.1 init方法 後端

首先看 Bootstrap 的 init 方法tomcat

/**
     * Initialize daemon.
     * @throws Exception Fatal initialization error
     */
    public void init() throws Exception {

        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");
        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;
        Method method =
            startupInstance.getClass().getMethod(methodName, paramTypes);
        method.invoke(startupInstance, paramValues);

        catalinaDaemon = startupInstance;

    }

init 方法也比較簡單,先調用了 initClassLoaders() 來初始化一些 ClassLoader,tomcat 須要加載應用程序裏,因此須要 ClassLoader,而後設置了一下當天線程的 contextClassLoader 爲
catalinaLoader,這個 catalinaLoader就是在 initClassLoaders() 初始化的,最後經過反射建立了一個 Catalina 類型的 startupInstance 對象,並調用了它的 setParentClassLoader 方法。ide

1.1.1 initClassLoaders 方法this

接着看一下 initClassLoaders() 方法url

ClassLoader commonLoader = null;
    ClassLoader catalinaLoader = null;
    ClassLoader sharedLoader = null;



    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() 就是爲了初始化 Bootstrap 類裏的三個 ClassLoader 成員變量,看 createClassLoader 方法的定義

private ClassLoader createClassLoader(String name, ClassLoader parent)

能夠看出,catalinaLoader 和 sharedLoader 的 parentClassLoader 是 commonLoader。
1.1.2 initClassLoaders 方法
下面看看 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);
    }

方法的邏輯也比較簡單
就是從 catalina.property文件裏找 common.loader, shared.loader, server.loader 對應的值,而後構形成Repository 列表,再將Repository 列表傳入ClassLoaderFactory.createClassLoader 方法,ClassLoaderFactory.createClassLoader 返回的是 URLClassLoader,而Repository 列表就是這個URLClassLoader 能夠加在的類的路徑。
在catalina.property文件裏

common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
server.loader=
shared.loader=

其中 shared.loader, server.loader 是沒有值的,createClassLoader 方法裏若是沒有值的話,就返回傳入的 parent ClassLoader,也就是說,commonLoader,catalinaLoader,sharedLoader 實際上是一個對象。在Tomcat以前的版本里,這三個是不一樣的URLClassLoader對象。

Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
        Object startupInstance = startupClass.getConstructor().newInstance();

初始化完三個ClassLoader對象後,init() 方法就使用 catalinaClassLoader 加載了org.apache.catalina.startup.Catalina 類,並建立了一個對象,而後經過反射調用這個對象的 setParentClassLoader 方法,傳入的參數是 sharedClassLoader。最後吧這個 Catania 對象複製給 catalinaDaemon 屬性。

1.2 start方法

main 方法初始化完 Bootstrap 對象後,就根據傳入的參數,分別調用 Bootstrap 不一樣的方法,以 "start" 參數爲例,調用了這三個方法

daemon.setAwait(true);
daemon.load(args);
daemon.start();

這三個方法的調用的共同點是經過反射去調用 init 方法裏初始化過的 Catalina 對象的同名的方法。這三個方法將在下一篇文章總結。

1.2 Bootstrap 的 static 塊

Bootstrap 有一個 static 的代碼塊,這個代碼塊的做用是用來初始化 Bootstrap 裏的兩個static屬性的

private static final File catalinaBaseFile;
private static final File catalinaHomeFile;

2. 小結能夠看出 Bootstrap 的做用是初始化公共資源,建立一個 Catalina 類,並執行其相關方法,起了一個引導的做用。

相關文章
相關標籤/搜索