前言
Tomcat
这个中间件对于 Web 开发者来说,人人皆知。大部分了解又不完全了解,甚至只是了解安装和配置 Catalina Home 环境变量。这个熟悉的陌生中间件,值得再稍微微深入学习一番。
从 Servlet Demo 开始
最原始的 Java Web 开发,追溯到 10 年前,大部分还是在使用 Servlet + JSP 开发,开发完成打成 War 包,扔进 Tomcat webapps 文件夹,启动 Tomcat 即可运行。
一个简单例子如下。
1、web.xml 配置
<web-app>
<display-name>Test Web Application</display-name>
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.lyqiang.learnjava.servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
2、Servlet 类
/**
* @author lyqiang
*/
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
private void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
printRespJsp(request, response);
}
private void printRespJsp(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
response.setContentType("text/html;charset=UTF-8");
request.setAttribute("title", "Hello");
String content = "你好, 下次一定。<br/>你好, 下次一定。<br/>你好, 下次一定。<br/>";
request.setAttribute("content", content);
RequestDispatcher rd = request.getRequestDispatcher("/WEB-INF/jsp/hello.jsp");
rd.forward(request, response);
}
}
3、JSP 页面
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><%=request.getAttribute("title")%>
</title>
</head>
<body>
<h1><%=request.getAttribute("content")%>
</h1>
</body>
</html>
4、运行
访问 locahost:8080/hello
Spring MVC 与 Spring Boot
Spring MVC 框架的出现,在一定程度上简化了 Web 开发,并配合 FreeMarker、Thymeleaf 模版引擎更好地进行页面展现。Spring MVC 使用一个名为 DispatcherServlet
的 Servlet 处理所有的请求,开发者基于 @Controller
、@RequestMapping
注解就可以接受处理请求并返回结果。
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
@RequestMapping(value = "helloAbc", method = RequestMethod.GET)
public Object hello(Model model) {
CommentEntity comment = helloService.getCommentById(1);
String content = "用户:" + comment.getUserName() + "<br/>" + "评论:" + comment.getContent() + "<br/>";
model.addAttribute("title", "Hi~");
model.addAttribute("content", content);
return "helloAbc";
}
Spring MVC 的处理流程如下。
后来 Spring Boot 的出现,更为激进地简化了 Java 后端的开发,对 Web 开发而言,甚至把 Tomcat 都集成内嵌了。
引入 spring-boot-starter-web
这个 starter 包就可以开始编写接口了,十分便捷。
从包的依赖关系上可以看到,它包含了 spring-webmvc
、spring-web
、spring-boot-starter-tomcat
。
请求的流转
当在浏览器输入接口地址,敲击回车时发生了什么,如何流转到后台逻辑?
首先,浏览器需要先构造数据,然后发送 HTTP 请求,通过操作系统建立 TCP 连接,将数据发送出去;
以 linux 为例,操作系统底层通过 tcp_conect()
方法建立 TCP,但浏览器并不能直接使用操作系统底层的建立方式,而是操作系统提供一套 API 供应用程序调用,这个就是 Socket
– 套接字 (这都 tm 什么神翻译
)。
服务端,Tomcat 通过 JDK 提供的 Socket 接口,与操作系统交互,创建 TCP 连接,保持监听,将接收的数据包,进行解析,封装成 Request, 传递给容器 (Engine -> Host -> Context -> Wrapper -> FilterChain -> Servlet),创建对应 Servlet 实例,并调用 service() 方法,执行业务逻辑。
javax.servlet.http.HttpServlet.service()
方法其实是根据不同类型的请求,调用了不同的实现方法。
Tomcat 组件
从上述的大致流程可以概括出,
Tomcat 的作用是作为 Servlet 容器,接收 HTTP 请求,转换成 ServletRequest ,并按照映射关系创建 Servlet 实例,并执行方法。
Tomcat 官网架构介绍:https://tomcat.apache.org/tomcat-8.5-doc/architecture/overview.html
Tomcat 内部分了不同的组件,和 server.xml 中的层级关系是一一对应的。
<Server>
<Service>
<Connector>
</Connector>
<Engine>
<Host>
<Context>
</Context>
</Host>
</Engine>
</Service>
</Server>
Server
Server
元素在最顶层,代表整个 Tomcat 容器,一个 Server
元素中可以有一个或多个 Service
元素。
Service
Service
将 Connector
和 Engine
外面包了一层,把它们组装在一起对外提供服务。一个 Service
可以包含多个 Connector
,但是只能包含一个 Engine
。Connector
的作用是从客户端接收请求,Engine
的作用是处理接收进来的请求。
Connector
Connector
主要的职责就是接收客户端连接并接收消息报文,消息报文经由它解析后送往容器中处理。因为存在不同的通信协议,例如 HTTP 协议、AJP 协议等,所以需要不同的 Connector
组件,每种协议对应一个 Connector
组件,目前 Tomcat 包含 HTTP 和 AJP 两种协议的 Connector
。
Engine
Engine
是 Service
中的请求处理组件,从一个或多个 Connector
中接收请求并处理,并将完成的响应返回给 Connector
,最终传递给客户端。
Host
Host
是 Engine
的子容器, Engine
组件中可以内嵌 1 个或多个 Host
组件,每个 Host
是一个虚拟主机。虚拟主机的作用是运行多个 Web 应用(一个 Context 代表一个 Web 应用),并负责安装、启动和结束每个 Web 应用。Tomcat 从 HTTP 头中提取出主机名,寻找名称匹配的主机。如果没有匹配,请求将发送至默认主机。
Context
Context
是 Host
的子容器,每个 Host
中可以定义任意多的 Context
元素。Context
元素代表在特定虚拟主机上运行的一个 Web 应用,Web 应用对应的是一个 WAR 文件,或 WAR 文件解压后的目录。
Tomcat 内部如何流转
-
当 Tomcat 启动后, Connector
组件的接收器(Acceptor)将会监听是否有客户端套接字连接并接收Socket
。 -
一旦监听到客户端连接,则将连接交由线程池 Executor
处理,开始执行请求响应任务。 -
Http11Processor
组件负责从客户端连接中读取消息报文,然后开始解析 HTTP 的请求行、请求头部、请求体,将解析后的报文封装成 Request 对象。 -
Mapper
组件根据 HTTP 协议请求行的 URL 属性值和请求头部的 Host 属性值匹配由哪个Host
容器、Context
容器、Wrapper
容器处理请求,这个过程其实就是根据请求从 Tomcat 中找到对应的 Servlet,然后将路由的结果封装到 Request 对象中,后续通过 Request 对象选择容器。 -
CoyoteAdaptor
组件负责将Connector
组件和Engine
容器连接起来,把前面处理过程中生成的请求对象 Request 和响应对象 Response 传递到Engine
容器,调用它的管道。 -
Engine
容器的管道开始处理请求,管道里包含若干阀门(Valve),每个阀门负责某些处理逻辑。 -
Host
容器的管道开始处理请求,它同样也包含若干阀门,首先执行这些阀门,然后执行基础阀门HostValve
,它继续往下调用Context
容器的管道。 -
Context
容器的管道开始处理请求,首先执行若干阀门,然后执行基础阀门ContextValve
,它负责调用Wrapper
容器的管道。 -
Wrapper
容器的管道开始处理请求,首先执行若干阀门,然后执行基础阀门WrapperValve
,它会执行该Wrapper
容器对应的 Servlet 对象的处理方法,包括init()
、service()
方法对请求进行逻辑处理,并将结果输出到客户端。
总结
目前最流行的 Web 服务器当属 Tomcat、Jetty、Undertow,它们都对 Servlet 规范进行了支持,为 Java 程序提供 Web 容器,各有优劣。
相比较而言 Tomcat 更重量级,以多级容器为基础构建,设计更复杂,以应对多场景的使用。更深入细节的内容需要阅读 Tomcat 的源码一探究竟了。
关注一下,不迷路。
原文始发于微信公众号(郭儿的跋涉):Tomcat 是个啥
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/35474.html