Servlet入门
前言
Servlet是JavaWeb开发的基础,本系列文章是笔者学习Servlet的笔记或者视频,本系列以Kotlin开发,从环境配置开始一步步入门Servlet。
开发环境
- IntelliJ IDEA 2022.2.4 (Ultimate Edition)
 - JDK 8
 - Gradle 7.4.2
 - Kotlin 1.7.21
 - Tomcat 10.1.13
 
创建项目

打开 IDEA 点击 New Project ,弹出上图的界面,选择 New Project 选项,填写以下内容:
- 项目名称,
 - 项目位置,
 - 选择 Kotlin 语言,
 - 选择 Gradle 构建系统,
 - 选择 JDK 1.8 版本,
 - Gradle DSL 选择 Kotlin,
 - 其他的选项暂时不做修改。
 
最后点击 Create 按钮创建项目,创建完成后如下图:

在上图中,笔者已经编辑了 build.gradle.kts 文件,并添加了 war 插件和 jakarta.servlet-api 依赖:
plugins {
    kotlin("jvm") version "1.7.21"
+   war
}
dependencies {
+   providedCompile("jakarta.servlet:jakarta.servlet-api:6.0.0")
    testImplementation(kotlin("test"))
}
引入 war 插件是因为需要打包成 war 而不是 jar,表示:Java Web Application Archive。
通过 war 插件提供的依赖关系配置 providedCompile 引入Servlet API,表示仅在编译时使用,但不会打包到 .war 文件中,因为在运行期 Web 服务器本身已经提供了 Servlet API相关的 jar 包。
Servlet 版本
要务必注意 servlet-api 的版本。4.0 及之前的 servlet-api 由 Oracle 官方维护,引入的依赖项是 javax.servlet:javax.servlet-api,编写代码时引入的包名为:
import javax.servlet.*;
而 5.0 及以后的 servlet-api 由 Eclipse 开源社区维护,引入的依赖项是 jakarta.servlet:jakarta.servlet-api,编写代码时引入的包名为:
import jakarta.servlet.*;
本系列笔记使用最新的 Servlet 6.0.0 版本,可以在 Maven Central Servlet-API 中查询 Servlet 版本。
Tomcat 版本
Servlet 版本与 Tomcat 版本的对应关系:
| Servlet | Tomcat | Java | 
|---|---|---|
| 4.0 | 9.0.x | >= 8 | 
| 5.0 | 10.0.x | >= 8 | 
| 6.0 | 10.1.x | >= 11 | 
本系列笔记使用最新的 Tomcat 10.1.13 版本,可以在 Apache Tomcat® - Which Version 查询最新的版本。
新建Servlet
在 main -> kotlin 目录下新建一个名为 servlet 的包并在其中新建一个名为 HelloServlet.kt 的 Kotlin 类,并添加以下代码:
package servlet
import jakarta.servlet.http.HttpServlet
class HelloServlet : HttpServlet() {
}
我们不应该直接实现 Servlet 接口,我们应该继承 HttpServlet 抽象类,然后复写其中的 init() 和 doGet() 函数,并导入相关的类,代码如下所示:
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
class HelloServlet : HttpServlet() {
    override fun init() {
    }
    override fun doGet(req: HttpServletRequest, resp: HttpServletResponse) {
    }
}
注意:区分
init()函数与 Kotlin 中的init{}代码块
在 init() 函数中添加以下代码:
println("HelloServlet init")
当 init() 函数调用时输出 HelloServlet init 字符串。
在 doGet() 函数体中添加以下代码:
resp.contentType = "text/html"
with(resp.writer) {
	write("<h1>Hello Servlet!</h1>")
	flush()
}
- 通过 
resp.contentType设置响应类型, - 然后在 
with函数中调用resp.writer对象的write函数写入响应内容, - 最后调用 
flush函数强制输出。 
配置文件
低版本
在低版本的 Servlet 中,需要在 web.xml 配置文件中配置我们写好的 Servlet。在 main 目录下新建一个名为 webapp 的目录,在其中再新建名为 WEB-INF 的目录,在 WEB-INF 目录下新建 web.xml 配置文件,目录结构如下:
main
├── kotlin
│   └── servlet
│       └── HelloServlet.kt
├── resources
└── webapp
    └── WEB-INF
        └── web.xml
