Tomcat源碼分析三:Tomcat啓動加載過程(一)的源碼解析

Tomcat啓動加載過程(一)的源碼解析

今天,我將分享用源碼的方式講解Tomcat啓動的加載過程,關於Tomcat的架構請參閱《Tomcat源碼分析二:先看看Tomcat的總體架構》一文。java

先看看應用狀況

在《Servlet與Tomcat運行示例》一文中,我詳細的記錄了Tomcat是如何啓動一個Servlet的程序的步驟。其中,第6步驟是啓動Tomcat,也就是在windows系統上執行startup.bat, 在linux操做系統上執行startup.sh的腳本。那麼,咱們就從這個腳本出發,走進Tomcat,看看它是如何啓動的?這裏,咱們以startup.sh爲例,windows端的startup.bat相似。linux

startup.sh的內容是什麼?

咱們先看看tomcat的啓動腳本startup.sh的內容是什麼,先看看其腳本內容(省略部分註釋),以下:apache

#!/bin/sh

# -----------------------------------------------------------------------------
# Start Script for the CATALINA Server
# -----------------------------------------------------------------------------

# Better OS/400 detection: see Bugzilla 31132
os400=false
case "`uname`" in
OS400*) os400=true;;
esac

# resolve links - $0 may be a softlink
PRG="$0"

while [ -h "$PRG" ] ; do
  ls=`ls -ld "$PRG"`
  link=`expr "$ls" : '.*-> \(.*\)$'`
  if expr "$link" : '/.*' > /dev/null; then
    PRG="$link"
  else
    PRG=`dirname "$PRG"`/"$link"
  fi
done

PRGDIR=`dirname "$PRG"`
EXECUTABLE=catalina.sh

# Check that target executable exists
if $os400; then
  # -x will Only work on the os400 if the files are:
  # 1. owned by the user
  # 2. owned by the PRIMARY group of the user
  # this will not work if the user belongs in secondary groups
  eval
else
  if [ ! -x "$PRGDIR"/"$EXECUTABLE" ]; then
    echo "Cannot find $PRGDIR/$EXECUTABLE"
    echo "The file is absent or does not have execute permission"
    echo "This file is needed to run this program"
    exit 1
  fi
fi

exec "$PRGDIR"/"$EXECUTABLE" start "$@"

提取其中主要的幾句:bootstrap

PRGDIR=`dirname "$PRG"`
EXECUTABLE=catalina.sh
exec "$PRGDIR"/"$EXECUTABLE" start "$@"

簡而概之,該腳本的執行內容爲:調用catalina.sh腳本。下面,咱們繼續來看下catalina.sh腳本的內容windows

catalina.sh腳本

因爲catalina.sh腳本內容比較多,這裏提取一些重要的內容,而後解釋其用途:tomcat

再簡要的描述下在catalina.sh中做用:完成環境檢查、環境初始化、參數初始化、啓動操做步驟。注意一下上圖中被綠色框出來的內容,能夠看到其調用執行的是org.apache.catalina.startup.Bootstrap類,而且傳輸過去的command指令爲start。微信

迴歸Java代碼

Bootstrap類進行了什麼操做呢?

接下來,咱們帶着這幾個問題來去探索一下Bootstrap類:架構

  • Bootstrap類在接收到start指令後要去幹什麼?
  • Bootstrap類在啓動過程當中的職責是什麼?

下面,咱們帶着上面的幾個問題來具體的探討一下Tomcat的源碼。先來看看Bootstrap類的main方法:app

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

從這段代碼中,能夠看出,其主要實現了兩個功能:源碼分析

  • 初始化一個守護進程變量daemon
  • 加載catalina.sh傳遞過來的參數,解析catalina.sh傳遞過來的指令,並按照指令執行程序,控制守護進程daemon的啓停等操做

bootstrap.init();有什麼操做呢?

針對上面的兩個功能,咱們進入到 init()方法看下有什麼操做,先看下init()方法的代碼:

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()的做用是初始化三個類加載器,代碼以下:

/**
     * Daemon reference.
     */
    private Object catalinaDaemon = null;

    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);
        }
    }
		
	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.properties
common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
  • commonLoader: 根據common.loader屬性的配置(經過代碼CatalinaProperties.getProperty(name + ".loader");讀取:catalina.properties), 建立commonLoader類加載器, 默認狀況下順序加載 ${catalina.base}/lib, ${catalina.base}/lib/.jar, ${catalina.home}/lib, ${catalina.home}/lib/.jar 四個目錄下的class和jar.
  • catalinaLoader: 根據server.loader屬性的配置, 建立catalinaLoader類加載器,其父類加載其爲commonLoader, 默認server.loader屬性爲空, 直接使用commonLoader.
  • sharedLoader:根據shared.loader屬性配置,建立sharedLoader類加載器,其父類加載其爲commonLoader, 默認shared.loader屬性爲空, 直接使用commonLoader.

