Tomcat的启动流程

Tomcat不管在学习还是在工作中,都是用得很多的servlet容器。本文先来分析一下Tomcat的启动流程,熟悉整个流程后有助于后续对一些细节问题的分析。


虽然本文的标题叫作启动流程,但实际上包含了Tomcat中各个组件的创建和初始化过程。只有先创建和初始化了组件,才能启动组件。

启动方式

独立启动器

脚本

一般我们会在shell执行Tomcat的启动脚本startup.sh,而该脚本会调用到另一个脚本catalina.sh,在这个脚本中会调用Bootstrap类的main方法,并传入start参数。

v8.5.59
startup.sh
catalina.sh
<
>
sh
bin/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"`
# 将catalina.sh作为可执行程序,后续会调用
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

# 调用catalina.sh
exec "$PRGDIR"/"$EXECUTABLE" start "$@"
sh
bin/catalina.sh
eval $_NOHUP "\"$_RUNJAVA\"" "\"$CATALINA_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 \
  >> "$CATALINA_OUT" 2>&1 "&"

Bootstrap

v8.5.59
java
java/org/apache/catalina/startup/Bootstrap.java
private static volatile Bootstrap daemon = null;
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();
            }
            daemon = bootstrap;
        } else {
            // 设置线程上下文类加载器
            Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
        }
    }

    try {
        // 默认命令是start
        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);
            /*
             * 调用Catalina的load方法,
             * 内部会解析server.xml,这也是load的含义。
             * 在这个过程中会创建各个组件,并设置好父子关系。
             * 注意在add组件的方法中,存在start逻辑,但是要注意getState().isAvailable()返回的是false。
             * 所以在第一次启动的时候,添加组件并不会start组件。
             *
             * 解析完server.xml后,各个组件的创建过程已完成。
             * 接下来就执行从顶层组件Server组件到各个子组件的init操作。
             */
            daemon.load(args);
            /*
             * 调用Catalina的start方法,同样地,也是��顶层组件Server到各个子组件都执行一遍。
             * 从这里可以看出,是先调用init方法再调用start方法。
             */
            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.");
        }
    }
}

本文关注的是start操作,所以可以分为下面3个步骤:

  • init
  • load
  • start
v8.5.59
init
load
start
<
>
java
java/org/apache/catalina/startup/Bootstrap.java
public void init(String[] arguments) throws Exception {

    init();
    load(arguments);
}
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");
    // 加载Catalina类
    Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
    // 创建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];
    // 将sharedLoader作为Catalina对象的parentClassLoader对象
    paramValues[0] = sharedLoader;
    // 获取Catalina中的setParentClassLoader方法
    Method method =
        startupInstance.getClass().getMethod(methodName, paramTypes);
    // 为Catalina对象设置parentClassLoader属性
    method.invoke(startupInstance, paramValues);

    catalinaDaemon = startupInstance;
}
java
java/org/apache/catalina/startup/Bootstrap.java
private void load(String[] arguments) throws Exception {

    // Call the load() method
    String methodName = "load";
    // 准备调用方法所需的参数
    Object param[];
    Class<?> paramTypes[];
    if (arguments==null || arguments.length==0) {
        paramTypes = null;
        param = null;
    } else {
        paramTypes = new Class[1];
        paramTypes[0] = arguments.getClass();
        param = new Object[1];
        param[0] = arguments;
    }
    // 获取Catalina中的load方法
    Method method =
        catalinaDaemon.getClass().getMethod(methodName, paramTypes);
    if (log.isDebugEnabled()) {
        log.debug("Calling startup class " + method);
    }
    // 反射调用Catalina中的load方法
    method.invoke(catalinaDaemon, param);
}
java
java/org/apache/catalina/startup/Bootstrap.java
public void start() throws Exception {
    if (catalinaDaemon == null) {
        // 如果还没有初始化则执行初始化
        init();
    }

    // 获取Catalina类的start方法
    Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
    // 反射调用
    method.invoke(catalinaDaemon, (Object [])null);
}

这三个方法中都会调用到Catalina类中的方法,所以这也是为什么把Bootstrap类叫作门面类的原因。 在init方法中,主要是处理类加载器相关的工作,本文主要是分析启动过程,类加载机制后面单独写文章分析。

Catalina

下面来看看Catalina类的load方法。

v8.5.59
load
configFile
<
>
java
java/org/apache/catalina/startup/Catalina.java
public void load() {

    // 避免重复加载
    if (loaded) {
        return;
    }
    loaded = true;

    long t1 = System.nanoTime();

    // 空操作
    initDirs();

    // Before digester - it may be needed
    // 设置解析配置文件需要的一些属性
    initNaming();

    // Create and execute our Digester
    // 创建配置文件解析器
    Digester digester = createStartDigester();

    InputSource inputSource = null;
    InputStream inputStream = null;
    File file = null;
    try {
        try {
            // 加载server.xml配置文件
            file = configFile();
            // 创建文件流对象
            inputStream = new FileInputStream(file);
            // 将配置文件封装为输入源对象
            inputSource = new InputSource(file.toURI().toURL().toString());
        } catch (Exception e) {
            if (log.isDebugEnabled()) {
                log.debug(sm.getString("catalina.configFail", file), e);
            }
        }


        try {
            /*
             * 通过Digester库解析server.xml
             */
            inputSource.setByteStream(inputStream);
            digester.push(this);
            // 这里会设置this的server属性为StandardServer
            digester.parse(inputSource);
        }
    } finally {
    }

    /*
     * 给server设置catalina属性
     */
    getServer().setCatalina(this);
    // 设置catalina.home属性
    getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
    // 设置catalina.base属性
    getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());

    // Stream redirection
    // 对System的流进行重定向
    initStreams();

    // Start the new server
    try {
        /*
         * 初始化Server对象,为后面的start做准备。
         */
        getServer().init();
    }
}
java
java/org/apache/catalina/startup/Catalina.java
protected String configFile = "conf/server.xml";
protected File configFile() {

    File file = new File(configFile);
    if (!file.isAbsolute()) {
        // 转为绝对路径
        file = new File(Bootstrap.getCatalinaBase(), configFile);
    }
    return file;

}

这里会先读取配置文件,一般是$TOMCAT_HOME/conf/server.xml文件,然后对配置文件进行XML解析,解析过程还是比较复杂的,这里不介绍。 然后为Server组件设置catalina.basecatalina.home属性,最后调用Server组件的init方法来初始化Server组件。

接下来看看start方法。

v8.5.59
java
java/org/apache/catalina/startup/Catalina.java
public void start() {

    // 一般在bootstrap中就已经调用过load()方法了,所以这里不会是null。
    if (getServer() == null) {
        // 初始化server
        load();
    }

    // 确保server组件存在
    if (getServer() == null) {
        log.fatal("Cannot start server. Server instance is not configured.");
        return;
    }

    long t1 = System.nanoTime();

    // Start the new server
    try {
        // 启动server
        getServer().start();
    }

    // Register shutdown hook
    if (useShutdownHook) {
        if (shutdownHook == null) {
            shutdownHook = new CatalinaShutdownHook();
        }
        /*
         * 注册JVM shutdown钩子
         * 将hook对象添加到JVM退出时执行列表中,
         * JVM退出时会执行hook对象的run方法。
         */
        Runtime.getRuntime().addShutdownHook(shutdownHook);

    }

    if (await) {
        // 让当前线程等待Tomcat结束
        await();
        stop();
    }
}

