Tomcat 9 源码分析(1)— 启动与停止

Tomcat 9 源码分析

前言

本文分析的Tomcat版本为Tomcat9.0,该版本与Tomcat8,Tomcat7大体一致,仅在部分地方有所改动,而目前最新的Tomcat10则与之前的版本相差较大。

这是本人第一次阅读主流技术的源码,碍于个人能力原因,无法独自完成源码的阅读,因此本文参考了许多大佬们的文章,在本次学习中主要参考了泰山不老生博主的博客,很感谢能够翻阅到大佬的文章,所学甚多,受益匪浅。

愿编码半生,如一生老友,你也加油。

Tomcat模块

avatar

Tomcat的模块如图,接下来便从Tomcat的启动开始分析

启动

Tomcat的启动往往是调用tomcat/bin目录下的startup脚本,而startup.sh将会调用catalina.sh脚本

tomcat/bin/startup.sh

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

if $os400; then
  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 "$@"

主要变量:

  • PRGDIR:当前shell脚本所在的路径
  • EXECUTABLE:脚本catalina.sh

startup.sh内最后会调用catalina.sh,初始化catalina_base,catalina_home等参数,并传递参数start,catalina.sh脚本则初传递各种参数调用Bootstrap.jar执行其main方法且传递传输start

tomcat/bin/catalina.sh

elif [ "$1" = "start" ] ; then
    
  #省略参数校验脚本
  
  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 "&"

由此可知,最后使用java命令执行了org.apache.catalina.startup.Bootstrap类中的main方法,参数是start,在Bootstrap.main()中,当传递参数start时变量command等于start

Bootstarp.main()内先调用init()初始化,利用反射机制,正式载入Catalina类,创建其实例返回并赋值给Bootstrap类的catalinaDaemon对象

org.apche.catalina.startup.Bootstrap.main()

public static void main(String args[]) {

    synchronized (daemonLock) {
        if (daemon == null) {
            // Don't set daemon until init() has completed
            Bootstrap bootstrap = new Bootstrap();
            try {
                bootstrap.init();
            } catch (Throwable t) {
                handleThrowable(t);
                t.printStackTrace();
                return;
            }
            daemon = bootstrap;
        } else {
            Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
        }
    }
    // 省略后续操作,在后文分析
   
}

init()

org.apache.catalina.startup.Bootstrap.init()

public void init() throws Exception {
    //  初始化catalina.properties中的commonLoader,serverLoader,sharedLoader
    //  如果catalina.properties中的server.loader和sharedLoader为空则以common.Loader替代
    //  查找各个loader中的系统变量,其中包含了以","分隔的文件路径(URL、JAR、目录等),称之为repository
    //  调用ClassLoaderFactory.createClassLoader()产生classLoader
    initClassLoaders();

    //  把生成的catalinaLoader设置为当前线程上下文的classloader
    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");
    Object startupInstance = startupClass.getConstructor().newInstance();

    //  Set the shared extensions class loader
    //  调用setParentClassLoader()将sharedLoader设置为父类加载器
    //  将catalinaLoader实例赋值给Bootstrap类的catalinaDaemon对象
    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;
}

initClassLoaders()将初始化catalina.properties中的commonLoader,serverLoader,sharedLoader,且以commonLoader为父类加载器

org.apache.catalina.startup.Bootstrap.initClassLoaders()

private void initClassLoaders() {
    try {
        commonLoader = createClassLoader("common", null);
        if (commonLoader == null) {
            // no config file, default to this loader - we might be in a 'single' env.
            commonLoader = this.getClass().getClassLoader();
        }
        catalinaLoader = createClassLoader("server", commonLoader);
        sharedLoader = createClassLoader("shared", commonLoader);
    } catch (Throwable t) {
        handleThrowable(t);
        log.error("Class loader creation threw exception", t);
        System.exit(1);
    }
}
//common.loader:tomcat自身和各web服务都会用到的类
//server.loader:tomcat自身会用到的类 如果为空则由common.loader替代
//shared.loader:各web服务会用到的类 如果为空则由common.loader替代
common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
server.loader=
shared.loader=

这三个类加载器都是UrlClassLoader

org.apache.catalina.startup.ClassLoaderFactory.createClassLoader()

return AccessController.doPrivileged(
        (PrivilegedAction<URLClassLoader>) () -> {
            if (parent == null)
                return new URLClassLoader(array);
            else
                return new URLClassLoader(array, parent);
        });

