Tomcat综述


Web容器

HTTP服务器(比如Apache、Nginx)向浏览器返回静态HTML,浏览器负责解析HTML,将结果呈现给用户。我们希望通过一些交互操作,来获取动态结果,需要一些扩展机制能够让HTTP服务器调用服务端程序。于是Sun公司推出了Servlet技术。可以把Servlet简单理解为运行在服务端的Java小程序,Servlet没有main方法,不能独立运行,所以必须把它部署到Servlet容器中,由容器来实例化并调用Servlet。

Tomcat和Jetty就是一个Servlet容器。为了方便使用,它们也具有HTTP服务器的功能,因此Tomcat或者Jetty就是一个“HTTP服务器 + Servlet容器”,我们也叫它们Web容器。Tomcat和Jetty算是一个轻量级的应用服务器。

Tomcat是Spring Boot默认的嵌入式Servlet容器。最新版本Tomcat和Jetty都支持Servlet 4.0规范。

HTTP相关

HTTP本质

HTTP协议是浏览器与服务器之间的数据传送协议。作为应用层协议,HTTP是基于TCP/IP协议来传递数据的(HTML文件、图片、查询结果等),HTTP协议不涉及数据包(Packet)传输,主要规定了客户端和服务器之间的通信格式。HTTP协议的本质就是一种浏览器与服务器之间约定好的通信格式

HTTP通信机制是在一次完整的HTTP通信过程中,Web浏览器与Web服务器之间将完成的步骤:

  1. 域名解析
  2. 发起TCP的3次握手
  3. Web浏览器向Web服务器发送http请求命令 。例如,GET/sample/hello.jsp HTTP/1.1。
  4. Web浏览器发送http请求头信息 ,浏览器发送其请求命令之后,还要以头信息的形式向Web服务器发送一些别的信息,之后浏览器发送了一空白行来通知服务器,它已经结束了该头信息的发送。
  5. Web服务器应答 ,客户机向服务器发出请求后,服务器会客户机回送应答, HTTP/1.1 200 OK ,应答的第一部分是协议的版本号和应答状态码。
  6. Web服务器发送应答头信息 ,服务器也会随同应答向用户发送关于它自己的数据及被请求的文档。
  7. Web服务器向浏览器发送数据 ,Web服务器向浏览器发送头信息后,它会发送一个空白行来表示头信息的发送到此为结束,接着,就以Content-Type应答头信息所描述的格式发送用户所请求的实际数据。
  8. Web服务器关闭TCP连接 ,一般情况下,一旦Web服务器向浏览器发送了请求数据,它就要关闭TCP连接,然后如果浏览器或者服务器在其头信息加入了这行代码:Connection:keep-alive ,TCP连接在发送后将仍然保持打开状态,于是,浏览器可以继续通过相同的连接发送请求。保持连接节省了为每个请求建立新连接所需的时间,还节约了网络带宽。

HTTP请求响应

HTTP请求数据由三部分组成,分别是请求行、请求报头、请求正文。当这个HTTP请求数据到达Tomcat后,Tomcat会把HTTP请求数据字节流解析成一个Request对象,这个Request对象封装了HTTP所有的请求信息。接着Tomcat把这个Request对象交给Web应用去处理,处理完后得到一个Response对象,Tomcat会把这个Response对象转成HTTP格式的响应数据并发送给浏览器。

4
4

HTTP的响应也是由三部分组成,分别是状态行、响应报头、报文主体

Cookie和Session

Cookie是HTTP报文的一个请求头,Web应用可以将用户的标识信息或者其他一些信息(用户名等)存储在Cookie中。用户经过验证之后,每次HTTP请求报文中都包含Cookie,这样服务器读取这个Cookie请求头就知道用户是谁了。Cookie本质上就是一份存储在用户本地的文件,里面包含了每次请求中都需要传递的信息

Session

Cookie以明文的方式存储在本地,而Cookie中往往带有用户信息,这样就造成了非常大的安全隐患。而Session的出现解决了这个问题,Session可以理解为服务器端开辟的存储空间,里面保存了用户的状态,用户信息以Session的形式存储在服务端。当用户请求到来时,服务端可以把用户的请求和用户的Session对应起来。通过Cookie来将Session和请求对应起来。浏览器在Cookie中填充了一个Session ID之类的字段用来标识请求。

