Java后端自顶向下方法——Tomcat初步

Java后端自顶向下方法——Tomcat初步

(一)了解Tomcat

讲了这么多,终于讲到我们的Tomcat(不就是汤姆猫?我也不知道为啥要起这个名字)了。首先我们要明白Tomcat到底是什么?Tomcat其实就是就是一个Servlet容器。一个Servlet容器对于发送到每个Servlet的HTTP请求会完成以下事情:
(1)当Servlet 第一次被调用的时候,加载了Servlet类并调用它的init方法(仅调用一次)
(2)响应每次请求的时候,构建ServletRequest和ServletResponse实例。
(3)调用Servlet的service方法,将ServletRequest对象和ServletResponse对象当作参数传入。
(4)当servlet类关闭的时候,调用Servlet的destroy方法,并卸载Servlet类。

因此,Tomcat和Servlet是密不可分的,了解Servlet才能帮助我们更好的了解Tomcat。首先我先把Tomcat的原本的配置文件(注释已经都去掉了)写在这,方便参考:

<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
  <GlobalNamingResources>
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>
  <Service name="Catalina">
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    <Engine name="Catalina" defaultHost="localhost">
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>

      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">

        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />
      </Host>
    </Engine>
  </Service>
</Server>

(二)Tomcat顶层结构

在这里插入图片描述

我们先来看这张图,里面有Server、Service、Connector、Container之类的东西,我们就挑选一些重要的来讲。

1. Server

Server是服务器的意思,代表整个Tomcat服务器,一个Tomcat只有一个Server。Server是Tomcat最顶层的容器,Server中包含至少一个Service组件,用于提供具体服务。Tomcat中其标准实现是:org.apache.catalina.core.StandardServer类。

我们可以思考一下,我们的Tomcat服务器主要要实现哪些功能。首先是接收客户端的请求,然后解析请求,完成相应的业务逻辑,然后把处理后的结果返回给客户端。服务器一般需要提供两个基本方法,一个start打开服务Socket连接,监听服务端口,一个stop停止服务释放网络资源。

但是这种将监听请求可处理请求放在一起的思路会影响扩展性,比如我们不想用HTTP了,我们想换一个网络协议,那我们的Tomcat就需要大量的修改,显然是很不方便的。所以,Tomcat开发者将监听请求和处理请求分开,这就有了我们的Connector模块和Container模块。

2. Connector

Connector是连接器,用于监听请求并将请求封装成ServletRequest和ServletResponse对象,然后交给Container进行处理,Container处理完之后在交给Connector返回给客户端。一个Connector会监听一个独立的端口来处理来自客户端的请求。server.xml中默认配置了一个Connector,端口号为8080。

在这里插入图片描述
其实Connector结构非常复杂,不过我们暂时不需要了解这么多,我们只需要知道他的功能即可,详细的东西我会在Tomcat源码分析中讲。

3. Container

在这里插入图片描述

我们可以看到,Container实际上是一个Engine,一个Engine里面可以有多个Host,一个Host里面有多个Context,每个Context里面又有多个Wrapper,而每个Wrapper里面都有一个Servlet。这就是Container大体的结构。下面我们来看看细节:

首先我们来看Engine,Engine表示整个Servlet引擎,一个Engine下面可以包含一个或者多个Host,即一个Tomcat实例可以配置多个虚拟主机,默认的情况下server.xml配置文件中<Engine name=“Catalina” defaultHost=“localhost”> 定义了一个名为Catalina的Engine。

然后是Host,代表一个站点,也可以叫虚拟主机,一个Host可以配置多个Context,在server.xml文件中的默认配置为<Host name=“localhost” appBase=“webapps” unpackWARs=“true” autoDeploy=“true”>。一个Engine包含多个Host的设计,使得一个服务器实例可以承担多个域名的服务,是很灵活的设计。

紧接着是最重要的Context。Context代表一个应用程序,也就是我们开发的应用,或者一个WEB-INF目录以及下面的web.xml文件,换句话说每一个运行的webapp最终都是以Context的形式存在,每个Context都有一个根路径和请求路径。Context与Host的区别是Context代表一个应用,默认配置下webapps下的每个目录都是一个应用,其中ROOT目录中存放主应用,其他目录存放别的子应用,而整个webapps是一个站点,也就是Host。

最后是Wrapper,在Tomcat中Servlet被称为Wrapper,至于为什么要用Wrapper,这与Tomcat的处理机制有关,现在暂时不用了解。

4. Service

很显然,Tomcat可以有多个Connector,同时Container也可以有多个。那这就存在一个问题,哪个Connector对应哪个Container?相信看过server.xml文件的人已经知道了Tomcat是怎么处理的了。

没错,Service就是这样来的。在server.xml文件中,可以看到Service组件包含了Connector组件和Engine组件(Engine就是一种Container),即Service相当于Connector和Engine组件的包装器,将一个或者多个Connector和一个Engine建立关联关系。

