# 完整的 Java Servlet 教程
> 原文: [https://howtodoinjava.com/servlets/complete-java-servlets-tutorial/](https://howtodoinjava.com/servlets/complete-java-servlets-tutorial/)
Servlet 是符合 Java Servlet API 的 Java 类,该 Java Servlet API 允许 Java 类响应请求。 尽管 Servlet 可以响应任何类型的请求,但最常见的是将其编写为响应基于 Web 的请求。 必须将 servlet 部署到 Java servlet 容器中才能使用。 尽管许多开发人员都使用诸如 [**Java Server Pages(JSP)**](https://en.wikipedia.org/wiki/JavaServer_Pages "jsp")和 [**Java Server Faces(JSF)**](https://en.wikipedia.org/wiki/JavaServer_Faces "jsf")之类的 servlet 框架,但这两种技术通过 servlet 容器将页面编译为后台的 Java servlet。 也就是说,Java servlet 技术的**基础知识**对于任何 Java Web 开发人员都可能非常有用。
在本教程中,我们将涵盖以下主题,以全面了解 Java Servlet 技术。
```java
Table of Contents
Writing your first Servlet
Servlet Life Cycle Methods
Develop Servlet with @WebServlet Annotation
Packaging and Deploying Servlet into Tomcat Server
Writing dynamic content in Servlet response
Handling Servlet Request and Response
Listening for Servlet Container Events
Passing Servlet Initialization Parameters
Adding Servlet Filters for Specific URL Requests
Downloading a binary file using Servlet
Forward request to another servlet using RequestDispatcher.forward()
Redirect request to another servlet using HttpServletResponse.sendRedirect()
Writing and Reading Cookie using Servlets
```
让我们开始逐步了解 servlet。
## 编写您的第一个 Servlet
我们的第一个 servlet 是非常简单的 servlet,它具有很少的代码,因此您只能专注于重要的事情。
```java
package com.howtodoinjava.servlets;
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 MyFirstServlet extends HttpServlet {
private static final long serialVersionUID = -1915463532411657451L;
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException
{
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
try {
// Write some content
out.println("");
out.println("
");
out.println("MyFirstServlet");
out.println("");
out.println("");
out.println("Servlet MyFirstServlet at " + request.getContextPath() + "
");
out.println("");
out.println("");
} finally {
out.close();
}
}
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
//Do some other work
}
@Override
public String getServletInfo() {
return "MyFirstServlet";
}
}
```
**要在 Web 容器上方注册上述 Servlet,您将为您的应用创建一个入口`web.xml`文件。**
```java
/MyFirstServlet
MyFirstServlet
com.howtodoinjava.servlets.MyFirstServlet
MyFirstServlet
/MyFirstServlet
```
Servlet 上面没有做一些重要的事情,您可能需要学习。
1. `MyFirstServlet`扩展了`HttpServlet`。 这是强制性的,因为所有 servlet 必须是扩展`javax.servlet.GenericServlet`的通用 servlet 或扩展`javax.servlet.http.HttpServlet`的 HTTP servlet。
2. 覆盖`doGet()`和`doPost()`方法。 这些方法在`HttpServlet`类中定义。 每当 GET 或 POST 请求到来时,都会将其映射到其相应的方法,例如,如果您对此 Servlet 发出 HTTP GET 请求,然后调用`doGet()`方法。
3. 还有一些其他有用的方法,您可以覆盖它们以在运行时控制应用,例如`getServletInfo()`。
4. `HttpServletRequest`和`HttpServletResponse`是所有`doXXX()`方法的默认参数。 我们将在后面的部分中了解有关这些对象的更多信息。
这就是您应该知道的简单 servlet 的全部内容。
## Servlet 生命周期方法
无论何时在您的应用中,都会加载并使用一个 servlet。 在该 Servlet 的初始化和销毁过程中发生了一系列事件。 这些被称为 servlet 的**生命周期事件(或方法)**。 让我们详细了解它们。
三种方法对于 Servlet 的生命周期至关重要。 它们是`init()`,`service()`和`destroy()`。 它们由每个 servlet 实现,并在运行时在特定时间调用。
1)在 Servlet 生命周期的初始化阶段, **Web 容器通过调用`init()`方法**并传递实现`javax.servlet.ServletConfig`接口的对象来初始化 Servlet 实例。 此配置对象允许 Servlet 访问 Web 应用的`web.xml`文件中定义的名称 - 值初始化参数。 它**在该 Servlet 实例**的生存期内仅被调用一次。
初始化方法的定义如下所示:
```java
public void init() throws ServletException {
//custom initialization code
}
```
2)初始化后,Servlet 实例可以为客户端请求提供服务。 **Web 容器针对每个请求**调用 servlet 的`service()`方法。 `service()`方法确定发出的请求的类型,并将其分派给适当的方法以处理该请求。 Servlet 的开发人员必须提供这些方法的实现。 如果对不是由 Servlet 实现的方法提出了请求,则将调用父类的方法,通常会导致将错误返回给请求者。
几乎在所有情况下都无需覆盖此方法。
```java
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < (lastModified / 1000 * 1000)) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
```
3)最后, **Web 容器调用`destroy()`方法,该方法使 Servlet 退出服务**。 如果要在 Servlet 超出范围之前关闭或销毁某些文件系统或网络资源,则应调用此方法。 像`init()`一样,`destroy()`方法在 Servlet 的生命周期中仅被调用一次。
```java
public void destroy() {
//
}
```
通常,在大多数情况下,您不需要在 servlet 中覆盖它们中的任何一个。
> **阅读更多**: [Web 服务器如何工作?](//howtodoinjava.com/for-fun-only/a-birds-eye-view-on-how-web-servers-work/ "A birds-eye view on “how web servers work?”")
## 使用`@WebServlet`注解开发 Servlet
如果您不太喜欢 xml 配置,而是特别喜欢注释,那么 Servlets API 也可以。 您可以使用[`@WebServlet`](https://docs.oracle.com/javaee/6/api/javax/servlet/annotation/WebServlet.html "WebServlet")注释,如下例所示,然后您无需在`web.xml`中进行任何输入。 **容器将自动将您的 servlet 注册到运行时,并像往常一样处理它**。
```java
package com.howtodoinjava.servlets;
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(name = "MyFirstServlet", urlPatterns = {"/MyFirstServlet"})
public class MyFirstServlet extends HttpServlet {
private static final long serialVersionUID = -1915463532411657451L;
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException
{
//Do some work
}
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
//Do some other work
}
}
```
## 将 Servlet 打包和部署到 Tomcat 服务器
如果您使用的是任何 IDE(例如 eclipse),则打包和部署应用只是一个步骤。 `Right click on project > Run As > Run As Server`。 如果尚未配置服务器,则准备好进行滚动。
如果您不使用任何 IDE,那么您需要做一些额外的工作,例如从命令提示符下编译应用,使用 ANT 创建 WAR 文件等。但是我非常相信当今的每个人都使用一些 IDE 进行开发,因此我将 不要在本节中浪费更多时间。
当您在 tomcat 中部署我们的第一个 servlet 并在浏览器中访问 URL“`http://localhost:8080/servletexamples/MyFirstServlet`”时,您将获得以下响应。
![servlet example](img/3d2f85d593519a8775a34d6178f0bb30.png)
## 在 Servlet 响应中编写动态内容
Java servlet 之所以如此有用的原因之一是因为它们允许将动态内容显示在网页上。 内容可以取自服务器本身,数据库,另一个网站或许多其他可从 Web 访问的资源。 Servlet 不是静态网页。 他们充满活力,可以说是他们最大的优势。
让我们以一个 servlet 为例,该 servlet 负责向用户显示当前日期和时间,以及其名称和一些自定义消息。 让我们为其编写代码。
```java
package com.howtodoinjava.servlets;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
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(name = "CalendarServlet", urlPatterns = {"/CalendarServlet"})
public class CalendarServlet extends HttpServlet {
private static final long serialVersionUID = -1915463532411657451L;
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException
{
Map data = getData();
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
try {
// Write some content
out.println("");
out.println("");
out.println("CalendarServlet");
out.println("");
out.println("");
out.println("Hello " + data.get("username") + ", " + data.get("message") + "
");
out.println("The time right now is : " + new Date() + "
");
out.println("");
out.println("");
} finally {
out.close();
}
}
//This method will access some external system as database to get user name, and his personalized message
private Map getData()
{
Map data = new HashMap();
data.put("username", "Guest");
data.put("message", "Welcome to my world !!");
return data;
}
}
```
当您在 tomcat 中的 servlet 上方运行并在浏览器中访问 URL“`http://localhost:8080/servletexamples/CalendarServlet`”时,将得到以下响应。
![dynamic content in servlet](img/f1587c9576277355f3796da26d79564a.png)
## 处理 Servlet 请求和响应
Servlet 使创建符合请求和响应生命周期的 Web 应用变得容易。 它们具有提供 HTTP 响应的能力,并且还可以在同一代码体内处理业务逻辑。 处理业务逻辑的能力使 servlet 比标准 HTML 代码强大得多。
在实际的应用中,HTML Web 表单包含发送到 Servlet 的参数。 然后,该 Servlet 以某种方式处理这些参数,并发布客户端可以看到的响应。 对于`HttpServlet`对象,客户端是 Web 浏览器,响应是 Web 页面。 `