zoukankan      html  css  js  c++  java
  • Tomcat知多少 -- 01. Tomcat的启动概述

    0. 写在前面

    本文讲述的Tomcat相关内容,基于Tomcat9.0.34版本。
    学习Tomcat源码时本人使用Idea工具,导入如下依赖来查看源码:

    <dependency>
        <groupId>org.apache.tomcat</groupId>
        <artifactId>tomcat-catalina</artifactId>
        <version>9.0.34</version>
    </dependency>
    

    1. 始于一条Shell语句

    于Linux系统下启动Tomcat,只需要一条shell语句>$ startup.sh,该shell脚本位于apache-tomcat-9.0.34in目录下,该脚本最后一句话最为重要:

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

    若将其中的变量替换掉就是:

    exec catalina.sh start [参数]
    

    所以实际的启动脚本是同目录下的catalina.sh,在该脚本中,找到 $1 = start的分支,启动Tomcat的shell基本如下:

    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 start加上了一些启动参数。

    所以,Tomcat的启动入口就是 org.apache.catalina.startup.Bootstrap类。

    2. Bootstrap类

    org.apache.catalina.startup.Bootstrap类是一个拥有public static void main(String args[])方法的类。在启动Tomcat时,main方法所传入的参数是start

    对main方法做简化,其主要过程如下:

    public static void main(String args[]) {
        // 1. 生成bootstrap实例
        Bootstrap bootstrap = new Bootstrap();
        // 2. 调用init()方法
        bootstrap.init();
        // 3. 调用load()方法
        bootstrap.load(args);
        // 4. 调用start()方法
        bootstrap.start();
        .....
    }
    

    注意,main方法中处理了其他命令行参数的情况,上述简化后的过程,只针对启动时相关的代码。

    init()方法主要内容如下:

    public void init() throws Exception {
        // 初始化类加载器
        initClassLoaders();
        // 设置当前线程的ClassLoader为catalinaLoader
        Thread.currentThread().setContextClassLoader(catalinaLoader);
        // 通过反射,生成Catalina实例
        Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
        Object startupInstance = startupClass.getConstructor().newInstance();
        // 通过反射,设置Catalina实例的ClassLoader
        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;
    }
    

    为什么需要初始化类加载器呢?实际上,Tomcat自定义了类加载器,以方便隔离不同Web应用所使用的类。这里先不做进一步说明,等介绍到加载一个Web应用时再详细阐述。

    init()方法调用完后,load()start()方法实际上就是通过反射调用了Catalina的实例方法load()start(),因此,实际的启动动作是在Catalina类中完成的

    3. Catalina类

    Catalina类是Tomcat启动各种组件的主要场所,本文在讲述启动过程时,只关注重要组件的启动过程,而诸如JNDI、JMX或者监听器等则不予关注。

    既然是启动各种组件,那么在看代码之前,有必要对Tomcat的主要组件做些介绍。

    3.1 Tomcat的主要组件

    有关Tomcat组件的介绍目前网络上文章很多,这里不再赘述,挑选两篇作为参考。
    官方参考
    不错的中文介绍

    3.2 组件的生命周期

    Tomcat的各个组件都遵循Tomcat生命周期定义,而作为顶层组件的Server实例管控着大多数情况下的组件生命周期。因此,想要顺畅的浏览Tomcat源码,需要对生命周期的状态流转有一定了解。
    Lifecycle生命周期状态图

    上图是Tomcat生命周期状态转移图,有了对生命周期的认识,我们只需要关注重要组件的initInternal()startInternal()stopInternal()destroyInternal()等方法,就可以快速地分析组件的行为了。

    3.3 Digester工具类

    使用过Tomcat的开发者应该对server.xml文件不陌生,该文件配置了Tomcat重要组件的一些参数,比如常见的监听端口,使用的字符集等。

    在Tomcat启动时,会调用Catalina类的实例方法createStartDigester()来解析server.xml并生成各种组件实例,而完成这一过程使用的是Digester工具类。

    Digester是一个类似于SAX(Simple API for XML Parsing)的基于事件驱动的xml文件解析工具。所谓事件驱动,就是当遇到某个元素节点就会做什么,而不必要将整个xml文件全部读取并构建dom树后再解析。

    如何做到事件驱动呢?Digester使用Rule来实现。Rule就是当遇到某个元素(或者说标签)时所要遵守的规则,如创建对象、设置属性、调用某个方法等。总体而言,Digester在解析xml文件时,每当预先设定的规则匹配到某个xml元素时,就会执行规则定义的行为。

    Rule有四个重要的方法需要子类实现:

    • begin() 在元素匹配时调用;
    • body() 当遇到匹配元素的嵌套内容时调用;
    • end() 当遇到匹配元素的关闭标签时调用;
    • finish() 当parse过程结束时调用,用来清理每个规则的使用的资源。

    更为详细的Digester介绍请参考: Digester官网。Tomcat使用的Digester与组件形式的Digester稍有不同,但是总体上使用方式一致。

    下面举例说明Digester是如何解析server.xml文件的:

    digester.addObjectCreate("Server",
                             "org.apache.catalina.core.StandardServer",
                             "className");
    digester.addSetProperties("Server");
    digester.addSetNext("Server",
                        "setServer",    // 方法名
                        "org.apache.catalina.Server");
    

    digester.addObjectCreate()方法意味着解析xml文档时,如果遇到了<Server> </Server>中的前一个,就会创建一个org.apache.catalina.core.StandardServer类的实例。默认的,Digester内部会维护一个Java对象栈,每当创建对象时,就会将对象压入栈顶。所以,创建的StandardServer对象目前处于栈顶。

    digester.addSetProperties("Server")表示会解析xml标签中遇到的Server元素的属性标签,并调用栈顶元素的对应setter()方法来设置属性。

    digester.addSetNext("Server","setServer","org.apache.catalina.Server")表示遇到Server标签时,会调用栈顶的下一个元素setServer()方法,并将栈顶元素作为参数传入。一般这个方法用途与addChild()类似。在上述例子中,(解析开始之前)会先将Catalina的实例压入栈,而后Server的实例入栈,这样就完成了catalina.setServer()的调用。

    3.4 Catalina类的主要作用

    让我们说回Catalina类。启动过程中,Catalina类中的两个方法依次被调用:load() --> start()

    load()方法中,最主要的动作是createStartDigester() 也就是通过Digester解析server.xml生成各个组件的实例。之后,在load()中会调用getServer().init(),因为Server包含着其他的所有组件,因此,该调用会完成所有组件的初始化操作。

    start()方法调用时,所有组件都已经初始化完毕,因此直接调用getServer().start()来启动所有的组件。

    -------------------------------------
    吾生也有涯,而知也无涯。
  • 相关阅读:
    ASP.NET MVC使用Bootstrap系列(3)——使用Bootstrap 组件
    ASP.NET MVC使用Bootstrap系统(2)——使用Bootstrap CSS和HTML元素
    ASP.NET MVC使用Bootstrap系列(1)——开始使用Bootstrap
    C# 调用颜色的RGB值_RGB颜色转换十六进制颜色
    在C#中,Json的序列化和反序列化的几种方式总结
    Newtonsoft.Json(Json.Net)学习笔记
    C#,WebRequest类、HttpWebRequest类与HttpRequest类的区别
    python遍历目录的方法 walk listdir
    Debug始于71年前
    如何实现前端微服务化
  • 原文地址:https://www.cnblogs.com/SanjiApollo/p/12762767.html
Copyright © 2011-2022 走看看