在默认的配置文件中,定义了一个叫Catalina的service,它将HTTP/1.1和AJP/1.3这两个Connector与一个名为Catalina的Engine关联起来。一个Server可以包含多个Service,它们相互独立,一个Service负责维护多个Connector和一个Container。

下面两张图帮我们将Tomcat结构总结一下:

在这里插入图片描述
在这里插入图片描述
我们要记住的就是各个模块的作用,以及所有模块的层次关系,这样在Tomcat出现问题的时候我们才能快速定位到错误的位置和原因。

(三)Tomcat启动流程

你一定会很好奇Tomcat是如何启动的,为什么在idea里面鼠标一点就可以启动我们的服务器?在这段时间中Tomcat又为我们做了什么?想了解这些,我们有必要来看看Tomcat的启动流程。

因为具体讲启动流程会涉及到源码,但初学阶段不建议查阅源码,我们就用图形大概的看看就行。以后我会出Tomcat源码分析来具体讲这个问题。

在这里插入图片描述
由于Tomcat组件之间的包含关系,一个组件中可以包含多个子组件,且这些组件都实现了Lifecycle接口。因此只要启动最上层的server组件,它包含的所有组件也会跟着启动了,例如启动server,所有子service都会跟着启动,service启动了,它的Container和Connector等也会跟着启动。因此,tomat要启动,只需要启动server就可以了。那我们的Server如何启动呢?这就需要Catalina类。

Catalina是整个Tomcat的管理类,他有三个方法load、start、stop分别用来管理整个服务器的生命周期。load方法用于加载Tomcat/conf目录下的server.xml配置文件,用来创建Server并调用Server的init方法进行初始化操作,start用于启动服务器,stop用于停止服务器,start和stop方法在内部分别调用Server的start和stop方法,load方法在内部调用了Server的init方法,这三个方法都会按层次分逐层调用相应的方法。Catalina的启动又依赖Bootstrap。

Bootstrap是main方法所在地,是Tomcat真正的入口类,我们可以简单看一下main方法里面有什么:

public static void main(String args[]) {
    
    
        //首先判断Bootstrap daemon 是否为空,就是创建一个Bootstrap实例daemon
        if (daemon == null) {
    
    
            // Don't set daemon until init() has completed
            Bootstrap bootstrap = new Bootstrap();
            try {
    
    
                bootstrap.init(); //初始化ClassLoader,并用ClassLoader创建了一个Catalina实例,并将这个实例赋值给了cataLinaDaemon
            } catch (Throwable t) {
    
    
                handleThrowable(t);
                t.printStackTrace();
                return;
            }
            daemon = bootstrap;
        } else {
    
    
            Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
        }

        try {
    
    
            String command = "start"; //指定默认的执行指令是start
            if (args.length > 0) {
    
    
                command = args[args.length - 1];
            }

            if (command.equals("startd")) {
    
    
                args[args.length - 1] = "start";
                daemon.load(args); //BootStrap的load方法,其实是调用Calatina的load方法
                daemon.start();
            } else if (command.equals("stopd")) {
    
    
                args[args.length - 1] = "stop";
                daemon.stop();
            } else if (command.equals("start")) {
    
    
                daemon.setAwait(true); //setWait方法 主要是为了设置Server启动完成后是否进入等待状态的标志,如果为true则进入,否则不进入
                daemon.load(args);   //load方法用于加载配置文件,创建并初始化Server
                daemon.start();     //start方法用于启动服务器
            } else if (command.equals("stop")) {
    
    
                daemon.stopServer(args);
            } else if (command.equals("configtest")) {
    
    
                daemon.load(args);
                if (null == daemon.getServer()) {
    
    
                    System.exit(1);
                }
                System.exit(0);
            } else {
    
    
                log.warn("Bootstrap: command \"" + command + "\" does not exist.");
            }
        } catch (Throwable t) {
    
    
            // Unwrap the Exception for clearer error reporting
            if (t instanceof InvocationTargetException &&
                    t.getCause() != null) {
    
    
                t = t.getCause();
            }
            handleThrowable(t);
            t.printStackTrace();
            System.exit(1);
        }
    }

这段代码基本人人都能看得懂,就是创建了一个Bootstrap对象并借助init方法初始化了Catalina对象,接着将这个Bootstrap对象赋值给daemon。然后通过判断main函数的参数来决定调用daemon的哪个方法。很显然,如果main函数的参数为start,那么就会调用daemon的start方法,不用看源码都知道,这个方法肯定帮助我们调用了Catalina的start方法,然后启动整个服务器。具体的代码我会在Tomcat源码分析里面详细说明。

2020年5月27日

猜你喜欢

转载自blog.csdn.net/weixin_43907422/article/details/106379654
今日推荐