當執行完initClassLoaders()方法以後,調用Thread.currentThread().setContextClassLoader(catalinaLoader);設置上下文類加載器爲catalinaLoader,從上面解析的狀況看,其實設置的上下文類加載器爲catalinaLoader的父類commonLoader。

SecurityClassLoad.securityClassLoad(catalinaLoader) 的做用是若是有SecurityManager,提早加載部分類。

以後,經過使用catalinaLoader加載org.apache.catalina.startup.Catalina類,建立實例Catalina並利用反射調用方法setParentClassLoader(),設置Catalina實例的parentClassLoader屬性爲sharedLoader類加載器(也就是commonLoader)。

最後,設置daemon爲新建立的實例Bootstrap。接下來,看一下main()方法下的指令處理。

傳遞過來的command指令是如何處理的呢?

咱們觀察一下main()方法的後半段,這裏貼一下代碼:

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) {
	// ...... 省略
}

能夠看到,其默認指令爲start, 而後,其根據接收到的參數區分爲startd、stopd、start、stop、configtest和其餘6種指令狀況。這裏咱們主要看一下start指令的執行邏輯。

  • daemon.setAwait(true) :這句代碼有什麼含義呢,下面咱們來具體的分析一下:
/**
     * Set flag.
     * @param await <code>true</code> if the daemon should block
     * @throws Exception Reflection error
     */
    public void setAwait(boolean await)
        throws Exception {

        Class<?> paramTypes[] = new Class[1];
        paramTypes[0] = Boolean.TYPE;
        Object paramValues[] = new Object[1];
        paramValues[0] = Boolean.valueOf(await);
        Method method =
            catalinaDaemon.getClass().getMethod("setAwait", paramTypes);
        method.invoke(catalinaDaemon, paramValues);
    }

這段代碼的主要做用是經過反射調用Catalina.setAwait(true),主要目的是當啓動完成後, 阻塞main線程,等待stop命令到來。 若是不設置daemon.setAwait(true), 則main線程執行完以後就 直接退出了。

  • **daemon.load(args) ** daemon.load(args);實際上是最終執行的Catalina.load(),在Catalina.load()方法中,主要功能是首先初始化temp目錄,而後再初始化naming的一些系統屬性,而後獲取server.xml配置文件, 建立Digester實例, 開始解析server.xml的操做。
/**
     * Start a new server instance.
     */
    public void load() {

        if (loaded) {
            return;
        }
        loaded = true;

        long t1 = System.nanoTime();

        initDirs();

        // Before digester - it may be needed
        initNaming();

        // Set configuration source
        ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(Bootstrap.getCatalinaBaseFile(), getConfigFile()));
        File file = configFile();

        // Create and execute our Digester
        Digester digester = createStartDigester();

        try (ConfigurationSource.Resource resource = ConfigFileLoader.getSource().getServerXml()) {
            InputStream inputStream = resource.getInputStream();
            InputSource inputSource = new InputSource(resource.getURI().toURL().toString());
            inputSource.setByteStream(inputStream);
            digester.push(this);
            digester.parse(inputSource);
        } catch (Exception e) {
            log.warn(sm.getString("catalina.configFail", file.getAbsolutePath()), e);
            if (file.exists() && !file.canRead()) {
                log.warn(sm.getString("catalina.incorrectPermissions"));
            }
            return;
        }

        getServer().setCatalina(this);
        getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
        getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());

        // Stream redirection
        initStreams();

        // Start the new server
        try {
            getServer().init();
        } catch (LifecycleException e) {
            if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
                throw new java.lang.Error(e);
            } else {
                log.error(sm.getString("catalina.initError"), e);
            }
        }

        long t2 = System.nanoTime();
        if(log.isInfoEnabled()) {
            log.info(sm.getString("catalina.init", Long.valueOf((t2 - t1) / 1000000)));
        }
    }
  • daemon.start(): 啓動Tomcat

經過調用daemon.start()啓動Tomcat,其內容以下:

/**
     * Start the Catalina daemon.
     * @throws Exception Fatal start error
     */
    public void start() throws Exception {
        if (catalinaDaemon == null) {
            init();
        }

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

程序經過反射的方式調用Catalina.start()方式啓動Tomcat,下面,咱們看下Catalina.start()方法的實現邏輯:

/**
     * Start a new server instance.
     */
    public void start() {

        if (getServer() == null) {
            load();
        }

        if (getServer() == null) {
            log.fatal(sm.getString("catalina.noServer"));
            return;
        }

        long t1 = System.nanoTime();

        // Start the new server
        try {
            getServer().start();
        } catch (LifecycleException e) {
            log.fatal(sm.getString("catalina.serverStartFail"), e);
            try {
                getServer().destroy();
            } catch (LifecycleException e1) {
                log.debug("destroy() failed for failed Server ", e1);
            }
            return;
        }

        long t2 = System.nanoTime();
        if(log.isInfoEnabled()) {
            log.info(sm.getString("catalina.startup", Long.valueOf((t2 - t1) / 1000000)));
        }

        // Register shutdown hook
        if (useShutdownHook) {
            if (shutdownHook == null) {
                shutdownHook = new CatalinaShutdownHook();
            }
            Runtime.getRuntime().addShutdownHook(shutdownHook);

            // If JULI is being used, disable JULI's shutdown hook since
            // shutdown hooks run in parallel and log messages may be lost
            // if JULI's hook completes before the CatalinaShutdownHook()
            LogManager logManager = LogManager.getLogManager();
            if (logManager instanceof ClassLoaderLogManager) {
                ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                        false);
            }
        }

        if (await) {
            await();
            stop();
        }
    }

能夠看出,程序調用getServer().start()啓動,getServer()方法返回的是一個StandardServer類,繼而其調用的是StandardServer.startInternal()方法,在StandardServer中,又調用到StandardService.startInternal()方法。

// StandardServer.java
	protected void startInternal() throws LifecycleException {

        fireLifecycleEvent(CONFIGURE_START_EVENT, null);
        setState(LifecycleState.STARTING);

        globalNamingResources.start();

        // Start our defined Services
        synchronized (servicesLock) {
            for (int i = 0; i < services.length; i++) {
                services[i].start();
            }
        }
		// ......省略部分代碼
	}

    protected void startInternal() throws LifecycleException {

        if(log.isInfoEnabled())
            log.info(sm.getString("standardService.start.name", this.name));
        setState(LifecycleState.STARTING);

        // Start our defined Container first
        if (engine != null) {
            synchronized (engine) {
                engine.start();
            }
        }

        synchronized (executors) {
            for (Executor executor: executors) {
                executor.start();
            }
        }

        mapperListener.start();

        // Start our defined Connectors second
        synchronized (connectorsLock) {
            for (Connector connector: connectors) {
                // If it has already failed, don't try and start it
                if (connector.getState() != LifecycleState.FAILED) {
                    connector.start();
                }
            }
        }
    }

注意,這裏爲何不是start()方法,而是startInternal()方法呢?緣由是StandardServer和StandService類都繼承了LifecycleMBeanBase類,而LifecycleMBeanBase類又繼承了LifecycleBase類。下面看下LifecycleBase類的start()方法:

public final synchronized void start() throws LifecycleException {

        if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
                LifecycleState.STARTED.equals(state)) {

            if (log.isDebugEnabled()) {
                Exception e = new LifecycleException();
                log.debug(sm.getString("lifecycleBase.alreadyStarted", toString()), e);
            } else if (log.isInfoEnabled()) {
                log.info(sm.getString("lifecycleBase.alreadyStarted", toString()));
            }

            return;
        }

        if (state.equals(LifecycleState.NEW)) {
            init();
        } else if (state.equals(LifecycleState.FAILED)) {
            stop();
        } else if (!state.equals(LifecycleState.INITIALIZED) &&
                !state.equals(LifecycleState.STOPPED)) {
            invalidTransition(Lifecycle.BEFORE_START_EVENT);
        }

        try {
            setStateInternal(LifecycleState.STARTING_PREP, null, false);
            startInternal();
            if (state.equals(LifecycleState.FAILED)) {
                // This is a 'controlled' failure. The component put itself into the
                // FAILED state so call stop() to complete the clean-up.
                stop();
            } else if (!state.equals(LifecycleState.STARTING)) {
                // Shouldn't be necessary but acts as a check that sub-classes are
                // doing what they are supposed to.
                invalidTransition(Lifecycle.AFTER_START_EVENT);
            } else {
                setStateInternal(LifecycleState.STARTED, null, false);
            }
        } catch (Throwable t) {
            // This is an 'uncontrolled' failure so put the component into the
            // FAILED state and throw an exception.
            handleSubClassException(t, "lifecycleBase.startFail", toString());
        }
    }

能夠看出,調用start()方法,最終都會調用到startInternal()方法。在下篇文章中,咱們將詳細看下StandardService.java中的engine.start()、executor.start()、connector.start()都分別啓動了什麼?敬請期待!

微信公衆號: 源碼灣

歡迎關注本人微信公衆號: 源碼灣。 本公衆號將不按期進行相關源碼及相關開發技術的分享,共同成長,共同進步~

感謝您的觀看,若有寶貴意見,煩請及時提出,謝謝。歡迎關注微信公衆號:源碼灣~

相關文章
相關標籤/搜索