这里调用的也是Server组件的start方法。关于Server组件的initstart方法,在下面进行分析。

内嵌启动器

随着Spring Boot的流行,Tomcat越来越被作为内嵌servlet容器来启动。在Spring Boot中的Web服务器一文中,介绍了Tomcat容器的创建过程,但是并没有深入分析Tomcat类的实现细节。

v2.7.x
java
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
    // 禁用JMX注册
    if (this.disableMBeanRegistry) {
        Registry.disableRegistry();
    }
    // 创建Tomcat实例
    Tomcat tomcat = new Tomcat();
    // 获取或者创建Tomcat的工作目录
    File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
    // 设置工作目录
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    for (LifecycleListener listener : this.serverLifecycleListeners) {
        tomcat.getServer().addLifecycleListener(listener);
    }
    // 创建连接器组件
    Connector connector = new Connector(this.protocol);
    // 设置在失败时抛出异常
    connector.setThrowOnFailure(true);
    // 获取service组件,不存在则会创建,然后添加连接器组件
    tomcat.getService().addConnector(connector);
    // 定制化连接器,会设置监听端口
    customizeConnector(connector);
    // 为tomcat实例设置连接器
    tomcat.setConnector(connector);
    // 获取Host组件,内部会先获取Engine组件,如果不存在则新建;然后设置主机自动部署任务为false
    tomcat.getHost().setAutoDeploy(false);
    // 配置Engine组件
    configureEngine(tomcat.getEngine());
    // 添加额外的连接器组件
    for (Connector additionalConnector : this.additionalTomcatConnectors) {
        tomcat.getService().addConnector(additionalConnector);
    }
    // 创建Tomcat中的Context组件,并设置其属性,然后将其添加到Host组件下
    prepareContext(tomcat.getHost(), initializers);
    // 根据Tomcat实例,创建TomcatWebServer实例对象
    return getTomcatWebServer(tomcat);
}

在该方法中,先是创建了Tomcat对象,然后一次调用了getService方法来获取Service组件,先来看看该方法是怎么实现的。

创建ServerService组件

v8.5.59
getService
getServer
<
>
java
java/org/apache/catalina/startup/Tomcat.java
public Service getService() {
    return getServer().findServices()[0];
}
public Server getServer() {

    // 如果创建过server,
    if (server != null) {
        // 则直接返回
        return server;
    }

    System.setProperty("catalina.useNaming", "false");

    // 创建Server组件
    server = new StandardServer();

    // 初始化工作目录,包括Catalina的base和home目录
    initBaseDir();

    server.setPort( -1 );

    // 创建Service组件
    Service service = new StandardService();
    service.setName("Tomcat");
    // 把service添加到server中
    server.addService(service);
    return server;
}
java
java/org/apache/catalina/startup/Tomcat.java
public Server getServer() {

    // 如果创建过server,
    if (server != null) {
        // 则直接返回
        return server;
    }

    System.setProperty("catalina.useNaming", "false");

    // 创建Server组件
    server = new StandardServer();

    // 初始化工作目录,包括Catalina的base和home目录
    initBaseDir();

    server.setPort( -1 );

    // 创建Service组件
    Service service = new StandardService();
    service.setName("Tomcat");
    // 把service添加到server中
    server.addService(service);
    return server;
}

可以看到,虽然方法名称是getXXX,但是会判断对应的组件是否存在,如果不存在会创建的,其他的getXXX方法也是相似的。 这里创建了StandardServerStandardService两个组件。

创建Connector组件

在Spring Boot的TomcatServletWebServerFactory类的getWebServer方法中,直接创建了Connector组件,然后对其进行设置。由于其创建过程还是比较复杂的,涉及到协议相关子组件的创建,所以在下面统一进行分析。

创建EngineHost组件

因为在Spring Boot的TomcatServletWebServerFactory类中的getWebServer方法中,因为先调用了TomcatgetHost方法,然后再调用的getEngine方法。但因为host组件是engine组件的子组件,所以会先创建Engine组件再创建Host组件。

v8.5.59
java
java/org/apache/catalina/startup/Tomcat.java
public Host getHost() {
    // 获取engine组件
    Engine engine = getEngine();
    // 如果存在host子组件,则直接返回
    if (engine.findChildren().length > 0) {
        return (Host) engine.findChildren()[0];
    }

    // 创建host组件
    Host host = new StandardHost();
    host.setName(hostname);
    // 将host组件添加到engine组件中
    getEngine().addChild(host);
    return host;
}
public Engine getEngine() {
    // 获取server中的第一个service组件
    Service service = getServer().findServices()[0];
    if (service.getContainer() != null) {
        return service.getContainer();
    }
    // 创建Engine对象
    Engine engine = new StandardEngine();
    engine.setName( "Tomcat" );
    engine.setDefaultHost(hostname);
    engine.setRealm(createDefaultRealm());
    // 将engine设置到service中
    service.setContainer(engine);
    return engine;
}

这里还会把Host组件添加到Engine组件的children属性中。

创建Context组件

v2.7.x
java
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
    File documentRoot = getValidDocumentRoot();
    // 创建tomcat中的context组件,该类是Spring Boot中实现的,继承tomcat中的StandardContext
    TomcatEmbeddedContext context = new TomcatEmbeddedContext();
    if (documentRoot != null) {
        context.setResources(new LoaderHidingResourceRoot(context));
    }
    // 为context组件设置一些属性
    context.setName(getContextPath());
    context.setDisplayName(getDisplayName());
    context.setPath(getContextPath());
    File docBase = (documentRoot != null) ? documentRoot : createTempDir("tomcat-docbase");
    context.setDocBase(docBase.getAbsolutePath());
    context.addLifecycleListener(new FixContextListener());
    context.setParentClassLoader((this.resourceLoader != null) ? this.resourceLoader.getClassLoader()
            : ClassUtils.getDefaultClassLoader());
    resetDefaultLocaleMapping(context);
    addLocaleMappings(context);
    try {
        context.setCreateUploadTargets(true);
    }
    catch (NoSuchMethodError ex) {
        // Tomcat is < 8.5.39. Continue.
    }
    configureTldPatterns(context);
    WebappLoader loader = new WebappLoader();
    // 设置类加载器名称
    loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());
    loader.setDelegate(true);
    // 设置web应用加载器
    context.setLoader(loader);
    // 是否注册默认servlet,该方法默认返回false
    if (isRegisterDefaultServlet()) {
        // 注册默认的servlet
        addDefaultServlet(context);
    }
    // JSP相关
    if (shouldRegisterJspServlet()) {
        addJspServlet(context);
        addJasperInitializer(context);
    }
    // 添加声明周期监听器,用于配置静态资源
    context.addLifecycleListener(new StaticResourceConfigurer(context));
    // 添加一些默认的初始化器,并和上层传递过来的合并在一起
    ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
    // 为host添加context子组件
    host.addChild(context);
    // 将initializers设置到context中
    configureContext(context, initializersToUse);
    // 默认是空操作
    postProcessContext(context);
}

