HttpServlet抽象类
对于Web开发来说前后端交互使用的协议主要是HTTP协议,而HTTP协议中有很多请求方法,比如:GET、POST、DELETE等,虽然GenericServlet抽象类在Servlet接口的基础上进一步简化了开发过程,但是在面对多请求的HTTP协议来说,开发者仍然需要考虑在不同请求方法的情况下如何处理业务逻辑,这仍然可能会存在代码冗余

对此,Servlet规范中定义了HttpServlet抽象类,HttpServlet抽象类继承自GenericServlet抽象类,并且对于不同的请求方式定义了不同的方法
由于HttpServlet抽象类源码复杂且庞大,所以下面会将关键源码分片展示
HttpServlet抽象类中重写了GenericServlet中的service()方法,并且在方法中将ServletRequest和ServletResponse强制转型为HTTP专属的HttpServletRequest和HttpServletResponse(处理HTTP请求),为下面调用重载的service(HttpServletRequest, HttpServletResponse)方法做准备
接着调用重载的service(HttpServletRequest, HttpServletResponse)方法,进入HTTP协议处理流程
上面是HttpServlet自定义的service()重载方法,是整个类的核心分发器,采用模板方法设计模式:
-
通过request.getMethod()获取前端的HTTP请求方式(如 GET、POST)
-
根据不同的请求方式,自动调用对应的处理方法(如doGet()、doPost())
在Web开发中常用的是GET请求方式和POST请求方式
对比维度 GET 请求 POST 请求 数据传递方式 数据拼在URL地址中 数据放在请求体中 数据大小限制 受浏览器/服务器限制 理论无限制 缓存特性 会被浏览器缓存(URL相同则返回缓存结果) 不会被缓存 浏览器历史记录 请求会保留在历史记录 请求不会保留在历史记录 相对安全性 数据暴露在URL中,不安全(防君子不防小人) 数据在请求体中,相对安全(仍需加密敏感数据) 重复请求影响 重复请求结果一致,无副作用 重复请求可能有副作用,如重复提交订单
- 如果是「查数据、不修改、非敏感」→ 用 GET✅
(比如搜索、分页、加载页面,核心是「读」)
- 如果是「改数据、传敏感 / 大量数据、有副作用」→ 用 POST☑️
(比如注册、登录、上传、支付,核心是「写」)
在开发Servlet的时候只需要继承HttpServlet后,无需重写通用的service()方法,而是根据业务需求重写对应的doXXX()方法即可
package com.mohan.javaWeb.web.action;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public class ServletTest extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("这是处理GET请求的业务逻辑");
}
@Override
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("这是处理POST请求的业务逻辑");
}
}
启动服务器并在浏览器输入相应URL可在IDEA中观察到结果输出
这是因为浏览器访问URL默认请求方式是GET
若后端在 Servlet 中仅实现了doPost()方法(未实现doGet()),但前端发送的是GET请求,或者后端仅实现了doGet()方法(未实现doPost()),但前端发送的是POST请求,会发生什么?
package com.mohan.javaWeb.web.action;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public class ServletTest extends HttpServlet {
@Override
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("这是处理POST请求的业务逻辑");
}
}
启动服务器并在浏览器观察结果(浏览器默认请求方式是GET)
这两种情况下都会触发HttpServlet父类中对应未实现方法的默认逻辑 —— 返回405错误(Method Not Allowed,请求方法不被允许),浏览器上会显示 “当前请求方式不支持” 的报错信息
为啥呢?
这是因为浏览器默认是GET请求,而后端仅实现POST方法,这时会调用HttpServlet抽象类中的GET方法
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String msg =
lStrings.getString("http.method_get_not_supported");
sendMethodNotAllowed(req, resp, msg);
}
其中sendMethodNotAllowed(req, resp, msg);这条语句会导致前端报405错误,POST请求同理
只要HttpServlet中的doGet方法或doPost方法执行了,必然导致405错误
- ✅前端用GET方式请求 → 后端必须重写doGet()
- ✅前端用POST方式请求 → 后端必须重写doPost()
为什么不建议重写HttpServlet抽象类中的service()方法?
虽然技术上可以重写service()方法,但不推荐,原因是:
- 失去405错误的提示功能:405错误是HTTP协议的标准错误,用于明确告知请求方式不支持,重写service()后无法触发该机制
- 重复实现分发逻辑:HttpServlet的service()已经做好了根据请求方式分发到对应doXxx()的工作,重写会导致代码冗余
- 失去HTTP专属特性:HttpServlet的设计初衷就是封装HTTP细节,直接重写service()会绕过这些封装,降低开发效率
模板方法设计模式
在父类中定义一个算法的骨架(即核心步骤和执行顺序),而将算法中某些具体步骤的实现延迟到子类中,这样既能保证算法的整体结构不变,又能让子类根据需求灵活定制部分步骤,实现复用骨架、定制细节的效果
比如:HttpServlet的service()方法中固定了Tomcat对于HTTP请求的核心流程(先进行参数类型转换->获取请求方法->执行相应doXXX()方法),而对于具体的doXXX的逻辑方法实现由子类实现(父类中也可以把需要子类实现的方法定义为抽象方法,子类必须实现,而HttpServlet抽象类中则是使用报错来强制子类实现相应的doXXX方法)
ServletRequest接口
HttpServlet抽象类的实现类在实现doGet()方法以及doPost()方法的时候会传递两个参数分别是HttpServletRequest接口类型的req以及HttpServletResponse接口类型的resp
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("这是处理GET请求的业务逻辑");
}
@Override
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("这是处理POST请求的业务逻辑");
}
下面就了解一下这两个接口的继承关系及其方法
ServlvetRequest接口及ServletResponse接口这两部分主要掌握常用方法,能够对接收的客户端请求以及向客户端发送的响应进行操作即可
HttpServletRequest接口是ServletRequest接口的子接口,专门用于处理HTTP协议的请求,ServletRequest是通用接口,定义了所有请求(如HTTP、FTP等)的公用方法,而HttpServletReques接口则增加了HTTP协议特有的方法(如获取请求方式、请求路径等)
-
ServletRequest接口的公用方法
获取请求参数
方法声明 功能描述 String getParameter(String name) 根据参数名获取参数值 String[] getParameterValues(String name) 根据参数名获取多值参数 Enumeration<String> getParameterNames() 获取所有参数名枚举集合 Map<String, String[]> getParameterMap() 获取所有参数键值对映射 获取请求属性
方法声明 功能描述 Object getAttribute(String name) 根据属性名获取属性值 void setAttribute(String name, Object value) 设置请求属性 void removeAttribute(String name) 移除指定属性 Enumeration<String> getAttributeNames() 获取所有属性名枚举集合 获取客户端与请求的基本信息
方法声明 功能描述 String getRemoteAddr() 获取客户端IP地址 int getRemotePort() 获取客户端端口号 String getCharacterEncoding() 获取请求数据的字符编码 void setCharacterEncoding(String env) 设置请求编码(解决中文乱码) ServletInputStream getInputStream() 获取请求体字节输入流(适用非表单数据,如JSON) BufferedReader getReader() 获取请求体字符输入流(适用于文本数据) 获取服务器相关信息
方法声明 功能描述 String getServerName() 获取服务器主机名 int getServerPort() 获取服务器端口号 ServletContext getServletContext() 获取Web应用ServletContext对象 -
HttpServletRequest接口方法
方法声明 功能描述 String getMethod() 获取HTTP请求方式 String getRequestURI() 获取请求路径(如 /project/login) String getQueryString() 获取URL中的查询字符串 String getHeader(String name) 获取指定HTTP请求头的值 HttpSession getSession() 获取当前请求的会话HttpSession对象 String getContextPath() 获取当前 Web 应用的上下文路径
ServletResponse接口
HttpServletResponse接口是Servlet规范中专门处理HTTP响应的核心接口,它继承自通用的ServletResponse接口(ServletResponse接口中包含处理响应的通用方法),HttpServletResponse接口针对HTTP协议特性扩展了专属能力(HttpServletResponse接口中包含处理HTTP响应的专属方法),通过HttpServletResponse接口,Servlet可完成向客户端发送响应数据、配置响应头、控制响应状态码等关键操作,是Web开发中构建HTTP响应的核心工具
-
ServletResponse接口通用方法
获取输出流
方法声明 功能描述 ServletOutputStream getOutputStream() 获取字节输出流,用于发送二进制数据(如图片、文件) PrintWriter getWriter() 获取字符输出流,用于发送文本数据(如 HTML、JSON) 编码设置
方法声明 功能描述 void setCharacterEncoding(String charset) 设置字符输出流的编码(仅影响getWriter(),需在获取流前调用) String getCharacterEncoding() 获取当前字符编码 内容类型
方法声明 功能描述 void setContentType(String type) 设置响应数据的 MIME 类型(如text/html、image/jpeg),可附加编码 缓冲区操作
方法声明 功能描述 void setBufferSize(int size) 设置响应缓冲区大小(字节) int getBufferSize() 获取当前缓冲区大小 void flushBuffer() 强制刷新缓冲区,将数据发送到客户端 void resetBuffer() 清空缓冲区内容(未提交时可用) boolean isCommitted() 判断响应是否已提交(数据是否已发送到客户端) 其他
方法声明 功能描述 void reset() 清空缓冲区并重置响应头/状态码(未提交时可用) void setLocale(Locale loc) 设置响应的本地化信息(影响日期、数字等格式) Locale getLocale() 获取当前本地化信息 -
HttpServletResponse接口专属方法
状态码设置
方法声明 功能描述 void setStatus(int sc) 设置 HTTP 响应状态码(如 200、404) void sendError(int sc) 发送错误状态码(如 404),并触发默认错误页面 void sendError(int sc, String msg) 发送错误状态码并附加错误信息 响应头操作
方法声明 功能描述 void setHeader(String name, String value) 设置响应头(覆盖原有值) void addHeader(String name, String value) 添加响应头(不覆盖,允许重复) void setIntHeader(String name, int value) 设置整数类型响应头 void setDateHeader(String name, long date) 设置日期类型响应头(毫秒时间戳) 重定向
方法声明 功能描述 void sendRedirect(String location) 发送重定向响应(自动设置 302 状态码和 Location 头) Cookie 操作
方法声明 功能描述 void addCookie(Cookie cookie) 向客户端发送 Cookie HTTP 特性
方法声明 功能描述 boolean containsHeader(String name) 判断是否包含指定响应头 String encodeURL(String url) 对URL进行编码(自动附加Session ID,解决URL重写问题) String encodeRedirectURL(String url) 对重定向URL进行编码(功能同上,用于sendRedirect)
HTTP协议的响应状态码
状态码分类 | 含义 | 常用状态码 |
---|---|---|
2xx(成功) | 请求处理成功 | 200成功 204成功无响应体 |
3xx(重定向) | 需要客户端进一步操作 | 302临时重定向 304资源未修改(使用缓存) |
4xx(客户端错误) | 客户端请求有误 | 400请求参数错误 404资源不存在 405请求方式不支持 |
5xx(服务器错误) | 服务器处理出错 | 500服务器内部错误 503服务暂时不可用 |
通常以4开头的错误都是前端的问题,以5开头的错误都是后端的问题