至此org.apache.catalina.startup.Bootstrap.init()方法执行完毕,接下来main方法继续执行,在后续代码中根据catalina.sh最后的%ACTION%参数进行不同的操作,而main方法内的setAwait、load、start均为使用反射调用catalinaDaemon(即Catalina类)对应的方法

org.apache.catalina.startup.Bootstrap.main()

public static void main(String args[]) {
	// 省略初始化代码,前文已分析

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

setAwait()

当启动tomcat时传递的参数command为start,此时首先会调用Bootstrap.setAwait(),在setAwait方法内会使用反射调用Catalina类中的setAwait方法设置启动flag

org.apache.catalina.startup.Catalina.setAwait()

public void setAwait(boolean await)
    throws Exception {

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

load()

在执行完setWait后便调用load正式初始化载入

org.apache.catalina.startup.Catalina.load()

public void load() {
    if (loaded) {
        return;
    }
    loaded = true;
    long t1 = System.nanoTime();
    initDirs();
    // Before digester - it may be needed
    initNaming();
    //  Parse main server.xml
    //  处理server.xml并初始化实例化部分模块
    parseServerXml(true);
    Server s = getServer();
    if (s == null) {
        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);
        }
    }
    if(log.isInfoEnabled()) {
        log.info(sm.getString("catalina.init", Long.toString(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t1))));
    }
}

在load方法中调用了parseServerXml()处理server.xml并初始化实例化部分模块

org.apache.catalina.startup.Catalina.parseServerXml()

//  server.xml
try (ConfigurationSource.Resource resource = ConfigFileLoader.getSource().getServerXml()) {
    //  Create and execute our Digester
    //  根据inputSource和createStartDigester创建的digester对象所包含的解析规则生成相应对象
    //  并调用相应方法将对象之间关联起来
    Digester digester = start ? createStartDigester() : createStopDigester();
    InputStream inputStream = resource.getInputStream();
    InputSource inputSource = new InputSource(resource.getURI().toURL().toString());
    inputSource.setByteStream(inputStream);
    digester.push(this);
    if (generateCode) {
        digester.startGeneratingCode();
        generateClassHeader(digester, start);
    }
    digester.parse(inputSource);
}

parseServerXml()核心代码为以上代码,其内的crateStartDigester()中对Server,Service,Executor,Connector, Engine,Host,Context模块进行初始化实例化

org.apache.catalina.startup.Catalina.crateStartDigester()