这里创建了Context组件,是Spring Boot中提供的一个类,该类继承了Tomcat中的StandardContext类。并将Context组件设置为Host组件的子组件。

启动Server组件

在Spring Boot的TomcatWebServer类的initialize方法中,会启动Tomcat中的Server组件。

v2.7.x
java
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatWebServer.java
private void initialize() throws WebServerException {
    logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
    synchronized (this.monitor) {
        try {
            addInstanceIdToEngineName();

            Context context = findContext();
            // 添加生命周期监听器
            context.addLifecycleListener((event) -> {
                if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
                    // Remove service connectors so that protocol binding doesn't
                    // happen when the service is started.
                    removeServiceConnectors();
                }
            });

            // Start the server to trigger initialization listeners
            /*
             * ==========
             * 启动tomcat
             * ==========
             */
            this.tomcat.start();

            // We can re-throw failure exception directly in the main thread
            rethrowDeferredStartupExceptions();

            try {
                // 绑定类加载器
                ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
            }
            catch (NamingException ex) {
                // Naming is not enabled. Continue
            }

            // Unlike Jetty, all Tomcat threads are daemon threads. We create a
            // blocking non-daemon to stop immediate shutdown
            startDaemonAwaitThread();
        }
        catch (Exception ex) {
            stopSilently();
            destroySilently();
            throw new WebServerException("Unable to start embedded Tomcat", ex);
        }
    }
}

会调用到Tomcat类的start方法。

v8.5.59
java
java/org/apache/catalina/startup/Tomcat.java
/*
 * SpringBoot中的TomcatWebServer在创建过程中,调用了该类的start()方法。
 */
public void start() throws LifecycleException {
    // 确保Server组件和Service组件存在,如果不存在的话会新建
    getServer();
    // 确保Connector组件存在,如果不存在的话会新建
    getConnector();
    // 从server开始执行各个组件的start操作
    server.start();
}

可以看出来,Tomcat也像是一个门面类,负责创建、设置、启动、关闭Tomcat中的其他组件。只是Tomcat负责内嵌启动,BootstrapCatalina负责独立启动。

总结

不管是独立启动还是内嵌启动,最终都会调用到Server组件的start方法。只不过独立启动的方式,会先调用其init方法来初始化,虽然Spring Boot没有调用Tomcat init方法,但是在各个组件的start方法中,会判断有没有初始化过,如果没有则会先初始化。

由于Tomcat中的组件众多,一个组件中包含其他组件,调用链条比较长。但是好在逻辑还是比较清晰的,本文涉及到的就主要是两条主线:

  • 初始化
  • 启动 针对这种调用链条较长的情况,不适合垂直线性分析,本文会使用横向分析方法,依次分析各个组件的上面两个方面的内容,如果涉及到其他方面的内容,到时候再介绍。 另外,大多数组件都定义了接口,但主要使用的都是StardardXXX类,所以本文仅会分析这些类。

组件生命周期

在具体分析各个组件之前,先来了解一下组件的生命周期。Lifecycle接口定义了各种生命周期阶段名称,以及生命周期方法,比如initstart。但各个组件一般没有直接实现该接口,而是继承另一个实现了Lifecycle接口的抽象类LifecycleBase。这个抽象类中实现了initstart方法。

v8.5.59
init
start
<
>
java
java/org/apache/catalina/util/LifecycleBase.java
@Override
public final synchronized void init() throws LifecycleException {
    // 如果组件状态不是NEW,则不能初始化
    if (!state.equals(LifecycleState.NEW)) {
        invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
    }

    try {
        // 设置状态为正在初始化
        setStateInternal(LifecycleState.INITIALIZING, null, false);
        // 调用各个组件的实现
        initInternal();
        // 设置状态为已初始化
        setStateInternal(LifecycleState.INITIALIZED, null, false);
    } catch (Throwable t) {
        handleSubClassException(t, "lifecycleBase.initFail", toString());
    }
}
protected abstract void initInternal() throws LifecycleException;
java
java/org/apache/catalina/util/LifecycleBase.java
@Override
public final synchronized void start() throws LifecycleException {

    // 不能启动的状态
    if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
            LifecycleState.STARTED.equals(state)) {


        return;
    }

    // 如果没有初始化过
    if (state.equals(LifecycleState.NEW)) {
        /*
         * 如果没有初始化过则先初始化,
         * 在SpringBoot中,在创建TomcatWebServer的时候,会调用Tomcat的start方法,
         * 但该方法直接启动StandardServer的server方法,并没有先执行init。
         *
         * 所以在这里可以发现,start的时候发现如果没有初始化过,则先初始化。
         */
        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());
    }
}
protected abstract void startInternal() throws LifecycleException;

可以发现,这里实现了initstart方法,主要实现各组件公共的状态机。具体的初始化和启动操作通过各组件重写initInternalstartInternal这两个模板方法来实现。所以在很多组件的实现类中找不到initstart方法,这是因为它们实现的是LifecycleBase中的生命周期模板方法。 在start方法中,如果发现组成还没有初始化过,那么会在调用startInternal方法之前,会先调用组件的init方法来初始化。这就能解释为什么在Spring Boot中的TomcatWebServer类的initialize方法中直接调用Tomcat类的start方法而没有调用init方法也能正常工作了。

注意大多数组件并不是直接继承LifecycleBase,而是LifecycleMBeanBaseLifecycleMBeanBase也实现了LifecycleBase中的生命周期模板方法,主要提供接入JMX的功能,本文不会分析到JMX,所以忽略。

强烈建议下面的内容不要按着顺序看,而是先看创建过程,再看初始化过程,最后看启动过程。而且遇到什么组件就去看什么组件,下面有些组件的顺序是打乱的。不过也没办法,本来Tomcat的组件结构就是树形的,没办法进行线性排版。

StandardServer

初始化