Session创建与存储

Java中,Web应用程序在调用HttpServletRequest的getSession方法时,由Web容器(比如Tomcat)创建的。

Tomcat的Session管理器提供了多种持久化方案来存储Session,通常会采用高性能的存储方式,比如Redis,并且通过集群部署的方式,防止单点故障,从而提升高可用。同时,Session有过期时间,因此Tomcat会开启后台线程定期的轮询,如果Session过期了就将Session失效。

Servlet规范与容器

Servlet接口其实是Servlet容器跟具体业务类之间的接口

5
5

HTTP服务器不直接调用业务类,而是把请求交给容器来处理,容器通过Servlet接口调用业务类。因此Servlet接口和Servlet容器的出现,达到了HTTP服务器与业务类解耦的目的。

Servlet接口和Servlet容器这一整套规范叫作Servlet规范。Tomcat和Jetty都按照Servlet规范的要求实现了Servlet容器,同时它们也具有HTTP服务器的功能。作为Java程序员,如果要实现新的业务功能,只需要实现一个Servlet,并把它注册到Tomcat(Servlet容器)中,剩下的事情就由Tomcat处理了。

Servlet接口

Servlet接口定义了下面五个方法:

6
6

其中最重要是的service方法,具体业务类在这个方法里实现处理逻辑。这个方法有两个参数:ServletRequestServletResponse。ServletRequest用来封装请求信息,ServletResponse用来封装响应信息,因此本质上这两个类是对通信协议的封装。

比如HTTP协议中的请求和响应就是对应了HttpServletRequestHttpServletResponse这两个类。可以通过HttpServletRequest来获取所有请求相关的信息,包括请求路径、Cookie、HTTP头、请求参数等。此外,还可以通过HttpServletRequest来创建和获取Session。而HttpServletResponse是用来封装HTTP响应的。

可以看到接口中还有两个跟生命周期有关的方法initdestroy,这是一个比较贴心的设计,Servlet容器在加载Servlet类的时候会调用init方法,在卸载的时候会调用destroy方法。可能会在init方法里初始化一些资源,并在destroy方法里释放这些资源,比如Spring MVC中的DispatcherServlet,就是在init方法里创建了自己的Spring容器。

ServletConfig的作用就是封装Servlet的初始化参数。可以在web.xml给Servlet配置参数,并在程序里通过getServletConfig方法拿到这些参数。

有接口一般就有抽象类,抽象类用来实现接口和封装通用的逻辑,因此Servlet规范提供了GenericServlet抽象类,可以通过扩展它来实现Servlet。虽然Servlet规范并不在乎通信协议是什么,但是大多数的Servlet都是在HTTP环境中处理的,因此Servet规范还提供了HttpServlet来继承GenericServlet,并且加入了HTTP特性。这样通过继承HttpServlet类来实现自己的Servlet,只需要重写两个方法:doGetdoPost

Servlet容器

工作流程

当客户请求某个资源时,HTTP服务器会用一个ServletRequest对象把客户的请求信息封装起来,然后调用Servlet容器的service方法,Servlet容器拿到请求后,根据请求的URL和Servlet的映射关系,找到相应的Servlet,如果Servlet还没有被加载,就用反射机制创建这个Servlet,并调用Servlet的init方法来完成初始化,接着调用Servlet的service方法来处理请求,把ServletResponse对象返回给HTTP服务器,HTTP服务器会把响应发送给客户端。

7
7

Web应用

一般来说,以Web应用程序的方式来部署Servlet的。根据Servlet规范,Web应用程序有一定的目录结构,在这个目录下分别放置了Servlet的类文件、配置文件以及静态资源,Servlet容器通过读取配置文件,就能找到并加载Servlet。Web应用的目录结构大概是下面这样的:

8
8

Servlet规范里定义了ServletContext这个接口来对应一个Web应用。Web应用部署好后,Servlet容器在启动时会加载Web应用,并为每个Web应用创建唯一的ServletContext对象。

