This commit is contained in:
louzefeng
2024-07-11 05:50:32 +00:00
parent bf99793fd0
commit d3828a7aee
6071 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,86 @@
<audio id="audio" title="01 | Web容器学习路径" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/34/d3/34cf9f358085b505f803d4bfdd2c0cd3.mp3"></audio>
你好,我是李号双。在开篇词里我提到要成长为一名高级程序员或者架构师,我们需要提高自己知识的广度和深度。你可以先突破深度,再以点带面拓展广度,因此我建议通过深入学习一些优秀的开源系统来达到突破深度的目的。
我会跟你一起在这个专栏里深入学习Web容器Tomcat和Jetty而作为专栏更新的第1篇文章我想和你谈谈什么是Web容器以及怎么学习Web容器。根据我的经验在学习一门技术之前想一想这两个问题往往可以达到事半功倍的效果。
## Web容器是什么
让我们先来简单回顾一下Web技术的发展历史可以帮助你理解Web容器的由来。
早期的Web应用主要用于浏览新闻等静态页面HTTP服务器比如Apache、Nginx向浏览器返回静态HTML浏览器负责解析HTML将结果呈现给用户。
随着互联网的发展我们已经不满足于仅仅浏览静态页面还希望通过一些交互操作来获取动态结果因此也就需要一些扩展机制能够让HTTP服务器调用服务端程序。
于是Sun公司推出了Servlet技术。你可以把Servlet简单理解为运行在服务端的Java小程序但是Servlet没有main方法不能独立运行因此必须把它部署到Servlet容器中由容器来实例化并调用Servlet。
而Tomcat和Jetty就是一个Servlet容器。为了方便使用它们也具有HTTP服务器的功能因此**Tomcat或者Jetty就是一个“HTTP服务器 + Servlet容器”我们也叫它们Web容器。**
其他应用服务器比如JBoss和WebLogic它们不仅仅有Servlet容器的功能也包含EJB容器是完整的Java EE应用服务器。从这个角度看Tomcat和Jetty算是一个轻量级的应用服务器。
在微服务架构日渐流行的今天开发人员更喜欢稳定的、轻量级的应用服务器并且应用程序用内嵌的方式来运行Servlet容器也逐渐流行起来。之所以选择轻量级是因为在微服务架构下我们把一个大而全的单体应用拆分成一个个功能单一的微服务在这个过程中服务的数量必然要增加但为了减少资源的消耗并且降低部署的成本我们希望运行服务的Web容器也是轻量级的Web容器本身应该消耗较少的内存和CPU资源并且由应用本身来启动一个嵌入式的Web容器而不是通过Web容器来部署和启动应用这样可以降低应用部署的复杂度。
因此轻量级的Tomcat和Jetty就是一个很好的选择并且Tomcat它本身也是Spring Boot默认的嵌入式Servlet容器。最新版本Tomcat和Jetty都支持Servlet 4.0规范。
读到这里我想你应该对Web容器有了基本的认识可以结合平时工作再去细细体会一下。如果你对HTTP协议和Servlet依然是一头雾水不用担心在预习模块中我还会和你聊聊你应该掌握的HTTP协议和Servlet的相关知识帮你打好学习的基础。
## Web容器该怎么学
Java Web技术发展日新月异各种框架也是百花齐放。在从事Java Web开发相关的工作时面对这些眼花缭乱的技术时你是否会感到一丝迷茫可能有些初学者不知道从哪里开始我身边还有些已经进入了这个行业并且有了一定Java基础的人对于系统设计的体会可能还不够深刻编程的时候还停留在完成功能的层次。这样不仅业务上难有突破对于个人成长也很不利。
为了打破这个瓶颈就需要我们在深度上多下功夫找准一个点深挖下去彻底理解它的原理和设计精髓。并且在深入学习Tomcat和Jetty这样的Web容器之前你还需要掌握一定的基础知识这样才能达到事半功倍的效果。
下面我列举一些在学习Web容器之前需要掌握的关键点我建议你在学习专栏的同时再去复习一下这些基础知识。你可以把这些基础知识当作成为架构师的必经之路在专栏以外也要花时间深入进去。当然为了让你更好地理解专栏每期所讲的内容重点的基础知识我也会在文章里帮你再梳理一遍。
**操作系统基础**
Java语言其实是对操作系统API的封装上层应用包括Web容器都是通过操作系统来工作的因此掌握相关的操作系统原理是我们深刻理解Web容器的基础。
对于Web容器来说操作系统方面你应该掌握它的工作原理比如什么是进程、什么是内核、什么是内核空间和用户空间、进程间通信的方式、进程和线程的区别、线程同步的方式、什么是虚拟内存、内存分配的过程、什么是I/O、什么是I/O模型、阻塞与非阻塞的区别、同步与异步的区别、网络通信的原理、OSI七层网络模型以及TCP/IP、UDP和HTTP协议。
总之一句话基础扎实了你学什么都快。关于操作系统的学习我推荐你读一读《UNIX环境高级编程》这本经典书籍。
**Java语言基础**
Java的基础知识包括Java基本语法、面向对象设计的概念封装、继承、多态、接口、抽象类等、Java集合的使用、Java I/O体系、异常处理、基本的多线程并发编程包括线程同步、原子类、线程池、并发容器的使用和原理、Java网络编程I/O模型BIO、NIO、AIO的原理和相应的Java API、Java注解以及Java反射的原理等。
此外你还需要了解一些JVM的基本知识比如JVM的类加载机制、JVM内存模型、JVM内存空间分布、JVM内存和本地内存的区别以及JVM GC的原理等。
这方面我推荐的经典书籍有[《Java核心技术》](time://mall?url=http%3A%2F%2Fh5.youzan.com%2Fv2%2Fgoods%2F2fnx3ed6fpk3c)、[《Java编程思想》](time://mall?url=http%3A%2F%2Fh5.youzan.com%2Fv2%2Fgoods%2F3f0ddticdedfc)、[《Java并发编程实战》](time://mall?url=http%3A%2F%2Fh5.youzan.com%2Fv2%2Fgoods%2F2758xqdzr6uuw)和[《深入理解Java虚拟机JVM高级特性与最佳实践》](time://mall?url=http%3A%2F%2Fh5.youzan.com%2Fv2%2Fgoods%2F36a92yq65q4x4)等。
**Java Web开发基础**
具备了一定的操作系统和Java基础接下来就可以开始学习Java Web开发你可以开始学习一些通用的设计原则和设计模式。这个阶段的核心任务就是了解Web的工作原理**同时提高你的设计能力**注重代码的质量。我的建议是可以从学习Servlet和Servlet容器开始。我见过不少同学跳过这个阶段直接学Web框架这样做的话结果会事倍功半。
为什么这么说呢Web框架的本质是开发者在使用某种语言编写Web应用时总结出的一些经验和设计思路。很多Web框架都是从实际的Web项目抽取出来的其目的是用于简化Web应用程序开发。
我以Spring框架为例给你讲讲Web框架是怎么产生的。Web应用程序的开发主要是完成两方面的工作。
<li>
设计并实现类,包括定义类与类之间的关系,以及实现类的方法,方法对数据的操作就是具体的业务逻辑。
</li>
<li>
类设计好之后,需要创建这些类的实例并根据类与类的关系把它们组装在一起,这样类的实例才能一起协作完成业务功能。
</li>
就好比制造一辆汽车汽车是由零件组装而成的。第一步是画出各种零件的图纸以及定义零件之间的接口。第二步把把图纸交给工厂去生产零件并组装在一起。因此对于Web应用开发来说第一步工作是具体业务逻辑的实现每个应用都不一样。而第二步工作相对来说比较通用和标准化工厂拿到零件的图纸就知道怎么生产零件并按照零件之间的接口把它们组装起来因此这个工作就被抽取出来交给Spring框架来做。
Spring又是用容器来完成这个工作的的容器负责创建、组装和销毁这些类的实例而应用只需要通过配置文件或者注解来告诉Spring类与类之间的关系。但是容器的概念不是Spring发明的最开始来源于Servlet容器并且Servlet容器也是通过配置文件来加载Servlet的。你会发现它们的“元神”是相似的在Web应用的开发中有一些本质的东西是不变的而很多“元神”就藏在“老祖宗”那里藏在Servlet容器的设计里。
Spring框架就是对Servlet的封装Spring应用本身就是一个Servlet而Servlet容器是管理和运行Servlet的因此我们需要先理解Servlet和Servlet容器是怎样工作的才能更好地理解Spring。
## 本期精华
今天我谈了什么是Web容器以及该如何学习Web容器。在深入学习之前你需要掌握一些操作系统、Java和Web的基础知识。我希望你在学习专栏的过程中多温习一下这些基础知识有扎实的基础再结合专栏深入学习Web容器就比较容易了。
等你深刻理解了Web容器的工作原理和设计精髓以后你就可以把学到的知识扩展到其他领域你会发现它们的本质都是相通的这个时候你可以站在更高的角度来学习和审视各种Web框架。虽然Web框架的更新比较快但是抓住了框架的本质在学习的过程中往往会更得心应手。
不知道你有没有遇到过这样的场景,当你在看一个框架的技术细节时,会突然恍然大悟:对啊,就是应该这么设计!如果你有这种感觉,说明你的知识储备起到了作用,你对框架的运用也会更加自如。
## 课后思考
请你分享一下你对Web容器的理解或者你在学习、使用Web容器时遇到了哪些问题
不知道今天的内容你消化得如何?如果还有疑问,请大胆的在留言区提问,也欢迎你把你的课后思考和心得记录下来,与我和其他同学一起讨论。如果你觉得今天有所收获,欢迎你把它分享给你的朋友。

View File

@@ -0,0 +1,110 @@
<audio id="audio" title="02 | HTTP协议必知必会" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d8/93/d8f55a07283b9d1553f64efaa4a5c793.mp3"></audio>
在开始学习Web容器之前我想先问你一个问题HTTP和HTML有什么区别
为什么我会问这个问题你可以把它当作一个入门测试检测一下自己的对HTTP协议的理解。因为Tomcat和Jetty本身就是一个“HTTP服务器 + Servlet容器”如果你想深入理解Tomcat和Jetty的工作原理我认为理解HTTP协议的工作原理是学习的基础。
如果你对这个问题还稍有迟疑那么请跟我一起来回顾一下HTTP协议吧。
## HTTP的本质
HTTP协议是浏览器与服务器之间的数据传送协议。作为应用层协议HTTP是基于TCP/IP协议来传递数据的HTML文件、图片、查询结果等HTTP协议不涉及数据包Packet传输主要规定了客户端和服务器之间的通信格式。
下面我通过一个例子来告诉你HTTP的本质是什么。
假如浏览器需要从远程HTTP服务器获取一个HTML文本在这个过程中浏览器实际上要做两件事情。
<li>
与服务器建立Socket连接。
</li>
<li>
生成**请求数据**并通过Socket发送出去。
</li>
第一步比较容易理解,浏览器从地址栏获取用户输入的网址和端口,去连接远端的服务器,这样就能通信了。
我们重点来看第二步,这个请求数据到底长什么样呢?都请求些什么内容呢?或者换句话说,浏览器需要告诉服务端什么信息呢?
首先最基本的是你要让服务端知道你的意图你是想获取内容还是提交内容其次你需要告诉服务端你想要哪个内容。那么要把这些信息以一种什么样的格式放到请求里去呢这就是HTTP协议要解决的问题。也就是说**HTTP协议的本质就是一种浏览器与服务器之间约定好的通信格式**。那浏览器与服务器之间具体是怎么工作的呢?
## HTTP工作原理
请你来看下面这张图我们过一遍一次HTTP的请求过程。
<img src="https://static001.geekbang.org/resource/image/f5/ca/f5bd0c7840160d5a121c191e7e54b4ca.jpg" alt="">
从图上你可以看到,这个过程是:
1.用户通过浏览器进行了一个操作,比如输入网址并回车,或者是点击链接,接着浏览器获取了这个事件。
2.浏览器向服务端发出TCP连接请求。
3.服务程序接受浏览器的连接请求并经过TCP三次握手建立连接。
4.浏览器将请求数据打包成一个HTTP协议格式的数据包。
5.浏览器将该数据包推入网络,数据包经过网络传输,最终达到端服务程序。
6.服务端程序拿到这个数据包后同样以HTTP协议格式解包获取到客户端的意图。
7.得知客户端意图后进行处理,比如提供静态文件或者调用服务端程序获得动态结果。
8.服务器将响应结果可能是HTML或者图片等按照HTTP协议格式打包。
9.服务器将响应数据包推入网络,数据包经过网络传输最终达到到浏览器。
10.浏览器拿到数据包后以HTTP协议的格式解包然后解析数据假设这里的数据是HTML。
11.浏览器将HTML文件展示在页面上。
那我们想要探究的Tomcat和Jetty作为一个HTTP服务器在这个过程中都做了些什么事情呢主要是接受连接、解析请求数据、处理请求和发送响应这几个步骤。这里请你注意可能有成千上万的浏览器同时请求同一个HTTP服务器因此Tomcat和Jetty为了提高服务的能力和并发度往往会将自己要做的几个事情并行化具体来说就是使用多线程的技术。这也是专栏所关注的一个重点我在后面会进行专门讲解。
## HTTP请求响应实例
你有没有注意到在浏览器和HTTP服务器之间通信的过程中首先要将数据打包成HTTP协议的格式那HTTP协议的数据包具体长什么样呢这里我以极客时间的登陆请求为例用户在登陆页面输入用户名和密码点击登陆后浏览器发出了这样的HTTP请求
<img src="https://static001.geekbang.org/resource/image/f5/14/f58bf57649ec9eb35eb24e0679bb2514.png" alt="">
你可以看到HTTP请求数据由三部分组成分别是**请求行、请求报头、请求正文**。当这个HTTP请求数据到达Tomcat后Tomcat会把HTTP请求数据字节流解析成一个Request对象这个Request对象封装了HTTP所有的请求信息。接着Tomcat把这个Request对象交给Web应用去处理处理完后得到一个Response对象Tomcat会把这个Response对象转成HTTP格式的响应数据并发送给浏览器。
我们再来看看HTTP响应的格式HTTP的响应也是由三部分组成分别是**状态行、响应报头、报文主体**。同样,我还以极客时间登陆请求的响应为例。
<img src="https://static001.geekbang.org/resource/image/84/b7/84f4fe4c411dfb9fd83a1d53cf2915b7.png" alt="">
具体的HTTP协议格式你可以去网上搜索我就不再赘述了。为了更好地帮助你理解HTTP服务器比如Tomcat的工作原理接下来我想谈一谈Cookie跟Session的原理。
## Cookie和Session
我们知道HTTP协议有个特点是无状态请求与请求之间是没有关系的。这样会出现一个很尴尬的问题Web应用不知道你是谁。比如你登陆淘宝后在购物车中添加了三件商品刷新一下网页这时系统提示你仍然处于未登录的状态购物车也空了很显然这种情况是不可接受的。因此HTTP协议需要一种技术让请求与请求之间建立起联系并且服务器需要知道这个请求来自哪个用户于是Cookie技术出现了。
**1. Cookie技术**
Cookie是HTTP报文的一个请求头Web应用可以将用户的标识信息或者其他一些信息用户名等存储在Cookie中。用户经过验证之后每次HTTP请求报文中都包含Cookie这样服务器读取这个Cookie请求头就知道用户是谁了。**Cookie本质上就是一份存储在用户本地的文件里面包含了每次请求中都需要传递的信息**。
**2. Session技术**
由于Cookie以明文的方式存储在本地而Cookie中往往带有用户信息这样就造成了非常大的安全隐患。而Session的出现解决了这个问题**Session可以理解为服务器端开辟的存储空间里面保存了用户的状态**用户信息以Session的形式存储在服务端。当用户请求到来时服务端可以把用户的请求和用户的Session对应起来。那么Session是怎么和请求对应起来的呢答案是通过Cookie浏览器在Cookie中填充了一个Session ID之类的字段用来标识请求。
具体工作过程是这样的服务器在创建Session的同时会为该Session生成唯一的Session ID当浏览器再次发送请求的时候会将这个Session ID带上服务器接受到请求之后就会依据Session ID找到相应的Session找到Session后就可以在Session中获取或者添加内容了。而这些内容只会保存在服务器中发到客户端的只有Session ID这样相对安全也节省了网络流量因为不需要在Cookie中存储大量用户信息。
**3. Session创建与存储**
那么Session在何时何地创建呢当然还是在服务器端程序运行的过程中创建的不同语言实现的应用程序有不同的创建Session的方法。在Java中是Web应用程序在调用HttpServletRequest的getSession方法时由Web容器比如Tomcat创建的。那HttpServletRequest又是什么呢别着急我们下一期再聊。
Tomcat的Session管理器提供了多种持久化方案来存储Session通常会采用高性能的存储方式比如Redis并且通过集群部署的方式防止单点故障从而提升高可用。同时Session有过期时间因此Tomcat会开启后台线程定期的轮询如果Session过期了就将Session失效。
## 本期精华
HTTP协议和其他应用层协议一样本质上是一种通信格式。回到文章开头我问你的问题其实答案很简单HTTP是通信的方式HTML才是通信的目的就好比HTTP是信封信封里面的信HTML才是内容但是没有信封信也没办法寄出去。HTTP协议就是浏览器与服务器之间的沟通语言具体交互过程是请求、处理和响应。
由于HTTP是无状态的协议为了识别请求是哪个用户发过来的出现了Cookie和Session技术。Cookie本质上就是一份存储在用户本地的文件里面包含了每次请求中都需要传递的信息Session可以理解为服务器端开辟的存储空间里面保存的信息用于保持状态。作为Web容器Tomcat负责创建和管理Session并提供了多种持久化方案来存储Session。
## 课后思考
在HTTP/1.0时期每次HTTP请求都会创建一个新的TCP连接请求完成后之后这个TCP连接就会被关闭。这种通信模式的效率不高所以在HTTP/1.1中引入了HTTP长连接的概念使用长连接的HTTP协议会在响应头加入Connection:keep-alive。这样当浏览器完成一次请求后浏览器和服务器之间的TCP连接不会关闭再次访问这个服务器上的网页时浏览器会继续使用这一条已经建立的连接也就是说两个请求可能共用一个TCP连接。
今天留给你的思考题是我在上面提到HTTP的特点是无状态的多个请求之间是没有关系的这是不是矛盾了
不知道今天的内容你消化得如何?如果还有疑问,请大胆的在留言区提问,也欢迎你把你的课后思考和心得记录下来,与我和其他同学一起讨论。如果你觉得今天有所收获,欢迎你把它分享给你的朋友。

View File

@@ -0,0 +1,104 @@
<audio id="audio" title="03 | 你应该知道的Servlet规范和Servlet容器" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/b5/aa/b57e22e43a21499972eb20ad00177caa.mp3"></audio>
通过专栏上一期的学习我们知道浏览器发给服务端的是一个HTTP格式的请求HTTP服务器收到这个请求后需要调用服务端程序来处理所谓的服务端程序就是你写的Java类一般来说不同的请求需要由不同的Java类来处理。
那么问题来了HTTP服务器怎么知道要调用哪个Java类的哪个方法呢。最直接的做法是在HTTP服务器代码里写一大堆if else逻辑判断如果是A请求就调X类的M1方法如果是B请求就调Y类的M2方法。但这样做明显有问题因为HTTP服务器的代码跟业务逻辑耦合在一起了如果新加一个业务方法还要改HTTP服务器的代码。
那该怎么解决这个问题呢我们知道面向接口编程是解决耦合问题的法宝于是有一伙人就定义了一个接口各种业务类都必须实现这个接口这个接口就叫Servlet接口有时我们也把实现了Servlet接口的业务类叫作Servlet。
但是这里还有一个问题对于特定的请求HTTP服务器如何知道由哪个Servlet来处理呢Servlet又是由谁来实例化呢显然HTTP服务器不适合做这个工作否则又和业务类耦合了。
于是还是那伙人又发明了Servlet容器Servlet容器用来加载和管理业务类。HTTP服务器不直接跟业务类打交道而是把请求交给Servlet容器去处理Servlet容器会将请求转发到具体的Servlet如果这个Servlet还没创建就加载并实例化这个Servlet然后调用这个Servlet的接口方法。因此Servlet接口其实是**Servlet容器跟具体业务类之间的接口**。下面我们通过一张图来加深理解。
<img src="https://static001.geekbang.org/resource/image/df/01/dfe304d3336f29d833b97f2cfe8d7801.jpg" alt="">
图的左边表示HTTP服务器直接调用具体业务类它们是紧耦合的。再看图的右边HTTP服务器不直接调用业务类而是把请求交给容器来处理容器通过Servlet接口调用业务类。因此Servlet接口和Servlet容器的出现达到了HTTP服务器与业务类解耦的目的。
而Servlet接口和Servlet容器这一整套规范叫作Servlet规范。Tomcat和Jetty都按照Servlet规范的要求实现了Servlet容器同时它们也具有HTTP服务器的功能。作为Java程序员如果我们要实现新的业务功能只需要实现一个Servlet并把它注册到TomcatServlet容器剩下的事情就由Tomcat帮我们处理了。
接下来我们来看看Servlet接口具体是怎么定义的以及Servlet规范又有哪些要重点关注的地方呢
## Servlet接口
Servlet接口定义了下面五个方法
```
public interface Servlet {
void init(ServletConfig config) throws ServletException;
ServletConfig getServletConfig();
void service(ServletRequest req, ServletResponse resthrows ServletException, IOException;
String getServletInfo();
void destroy();
}
```
其中最重要是的service方法具体业务类在这个方法里实现处理逻辑。这个方法有两个参数ServletRequest和ServletResponse。ServletRequest用来封装请求信息ServletResponse用来封装响应信息因此**本质上这两个类是对通信协议的封装。**
比如HTTP协议中的请求和响应就是对应了HttpServletRequest和HttpServletResponse这两个类。你可以通过HttpServletRequest来获取所有请求相关的信息包括请求路径、Cookie、HTTP头、请求参数等。此外我在专栏上一期提到过我们还可以通过HttpServletRequest来创建和获取Session。而HttpServletResponse是用来封装HTTP响应的。
你可以看到接口中还有两个跟生命周期有关的方法init和destroy这是一个比较贴心的设计Servlet容器在加载Servlet类的时候会调用init方法在卸载的时候会调用destroy方法。我们可能会在init方法里初始化一些资源并在destroy方法里释放这些资源比如Spring MVC中的DispatcherServlet就是在init方法里创建了自己的Spring容器。
你还会注意到ServletConfig这个类ServletConfig的作用就是封装Servlet的初始化参数。你可以在`web.xml`给Servlet配置参数并在程序里通过getServletConfig方法拿到这些参数。
我们知道有接口一般就有抽象类抽象类用来实现接口和封装通用的逻辑因此Servlet规范提供了GenericServlet抽象类我们可以通过扩展它来实现Servlet。虽然Servlet规范并不在乎通信协议是什么但是大多数的Servlet都是在HTTP环境中处理的因此Servet规范还提供了HttpServlet来继承GenericServlet并且加入了HTTP特性。这样我们通过继承HttpServlet类来实现自己的Servlet只需要重写两个方法doGet和doPost。
## Servlet容器
我在前面提到为了解耦HTTP服务器不直接调用Servlet而是把请求交给Servlet容器来处理那Servlet容器又是怎么工作的呢接下来我会介绍Servlet容器大体的工作流程一起来聊聊我们非常关心的两个话题**Web应用的目录格式是什么样的以及我该怎样扩展和定制化Servlet容器的功能**。
**工作流程**
当客户请求某个资源时HTTP服务器会用一个ServletRequest对象把客户的请求信息封装起来然后调用Servlet容器的service方法Servlet容器拿到请求后根据请求的URL和Servlet的映射关系找到相应的Servlet如果Servlet还没有被加载就用反射机制创建这个Servlet并调用Servlet的init方法来完成初始化接着调用Servlet的service方法来处理请求把ServletResponse对象返回给HTTP服务器HTTP服务器会把响应发送给客户端。同样我通过一张图来帮助你理解。
<img src="https://static001.geekbang.org/resource/image/b7/15/b70723c89b4ed0bccaf073c84e08e115.jpg" alt="">
**Web应用**
Servlet容器会实例化和调用Servlet那Servlet是怎么注册到Servlet容器中的呢一般来说我们是以Web应用程序的方式来部署Servlet的而根据Servlet规范Web应用程序有一定的目录结构在这个目录下分别放置了Servlet的类文件、配置文件以及静态资源Servlet容器通过读取配置文件就能找到并加载Servlet。Web应用的目录结构大概是下面这样的
```
| - MyWebApp
| - WEB-INF/web.xml -- 配置文件用来配置Servlet等
| - WEB-INF/lib/ -- 存放Web应用所需各种JAR包
| - WEB-INF/classes/ -- 存放你的应用类比如Servlet类
| - META-INF/ -- 目录存放工程的一些信息
```
Servlet规范里定义了**ServletContext**这个接口来对应一个Web应用。Web应用部署好后Servlet容器在启动时会加载Web应用并为每个Web应用创建唯一的ServletContext对象。你可以把ServletContext看成是一个全局对象一个Web应用可能有多个Servlet这些Servlet可以通过全局的ServletContext来共享数据这些数据包括Web应用的初始化参数、Web应用目录下的文件资源等。由于ServletContext持有所有Servlet实例你还可以通过它来实现Servlet请求的转发。
**扩展机制**
不知道你有没有发现引入了Servlet规范后你不需要关心Socket网络通信、不需要关心HTTP协议也不需要关心你的业务类是如何被实例化和调用的因为这些都被Servlet规范标准化了你只要关心怎么实现的你的业务逻辑。这对于程序员来说是件好事但也有不方便的一面。所谓规范就是说大家都要遵守就会千篇一律但是如果这个规范不能满足你的业务的个性化需求就有问题了因此设计一个规范或者一个中间件要充分考虑到可扩展性。Servlet规范提供了两种扩展机制**Filter**和**Listener**。
**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容器的工作原理有了深入的了解只有理解了这些原理我们才能更好的理解Tomcat和Jetty因为它们都是Servlet容器的具体实现。后面我还会详细谈到Tomcat和Jetty是如何设计和实现Servlet容器的虽然它们的实现方法各有特点但是都遵守了Servlet规范因此你的Web应用可以在这两个Servlet容器中方便的切换。
## 本期精华
今天我们学习了什么是Servlet回顾一下Servlet本质上是一个接口实现了Servlet接口的业务类也叫Servlet。Servlet接口其实是Servlet容器跟具体Servlet业务类之间的接口。Servlet接口跟Servlet容器这一整套规范叫作Servlet规范而Servlet规范使得程序员可以专注业务逻辑的开发同时Servlet规范也给开发者提供了扩展的机制Filter和Listener。
最后我给你总结一下Filter和Listener的本质区别
<li>
**Filter是干预过程的**,它是过程的一部分,是基于过程行为的。
</li>
<li>
**Listener是基于状态的**,任何行为改变同一个状态,触发的事件是一致的。
</li>
## 课后思考
Servlet容器与Spring容器有什么关系
不知道今天的内容你消化得如何?如果还有疑问,请大胆的在留言区提问,也欢迎你把你的课后思考和心得记录下来,与我和其他同学一起讨论。如果你觉得今天有所收获,欢迎你把它分享给你的朋友。

View File

@@ -0,0 +1,250 @@
<audio id="audio" title="04 | 实战纯手工打造和运行一个Servlet" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/bf/2e/bf8e5c8f4c1c003b36f013012072d32e.mp3"></audio>
作为Java程序员我们可能已经习惯了使用IDE和Web框架进行开发IDE帮我们做了编译、打包的工作而Spring框架在背后帮我们实现了Servlet接口并把Servlet注册到了Web容器这样我们可能很少有机会接触到一些底层本质的东西比如怎么开发一个Servlet如何编译Servlet如何在Web容器中跑起来
今天我们就抛弃IDE、拒绝框架自己纯手工编写一个Servlet并在Tomcat中运行起来。一方面进一步加深对Servlet的理解另一方面还可以熟悉一下Tomcat的基本功能使用。
主要的步骤有:
1.下载并安装Tomcat。<br>
2.编写一个继承HttpServlet的Java类。<br>
3.将Java类文件编译成Class文件。<br>
4.建立Web应用的目录结构并配置`web.xml`<br>
5.部署Web应用。<br>
6.启动Tomcat。<br>
7.浏览器访问验证结果。<br>
8.查看Tomcat日志。
下面你可以跟我一起一步步操作来完成整个过程。Servlet 3.0规范支持用注解的方式来部署Servlet不需要在`web.xml`里配置最后我会演示怎么用注解的方式来部署Servlet。
**1. 下载并安装Tomcat**
最新版本的Tomcat可以直接在[官网](https://tomcat.apache.org/download-90.cgi)上下载根据你的操作系统下载相应的版本这里我使用的是Mac系统下载完成后直接解压解压后的目录结构如下。
<img src="https://static001.geekbang.org/resource/image/0f/d6/0f9c064d26fec3e620f494caabbab8d6.png" alt="">
下面简单介绍一下这些目录:
/bin存放Windows或Linux平台上启动和关闭Tomcat的脚本文件。<br>
/conf存放Tomcat的各种全局配置文件其中最重要的是`server.xml`<br>
/lib存放Tomcat以及所有Web应用都可以访问的JAR文件。<br>
/logs存放Tomcat执行时产生的日志文件。<br>
/work存放JSP编译后产生的Class文件。<br>
/webappsTomcat的Web应用目录默认情况下把Web应用放在这个目录下。
**2. 编写一个继承HttpServlet的Java类**
我在专栏上一期提到,`javax.servlet`包提供了实现Servlet接口的GenericServlet抽象类。这是一个比较方便的类可以通过扩展它来创建Servlet。但是大多数的Servlet都在HTTP环境中处理请求因此Servlet规范还提供了HttpServlet来扩展GenericServlet并且加入了HTTP特性。我们通过继承HttpServlet类来实现自己的Servlet只需要重写两个方法doGet和doPost。
因此今天我们创建一个Java类去继承HttpServlet类并重写doGet和doPost方法。首先新建一个名为`MyServlet.java`的文件,敲入下面这些代码:
```
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println(&quot;MyServlet 在处理get请求...&quot;);
PrintWriter out = response.getWriter();
response.setContentType(&quot;text/html;charset=utf-8&quot;);
out.println(&quot;&lt;strong&gt;My Servlet!&lt;/strong&gt;&lt;br&gt;&quot;);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println(&quot;MyServlet 在处理post请求...&quot;);
PrintWriter out = response.getWriter();
response.setContentType(&quot;text/html;charset=utf-8&quot;);
out.println(&quot;&lt;strong&gt;My Servlet!&lt;/strong&gt;&lt;br&gt;&quot;);
}
}
```
这个Servlet完成的功能很简单分别在doGet和doPost方法体里返回一段简单的HTML。
**3. 将Java文件编译成Class文件**
下一步我们需要把`MyServlet.java`文件编译成Class文件。你需要先安装JDK这里我使用的是JDK 10。接着你需要把Tomcat lib目录下的`servlet-api.jar`拷贝到当前目录下,这是因为`servlet-api.jar`中定义了Servlet接口而我们的Servlet类实现了Servlet接口因此编译Servlet类需要这个JAR包。接着我们执行编译命令
```
javac -cp ./servlet-api.jar MyServlet.java
```
编译成功后,你会在当前目录下找到一个叫`MyServlet.class`的文件。
**4. 建立Web应用的目录结构**
我们在上一期学到Servlet是放到Web应用部署到Tomcat的而Web应用具有一定的目录结构所有我们按照要求建立Web应用文件夹名字叫MyWebApp然后在这个目录下建立子文件夹像下面这样
```
MyWebApp/WEB-INF/web.xml
MyWebApp/WEB-INF/classes/MyServlet.class
```
然后在`web.xml`中配置Servlet内容如下
```
&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;web-app xmlns=&quot;http://xmlns.jcp.org/xml/ns/javaee&quot;
xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
xsi:schemaLocation=&quot;http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd&quot;
version=&quot;4.0&quot;
metadata-complete=&quot;true&quot;&gt;
&lt;description&gt; Servlet Example. &lt;/description&gt;
&lt;display-name&gt; MyServlet Example &lt;/display-name&gt;
&lt;request-character-encoding&gt;UTF-8&lt;/request-character-encoding&gt;
&lt;servlet&gt;
&lt;servlet-name&gt;myServlet&lt;/servlet-name&gt;
&lt;servlet-class&gt;MyServlet&lt;/servlet-class&gt;
&lt;/servlet&gt;
&lt;servlet-mapping&gt;
&lt;servlet-name&gt;myServlet&lt;/servlet-name&gt;
&lt;url-pattern&gt;/myservlet&lt;/url-pattern&gt;
&lt;/servlet-mapping&gt;
&lt;/web-app&gt;
```
你可以看到在`web.xml`配置了Servlet的名字和具体的类以及这个Servlet对应的URL路径。请你注意**servlet和servlet-mapping这两个标签里的servlet-name要保持一致。**
**5. 部署Web应用**
Tomcat应用的部署非常简单将这个目录MyWebApp拷贝到Tomcat的安装目录下的webapps目录即可。
**6. 启动Tomcat**
找到Tomcat安装目录下的bin目录根据操作系统的不同执行相应的启动脚本。如果是Windows系统执行`startup.bat`.如果是Linux系统则执行`startup.sh`
**7. 浏览访问验证结果**
在浏览器里访问这个URL`http://localhost:8080/MyWebApp/myservlet`,你会看到:
```
My Servlet!
```
这里需要注意访问URL路径中的MyWebApp是Web应用的名字`myservlet`是在`web.xml`里配置的Servlet的路径。
**8. 查看Tomcat日志**
打开Tomcat的日志目录也就是Tomcat安装目录下的logs目录。Tomcat的日志信息分为两类 :一是运行日志,它主要记录运行过程中的一些信息,尤其是一些异常错误日志信息 二是访问日志它记录访问的时间、IP地址、访问的路径等相关信息。
这里简要介绍各个文件的含义。
- `catalina.***.log`
主要是记录Tomcat启动过程的信息在这个文件可以看到启动的JVM参数以及操作系统等日志信息。
- `catalina.out`
`catalina.out`是Tomcat的标准输出stdout和标准错误stderr这是在Tomcat的启动脚本里指定的如果没有修改的话stdout和stderr会重定向到这里。所以在这个文件里可以看到我们在`MyServlet.java`程序里打印出来的信息:
>
MyServlet在处理get请求…
- `localhost.**.log`
主要记录Web应用在初始化过程中遇到的未处理的异常会被Tomcat捕获而输出这个日志文件。
- `localhost_access_log.**.txt`
存放访问Tomcat的请求日志包括IP地址以及请求的路径、时间、请求协议以及状态码等信息。
- `manager.***.log/host-manager.***.log`
存放Tomcat自带的Manager项目的日志信息。
**用注解的方式部署Servlet**
为了演示用注解的方式来部署Servlet我们首先修改Java代码给Servlet类加上**@WebServlet**注解,修改后的代码如下。
```
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(&quot;/myAnnotationServlet&quot;)
public class AnnotationServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println(&quot;AnnotationServlet 在处理get请求...&quot;);
PrintWriter out = response.getWriter();
response.setContentType(&quot;text/html; charset=utf-8&quot;);
out.println(&quot;&lt;strong&gt;Annotation Servlet!&lt;/strong&gt;&lt;br&gt;&quot;);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println(&quot;AnnotationServlet 在处理post请求...&quot;);
PrintWriter out = response.getWriter();
response.setContentType(&quot;text/html; charset=utf-8&quot;);
out.println(&quot;&lt;strong&gt;Annotation Servlet!&lt;/strong&gt;&lt;br&gt;&quot;);
}
}
```
这段代码里最关键的就是这个注解它表明两层意思第一层意思是AnnotationServlet这个Java类是一个Servlet第二层意思是这个Servlet对应的URL路径是myAnnotationServlet。
```
@WebServlet(&quot;/myAnnotationServlet&quot;)
```
创建好Java类以后同样经过编译并放到MyWebApp的class目录下。这里要注意的是你**需要删除原来的web.xml**,因为我们不需要`web.xml`来配置Servlet了。然后重启Tomcat接下来我们验证一下这个新的AnnotationServlet有没有部署成功。在浏览器里输入`http://localhost:8080/MyWebApp/myAnnotationServlet`,得到结果:
```
Annotation Servlet!
```
这说明我们的AnnotationServlet部署成功了。可以通过注解完成`web.xml`所有的配置功能包括Servlet初始化参数以及配置Filter和Listener等。
## 本期精华
通过今天的学习和实践相信你掌握了如何通过扩展HttpServlet来实现自己的Servlet知道了如何编译Servlet、如何通过`web.xml`来部署Servlet同时还练习了如何启动Tomcat、如何查看Tomcat的各种日志并且还掌握了如何通过注解的方式来部署Servlet。我相信通过专栏前面文章的学习加上今天的练习实践一定会加深你对Servlet工作原理的理解。之所以我设置今天的实战练习是希望你知道IDE和Web框架在背后为我们做了哪些事情这对于我们排查问题非常重要因为只有我们明白了IDE和框架在背后做的事情一旦出现问题的时候我们才能判断它们做得对不对否则可能开发环境里的一个小问题就会折腾我们半天。
## 课后思考
我在Servlet类里同时实现了doGet方法和doPost方法从浏览器的网址访问默认访问的是doGet方法今天的课后思考题是如何访问这个doPost方法。
不知道今天的内容你消化得如何?如果还有疑问,请大胆的在留言区提问,也欢迎你把你的课后思考和心得记录下来,与我和其他同学一起讨论。如果你觉得今天有所收获,欢迎你把它分享给你的朋友。