v8.5.59
java
java/org/apache/catalina/core/StandardServer.java
@Override
protected void initInternal() throws LifecycleException {

    super.initInternal();

    // Register global String cache
    // Note although the cache is global, if there are multiple Servers
    // present in the JVM (may happen when embedding) then the same cache
    // will be registered under multiple names
    onameStringCache = register(new StringCache(), "type=StringCache");

    // Register the MBeanFactory
    MBeanFactory factory = new MBeanFactory();
    factory.setContainer(this);
    onameMBeanFactory = register(factory, "type=MBeanFactory");

    // Register the naming resources
    globalNamingResources.init();

    // Populate the extension validator with JARs from common and shared
    // class loaders
    // 注意内嵌启动的方式,并不会被设置Catalina对象
    if (getCatalina() != null) {
        // 在简化模式下,这里获取到的是commonClassLoader。
        ClassLoader cl = getCatalina().getParentClassLoader();
        // Walk the class loader hierarchy. Stop at the system class loader.
        // This will add the shared (if present) and common class loaders
        while (cl != null && cl != ClassLoader.getSystemClassLoader()) {
            if (cl instanceof URLClassLoader) {
                URL[] urls = ((URLClassLoader) cl).getURLs();
                for (URL url : urls) {
                    if (url.getProtocol().equals("file")) {
                        try {
                            File f = new File (url.toURI());
                            if (f.isFile() &&
                                    f.getName().endsWith(".jar")) {
                                ExtensionValidator.addSystemResource(f);
                            }
                        } catch (URISyntaxException e) {
                            // Ignore
                        } catch (IOException e) {
                            // Ignore
                        }
                    }
                }
            }
            cl = cl.getParent();
        }
    }
    // Initialize our defined Services
    for (Service service : services) {
        /*
         * 初始化Service
         */
        service.init();
    }
}

这里主要在遍历各个Service组件并调用其init方法。

从这里也可以看出来一个Server组件包含了多个Service组件,但在广泛被作为内嵌容器的情况下,往往一个Server组件中只有一个Service组件。

启动

v8.5.59
java
java/org/apache/catalina/core/StandardServer.java
@Override
protected void startInternal() throws LifecycleException {

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

    globalNamingResources.start();

    // Start our defined Services
    synchronized (servicesLock) {
        // 启动各个Service组件
        for (Service service : services) {
            service.start();
        }
    }
}

这里主要在遍历各个Service组件并调用其service方法。

StandardService

初始化

v8.5.59
java
java/org/apache/catalina/core/StandardService.java
@Override
protected void initInternal() throws LifecycleException {

    super.initInternal();

    if (engine != null) {
        // 初始化engine组件
        engine.init();
    }

    // Initialize any Executors
    /*
     * 获取Executor组件,不管是独立启动还是Spring Boot的内嵌启动,这里获取到的executors数量都是0.
     *
     */
    for (Executor executor : findExecutors()) {
        if (executor instanceof JmxEnabled) {
            ((JmxEnabled) executor).setDomain(getDomain());
        }
        // 初始化Executor,StandardThreadExecutor的init方法没有做额外操作
        executor.init();
    }

    // Initialize mapper listener
    // 初始化mapper监听器,没有做额外的操作
    mapperListener.init();

    // Initialize our defined Connectors
    synchronized (connectorsLock) {
        for (Connector connector : connectors) {
            try {
                // 初始化连接器
                connector.init();
            }
        }
    }
}

该方法中初始化了EngineExecutorConnector组件和MapperListener,但ExecutorMapperListener的初始化操作中没有做额外的操作。

从这里也可以看出来,一个Service组件包含了多个Connector组件和一个Engine组件。

启动

v8.5.59
java
java/org/apache/catalina/core/StandardService.java
@Override
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
            engine.start();
        }
    }

    synchronized (executors) {
        for (Executor executor: executors) {
            // 启动executor,会创建实际的线程池
            executor.start();
        }
    }

    // 启动MapperListener
    mapperListener.start();

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

这里启动了EngineExecutorConnector组件和MapperListener

Connector

创建

v8.5.59
java
java/org/apache/catalina/connector/Connector.java
public Connector() {
    this(null);
}


public Connector(String protocol) {
    setProtocol(protocol);
    // Instantiate protocol handler
    ProtocolHandler p = null;
    try {
        // 获取协议处理器类,默认是Http11NioProtocol
        Class<?> clazz = Class.forName(protocolHandlerClassName);
        // 创建协议处理器类
        p = (ProtocolHandler) clazz.getConstructor().newInstance();
    } catch (Exception e) {
        log.error(sm.getString(
                "coyoteConnector.protocolHandlerInstantiationFailed"), e);
    } finally {
        this.protocolHandler = p;
    }

    // 设置字符集
    if (Globals.STRICT_SERVLET_COMPLIANCE) {
        uriCharset = StandardCharsets.ISO_8859_1;
    } else {
        uriCharset = StandardCharsets.UTF_8;
    }

    // Default for Connector depends on this (deprecated) system property
    if (Boolean.parseBoolean(System.getProperty("org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH", "false"))) {
        encodedSolidusHandling = EncodedSolidusHandling.DECODE;
    }
}
protected String protocolHandlerClassName =
    "org.apache.coyote.http11.Http11NioProtocol";

初始化