然后在 web.xml 配置文件中添加以下内容:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
                      https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
  version="6.0"
  metadata-complete="true">
    <servlet>
        <servlet-name>Hello</servlet-name>
        <servlet-class>servlet.HelloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>Hello</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>
- 在 
<servlet>标签中通过<servlet-name>为HelloServlet起了一个别名,其中<servlet-class>标签中的内容是HelloServlet.kt的全限定名(Full Qualified Name), - 通过 
<servlet-mappig>标签为 Servlet 配置 URL 地址映射,其中的<servlet-name>是 Servlet 的别名,<url-pattern>是对应的 URL 地址映射。 
高版本
在高版本的 Servlet 中可以使用注解的方式配置 Servlet,而不需要再通过 web.xml 配置文件配置了。
注意:如果存在
web.xml配置文件,优先使用web.xml配置文件,这是对低版本的兼容。
接下来修改 HelloServlet.kt 为其增加以下注解:
@WebServlet(name = "Hello", value = arrayOf("/servlet"))
class HelloServlet : HttpServlet() {
    ... // 剩余代码
}
我们为 HelloServlet 类增加了 WebServlet 注解,其中的:
name参数对应web.xml中的<servlet-name>标签,此处为Hello,value或者urlPatterns参数对应web.xml中的<url-pattern>标签,此处为/servlet。
运行Servlet
在项目根目录下运行 Gradle 命令 ./gradlew :clean :war,然后在 /build/libs 目录下得到名为 learn-servlet-1.0-SNAPSHOT.war 文件,这个文件就是我们编译打包后的 Web 应用程序。
我们将在 Tomcat 10.1.13 Web 服务器运行这个 war 包。
首先需要创建一个 Tomcat Server,点击 Edit Configurations 按钮:

然后在弹出的对话框中点击左上角 + 号,下滑选择 Tomcat Server -> Local,如下图:

点击 Local 按钮后,弹出以下界面:

- 上图标记1处,JRE要选择为 Java 11,因为 Tomcat 10.1.x 版本最低支持 Java 11,如果低于 Java 11 服务器将无法启动,
 - 上图标记2处,HTTP port 默认是 8080,因与我本地的 Vuepress 项目端口冲突,所以改为 8081。
 
然后切换到 Deployment 标签页:

点击左上角 + 号按钮,在对话框中选择 Artifact,然后再弹出的对话框中选择第一个工件,如下图:

完成后如下图:

修改 Application context 为:/,最后点击 OK 按钮完成配置,然后启动服务器。
~/Spring/apache-tomcat-10.1.13/bin/catalina.sh run
服务器版本: Apache Tomcat/10.1.13
服务器版本号:      10.1.13.0
操作系统名称:      Mac OS X
OS.版本:           11.5
架构:              x86_64
Java虚拟机版本:    11.0.12+8-LTS-237
JVM.供应商:        Oracle Corporation
........................... 其他启动日志
初始化协议处理器 ["http-nio-8081"]
服务器在[657]毫秒内初始化
正在启动服务[Catalina]
正在启动 Servlet 引擎:[Apache Tomcat/10.1.13]
开始协议处理句柄["http-nio-8081"]
[91]毫秒后服务器启动
Connected to server
........................... 其他日志
HelloServlet init
在浏览器中访问 http://localhost:8081/servlet, 即可看到 HelloServlet 的输出:

我们在
HelloServlet的WebServlet注解中为其配置了url-pattern,所以访问时的路径是/servlet。
总结
本文学习了如何编写 Servlet 来处理 HTTP 请求,同时学习了如何在 Tomcat 服务器中运行 Servlet。
Servlet API 提供了 HttpServet 接口方便我们处理各种 HTTP 请求方法,同时也提供了 HttpServletRequest 和 HttpServletResponse 两个接口来封装 HTTP 的请求和响应。
注意 Servlet 版本的不同,相应的包名也不同。
Tomcat 版本对 Servlet 和 Java 版本敏感,需要我们配置对应的版本才能正常启动。
