深入淺出Tomcat/2 - Tomcat啟動和停止

張太國發表於2019-01-31

Tomcat啟動和停止

很明顯,我們啟動或停止Tomcat,一般呼叫的是bin下的startup.sh或shutdown.sh(以Linux為例,以下涉及到平臺,若無特殊說明,一般都指Linux)。我們看看startup.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 "$@"
最後一行,它執行的是"/"$EXECUTABLE,那它是什麼呢?參看前面的一行
EXECUTABLE=catalina.sh
是的,啟動指令碼還是呼叫的catalina.sh.
接下來我們看看catalina.sh指令碼有什麼內容。
程式碼比較長,為了看的更明白,刪除一些不必要的,完整的指令碼大家可以參看catalina.sh
#!/bin/sh


# Control Script for the CATALINA Server
#
# Environment Variable Prerequisites
#
#   Do not set the variables in this script. Instead put them into a script
#   setenv.sh in CATALINA_BASE/bin to keep your customizations separate.
#
#   CATALINA_HOME   May point at your Catalina "build" directory.
#
#   CATALINA_BASE   (Optional) Base directory for resolving dynamic portions
#                   of a Catalina installation.  If not present, resolves to
#                   the same directory that CATALINA_HOME points to.
#
#   CATALINA_OUT    (Optional) Full path to a file where stdout and stderr
#                   will be redirected.
#                   Default is $CATALINA_BASE/logs/catalina.out
#
#   #   JAVA_HOME       Must point at your Java Development Kit installation.
#                   Required to run the with the "debug" argument.
#
#   JRE_HOME        Must point at your Java Runtime installation.
#                   Defaults to JAVA_HOME if empty. If JRE_HOME and JAVA_HOME
#                   are both set, JRE_HOME is used.
#
#   JAVA_OPTS       (Optional) Java runtime options used when any command
#                   is executed.
#                   Include here and not in CATALINA_OPTS all options, that
#                   should be used by Tomcat and also by the stop process,
#                   the version command etc.
#                   Most options should go into CATALINA_OPTS.
#   Djava.util.logging.config.file=$CATALINA_BASE/conf/logging.properties"
## -----------------------------------------------------------------------------

# ----- Execute The Requested Command -----------------------------------------