v8.5.59
java
java/org/apache/catalina/connector/Connector.java
@Override
protected void initInternal() throws LifecycleException {

    super.initInternal();

    // Initialize adapter
    /*
     * 创建适配器,并传入了this。
     * 在adapter的service方法中,会调用connector通过pipeline完成请求处理。
     */
    adapter = new CoyoteAdapter(this);
    // 给协议处理器设置请求处理适配器
    protocolHandler.setAdapter(adapter);

    // Make sure parseBodyMethodsSet has a default
    if (null == parseBodyMethodsSet) {
        setParseBodyMethods(getParseBodyMethods());
    }


    try {
        // 初始化协议处理器,具体参考AbstractHttp11Protocol及其父类AbstractProtocol。
        protocolHandler.init();
    } catch (Exception e) {
        throw new LifecycleException(
                sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
    }
}

该方法中主要是在创建适配器,并初始化构造方法中创建的协议处理器。

启动

v8.5.59
java
java/org/apache/catalina/connector/Connector.java
@Override
protected void startInternal() throws LifecycleException {

    // Validate settings before starting
    // 检查端口
    if (getPort() < 0) {
        throw new LifecycleException(sm.getString(
                "coyoteConnector.invalidPort", Integer.valueOf(getPort())));
    }

    setState(LifecycleState.STARTING);

    try {
        /*
         * 启动协议处理器,start方法定义在AbstractProtocol中。
         * 内部会启动Endpoint,对于默认的Http11NioProtocol,endpoint是NioEndpoint类型的。
         */
        protocolHandler.start();
    } catch (Exception e) {
        throw new LifecycleException(
                sm.getString("coyoteConnector.protocolHandlerStartFailed"), e);
    }
}

该方法中主要是启动了协议处理器。

StandardEngine

该类继承自ContainerBase,说明这是一个容器组件。

创建

v8.5.59
java
java/org/apache/catalina/core/StandardEngine.java
public StandardEngine() {

    super();
    pipeline.setBasic(new StandardEngineValve());
    // By default, the engine will hold the reloading thread
    backgroundProcessorDelay = 10;

}

在这里会创建StandardEngineValve组件,并设置到pipeline中。

初始化

v8.5.59
java
java/org/apache/catalina/core/StandardEngine.java
@Override
protected void initInternal() throws LifecycleException {
    // Ensure that a Realm is present before any attempt is made to start
    // one. This will create the default NullRealm if necessary.
    // 与安全相关,忽略该组件
    getRealm();
    super.initInternal();
}

这里主要操作是在父组件中实现的。

v8.5.59
java
java/org/apache/catalina/core/ContainerBase.java
@Override
protected void initInternal() throws LifecycleException {
    BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue<>();
    // 该线程池的主要作用是用于启动子组件
    startStopExecutor = new ThreadPoolExecutor(
            getStartStopThreadsInternal(),
            getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,
            startStopQueue,
            new StartStopThreadFactory(getName() + "-startStop-"));
    startStopExecutor.allowCoreThreadTimeOut(true);
    super.initInternal();
}

这里主要是在创建一个用于启动子组件的线程池。

启动

v8.5.59
java
java/org/apache/catalina/core/StandardEngine.java
@Override
protected synchronized void startInternal() throws LifecycleException {

    // Log our server identification information
    if(log.isInfoEnabled())
        log.info( "Starting Servlet Engine: " + ServerInfo.getServerInfo());

    // Standard container startup
    super.startInternal();
}

主要的操作是在父类ContainerBase中完成的。

v8.5.59
startInternal
StartChild
<
>
java
java/org/apache/catalina/core/ContainerBase.java
@Override
protected synchronized void startInternal() throws LifecycleException {

    // Start our child containers, if any
    // 获取子组件
    Container children[] = findChildren();
    List<Future<Void>> results = new ArrayList<>();
    // 启动子组件
    for (Container child : children) {
        results.add(startStopExecutor.submit(new StartChild(child)));
    }

    MultiThrowable multiThrowable = null;

    // 遍历各个子组件的启动结果
    for (Future<Void> result : results) {
        try {
            // 等待子组件启动完成
            result.get();
        }

    }

    // Start the Valves in our pipeline (including the basic), if any
    if (pipeline instanceof Lifecycle) {
        // 启动pipeline
        ((Lifecycle) pipeline).start();
    }


    setState(LifecycleState.STARTING);

    // Start our thread
    threadStart();
}
java
java/org/apache/catalina/core/ContainerBase.java
private static class StartChild implements Callable<Void> {

    private Container child;

    public StartChild(Container child) {
        this.child = child;
    }

    @Override
    public Void call() throws LifecycleException {
        // 调用子组件的start方法,如果没有初始化过,则会初始化。
        child.start();
        return null;
    }
}

在该方法中,会启动子组件,但是好像没有初始化组件啊?其实调用各个组件的start方法,最终会走到LifecycleBase中的start方法,会检测如果组件没有被初始化,则会先初始化的,上面已经介绍过了。 StandardHostStandardContext也继承了ContainerBase类,所以其生命周期过程和这里介绍的一样,下面不会再贴出ContainerBase的相关方法。

StandardThreadExecutor

创建

如果在Tomcat的配置文件server.xml配置了Executor(默认情况下这个配置在配置文件中是被注释掉的),例如:

v8.5.59
xml
conf/server.xml
<!--
  minSpareThreads表示核心线程数量,maxThreads表示最大线程数量。
  具体参考StandardThreadExecutor类的startInternal方法。
-->
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
    maxThreads="150" minSpareThreads="4"/>
<Connector port="8080" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443"
           executor="tomcatThreadPool"
/>

那么Tomcat会解析Executor配置,并创建StandardThreadExecutor对象,然后添加到StandardService对象的executors列表中。 如果在Connector配置中配置了executor(需要引用配置文件中的其他Executor配置项的名称,如上例),那么会通过反射调用ProtocolHanldersetExecutor方法来设置,这样在下面NioEndpointstartInternal方法中就不会再创建线程池了。

上面的操作是在ConnectorCreateRule类中的begin方法中进行的。

v8.5.59
java
java/org/apache/catalina/startup/ConnectorCreateRule.java
@Override
public void begin(String namespace, String name, Attributes attributes)
        throws Exception {
    Service svc = (Service)digester.peek();
    Executor ex = null;
    // 如果配置文件中Connector标签中设置了executor属性
    if ( attributes.getValue("executor")!=null ) {
        ex = svc.getExecutor(attributes.getValue("executor"));
    }
    Connector con = new Connector(attributes.getValue("protocol"));
    if (ex != null) {
        // 设置线程池
        setExecutor(con, ex);
    }
    String sslImplementationName = attributes.getValue("sslImplementationName");
    if (sslImplementationName != null) {
        setSSLImplementationName(con, sslImplementationName);
    }
    digester.push(con);
}
private static void setExecutor(Connector con, Executor ex) throws Exception {
    // 获取到Connector的协议处理器类中的setExecutor方法
    Method m = IntrospectionUtils.findMethod(con.getProtocolHandler().getClass(),"setExecutor",new Class[] {java.util.concurrent.Executor.class});
    if (m!=null) {
        // 反射调用方法来设置线程池
        m.invoke(con.getProtocolHandler(), new Object[] {ex});
    }else {
        log.warn(sm.getString("connector.noSetExecutor", con));
    }
}

如果使用内嵌启动方式,也可以按照这种思路设置自定义的线程池。

启动

v8.5.59
java
java/org/apache/catalina/core/StandardThreadExecutor.java
// ---------------------------------------------- Properties
/**
 * Default thread priority
 */
protected int threadPriority = Thread.NORM_PRIORITY;

/**
 * Run threads in daemon or non-daemon state
 */
protected boolean daemon = true;

/**
 * Default name prefix for the thread name
 */
protected String namePrefix = "tomcat-exec-";

/**
 * max number of threads
 */
protected int maxThreads = 200;

/**
 * min number of threads
 */
protected int minSpareThreads = 25;

/**
 * idle time in milliseconds
 */
protected int maxIdleTime = 60000;

/**
 * The executor we use for this component
 */
protected ThreadPoolExecutor executor = null;

/**
 * the name of this thread pool
 */
protected String name;

/**
 * prestart threads?
 */
protected boolean prestartminSpareThreads = false;

/**
 * The maximum number of elements that can queue up before we reject them
 */
protected int maxQueueSize = Integer.MAX_VALUE;

/**
 * After a context is stopped, threads in the pool are renewed. To avoid
 * renewing all threads at the same time, this delay is observed between 2
 * threads being renewed.
 */
protected long threadRenewalDelay =
    org.apache.tomcat.util.threads.Constants.DEFAULT_THREAD_RENEWAL_DELAY;

private TaskQueue taskqueue = null;
@Override
protected void startInternal() throws LifecycleException {

    // 创建任务队列
    taskqueue = new TaskQueue(maxQueueSize);
    // 创建线程工厂
    TaskThreadFactory tf = new TaskThreadFactory(namePrefix,daemon,getThreadPriority());
    /*
     * 创建线程池,这里的ThreadPoolExecutor和JDK的类同名,是对后者的封装。
     * 默认是25个核心线程,200个最大线程,非核心线程最长空闲时长为60秒。
     */
    executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf);
    executor.setThreadRenewalDelay(threadRenewalDelay);
    // 这个属性默认是false,表示不预先启动核心线程
    if (prestartminSpareThreads) {
        // 预启动核心线程
        executor.prestartAllCoreThreads();
    }
    // 为任务队列绑定执行器
    taskqueue.setParent(executor);

    setState(LifecycleState.STARTING);
}

