• 深入浅出Tomcat/2


    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

  • 相关阅读:
    基于朴素贝叶斯的书籍评价信息分类任务
    贝叶斯原理
    knn算法手写字识别案例
    knn原理及借助电影分类实现knn算法
    航空公司案列分析
    k-meanas原理自实现
    df认识
    箱线图
    pandas认识
    分析system_call中断处理过程
  • 原文地址:https://www.cnblogs.com/confach/p/10306716.html
Copyright © 2020-2023  润新知