- Servlet 请求生命周期:一个请求从进入到离开的完整过程。
- 核心 API:
HttpServletRequest:这是获取请求信息的核心对象。 - 处理请求参数:如何获取表单数据、URL 参数等。
- 处理请求头:如何获取客户端发送的头部信息。
- 处理请求体:如何获取 POST 请求中的原始数据(如 JSON)。
- 请求属性:在请求范围内共享数据。
- 一个完整的示例:一个包含 GET 和 POST 请求处理的完整 Servlet。
- 现代替代方案:简述 Servlet 的演进。
Servlet 请求生命周期
当一个客户端(如浏览器)向服务器发送请求时,Servlet 容器(如 Tomcat)会执行以下步骤:
- 接收请求:容器监听特定端口,接收到针对某个 Servlet 的请求。
- 创建
Request和Response对象:容器创建一个HttpServletRequest对象(代表请求)和一个HttpServletResponse对象(代表响应)。 - 调用
service()方法:容器根据请求的 HTTP 方法(GET, POST, PUT, DELETE 等),调用 Servlet 实例的service()方法。 - 分发请求:
service()方法内部会根据 HTTP 方法,将请求分发给对应的doGet(),doPost(),doPut(),doDelete()等方法。我们通常只需要重写这些doXxx()方法即可。 - 处理业务逻辑:在
doGet()或doPost()方法中,我们通过HttpServletRequest获取请求信息,处理业务逻辑,然后通过HttpServletResponse生成响应。 - 发送响应:业务逻辑处理完毕后,容器将
HttpServletResponse中的内容发送回客户端。 - 销毁:当 Servlet 不再需要时(例如服务器关闭),容器会调用其
destroy()方法进行资源清理。
核心 API:HttpServletRequest
HttpServletRequest 接口是 Servlet API 的核心,它封装了所有的 HTTP 请求信息,在 doGet() 或 doPost() 方法中,它作为第一个参数传入。
常用方法按功能分类如下:
| 功能类别 | 常用方法 | 描述 |
|---|---|---|
| 请求行信息 | String getMethod() |
获取 HTTP 方法 (GET, POST 等) |
String getRequestURI() |
获取请求的 URI (e.g., /myapp/login) |
|
String getQueryString() |
获取 URL 中的查询字符串 (e.g., username=jack&age=20) |
|
String getProtocol() |
获取协议版本 (e.g., HTTP/1.1) |
|
String getRemoteAddr() |
获取客户端 IP 地址 | |
| 请求头信息 | String getHeader(String name) |
获取指定名称的请求头 |
Enumeration<String> getHeaderNames() |
获取所有请求头的名称 | |
int getIntHeader(String name) |
获取指定名称的请求头(int 类型) | |
| 请求参数 | String getParameter(String name) |
获取指定名称的参数值(单个) |
String[] getParameterValues(String name) |
获取指定名称的参数值(数组,用于复选框) | |
Enumeration<String> getParameterNames() |
获取所有参数的名称 | |
Map<String, String[]> getParameterMap() |
获取所有参数的键值对映射 | |
| 请求体/属性 | BufferedReader getReader() |
获取请求体的字符流(用于读取文本数据如 JSON) |
ServletInputStream getInputStream() |
获取请求体的字节流(用于读取文件等二进制数据) | |
void setAttribute(String name, Object o) |
设置请求属性 | |
Object getAttribute(String name) |
获取请求属性 | |
Enumeration<String> getAttributeNames() |
获取所有属性名称 | |
| 其他 | RequestDispatcher getRequestDispatcher(String path) |
获取请求转发器 |
ServletContext getServletContext() |
获取 Servlet 上下文 |
处理请求参数
这是最常见的场景,通常来自 HTML 表单。
HTML 表单示例 (form.html)
<!DOCTYPE html>
<html>
<head>Servlet Form</title>
</head>
<body>
<!-- GET 请求示例 -->
<h2>GET 请求</h2>
<form action="getParameter" method="GET">
姓名: <input type="text" name="username"><br>
年龄: <input type="number" name="age"><br>
<input type="submit" value="GET 提交">
</form>
<hr>
<!-- POST 请求示例 -->
<h2>POST 请求</h2>
<form action="getParameter" method="POST">
爱好:
<input type="checkbox" name="hobby" value="reading"> 阅读
<input type="checkbox" name="hobby" value="sports"> 运动
<input type="checkbox" name="hobby" value="music"> 音乐<br>
<input type="submit" value="POST 提交">
</form>
</body>
</html>
Servlet 处理示例 (ParameterServlet.java)
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
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("/getParameter") // 使用注解映射 URL
public class ParameterServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
// 处理 GET 请求
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 设置响应内容类型和字符编码
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
// 2. 获取单个参数
String username = request.getParameter("username");
String ageStr = request.getParameter("age");
out.println("<h1>GET 请求参数</h1>");
out.println("<p>用户名: " + (username != null ? username : "未提供") + "</p>");
out.println("<p>年龄: " + (ageStr != null ? ageStr : "未提供") + "</p>");
}
// 处理 POST 请求
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 设置响应内容类型和字符编码
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
// 2. 获取数组参数(用于复选框等)
String[] hobbies = request.getParameterValues("hobby");
out.println("<h1>POST 请求参数</h1>");
if (hobbies != null && hobbies.length > 0) {
out.println("<p>你的爱好是: " + Arrays.toString(hobbies) + "</p>");
} else {
out.println("<p>你没有选择任何爱好。</p>");
}
}
}
注意:
getParameter()返回的是String类型,需要自己进行类型转换(如Integer.parseInt(ageStr))。- 对于复选框,多个值会使用相同的
name,因此需要用getParameterValues()来获取一个字符串数组。 - 处理中文乱码:在获取参数之前,如果请求是
POST且可能包含中文,最好先设置请求编码。request.setCharacterEncoding("UTF-8");(需要 Tomcat 8+ 版本,否则需要手动处理)。
处理请求头
获取客户端发送的头部信息,User-Agent, Accept-Language 等。
// 在 doGet 或 doPost 方法中
String userAgent = request.getHeader("User-Agent");
String acceptLanguage = request.getHeader("Accept-Language");
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<p>浏览器信息: " + userAgent + "</p>");
out.println("<p>接受的语言: " + acceptLanguage + "</p>");
处理请求体(如 JSON 数据)
当使用 AJAX 发送 POST 请求(Content-Type 为 application/json)时,数据不在参数中,而在请求体里,此时需要读取请求体的原始数据。
假设前端发送了一个 JSON 字符串:{"name":"John", "city":"New York"}
Servlet 处理示例
import java.io.BufferedReader;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.google.gson.Gson; // 需要引入 Google Gson 库
@WebServlet("/json")
public class JsonRequestServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 设置响应类型
response.setContentType("application/json;charset=UTF-8");
// 2. 从请求体中读取数据
StringBuilder sb = new StringBuilder();
String line;
try (BufferedReader reader = request.getReader()) {
while ((line = reader.readLine()) != null) {
sb.append(line);
}
}
String jsonPayload = sb.toString();
// 3. 使用 JSON 库解析数据 (这里用 Gson 作为示例)
Gson gson = new Gson();
// 假设我们有一个 Person 类
Person person = gson.fromJson(jsonPayload, Person.class);
// 4. 处理数据并返回响应
response.getWriter().write("{\"status\":\"success\", \"message\":\"Received data for " + person.getName() + "\"}");
}
}
// 简单的 POJO 类
class Person {
private String name;
private String city;
// Getters and Setters (省略)
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getCity() { return city; }
public void setCity(String city) { this.city = city; }
}
请求属性
请求属性用于在同一个请求的多个组件之间共享数据,最典型的场景是请求转发。
// 在 Servlet1 中
request.setAttribute("user", "Alice"); // 设置属性
RequestDispatcher dispatcher = request.getRequestDispatcher("/servlet2"); // 转发到另一个 Servlet
dispatcher.forward(request, response); // 执行转发
// 在 Servlet2 中 (由 Servlet1 转发而来)
String user = (String) request.getAttribute("user"); // 获取属性
// user 的值是 "Alice"
请求转发 (forward) vs 重定向 (redirect):
- 转发:服务器端行为,URL 不会改变,共享同一个
request和response对象,速度快。 - 重定向:客户端行为,服务器返回一个 302 状态码和新的 URL,浏览器会重新向新 URL 发送请求,URL 会改变,
request对象是新的。
一个完整的示例
这是一个整合了 GET/POST 参数、请求头和请求属性的完整示例。
HelloServlet.java
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("/hello")
public class HelloServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 获取请求参数
String name = request.getParameter("name");
if (name == null || name.isEmpty()) {
name = "Guest"; // 默认值
}
// 2. 获取请求头
String userAgent = request.getHeader("User-Agent");
// 3. 设置请求属性,用于在 JSP 中使用
request.setAttribute("welcomeMessage", "Hello, " + name + "!");
request.setAttribute("browserInfo", userAgent);
// 4. 转发到 JSP 页面来显示结果
request.getRequestDispatcher("/welcome.jsp").forward(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// POST 请求处理完后会重定向到另一个页面,避免刷新重复提交
String feedback = request.getParameter("feedback");
// 简单的业务逻辑处理
System.out.println("Received feedback: " + feedback);
// 重定向到感谢页面
response.sendRedirect("thankyou.html");
}
}
welcome.jsp (位于 webapp 目录下)
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>Welcome Page</title>
</head>
<body>
<h1>${welcomeMessage}</h1>
<p>你的浏览器是: ${browserInfo}</p>
<p>这是通过请求转发过来的页面。</p>
</body>
</html>
现代替代方案
虽然 Servlet 是基石,但现代 Java Web 开发框架(如 Spring MVC)极大地简化了开发,它们底层依然基于 Servlet,但提供了更高级的抽象。
- Spring MVC: 使用
@Controller注解标记控制器类,用@RequestMapping或@GetMapping/@PostMapping来映射 URL 和方法,方法参数可以直接绑定到 POJO,自动完成类型转换和解析 JSON/XML,无需手动调用request.getParameter()。
Spring MVC 示例(对比上面的 Servlet):
import org.springframework.web.bind.annotation.*;
@RestController // @Controller + @ResponseBody 的组合
@RequestMapping("/api") // 类级别的路径映射
public class MySpringController {
// 对应 Servlet 的 doGet
@GetMapping("/hello") // 方法级别的路径映射,只处理 GET 请求
public String sayHello(@RequestParam(defaultValue = "Guest") String name,
@RequestHeader("User-Agent") String userAgent) {
// Spring 自动将 URL 参数 name 绑定到方法参数 name
// Spring 自动将请求头 User-Agent 绑定到方法参数 userAgent
return "Hello, " + name + "! Your browser: " + userAgent;
}
// 对应 Servlet 的 doPost
@PostMapping("/feedback")
public String processFeedback(@RequestBody Feedback feedback) {
// Spring 自动将请求体中的 JSON/XML 绑定到 Feedback 对象
System.out.println("Received feedback: " + feedback.getContent());
return "Feedback received successfully!";
}
}
class Feedback {
private String content;
// getters and setters...
}
可以看到,Spring MVC 通过约定优于配置和强大的自动绑定机制,让代码更加简洁、易读。
- Servlet 是 Java Web 的基石,理解其请求处理机制至关重要。
HttpServletRequest是获取所有请求信息的核心接口。- 根据数据来源(URL参数、请求头、请求体)选择合适的方法(
getParameter,getHeader,getReader)。 - 请求转发和重定向是控制流程的两个重要手段。
- 现代框架(如 Spring)在 Servlet 之上提供了更优雅的开发体验,但底层原理相通。