这里创建的线程池类型是Tomcat中自己封装JDK的,两者同名,要注意区分。默认情况下Tomcat会创建25个核心线程,最多200个线程。

ProtocolHandler

Tomcat提供了多种协议处理器,默认使用的是Http11NioProtocol,另外还有Http11Nio2Protocol,两者区别在于使用的NIO的版本不一样。

本文以默认使用的Http11NioProtocol为例。

创建

v8.5.59
Http11NioProtocol
AbstractHttp11JsseProtocol
AbstractHttp11Protocol
AbstractProtocol
<
>
java
java/org/apache/coyote/http11/Http11NioProtocol.java
public Http11NioProtocol() {
    /*
     * 创建NioEndPoint对象,并传入父类构造器。
     */
    super(new NioEndpoint());
}
java
java/org/apache/coyote/http11/AbstractHttp11JsseProtocol.java
public AbstractHttp11JsseProtocol(AbstractJsseEndpoint<S> endpoint) {
    super(endpoint);
}
java
java/org/apache/coyote/http11/AbstractHttp11Protocol.java
public AbstractHttp11Protocol(AbstractEndpoint<S> endpoint) {
    super(endpoint);
    // 设置连接超时时间,默认是60秒
    setConnectionTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
    // 创建并设置连接处理器
    ConnectionHandler<S> cHandler = new ConnectionHandler<>(this);
    setHandler(cHandler);
    // 给endpoint对象设置handler,在线程池中会调用到该对象。
    getEndpoint().setHandler(cHandler);
}
java
java/org/apache/coyote/AbstractProtocol.java
public AbstractProtocol(AbstractEndpoint<S> endpoint) {
    this.endpoint = endpoint;
    // 这里设置的值是 -1
    setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
    // 设置TCP无延迟,默认为true
    setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
}

初始化

对于ProtocolHandler组件而言,其没有实现Lifecycle接口,但是在ProtocolHanlder接口中定义了initstart等方法,所以这里和上面其他组件的生命周期方法不一样,注意进行区分。

v8.5.59
AbstractHttp11Protocol
AbstractProtocol
<
>
java
java/org/apache/coyote/http11/AbstractHttp11Protocol.java
@Override
public void init() throws Exception {
    // Upgrade protocols have to be configured first since the endpoint
    // init (triggered via super.init() below) uses this list to configure
    // the list of ALPN protocols to advertise
    // 协议升级相关
    for (UpgradeProtocol upgradeProtocol : upgradeProtocols) {
        configureUpgradeProtocol(upgradeProtocol);
    }

    // 会初始化端点组件
    super.init();
}
java
java/org/apache/coyote/AbstractProtocol.java
@Override
public void init() throws Exception {

    String endpointName = getName();
    /*
     * 给Endpoint设置名称,对于NioEndpoint而言,该名称会被作为线程池中线程名称的前缀。
     * 上面返回的endpointName是被引号括住的,这里去掉引号。
     */
    endpoint.setName(endpointName.substring(1, endpointName.length()-1));
    endpoint.setDomain(domain);

    // 初始化端点组件,参考NioEndpoint
    endpoint.init();
}

AbstractProtocolinit方法中会初始化端点(AbstractEndpoint)组件,并设置名称。这个名称比较重要,接下来看看设置的是什么内容。

v8.5.59
AbstractProtocol
Http11NioProtocol
<
>
java
java/org/apache/coyote/AbstractProtocol.java
public String getName() {
    return ObjectName.quote(getNameInternal());
}
private String getNameInternal() {
    // 这里考虑Http11NioProtocol中的getNamePrefix方法,返回的是http-nio
    StringBuilder name = new StringBuilder(getNamePrefix());
    name.append('-');
    // 如果设置了主机地址
    if (getAddress() != null) {
        // 获取主机地址
        name.append(getAddress().getHostAddress());
        name.append('-');
    }
    // 获取端口
    int port = getPort();
    // 如果端口是0
    if (port == 0) {
        // Auto binding is in use. Check if port is known
        name.append("auto-");
        name.append(getNameIndex());
        port = getLocalPort();
        if (port != -1) {
            name.append('-');
            name.append(port);
        }
    } else {
        // 追加上端口,如最常见的http-nio-8080
        name.append(port);
    }
    return name.toString();
}
protected abstract String getNamePrefix();
java
java/org/apache/coyote/http11/Http11NioProtocol.java
@Override
protected String getNamePrefix() {
    if (isSSLEnabled()) {
        return ("https-" + getSslImplementationShortName()+ "-nio");
    } else {
        // 这里返回的名称会被作为业务线程池中线程的名称
        return ("http-nio");
    }
}

这里我们就知道了在日志中经常出现的线程名称前缀“http-nio”是怎么来的了。

启动

v8.5.59
java
java/org/apache/coyote/AbstractProtocol.java
@Override
public void start() throws Exception {
    if (getLog().isInfoEnabled()) {
        getLog().info(sm.getString("abstractProtocolHandler.start", getName()));
    }

    // 启动端点组件,参考NioEndpoint
    endpoint.start();

    // Start timeout thread
    asyncTimeout = new AsyncTimeout();
    // 创建线程,周期性处理异步超时相关任务。
    Thread timeoutThread = new Thread(asyncTimeout, getNameInternal() + "-AsyncTimeout");
    int priority = endpoint.getThreadPriority();
    if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {
        priority = Thread.NORM_PRIORITY;
    }
    timeoutThread.setPriority(priority);
    timeoutThread.setDaemon(true);
    // 启动线程
    timeoutThread.start();
}

这里会启动端点组件。

设置适配器

Connectorinit方法中,会创建适配器并设置到协议处理器中。

v8.5.59
java
java/org/apache/coyote/AbstractProtocol.java
protected Adapter adapter;
@Override
public void setAdapter(Adapter adapter) { this.adapter = adapter; }
@Override
public Adapter getAdapter() { return adapter; }

在后续的请求处理中会调用到该适配器。

AbstractEndpoint

初始化

ProtocolHandler一样,AbstractEndpoint自己内部定义了initstart等方法,而且定义了xxxInternal的模板方法,这和LifecycleBase那一套很类似,但不是使用的LifecycleBase,注意区分。

v8.5.59
init
init
<
>
java
java/org/apache/tomcat/util/net/AbstractJsseEndpoint.java
@Override
public void init() throws Exception {
    // 测试加密套件
    testServerCipherSuitesOrderSupport();
    super.init();
}
java
java/org/apache/tomcat/util/net/AbstractEndpoint.java
public void init() throws Exception {
    if (bindOnInit) {
        // 执行bind操作
        bind();
        bindState = BindState.BOUND_ON_INIT;
    }
}
// 定义了一些模板方法让子类实现
public abstract void bind() throws Exception;
public abstract void unbind() throws Exception;
public abstract void startInternal() throws Exception;
public abstract void stopInternal() throws Exception;

这里主要是在调用子类实现的bind方法。

NioEndpoint

对于NioEndpoint,其bind方法如下。