protected Digester createStartDigester() {
    // 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
    //  创建Server实例 创建StandardServer对象,设置其对象的属性
    //  调用父节点Catalina的setServer方法将Server添加到Catalina中
    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");

    //  为Server添加生命周期监听器
    //  当匹配到"Server.Listener"节点,使用ListenerCreateRule解析
    //  第一个参数为className,即默认类名,为null。所以Listener不会有默认值类型
    //  因为Listener本来就是自定义的
    digester.addRule("Server/Listener",
            new ListenerCreateRule(null, "className"));
    digester.addSetProperties("Server/Listener");
    digester.addSetNext("Server/Listener",
                        "addLifecycleListener",
                        "org.apache.catalina.LifecycleListener");

    //  创建Service实例
    //  创建StandardService实例,调用StandardService实例的set方法设置属性
    //  最后调用父节点Server的addService方法,将service添加到Server
    digester.addObjectCreate("Server/Service",
                             "org.apache.catalina.core.StandardService",
                             "className");
    digester.addSetProperties("Server/Service");
    digester.addSetNext("Server/Service",
                        "addService",
                        "org.apache.catalina.Service");

    //  为Service添加生命周期监听器
    //  创建生命周期监听器,className上送值为null,所以不存在默认类型
    //  比如有"Server/Service/Listener"节点的className指定类型
    //  随后调用父节点Service的addLifecycleListener方法,将监听器添加到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
    //  创建StandardThreadExecutor实例,并调用父节点Service的addExecutor方法添加到Service
    //  通过配置克制,Tomcat桐乡Executor的级别为Service,Catalina默认情况下未配置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");

    //  创建Connector实例,并调用父组建Service的addConnector方法添加到Service
    //  设置相关属性时,添加设置executor,sslImplementationName,protocol
    digester.addRule("Server/Service/Connector",
                     new ConnectorCreateRule());
    digester.addSetProperties("Server/Service/Connector",
            new String[]{"executor", "sslImplementationName", "protocol"});
    digester.addSetNext("Server/Service/Connector",
                        "addConnector",
                        "org.apache.catalina.connector.Connector");

    digester.addRule("Server/Service/Connector", new AddPortOffsetRule());

    //  为Connector添加虚拟主机SSL配置
    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.addRule("Server/Service/Connector/SSLHostConfig/Certificate",
                     new CertificateCreateRule());
    digester.addSetProperties("Server/Service/Connector/SSLHostConfig/Certificate", new String[]{"type"});
    digester.addSetNext("Server/Service/Connector/SSLHostConfig/Certificate",
                        "addCertificate",
                        "org.apache.tomcat.util.net.SSLHostConfigCertificate");

    digester.addObjectCreate("Server/Service/Connector/SSLHostConfig/OpenSSLConf",
                             "org.apache.tomcat.util.net.openssl.OpenSSLConf");
    digester.addSetProperties("Server/Service/Connector/SSLHostConfig/OpenSSLConf");
    digester.addSetNext("Server/Service/Connector/SSLHostConfig/OpenSSLConf",
                        "setOpenSslConf",
                        "org.apache.tomcat.util.net.openssl.OpenSSLConf");

    digester.addObjectCreate("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd",
                             "org.apache.tomcat.util.net.openssl.OpenSSLConfCmd");
    digester.addSetProperties("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd");
    digester.addSetNext("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd",
                        "addCmd",
                        "org.apache.tomcat.util.net.openssl.OpenSSLConfCmd");

    //  为Connector添加生命周期监听器
    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");

    //  为Connector添加升级协议(用户支持HTTP/2)
    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
    //  容器组件解析
    //  指定Servlet容器相关的各级嵌套子节点的解析规则,而且没类嵌套子节点的规则封装为一个RuleSet
    //  包括Server,Service,Engine,Host,Context以及Cluster解析
    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/"));

    //  到这里通过如上Digester机制,实现server.xml解析过程
    //  并将相关的Tomcat组件实例化出来,并且维护好了组件的上下文关系
    //  后续的init和start也是针对这些实例化后的组件展开

    // When the 'engine' is found, set the parentClassLoader.
    digester.addRule("Server/Service/Engine",
                     new SetParentClassLoaderRule(parentClassLoader));
    addClusterRuleSet(digester, "Server/Service/Engine/Cluster/");

    return digester;

}

在crateStartDigester方法的最后执行了容器组件解析

//  容器组件解析
    //  指定Servlet容器相关的各级嵌套子节点的解析规则,而且没类嵌套子节点的规则封装为一个RuleSet
    //  包括Server,Service,Engine,Host,Context以及Cluster解析
    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/"));

org.apache.catalina.startup.EngineRuleSet.addRuleInstances()