练习:简易用户登录系统
目标:综合运用所有接口和类,实现登录功能,理解表单的前后端交互逻辑
参考步骤:
- 创建登录页面login.html(用户名、密码),提交到LoginServlet
- LoginServlet验证用户(假设正确的用户名为root,密码为123456)
- 登录成功在页面显示“登录成功”,否则显示“登录错误”
参考代码:
- web.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<welcome-file-list>
<welcome-file>login.html</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>login</servlet-name>
<servlet-class>com.mohan.javaWeb.web.action.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>login</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
</web-app>
- login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple Login</title>
<!-- 仅保留核心Tailwind和Font Awesome(图标简化) -->
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<style>
/* 去掉自定义工具类,用Tailwind原生类替代,减少代码 */
body {
background-color: #f9fafb; /* Tailwind浅灰背景,避免额外配置 */
}
</style>
</head>
<body class="min-h-screen flex items-center justify-center p-4">
<!-- 简化容器:仅保留核心居中、卡片样式 -->
<div class="w-full max-w-sm bg-white rounded-lg shadow-md p-6">
<!-- 标题简化:去掉多余间距,图标更简洁 -->
<h2 class="text-2xl font-bold text-center text-gray-800 mb-6">
<i class="fa fa-lock text-indigo-500 mr-2"></i>Login
</h2>
<!-- 表单简化:去掉id选择器(非必须),减少属性 -->
<form method="post" action="/test/login" class="space-y-4">
<!-- 用户名输入框 -->
<div>
<label class="block text-gray-700 text-sm font-medium mb-1">Username</label>
<input type="text" name="username"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
placeholder="Enter username" required>
</div>
<!-- 密码输入框 -->
<div>
<label class="block text-gray-700 text-sm font-medium mb-1">Password</label>
<input type="password" name="password"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
placeholder="Enter password" required>
</div>
<!-- 提交按钮:简化过渡效果,保留核心交互 -->
<button type="submit"
class="w-full bg-indigo-500 hover:bg-indigo-600 text-white font-medium py-2 px-4 rounded-md transition">
<i class="fa fa-sign-in mr-1"></i>Submit
</button>
</form>
</div>
</body>
</html>
- LoginServlet类
package com.mohan.javaWeb.web.action;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
String username = "root";
String password = "123456";
PrintWriter out = response.getWriter();
if (username.equals(request.getParameter("username"))&&password.equals(request.getParameter("password"))){
out.write("<h1>登录成功</h1>");
}else {
out.write("<h1>登录错误</h1>");
}
}
}
- 输出结果
部署项目后启动Tomcat服务器,访问浏览器URL:localhost:8080/test/login
输入Username:root和Password:123456后,页面显示登录成功字样
- 易错+注意点
需要注意三个地方
前后端表单中的name要一致
form表单的action路径要正确
乱码问题
当正确输入用户名和密码成功登录后或者登录错误后,浏览器页面中可能显示的中文出现乱码问题,比如这样:
这个时候需要为响应的内容设置编码,在服务端的Servlet中添加代码response.setContentType("text/html;charset=utf-8");
即如下所示:protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=utf-8"); String username = "root"; String password = "123456"; PrintWriter out = response.getWriter(); if (username.equals(request.getParameter("username")) &&password.equals(request.getParameter("password"))){ out.write("<h1>登录成功</h1>"); }else { out.write("<h1>登录错误</h1>"); } }
重新启动服务器再次登录问题就如愿解决!!!
如果服务端得到的客户端请求中含有中文,出现乱码问题仍然调用这个方法解决,不过是request对象调用request.setContentType("text/html;charset=utf-8");
如有错误请指出,不喜勿喷