v8.5.59
java
java/org/apache/tomcat/util/net/NioEndpoint.java
private NioSelectorPool selectorPool = new NioSelectorPool();
@Override
public void bind() throws Exception {

    if (!getUseInheritedChannel()) {
        // 创建服务端网络通道
        serverSock = ServerSocketChannel.open();
        // 这里socket()方法返回的是JDK的ServerSocketAdaptor,是ServerSocket的子类。
        socketProperties.setProperties(serverSock.socket());
        // 创建所要监听的地址信息
        InetSocketAddress addr = (getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
        /*
         * 绑定地址,第二个参数的默认值为100,表示等待处理的连接队列的长度。
         * 同时进行监听。
         */
        serverSock.socket().bind(addr,getAcceptCount());
    } else {
        // Retrieve the channel provided by the OS
        Channel ic = System.inheritedChannel();
        if (ic instanceof ServerSocketChannel) {
            serverSock = (ServerSocketChannel) ic;
        }
        if (serverSock == null) {
            throw new IllegalArgumentException(sm.getString("endpoint.init.bind.inherited"));
        }
    }
    /*
     * 配置为阻塞模式,为什么?
     * 如果请求没有到来,则会阻塞poller线程。
     */
    serverSock.configureBlocking(true); //mimic APR behavior

    /*
     * 如果没有设置acceptor和poller的数量,那么都设置为1。
     */
    // Initialize thread count defaults for acceptor, poller
    if (acceptorThreadCount == 0) {
        // FIXME: Doesn't seem to work that well with multiple accept threads
        acceptorThreadCount = 1;
    }
    // 默认最多是2
    if (pollerThreadCount <= 0) {
        //minimum one poller thread
        pollerThreadCount = 1;
    }
    setStopLatch(new CountDownLatch(pollerThreadCount));

    // Initialize SSL if needed
    initialiseSsl();

    // 打开选择器池
    selectorPool.open();
}

在这个方法中创建了ServerSocket,并进行了地址绑定和监听。

运行

v8.5.59
java
java/org/apache/tomcat/util/net/AbstractEndpoint.java
// 定义了一些模板方法让子类实现
public abstract void bind() throws Exception;
public abstract void unbind() throws Exception;
public abstract void startInternal() throws Exception;
public abstract void stopInternal() throws Exception;
    public final void start() throws Exception {
        // 如果子类没有重写init方法,那么在init方法中就执行了bind了
        if (bindState == BindState.UNBOUND) {
            // 如果还未绑定就先绑定
            bind();
            bindState = BindState.BOUND_ON_START;
        }
        // 调用子类实现
        startInternal();
    }

对于NioEndpoint而言,没有重写父类的init方法,所以在AbstractEndpointinit方法中就已经完成了绑定了,所以这里不会再次绑定,而是直接调用子类的实现。

NioEndpoint

v8.5.59
java
java/org/apache/tomcat/util/net/NioEndpoint.java
@Override
public void startInternal() throws Exception {

    // 记录启动过没有
    if (!running) {
        running = true;
        paused = false;

        processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                socketProperties.getProcessorCache());
        eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                        socketProperties.getEventCache());
        nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                socketProperties.getBufferPool());

        // Create worker collection
        // 如果没有设置线程池,
        if ( getExecutor() == null ) {
            // 则创建
            createExecutor();
        }

        // 初始化用于控制最大连接数量的门闩
        initializeConnectionLatch();

        // Start poller threads
        /*
         * 创建并启动n个poller线程
         */
        pollers = new Poller[getPollerThreadCount()];
        for (int i=0; i<pollers.length; i++) {
            pollers[i] = new Poller();
            // 创建poller线程
            Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
            pollerThread.setPriority(threadPriority);
            pollerThread.setDaemon(true);
            // 启动线程
            pollerThread.start();
        }

        /*
         * 启动acceptor线程
         */
        startAcceptorThreads();
    }
}

如果没有设置线程池,则调用父类中的createExecutor方法来创建线程池。

v8.5.59
java
java/org/apache/tomcat/util/net/AbstractEndpoint.java
/*
 * 注意区分这里的executor和StandardService中的组件Executor。
 * 前者直接就是JUC中的类,后者是tomcat中实现的继承了JUC中同名类的类。
 * 如果是server.xml文件中的Connector标签中设置了executor选项,并引用了Executor配置,
 * 那么Catalina在解析配置文件的时候时会自动通过反射来设置到该属性上的。
 */
private Executor executor = null;
public void setExecutor(Executor executor) {
    this.executor = executor;
    this.internalExecutor = (executor == null);
}
public Executor getExecutor() { return executor; }
private int minSpareThreads = 10;
private int maxThreads = 200;
public void createExecutor() {
    internalExecutor = true;
    // 创建任务队列
    TaskQueue taskqueue = new TaskQueue();
    // 创建线程工厂
    TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
    /*
     * 创建线程池对象,和Executor组件一样,这里的ThreadPoolExecutor也是tomcat中与JUC同名的类,并且继承了JUC中对应的同名类。
     */
    executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
    taskqueue.setParent( (ThreadPoolExecutor) executor);
}

可以看到,这里默认情况下是10个核心线程数,最大线程数为200。 另外这里创建的线程工厂时传递的线程名称前缀,还记得上面设置的名称是“http-nio-8080”(假设监听的端口是8080),那么这里拼接一下就成了“http-nio-8080”。

注意,如果在配置文件中的Connector标签中设置了executor属性引用其他的Executor配置,那么Tomcat会自动将该线程池设置到AbstractEndpointexecutor属性上。总之,如果其他地方设置了该属性,导致该属性不是null,都不会创建这里的默认线程池。

接下来创建用于控制最大连接数的门闩变量。

v8.5.59
java
java/org/apache/tomcat/util/net/AbstractEndpoint.java
private int maxConnections = 10000;
public void setMaxConnections(int maxCon) {
    this.maxConnections = maxCon;
    LimitLatch latch = this.connectionLimitLatch;
    if (latch != null) {
        // Update the latch that enforces this
        if (maxCon == -1) {
            // 如果不限制连接数,则将门闩变量设置为null
            releaseConnectionLatch();
        } else {
            // 设置新的最大连接数
            latch.setLimit(maxCon);
        }
    } else if (maxCon > 0) {
        // 重新创建过门闩变量
        initializeConnectionLatch();
    }
}

public int  getMaxConnections() { return this.maxConnections; }
protected LimitLatch initializeConnectionLatch() {
    // 如果是-1,则不用限制连接数
    if (maxConnections==-1) return null;
    if (connectionLimitLatch==null) {
        connectionLimitLatch = new LimitLatch(getMaxConnections());
    }
    return connectionLimitLatch;
}
protected void releaseConnectionLatch() {
    LimitLatch latch = connectionLimitLatch;
    if (latch!=null) latch.releaseAll();
    connectionLimitLatch = null;
}

再接下来就是创建poller线程。

v8.5.59
java
java/org/apache/tomcat/util/net/NioEndpoint.java
/*
 * poller个数最大不能超过2。
 */