elif [ "$1" = "start" ] ; then

  
  if [ -z "$CATALINA_OUT_CMD" ] ; then
    touch "$CATALINA_OUT"
    catalina_out_command=">> \"$CATALINA_OUT\" 2>&1"
  else
    catalina_out_command="| $CATALINA_OUT_CMD"
  fi
  if [ ! -z "$CATALINA_PID" ]; then
    catalina_pid_file="$CATALINA_PID"
  else
    catalina_pid_file=/dev/null
  fi
  if [ "$1" = "-security" ] ; then
    if [ $have_tty -eq 1 ]; then
      echo "Using Security Manager"
    fi
    shift
    eval \{ $_NOHUP "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \
      -D$ENDORSED_PROP="\"$JAVA_ENDORSED_DIRS\"" \
      -classpath "\"$CLASSPATH\"" \
      -Djava.security.manager \
      -Djava.security.policy=="\"$CATALINA_BASE/conf/catalina.policy\"" \
      -Dcatalina.base="\"$CATALINA_BASE\"" \
      -Dcatalina.home="\"$CATALINA_HOME\"" \
      -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
      org.apache.catalina.startup.Bootstrap "$@" start \
      2\>\&1 \& echo \$! \>\"$catalina_pid_file\" \; \} $catalina_out_command "&"

  else
    eval \{ $_NOHUP "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \
      -D$ENDORSED_PROP="\"$JAVA_ENDORSED_DIRS\"" \
      -classpath "\"$CLASSPATH\"" \
      -Dcatalina.base="\"$CATALINA_BASE\"" \
      -Dcatalina.home="\"$CATALINA_HOME\"" \
      -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
      org.apache.catalina.startup.Bootstrap "$@" start \
      2\>\&1 \& echo \$! \>\"$catalina_pid_file\" \; \} $catalina_out_command "&"

  fi

  echo "Tomcat started."

elif [ "$1" = "stop" ] ; then


  eval "\"$_RUNJAVA\"" $JAVA_OPTS \
    -D$ENDORSED_PROP="\"$JAVA_ENDORSED_DIRS\"" \
    -classpath "\"$CLASSPATH\"" \
    -Dcatalina.base="\"$CATALINA_BASE\"" \
    -Dcatalina.home="\"$CATALINA_HOME\"" \
    -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
    org.apache.catalina.startup.Bootstrap "$@" stop

  


elif [ "$1" = "version" ] ; then

    "$_RUNJAVA"   \
      -classpath "$CATALINA_HOME/lib/catalina.jar" \
      org.apache.catalina.util.ServerInfo

else

  echo "Usage: catalina.sh ( commands ... )"
  echo "commands:"
  if $os400; then
    echo "  debug             Start Catalina in a debugger (not available on OS400)"
    echo "  debug -security   Debug Catalina with a security manager (not available on OS400)"
  else
    echo "  debug             Start Catalina in a debugger"
    echo "  debug -security   Debug Catalina with a security manager"
  fi
  echo "  jpda start        Start Catalina under JPDA debugger"
  echo "  run               Start Catalina in the current window"
  echo "  run -security     Start in the current window with security manager"
  echo "  start             Start Catalina in a separate window"
  echo "  start -security   Start in a separate window with security manager"
  echo "  stop              Stop Catalina, waiting up to 5 seconds for the process to end"
  echo "  stop n            Stop Catalina, waiting up to n seconds for the process to end"
  echo "  stop -force       Stop Catalina, wait up to 5 seconds and then use kill -KILL if still running"
  echo "  stop n -force     Stop Catalina, wait up to n seconds and then use kill -KILL if still running"
  echo "  configtest        Run a basic syntax check on server.xml - check exit code for result"
  echo "  version           What version of tomcat are you running?"
  echo "Note: Waiting for the process to end and use of the -force option require that \$CATALINA_PID is defined"
  exit 1
從以上指令碼可以看出,無論是start還是stop,其實呼叫的都是org.apache.catalina.startup.Bootstrap 這個類。
 
整理一下,Tomcat整個程式的入口在Boostrap這個類這裡。
OK,我們看看Bootstrap的啟動過程。

Bootstrap的啟動過程

Bootstrap這個類在以下package裡

org.apache.catalina.startup

Bootstrap類有個main方法,這是Tomcat的入口。

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方法。接著處理main方法的傳入引數,是start還是stop等,當然預設是start了。
再看看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;

}

我們可以看到初始化ClassLoader,接著會建立一個startupClass的類,就是這段程式碼:

Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();
我們看到startup的類其實就是org.apache.catalina.startup.Catalina,最後建立了另外一個例項catalinaDaemon。Tomcat的啟動,停止都是呼叫它。
 
我們看看start方法啟動Tomcat
public void start()
    throws Exception {
    if( catalinaDaemon==null ) init();

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

}
這裡首先判斷有沒有初始化,如果沒有,呼叫init並對其初始化,然後使用Method進行反射呼叫Catalina的start方法。
 
:Method是java.lang.reflect包裡的類,代表了一個具體的方法,在這裡,只是start方法。Method.invoke方法是指執行該方法,它有2個引數,第一個引數是方法名,第二個是該方法的引數。
 

Catalina的啟動

追本溯源,最後Tomcat的管理落到了Catalina這個類了,所以Catalina才是最重要的類。
先看看Catalina的方法。

我們可以清晰看到幾個重要的方法:
  • await
  • load
  •  start
  • stop
  • usage
對於這幾個方法,也算是直觀。那麼我們從簡單的開始說起。
方法usage
見以下程式碼,其實就是顯示catalina的用法。
/**
 * Print usage information for this application.
 */
protected void usage() {
 
    System.out.println(sm.getString("catalina.usage"));
 
}

 

方法await
 
程式碼如下
/**
 * Await and shutdown.
 */
public void await() {
 
    getServer().await();
 
}

 

這個方法搭配setAwait來用,setAwait方法用於設定Server啟動完成後是否進入等待狀態,如果為true,則進入,否則不進入。
 
方法load
/**
 * 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) {
        if  (file == null) {
            log.warn(sm.getString("catalina.configFail", getConfigFile() + "] or [server-embed.xml"), e);
        } else {
            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)));
    }
}
這個方法會載入conf/server.xml這個配置檔案,並且將我們前面描述到的Server,Listener,Service,Engine等逐一載入。而且Server例項已經由Digester建立,以下是createStartDigester的實現,部分程式碼刪除。
protected Digester createStartDigester() {
    long t1=System.currentTimeMillis();
    // Initialize the digester
    Digester digester = new Digester();
    digester.setValidating(false);
    digester.setRulesValidation(true);
    Map<Class<?>, List<String>> fakeAttributes = new HashMap<>();
    // Ignore className on all elements
    List<String> objectAttrs = new ArrayList<>();
    objectAttrs.add("className");
    fakeAttributes.put(Object.class, objectAttrs);
    // Ignore attribute added by Eclipse for its internal tracking
    List<String> contextAttrs = new ArrayList<>();
    contextAttrs.add("source");
    fakeAttributes.put(StandardContext.class, contextAttrs);
    // Ignore Connector attribute used internally but set on Server
    List<String> connectorAttrs = new ArrayList<>();
    connectorAttrs.add("portOffset");
    fakeAttributes.put(Connector.class, connectorAttrs);
    digester.setFakeAttributes(fakeAttributes);
    digester.setUseContextClassLoader(true);
 
    // Configure the actions we will be using
    digester.addObjectCreate("Server",
                             "org.apache.catalina.core.StandardServer",
                             "className");
    digester.addSetProperties("Server");
    digester.addSetNext("Server",
                        "setServer",
                        "org.apache.catalina.Server");
 
    digester.addObjectCreate("Server/GlobalNamingResources",
                             "org.apache.catalina.deploy.NamingResourcesImpl");
    digester.addSetProperties("Server/GlobalNamingResources");
    digester.addSetNext("Server/GlobalNamingResources",
                        "setGlobalNamingResources",
                        "org.apache.catalina.deploy.NamingResourcesImpl");
 
    digester.addObjectCreate("Server/Listener",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties("Server/Listener");
    digester.addSetNext("Server/Listener",
                        "addLifecycleListener",
                        "org.apache.catalina.LifecycleListener");
 
    digester.addObjectCreate("Server/Service",
                             "org.apache.catalina.core.StandardService",
                             "className");
    digester.addSetProperties("Server/Service");
    digester.addSetNext("Server/Service",
                        "addService",
                        "org.apache.catalina.Service");
 
    digester.addObjectCreate("Server/Service/Listener",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties("Server/Service/Listener");
    digester.addSetNext("Server/Service/Listener",
                        "addLifecycleListener",
                        "org.apache.catalina.LifecycleListener");
 
    //Executor
    digester.addObjectCreate("Server/Service/Executor",
                     "org.apache.catalina.core.StandardThreadExecutor",
                     "className");
    digester.addSetProperties("Server/Service/Executor");
 
    digester.addSetNext("Server/Service/Executor",
                        "addExecutor",
                        "org.apache.catalina.Executor");
 
 
    digester.addRule("Server/Service/Connector",
                     new ConnectorCreateRule());
    digester.addRule("Server/Service/Connector", new SetAllPropertiesRule(
            new String[]{"executor", "sslImplementationName", "protocol"}));
    digester.addSetNext("Server/Service/Connector",
                        "addConnector",
                        "org.apache.catalina.connector.Connector");
 
    digester.addRule("Server/Service/Connector", new AddPortOffsetRule());
 
    digester.addObjectCreate("Server/Service/Connector/SSLHostConfig",
                             "org.apache.tomcat.util.net.SSLHostConfig");
    digester.addSetProperties("Server/Service/Connector/SSLHostConfig");
    digester.addSetNext("Server/Service/Connector/SSLHostConfig",
            "addSslHostConfig",
            "org.apache.tomcat.util.net.SSLHostConfig");
 
 
 
    digester.addObjectCreate("Server/Service/Connector/Listener",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties("Server/Service/Connector/Listener");
    digester.addSetNext("Server/Service/Connector/Listener",
                        "addLifecycleListener",
                        "org.apache.catalina.LifecycleListener");
 
    digester.addObjectCreate("Server/Service/Connector/UpgradeProtocol",
                              null, // MUST be specified in the element
                              "className");
    digester.addSetProperties("Server/Service/Connector/UpgradeProtocol");
    digester.addSetNext("Server/Service/Connector/UpgradeProtocol",
                        "addUpgradeProtocol",
                        "org.apache.coyote.UpgradeProtocol");
 
    // Add RuleSets for nested elements
    digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/"));
    digester.addRuleSet(new EngineRuleSet("Server/Service/"));
    digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
    digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));
    addClusterRuleSet(digester, "Server/Service/Engine/Host/Cluster/");
    digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/"));
 
    // When the 'engine' is found, set the parentClassLoader.
    digester.addRule("Server/Service/Engine",
                     new SetParentClassLoaderRule(parentClassLoader));
    addClusterRuleSet(digester, "Server/Service/Engine/Cluster/");
 
    long t2=System.currentTimeMillis();
    if (log.isDebugEnabled()) {
        log.debug("Digester for server.xml created " + ( t2-t1 ));
    }
    return digester;
 
}

 

方法start
start用來啟動一個Server,具體程式碼如下
/**
 * 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();
    }
}

首先判斷Server是否存在,如果不存在,呼叫load初始化一下。接著呼叫Server的start方法啟動它。最後如果啟用關閉的鉤子,還要註冊一下該鉤子。在末尾,如果如果await設定為true的話,需要呼叫await和stop。Await會直接呼叫Server的await方法,Server內部會進入一個while迴圈,以下程式碼是Server的await方法的實現。當await裡while結束,執行stop方法,停止伺服器。

public void await() {
        // Set up a server socket to wait on
    try {
        awaitSocket = new ServerSocket(getPortWithOffset(), 1,
                InetAddress.getByName(address));
    } catch (IOException e) {
        log.error(sm.getString("standardServer.awaitSocket.fail", address,
                String.valueOf(getPortWithOffset()), String.valueOf(getPort()),
                String.valueOf(getPortOffset())), e);
        return;
    }

    try {
        awaitThread = Thread.currentThread();

        // Loop waiting for a connection and a valid command
        while (!stopAwait) {
            ServerSocket serverSocket = awaitSocket;
            if (serverSocket == null) {
                break;
            }

            // Wait for the next connection
            Socket socket = null;
            StringBuilder command = new StringBuilder();
            try {
                InputStream stream;
                long acceptStartTime = System.currentTimeMillis();
                try {
                    socket = serverSocket.accept();
                    socket.setSoTimeout(10 * 1000);  // Ten seconds
                    stream = socket.getInputStream();
                } catch (SocketTimeoutException ste) {
                    // This should never happen but bug 56684 suggests that
                    // it does.
                    log.warn(sm.getString("standardServer.accept.timeout",
                            Long.valueOf(System.currentTimeMillis() - acceptStartTime)), ste);
                    continue;
                } catch (AccessControlException ace) {
                    log.warn(sm.getString("standardServer.accept.security"), ace);
                    continue;
                } catch (IOException e) {
                    if (stopAwait) {
                        // Wait was aborted with socket.close()
                        break;
                    }
                    log.error(sm.getString("standardServer.accept.error"), e);
                    break;
                }

                // Read a set of characters from the socket
                int expected = 1024; // Cut off to avoid DoS attack
                while (expected < shutdown.length()) {
                    if (random == null)
                        random = new Random();
                    expected += (random.nextInt() % 1024);
                }
                while (expected > 0) {
                    int ch = -1;
                    try {
                        ch = stream.read();
                    } catch (IOException e) {
                        log.warn(sm.getString("standardServer.accept.readError"), e);
                        ch = -1;
                    }
                    // Control character or EOF (-1) terminates loop
                    if (ch < 32 || ch == 127) {
                        break;
                    }
                    command.append((char) ch);
                    expected--;
                }
            } finally {
                // Close the socket now that we are done with it
                try {
                    if (socket != null) {
                        socket.close();
                    }
                } catch (IOException e) {
                    // Ignore
                }
            }

            // Match against our command string
            boolean match = command.toString().equals(shutdown);
            if (match) {
                log.info(sm.getString("standardServer.shutdownViaPort"));
                break;
            } else
                log.warn(sm.getString("standardServer.invalidShutdownCommand", command.toString()));
        }
    } finally {
        ServerSocket serverSocket = awaitSocket;
        awaitThread = null;
        awaitSocket = null;

        // Close the server socket and return
        if (serverSocket != null) {
            try {
                serverSocket.close();
            } catch (IOException e) {
                // Ignore
            }
        }
    }
}

Server的啟動

前面也提到了Catalina的start其實呼叫的是Server的start方法,我們看看Server的start的方法怎麼實現的。在深入前,我們看看Server的方法列表等資訊。
Server是一個介面,繼承了LifeCycle這個介面。
Server提供了2個方法addService()以及removeService(),分別用來增加或刪除Service。在前面我們講過,一個Server是可以包含多個Service的。
在Server 啟動前,Server包含的幾個Service也需要初始化,其實每個Service的初始化也是用Service本身的init方法來初始化。
Server的預設實現是org.apache.catalina.core.StandardServer,StandardServer又繼承了LifeCycleMBeanBase,
public final class StandardServer extends LifecycleMBeanBase implements Server 
LifeCycleMBeanBase又繼承了LifecycleBase
public abstract class LifecycleMBeanBase extends LifecycleBase 
LifeCycleBase又實現了LifeCycle
public abstract class LifecycleBase implements Lifecycle
因為init和start都是LifeCycleBase裡,這也是tomcat的生命週期重要的方法。Init和start也分別呼叫了initInternal和startInternal。至於怎麼實現,還得看子類的具體邏輯,前面說到StandardServer是預設的實現,那我們看看StandardServer的具體實現邏輯。繼續上程式碼:
protected void initInternal() throws LifecycleException {



    super.initInternal();
      

     //… …

    // Initialize our defined Services

    for (int i = 0; i < services.length; i++) {

        services[i].init();

    }

}
上面是initInternal的程式碼,我們發現其實就是呼叫Server包含的Service的init方法。
下面是startInternal的程式碼。 
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();

}

}

 

if (periodicEventDelay > 0) {

monitorFuture = getUtilityExecutor().scheduleWithFixedDelay(

new Runnable() {

@Override

public void run() {

startPeriodicLifecycleEvent();

}

}, 0, 60, TimeUnit.SECONDS);

}

}

首先需要啟動各個Service,這也是通過Service 的start方法來實現。

Service的啟動過程

前面已經說到,Server會呼叫Service的start方法來啟動各個Service,我們繼續看Service的啟動。Service的預設實現是org.apache.catalina.core.StandardService,StandardService也繼承了LifeCycleMBeanBase類,所以init和start最終也會呼叫initInternal和startInternal這兩個方法。還是先看StandardService的方法列表。

和StandardServer類似,一個Service可能包含多個Connector,所以我們看到了addConnector和removeConnector這兩個方法。
我們可以看到initInternal和startInternal兩個方法,我們抽取核心的程式碼在這裡展示一下:

@Override
protected void initInternal() throws LifecycleException {

    super.initInternal();

    if (engine != null) {
        engine.init();
    }

    // Initialize any Executors
    for (Executor executor : findExecutors()) {
        if (executor instanceof JmxEnabled) {
            ((JmxEnabled) executor).setDomain(getDomain());
        }
        executor.init();
    }

    // Initialize mapper listener
    mapperListener.init();

    // Initialize our defined Connectors
    synchronized (connectorsLock) {
        for (Connector connector : connectors) {
            connector.init();
        }
    }
}


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

根據以上程式碼可以看出,無論是initInternal還是startInternal,都涉及到了Engine,Executor,Listener以及Connector。
這裡需要說明的是Executor,前面並未提及,因為Tomcat預設把它註釋掉了,其實它就是Connector中管理執行緒的執行緒池。

<!--The connectors can use a shared executor, you can define one or more named thread pools-->

<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="150" minSpareThreads="4"/>

如何使用Executor?只需要在Connector加上executor這個屬性,並指向定義的Executor。

<Connector executor = “tomcatThreadPool” port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />

綜合上述,現在我們可以瞭解Tomcat的啟動過程了,先初始化,然後在啟動。
Startup.sh -> Bootstrap的main -> Catalina -> StandardServer -> StandardService->Container->Executor->Connector

相關文章