一个Web应用可能有多个Servlet,这些Servlet可以通过全局的ServletContext来共享数据,这些数据包括Web应用的初始化参数、Web应用目录下的文件资源等。由于ServletContext持有所有Servlet实例,还可以通过它来实现Servlet请求的转发。

扩展机制

设计一个规范或者一个中间件,要充分考虑到可扩展性。Servlet规范提供了两种扩展机制:FilterListener

Filter是过滤器,这个接口允许对请求和响应做一些统一的定制化处理,比如可以根据请求的频率来限制访问,或者根据国家地区的不同来修改响应内容。过滤器的工作原理是这样的:Web应用部署完成后,Servlet容器需要实例化Filter并把Filter链接成一个FilterChain。当请求进来时,获取第一个Filter并调用doFilter方法,doFilter方法负责调用这个FilterChain中的下一个Filter。

Listener是监听器,是另一种扩展机制。当Web应用在Servlet容器中运行时,Servlet容器内部会不断的发生各种事件,如Web应用的启动和停止、用户请求到达等。 Servlet容器提供了一些默认的监听器来监听这些事件,当事件发生时,Servlet容器会负责调用监听器的方法。当然,可以定义自己的监听器去监听感兴趣的事件,将监听器配置在web.xml中。比如Spring就实现了自己的监听器,来监听ServletContext的启动事件,目的是当Servlet容器启动时,创建并初始化全局的Spring容器。

Servlet容器与Spring容器

Tomcat&Jetty在启动时给每个Web应用创建一个全局的上下文环境,这个上下文就是ServletContext,其为后面的Spring容器提供宿主环境。

Tomcat&Jetty在启动过程中触发容器初始化事件,Spring的ContextLoaderListener会监听到这个事件,它的contextInitialized方法会被调用,在这个方法中,Spring会初始化全局的Spring根容器,这个就是Spring的IoC容器,IoC容器初始化完毕后,Spring将其存储到ServletContext中,便于以后来获取。

Tomcat&Jetty在启动过程中还会扫描Servlet,一个Web应用中的Servlet可以有多个,以SpringMVC中的DispatcherServlet为例,这个Servlet实际上是一个标准的前端控制器,用以转发、匹配、处理每个Servlet请求。

Servlet一般会延迟加载,当第一个请求达到时,Tomcat&Jetty发现DispatcherServlet还没有被实例化,就调用DispatcherServlet的init方法,DispatcherServlet在初始化的时候会建立自己的容器,叫做SpringMVC 容器,用来持有Spring MVC相关的Bean。同时,Spring MVC还会通过ServletContext拿到Spring根容器,并将Spring根容器设为SpringMVC容器的父容器,请注意,Spring MVC容器可以访问父容器中的Bean,但是父容器不能访问子容器的Bean, 也就是说Spring根容器不能访问SpringMVC容器里的Bean。说的通俗点就是,在Controller里可以访问Service对象,但是在Service里不可以访问Controller对象。

Tomcat目录与webapp项目目录

Tomcat目录结构

/bin:存放Windows或Linux平台上启动和关闭Tomcat的脚本文件。
/conf:存放Tomcat的各种全局配置文件,其中最重要的是server.xml。
/lib:存放Tomcat以及所有Web应用都可以访问的JAR文件。
/logs:存放Tomcat执行时产生的日志文件。
/work:存放JSP编译后产生的Class文件。
/webapps:Tomcat的Web应用目录,默认情况下把Web应用放在这个目录下。

Tomcat安装目录下的logs目录

  • catalina.***.log

主要是记录Tomcat启动过程的信息,在这个文件可以看到启动的JVM参数以及操作系统等日志信息。

  • catalina.out

catalina.out是Tomcat的标准输出(stdout)和标准错误(stderr),这是在Tomcat的启动脚本里指定的,如果没有修改的话stdout和stderr会重定向到这里。

  • localhost.**.log

主要记录Web应用在初始化过程中遇到的未处理的异常,会被Tomcat捕获而输出这个日志文件。

  • localhost_access_log.**.txt

存放访问Tomcat的请求日志,包括IP地址以及请求的路径、时间、请求协议以及状态码等信息。

  • manager.***.log/host-manager.***.log

存放Tomcat自带的manager项目的日志信息。

文件结构