private int pollerThreadCount = Math.min(2,Runtime.getRuntime().availableProcessors());
public void setPollerThreadCount(int pollerThreadCount) { this.pollerThreadCount = pollerThreadCount; }
public int getPollerThreadCount() { return pollerThreadCount; }

这里默认最多创建2个poller线程。

最后会启动acceptor线程。

v8.5.59
java
java/org/apache/tomcat/util/net/AbstractEndpoint.java
protected final void startAcceptorThreads() {
    // 获取acceptor线程的数量,默认是1
    int count = getAcceptorThreadCount();
    acceptors = new Acceptor[count];

    for (int i = 0; i < count; i++) {
        // 创建Acceptor对象
        acceptors[i] = createAcceptor();
        String threadName = getName() + "-Acceptor-" + i;
        acceptors[i].setThreadName(threadName);
        // 创建acceptor线程
        Thread t = new Thread(acceptors[i], threadName);
        t.setPriority(getAcceptorThreadPriority());
        t.setDaemon(getDaemon());
        // 启动acceptor线程
        t.start();
    }
}

这里的acceptor线程会获取客户端的连接请求,然后选择一个poller线程,并将请求放入其任务队列中,poller线程会从该队列中取出任务并提交到上面AbstractEndpointcreateExecutor方法创建的线程池中处理。具体细节在后面连接请求过程中再分析。

StandardHost

在上面介绍内嵌启动方式时,会看到在Spring Boot中会创建StandardHost组件。独立启动方式也是会启动的,只不过会通过解析配置文件来创建,没有分析具体的创建过程。 该类和StandardEngine一样,继承了ContainerBase,是一个容器组件。

创建

v8.5.59
java
java/org/apache/catalina/core/StandardHost.java
public StandardHost() {

    super();
    /*
     * 设置流水线的basic属性。
     */
    pipeline.setBasic(new StandardHostValve());

}

这里往流水线pipeline中添加了一个StandardHostValve组件。

启动

v8.5.59
java
java/org/apache/catalina/core/StandardHost.java
@Override
protected synchronized void startInternal() throws LifecycleException {

    // Set error report valve
    // 获取错误报告这个Valve的类名
    String errorValve = getErrorReportValveClass();
    // 如果存在这个类
    if ((errorValve != null) && (!errorValve.equals(""))) {
        try {
            boolean found = false;
            // 获取pipeline中的valves
            Valve[] valves = getPipeline().getValves();
            // 遍历valves数组
            for (Valve valve : valves) {
                // 找到错误报告ErrorReportValve
                if (errorValve.equals(valve.getClass().getName())) {
                    found = true;
                    break;
                }
            }
            if(!found) {
                // 如果没有找到,则创建一个并添加到pipeline中
                Valve valve =
                    (Valve) Class.forName(errorValve).getConstructor().newInstance();
                getPipeline().addValve(valve);
            }
        }
    }
    super.startInternal();
}

这里会判断pipeline中是否有负责错误报告的Valve,如果没有则会创建一个并添加到pipeline中。然后再次调用父类中的启动方法来启动子组件Context

StandardContext

创建

v8.5.59
java
java/org/apache/catalina/core/StandardContext.java
public StandardContext() {

    super();
    // 添加一个基本的Valve
    pipeline.setBasic(new StandardContextValve());
    broadcaster = new NotificationBroadcasterSupport();
    // Set defaults
    if (!Globals.STRICT_SERVLET_COMPLIANCE) {
        // Strict servlet compliance requires all extension mapped servlets
        // to be checked against welcome files
        resourceOnlyServlets.add("jsp");
    }
}

该构造方法中,主要是创建一个基本的Valve并添加到pipeline中。

初始化方法中主要是在做JNDI相关处理,这里忽略。

运行

该方法中原内容非常多,我挑选一些重要的步骤。

v8.5.59
java
java/org/apache/catalina/core/StandardContext.java

    // Start the Valves in our pipeline (including the basic),
    // if any
    if (pipeline instanceof Lifecycle) {
        // 启动pipeline
        ((Lifecycle) pipeline).start();
    }
// Call ServletContainerInitializers

// 遍历servlet容器初始化器,并调用其onStartup方法。
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
    initializers.entrySet()) {
    try {
        // 调用servlet上下文初始化器的onStartup方法
        entry.getKey().onStartup(entry.getValue(),
                getServletContext());
    } catch (ServletException e) {
        log.error(sm.getString("standardContext.sciFail"), e);
        ok = false;
        break;
    }
}

// Configure and call application event listeners
if (ok) {
    /*
     * 获取各种监听器,并执行ServletContextListener。
     * SpringMVC中的web.xml中配置listener为Spring的ContextLoadListener,就是在这里被调用的。
     */
    if (!listenerStart()) {
        log.error(sm.getString("standardContext.listenerFail"));
        ok = false;
    }
}

// Configure and call application filters
if (ok) {
    // 启动过滤器,会将过滤器定义对象转换为过滤器配置对象。
    if (!filterStart()) {
        log.error(sm.getString("standardContext.filterFail"));
        ok = false;
    }
}

// Load and initialize all "load on startup" servlets
if (ok) {
    /*
     * 会调用wrapper的load方法,内部会调用Servlet的init方法。
     * SpringBoot中的Context是StandardContext的子类,TomcatEmbeddedContext,重写了loadOnStartup方法,默认直接返回true。
     */
    if (!loadOnStartup(findChildren())){
        log.error(sm.getString("standardContext.servletFail"));
        ok = false;
    }
}

注释中已经说明各个阶段所执行的操作,不再赘述。注意该类中就没有再调用父类ContainerBase中的startInternal方法了。

StandardPipeline

在创建StandardEngineStandardHostStandardContext组件时,会创建StandardPipeline对象。

创建

创建的位置在ContainerBase这个抽象基类中,而不是在各个实现类中。

v8.5.59
java
java/org/apache/catalina/core/ContainerBase.java
/*
 * 流水线
 */
protected final Pipeline pipeline = new StandardPipeline(this);

重写的initInternal是空方法,所以没有必要介绍。

启动

除了StandardContext组件是在自己的startInternal方法中启动的pipeline以外,其他的那几个组件都是在ContainerBasestartInternal方法中启动的pipeline。

v8.5.59
java
java/org/apache/catalina/core/StandardPipeline.java
@Override
protected synchronized void startInternal() throws LifecycleException {

    // Start the Valves in our pipeline (including the basic), if any
    Valve current = first;
    if (current == null) {
        // 如果没有设置first,则从basic开始处理
        current = basic;
    }
    // 遍历Valve链
    while (current != null) {
        // 要求Valve实现了Lifecycle接口,才进行启动
        if (current instanceof Lifecycle)
            // 启动Valve
            ((Lifecycle) current).start();
        current = current.getNext();
    }

    setState(LifecycleState.STARTING);
}

这里在遍历Valve组件链,并进行启动。

总结

本文梳理了Tomcat中的各个组件,分析了各个组件的创建、初始化、启动过程。整体内容比较多,不适合从头到尾通读,而是需要在不同组件之间跳着看。