public void addRuleInstances(Digester digester) {

    //  创建实例,并通过setContainer添加到Service中
    //  为Engine添加一个生命周期的监听器EngineConfig,这个监听器是代码写死,不是通过server.xml配置的
    //  为Engine添加集群配置
    //  为Engine添加生命周期监听器,这里的监听器是server.xml文件中指定的
    //  为Engine添加Value,具体的拦截器由className属性指定,不指定默认值
    digester.addObjectCreate(prefix + "Engine",
                             "org.apache.catalina.core.StandardEngine",
                             "className");
    digester.addSetProperties(prefix + "Engine");
    digester.addRule(prefix + "Engine",
                     new LifecycleListenerRule
                     ("org.apache.catalina.startup.EngineConfig",
                      "engineConfigClass"));
    digester.addSetNext(prefix + "Engine",
                        "setContainer",
                        "org.apache.catalina.Engine");

    //Cluster configuration start
    digester.addObjectCreate(prefix + "Engine/Cluster",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Engine/Cluster");
    digester.addSetNext(prefix + "Engine/Cluster",
                        "setCluster",
                        "org.apache.catalina.Cluster");
    //Cluster configuration end

    digester.addObjectCreate(prefix + "Engine/Listener",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Engine/Listener");
    digester.addSetNext(prefix + "Engine/Listener",
                        "addLifecycleListener",
                        "org.apache.catalina.LifecycleListener");


    digester.addRuleSet(new RealmRuleSet(prefix + "Engine/"));

    digester.addObjectCreate(prefix + "Engine/Valve",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Engine/Valve");
    digester.addSetNext(prefix + "Engine/Valve",
                        "addValve",
                        "org.apache.catalina.Valve");
}

org.apache.catalina.startup.HostRuleSet.addRuleInstances()

public void addRuleInstances(Digester digester) {

    //  创建Host实例,通过addChild()添加到Engine上
    //  为Host添加了一个生命周期监听器HostConfig,这个监听器是代码里写死,不是通过server.xml配置的
    //  为Host配置集群,所以集群的配置即可在Engine级别,也可以在Host级别
    //  为Host添加生命周期监听器,这里的监听器是server.xml文件中配置的
    //  为Host添加Value,具体的拦截器由className属性指定,不指定默认值
    digester.addObjectCreate(prefix + "Host",
                             "org.apache.catalina.core.StandardHost",
                             "className");
    digester.addSetProperties(prefix + "Host");
    digester.addRule(prefix + "Host",
                     new CopyParentClassLoaderRule());
    digester.addRule(prefix + "Host",
                     new LifecycleListenerRule
                     ("org.apache.catalina.startup.HostConfig",
                      "hostConfigClass"));
    digester.addSetNext(prefix + "Host",
                        "addChild",
                        "org.apache.catalina.Container");

    digester.addCallMethod(prefix + "Host/Alias",
                           "addAlias", 0);

    //Cluster configuration start
    digester.addObjectCreate(prefix + "Host/Cluster",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Host/Cluster");
    digester.addSetNext(prefix + "Host/Cluster",
                        "setCluster",
                        "org.apache.catalina.Cluster");
    //Cluster configuration end

    digester.addObjectCreate(prefix + "Host/Listener",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Host/Listener");
    digester.addSetNext(prefix + "Host/Listener",
                        "addLifecycleListener",
                        "org.apache.catalina.LifecycleListener");

    digester.addRuleSet(new RealmRuleSet(prefix + "Host/"));

    digester.addObjectCreate(prefix + "Host/Valve",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Host/Valve");
    digester.addSetNext(prefix + "Host/Valve",
                        "addValve",
                        "org.apache.catalina.Valve");
}

org.apache.catalina.startup.ContextRuleSet.addRuleInstances()

public void addRuleInstances(Digester digester) {

    //  多数情况下,并不需要再server.xml中配置Context
    //  而是由HostConfig自动扫描部署目录,以context.xml文件为基础进行解析创建

    //  1.创建Context实例,通
    //  过server.xml配置Context时,create=true,需要创建Context
    //  通过HostConfig创建Context时,create为false,此时仅需解析节点即可
    if (create) {
        digester.addObjectCreate(prefix + "Context",
                "org.apache.catalina.core.StandardContext", "className");
        digester.addSetProperties(prefix + "Context");
    } else {
        digester.addSetProperties(prefix + "Context", new String[]{"path", "docBase"});
    }

    if (create) {
        digester.addRule(prefix + "Context",
                         new LifecycleListenerRule
                             ("org.apache.catalina.startup.ContextConfig",
                              "configClass"));
        digester.addSetNext(prefix + "Context",
                            "addChild",
                            "org.apache.catalina.Container");
    }

    //  2.为Context添加生命周期监听器
    digester.addObjectCreate(prefix + "Context/Listener",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Context/Listener");
    digester.addSetNext(prefix + "Context/Listener",
                        "addLifecycleListener",
                        "org.apache.catalina.LifecycleListener");

    //  3.为Context指定类加载器,默认为org.apache.catalina.loader.WebappLoader
    digester.addObjectCreate(prefix + "Context/Loader",
                        "org.apache.catalina.loader.WebappLoader",
                        "className");
    digester.addSetProperties(prefix + "Context/Loader");
    digester.addSetNext(prefix + "Context/Loader",
                        "setLoader",
                        "org.apache.catalina.Loader");

    //  4.为Context添加会话管理器,默认实现为StandardManager
    digester.addObjectCreate(prefix + "Context/Manager",
                             "org.apache.catalina.session.StandardManager",
                             "className");
    digester.addSetProperties(prefix + "Context/Manager");
    digester.addSetNext(prefix + "Context/Manager",
                        "setManager",
                        "org.apache.catalina.Manager");

    digester.addObjectCreate(prefix + "Context/Manager/Store",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Context/Manager/Store");
    digester.addSetNext(prefix + "Context/Manager/Store",
                        "setStore",
                        "org.apache.catalina.Store");

    digester.addObjectCreate(prefix + "Context/Manager/SessionIdGenerator",
                             "org.apache.catalina.util.StandardSessionIdGenerator",
                             "className");
    digester.addSetProperties(prefix + "Context/Manager/SessionIdGenerator");
    digester.addSetNext(prefix + "Context/Manager/SessionIdGenerator",
                        "setSessionIdGenerator",
                        "org.apache.catalina.SessionIdGenerator");

    //  5.为Context添加初始化参数,通过该配置,为Context添加初始化参数
    digester.addObjectCreate(prefix + "Context/Parameter",
                             "org.apache.tomcat.util.descriptor.web.ApplicationParameter");
    digester.addSetProperties(prefix + "Context/Parameter");
    digester.addSetNext(prefix + "Context/Parameter",
                        "addApplicationParameter",
                        "org.apache.tomcat.util.descriptor.web.ApplicationParameter");

    //  6.为Context添加安全配置以及web资源配置
    digester.addRuleSet(new RealmRuleSet(prefix + "Context/"));

    digester.addObjectCreate(prefix + "Context/Resources",
                             "org.apache.catalina.webresources.StandardRoot",
                             "className");
    digester.addSetProperties(prefix + "Context/Resources");
    digester.addSetNext(prefix + "Context/Resources",
                        "setResources",
                        "org.apache.catalina.WebResourceRoot");

    digester.addObjectCreate(prefix + "Context/Resources/PreResources",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Context/Resources/PreResources");
    digester.addSetNext(prefix + "Context/Resources/PreResources",
                        "addPreResources",
                        "org.apache.catalina.WebResourceSet");

    digester.addObjectCreate(prefix + "Context/Resources/JarResources",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Context/Resources/JarResources");
    digester.addSetNext(prefix + "Context/Resources/JarResources",
                        "addJarResources",
                        "org.apache.catalina.WebResourceSet");

    digester.addObjectCreate(prefix + "Context/Resources/PostResources",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Context/Resources/PostResources");
    digester.addSetNext(prefix + "Context/Resources/PostResources",
                        "addPostResources",
                        "org.apache.catalina.WebResourceSet");

    //  7.为Context添加资源连接,默认为ContextResourceLink,用户J2EE命名服务
    digester.addObjectCreate(prefix + "Context/ResourceLink",
            "org.apache.tomcat.util.descriptor.web.ContextResourceLink");
    digester.addSetProperties(prefix + "Context/ResourceLink");
    digester.addRule(prefix + "Context/ResourceLink",
            new SetNextNamingRule("addResourceLink",
                    "org.apache.tomcat.util.descriptor.web.ContextResourceLink"));

    //  8.为Context添加Value
    digester.addObjectCreate(prefix + "Context/Valve",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Context/Valve");
    digester.addSetNext(prefix + "Context/Valve",
                        "addValve",
                        "org.apache.catalina.Valve");

    //  9.为Context添加守护资源配置
    digester.addCallMethod(prefix + "Context/WatchedResource",
                           "addWatchedResource", 0);

    digester.addCallMethod(prefix + "Context/WrapperLifecycle",
                           "addWrapperLifecycle", 0);

    digester.addCallMethod(prefix + "Context/WrapperListener",
                           "addWrapperListener", 0);

    digester.addObjectCreate(prefix + "Context/JarScanner",
                             "org.apache.tomcat.util.scan.StandardJarScanner",
                             "className");
    digester.addSetProperties(prefix + "Context/JarScanner");
    digester.addSetNext(prefix + "Context/JarScanner",
                        "setJarScanner",
                        "org.apache.tomcat.JarScanner");

    digester.addObjectCreate(prefix + "Context/JarScanner/JarScanFilter",
                             "org.apache.tomcat.util.scan.StandardJarScanFilter",
                             "className");
    digester.addSetProperties(prefix + "Context/JarScanner/JarScanFilter");
    digester.addSetNext(prefix + "Context/JarScanner/JarScanFilter",
                        "setJarScanFilter",
                        "org.apache.tomcat.JarScanFilter");

    //  10.为Context添加Cookie处理器
    digester.addObjectCreate(prefix + "Context/CookieProcessor",
                             "org.apache.tomcat.util.http.Rfc6265CookieProcessor",
                             "className");
    digester.addSetProperties(prefix + "Context/CookieProcessor");
    digester.addSetNext(prefix + "Context/CookieProcessor",
                        "setCookieProcessor",
                        "org.apache.tomcat.util.http.CookieProcessor");
}

start()

Bootstrap.main()利用invoke调用的catalina.load()结束后继续往下执行,往后执行的便是start方法

BootStrap.main()节选

daemon.setAwait(true);
daemon.load(args);
daemon.start();

org.apache.catalina.startup.Catalina.start()

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

    if (log.isInfoEnabled()) {
        log.info(sm.getString("catalina.startup", Long.toString(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t1))));
    }

    if (generateCode) {
        
        generateLoader();
    }

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

        LogManager logManager = LogManager.getLogManager();
        if (logManager instanceof ClassLoaderLogManager) {
            ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                    false);
        }
    }

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

Catalina的start方法执行步骤如下:

  1. 验证Server容器是否已经实例化。如果没有实例化Server容器,还会再次调用Catalina的load方法加载和解析server.xml,这也说明Tomcat只允许Server容器通过配置在server.xml的方式生成,用户也可以自己实现Server接口创建自定义的Server容器以取代默认的StandardServer

  2. 启动Server容器,可以参考Tomcat生命周期管理

  3. 设置关闭勾子(shutdownHook)。Tomcat本身可能由于所在机器断点,程序bug甚至内存溢出导致进程退出,但是Tomcat可能需要再退出的时候偶做一些清理工作,比如:内部才能清理、对象销毁等。这些清理动作需要封装在一个Thread的实现中,然后将此Thread对象作为参数传递个Runtime的addShutdownHook方法即可

  4. 容器启动最后调用Catalina的await方法循环等待接收Tomcat的shutdown命令

  5. 如果Tomcat运行正常且没有收到shutdown命令,是不会想下执行stop方法的,当收到shutdown命令,Catalina的await方法会退出循环等待,然后顺序执行stop方法停止Tomcat

await()

Catalina的await方法实际只是代理执行了Server容器的await方法

org.apache.catalina.startup.Catalina

/**
 * Await and shutdown.
 */
public void await() {

    getServer().await();

}

以Server的默认实现StandardServer为例:其await方法执行步骤如下:

  • 创建socket连接的服务端对象ServerSocket
  • 循环等待接收客户端发出的命令,如果收到的命令与SHUTDOWN匹配(严格匹配大写),那么退出循环等待

org.apache.catalina.core.StandardServer.await()

/**
 * Wait until a proper shutdown command is received, then return.
 * This keeps the main thread alive - the thread pool listening for http
 * connections is daemon threads.
 */
@Override
public void await() {
    // Negative values - don't wait on port - tomcat is embedded or we just don't like ports
    if (getPortWithOffset() == -2) {
        // undocumented yet - for embedding apps that are around, alive.
        return;
    }
    if (getPortWithOffset() == -1) {
        try {
            awaitThread = Thread.currentThread();
            while(!stopAwait) {
                try {
                    Thread.sleep( 10000 );
                } catch( InterruptedException ex ) {
                    // continue and check the flag
                }
            }
        } finally {
            awaitThread = null;
        }
        return;
    }

    // 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
            }
        }
    }
}

至此,Tomcat启动完毕

停止

Tomcat正常停止是调用tomcat/bin/shutdown.sh脚本

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" stop "$@"

主要变量:

  • PRGDIR:当前shell脚本所在的路径
  • EXECUTABLE:脚本catalina.sh

最后一行 exec "$PRGDIR"/"$EXECUTABLE" stop "$@" 可知道执行了shell脚本catalina.sh,并且传递参数stop,当catalina.sh中接收到stop参数后的执行的脚本分支如下

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

  #省略参数校验脚本

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

由此可知,最后使用java命令执行了org.apache.catalina.startup.Bootstrap类中的main方法,参数是stop。

Bootstrap.main()中,当传递参数stop时变量command等于stop,此时main方法的执行步骤如下:

步骤一 初始化Bootstrap

步骤二 停止服务

调用Bootstrap的stopServer()停止Tomcat,其实质是用反射调用catalinaDaemon(类型是Catalina)的stopServer()

org.apache.catalina.startup.Bootstrap.stopServer()

public void stopServer(String[] arguments) throws Exception {

    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;
    }
    Method method =
        catalinaDaemon.getClass().getMethod("stopServer", paramTypes);
    method.invoke(catalinaDaemon, param);
}

org.apache.catalina.startup.Catalina.stopServer()

public void stopServer(String[] arguments) {

    if (arguments != null) {
        arguments(arguments);
    }

    Server s = getServer();
    if (s == null) {
        parseServerXml(false);
        if (getServer() == null) {
            log.error(sm.getString("catalina.stopError"));
            System.exit(1);
        }
    } else {
        // Server object already present. Must be running as a service
        try {
            s.stop();
            s.destroy();
        } catch (LifecycleException e) {
            log.error(sm.getString("catalina.stopError"), e);
        }
        return;
    }

    // Stop the existing server
    s = getServer();
    if (s.getPortWithOffset() > 0) {
        try (Socket socket = new Socket(s.getAddress(), s.getPortWithOffset());
                OutputStream stream = socket.getOutputStream()) {
            String shutdown = s.getShutdown();
            for (int i = 0; i < shutdown.length(); i++) {
                stream.write(shutdown.charAt(i));
            }
            stream.flush();
        } catch (ConnectException ce) {
            log.error(sm.getString("catalina.stopServer.connectException", s.getAddress(),
                    String.valueOf(s.getPortWithOffset()), String.valueOf(s.getPort()),
                    String.valueOf(s.getPortOffset())));
            log.error(sm.getString("catalina.stopError"), ce);
            System.exit(1);
        } catch (IOException e) {
            log.error(sm.getString("catalina.stopError"), e);
            System.exit(1);
        }
    } else {
        log.error(sm.getString("catalina.stopServer"));
        System.exit(1);
    }
}

Catalina的stopServer方法执行步骤如下:

  1. 创建Digester解析server.xml(此处只解析标签),以构造Server容器(此时Server容器的子容器没有被实例化)
  2. 从实例化的Server容器获取Server的socket监听端口地址,然后创建Socket对象连接启动Tomcat时创建的ServerSocket,最后向ServerSocket发送SHUTDOWN命令,ServerScoket循环等到接收到SHUTDOWN命令后,最终调用stop方法停止Tomcat

stop方()

org.apache.catalina.startup.Catalina.stop()

public void stop() {

    try {
        // Remove the ShutdownHook first so that server.stop()
        // doesn't get invoked twice
        if (useShutdownHook) {
            Runtime.getRuntime().removeShutdownHook(shutdownHook);

            // If JULI is being used, re-enable JULI's shutdown to ensure
            // log messages are not lost
            LogManager logManager = LogManager.getLogManager();
            if (logManager instanceof ClassLoaderLogManager) {
                ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                        true);
            }
        }
    } catch (Throwable t) {
        ExceptionUtils.handleThrowable(t);
        // This will fail on JDK 1.2. Ignoring, as Tomcat can run
        // fine without the shutdown hook.
    }

    // Shut down the server
    try {
        Server s = getServer();
        LifecycleState state = s.getState();
        if (LifecycleState.STOPPING_PREP.compareTo(state) <= 0
                && LifecycleState.DESTROYED.compareTo(state) >= 0) {
            // Nothing to do. stop() was already called
        } else {
            s.stop();
            s.destroy();
        }
    } catch (LifecycleException e) {
        log.error(sm.getString("catalina.stopError"), e);
    }

}

stop方法的步骤如下:

  1. 将启动过程中添加的shutdownHook移除,shutdownHook的作用是为了在JVM异常退出后,进行资源的回收工作。主动停止Tomcat时调用的stop方法里已经包含了资源回收的内容,所以将其remove
  2. 停止Server容器。有关容器停止的内容可以参考Tomcat生命周期管理

注:

Tomcat中ClassLoader关系,SharedClassLoader为用户自定义类加载器,保证了Tomcat应用的加载隔离

avatar

web.xml加载的配置在context.xml中

总结

Tomcat的启动和停止都离不开org.apache.catalina.startup.Bootstrap。当停止Tomcat时,已经启动的Tomcat作为socket服务端,停止脚本启动的Bootstrap进程作为socket客户端向服务端发送shutdown命令,两个进程通过共享server.xml里Server标签的端口以及地址信息打通了sockeet的通信

posted @ 2021-03-27 16:56  Kaiyko  阅读(380)  评论(0)    收藏  举报