Tomcat 7 啟動分析(二)Bootstrap 類中的 main 方法

預流發表於2018-01-28

之前分析了 Tomcat 的啟動指令碼,如果從 startup.bat 開始啟動 Tomcat 的話會發現最後會呼叫 org.apache.catalina.startup.Bootstrap 裡的 main 方法,並且傳過來的最後一個命令列引數是 start,接下來的啟動程式碼分析就從這裡開始。

先看下這個 main 方法的程式碼:


     1	/**
     2	     * Main method and entry point when starting Tomcat via the provided
     3	     * scripts.
     4	     *
     5	     * @param args Command line arguments to be processed
     6	     */
     7	    public static void main(String args[]) {
     8	
     9	        if (daemon == null) {
    10	            // Don't set daemon until init() has completed
    11	            Bootstrap bootstrap = new Bootstrap();
    12	            try {
    13	                bootstrap.init();
    14	            } catch (Throwable t) {
    15	                handleThrowable(t);
    16	                t.printStackTrace();
    17	                return;
    18	            }
    19	            daemon = bootstrap;
    20	        } else {
    21	            // When running as a service the call to stop will be on a new
    22	            // thread so make sure the correct class loader is used to prevent
    23	            // a range of class not found exceptions.
    24	            Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
    25	        }
    26	
    27	        try {
    28	            String command = "start";
    29	            if (args.length > 0) {
    30	                command = args[args.length - 1];
    31	            }
    32	
    33	            if (command.equals("startd")) {
    34	                args[args.length - 1] = "start";
    35	                daemon.load(args);
    36	                daemon.start();
    37	            } else if (command.equals("stopd")) {
    38	                args[args.length - 1] = "stop";
    39	                daemon.stop();
    40	            } else if (command.equals("start")) {
    41	                daemon.setAwait(true);
    42	                daemon.load(args);
    43	                daemon.start();
    44	            } else if (command.equals("stop")) {
    45	                daemon.stopServer(args);
    46	            } else if (command.equals("configtest")) {
    47	                daemon.load(args);
    48	                if (null==daemon.getServer()) {
    49	                    System.exit(1);
    50	                }
    51	                System.exit(0);
    52	            } else {
    53	                log.warn("Bootstrap: command \"" + command + "\" does not exist.");
    54	            }
    55	        } catch (Throwable t) {
    56	            // Unwrap the Exception for clearer error reporting
    57	            if (t instanceof InvocationTargetException &&
    58	                    t.getCause() != null) {
    59	                t = t.getCause();
    60	            }
    61	            handleThrowable(t);
    62	            t.printStackTrace();
    63	            System.exit(1);
    64	        }
    65	
    66	    }
複製程式碼

這裡的 daemon 是 Bootstrap 類中的一個靜態成員變數,型別就是 Bootstrap,第 10 行的註釋已經說明在呼叫過 init 方法之後才會給該變數賦值,初始時將是 null,所以首先將例項化一個 Bootstrap 物件,接著呼叫 init 方法,該方法程式碼如下:


     1	/**
     2	     * Initialize daemon.
     3	     */
     4	    public void init()
     5	        throws Exception
     6	    {
     7	
     8	        // Set Catalina path
     9	        setCatalinaHome();
    10	        setCatalinaBase();
    11	
    12	        initClassLoaders();
    13	
    14	        Thread.currentThread().setContextClassLoader(catalinaLoader);
    15	
    16	        SecurityClassLoad.securityClassLoad(catalinaLoader);
    17	
    18	        // Load our startup class and call its process() method
    19	        if (log.isDebugEnabled())
    20	            log.debug("Loading startup class");
    21	        Class startupClass =
    22	            catalinaLoader.loadClass
    23	            ("org.apache.catalina.startup.Catalina");
    24	        Object startupInstance = startupClass.newInstance();
    25	
    26	        // Set the shared extensions class loader
    27	        if (log.isDebugEnabled())
    28	            log.debug("Setting startup class properties");
    29	        String methodName = "setParentClassLoader";
    30	        Class paramTypes[] = new Class[1];
    31	        paramTypes[0] = Class.forName("java.lang.ClassLoader");
    32	        Object paramValues[] = new Object[1];
    33	        paramValues[0] = sharedLoader;
    34	        Method method =
    35	            startupInstance.getClass().getMethod(methodName, paramTypes);
    36	        method.invoke(startupInstance, paramValues);
    37	
    38	        catalinaDaemon = startupInstance;
    39	
    40	    }
複製程式碼

這裡不再逐句解釋程式碼的作用,總的來說這個方法主要做了一下幾件事:

  1. 設定 catalina.home、catalina.base 系統屬性,
  2. 建立commonLoader、catalinaLoader、sharedLoader類載入器(預設情況下這三個類載入器指向同一個物件。建議看看 createClassLoader 方法,裡面做的事情還挺多,比如裝載 catalina.properties 裡配置的目錄下的檔案和jar包,後兩個載入器的父載入器都是第一個,最後註冊了 MBean,可以用於 JVM 監控該物件),
  3. 例項化一個 org.apache.catalina.startup.Catalina 物件,並賦值給靜態成員 catalinaDaemon,以 sharedLoader 作為入參通過反射呼叫該物件的 setParentClassLoader 方法。

接下來去命令列最後一個引數,按文章開頭所說是 start,所以將執行 34 行到 36 行的程式碼,將會執行 Bootstrap 類中的 load、start 方法。

load 方法程式碼如下:


     1	    /**
     2	     * Load daemon.
     3	     */
     4	    private void load(String[] arguments)
     5	        throws Exception {
     6	
     7	        // Call the load() method
     8	        String methodName = "load";
     9	        Object param[];
    10	        Class paramTypes[];
    11	        if (arguments==null || arguments.length==0) {
    12	            paramTypes = null;
    13	            param = null;
    14	        } else {
    15	            paramTypes = new Class[1];
    16	            paramTypes[0] = arguments.getClass();
    17	            param = new Object[1];
    18	            param[0] = arguments;
    19	        }
    20	        Method method =
    21	            catalinaDaemon.getClass().getMethod(methodName, paramTypes);
    22	        if (log.isDebugEnabled())
    23	            log.debug("Calling startup class " + method);
    24	        method.invoke(catalinaDaemon, param);
    25	
    26	    }
複製程式碼

就是通過反射呼叫 catalinaDaemon 物件的 load 方法,catalinaDaemon 物件在上面的 init 方法中已經例項化過了。

start 方法與 load 方法相似,也是通過反射呼叫 catalinaDaemon 物件上的 start 方法:


     1	    /**
     2	     * Start the Catalina daemon.
     3	     */
     4	    public void start()
     5	        throws Exception {
     6	        if( catalinaDaemon==null ) init();
     7	
     8	        Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
     9	        method.invoke(catalinaDaemon, (Object [])null);
    10	
    11	    }
複製程式碼

下面一篇文章將分析 catalinaDaemon 物件中的 load、start 兩個方法,裡面會涉及一個有趣的話題 —— Digester 的使用。

相關文章