Servlet 技术详解:JavaWeb 的核心控制器
一、Servlet 是什么?
定义与本质
Servlet 是运行在 Web 服务器或应用服务器上的 Java 程序,用于扩展服务器的功能,处理客户端(通常是浏览器)的请求并生成响应。其本质是一个Java 接口,定义了处理请求的标准方法。
关键特性
- 平台无关性:基于 Java,可在任何支持 JVM 的服务器上运行
- 持久性:Servlet 是单例的,加载后常驻内存,处理多个请求
- 高效性:避免了 CGI 的进程创建开销,性能显著提升
- 可伸缩性:可利用 Java 的多线程机制高效处理并发请求
- 安全性:运行在服务器沙箱环境中,提供安全保障
二、Servlet 在 Web 架构中的位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| 客户端 (浏览器)
↓
HTTP 请求
↓
Web 服务器 (如 Apache/Nginx)
↓
Servlet 容器 (如 Tomcat)
├───→ Servlet (处理业务逻辑)
├───→ JSP (生成视图)
└───→ 其他资源
↓
HTTP 响应
↓
客户端 (浏览器)
|
- Servlet 容器(如 Tomcat 的 Catalina):负责管理 Servlet 生命周期、处理请求分发
- 角色定位:在 MVC 架构中,Servlet 通常充当 Controller 角色
三、Servlet 工作原理
请求处理流程
- 客户端发起 HTTP 请求(GET/POST 等)
- Web 服务器接收请求,根据 URL 映射决定是否转发给 Servlet 容器
- Servlet 容器:
- 查找对应的 Servlet 实例
- 若不存在,加载并实例化 Servlet
- 调用
service() 方法处理请求
- Servlet 处理请求:
- 容器将响应返回给 Web 服务器,最终返回给客户端
核心组件关系
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| +-----------------+
| Web 服务器 |
| (Apache/Nginx) |
+--------+--------+
|
+--------v--------+
| Servlet 容器 |
| (Tomcat) |
+--------+--------+
|
+---------------+---------------+
| | |
+---------v------+ +------+-------+ +-----v--------+
| HTTPServlet | | GenericServlet| | 其他Servlet |
+----------------+ +--------------+ +-------------+
|
+---------v------+
| 开发者自定义Servlet|
+----------------+
|
四、Servlet 生命周期
Servlet 的生命周期由容器管理,分为三个阶段:
1. 初始化阶段(Initialization)
- 触发时机:Servlet 首次被请求时,或容器启动时(配置了
<load-on-startup>)
- 关键方法:
public void init(ServletConfig config) throws ServletException
- 执行内容:
- 加载配置参数
- 初始化数据库连接池
- 加载缓存数据
- 一次性资源准备
- 特点:整个生命周期中只执行一次
2. 服务阶段(Service)
- 触发时机:每次接收到 HTTP 请求
- 关键方法:
public void service(ServletRequest req, ServletResponse res)
- 执行流程:
- 容器创建
HttpServletRequest 和 HttpServletResponse 对象
- 根据 HTTP 方法(GET/POST/PUT/DELETE)调用相应方法
- 在多线程环境下执行(线程不安全!)
- 特点:可执行多次,是处理业务的核心阶段
3. 销毁阶段(Destruction)
- 触发时机:容器关闭或应用卸载时
- 关键方法:
public void destroy()
- 执行内容:
- 特点:整个生命周期中只执行一次
五、核心接口与类
1. 核心接口
| 接口 |
说明 |
javax.servlet.Servlet |
最顶层接口,定义生命周期方法 |
javax.servlet.ServletConfig |
提供 Servlet 配置信息 |
javax.servlet.ServletContext |
提供应用上下文信息 |
javax.servlet.ServletRequest |
封装客户端请求信息 |
javax.servlet.ServletResponse |
封装服务器响应信息 |
javax.servlet.http.HttpServlet |
处理 HTTP 请求的抽象类 |
2. 常用实现类
| 类 |
说明 |
典型用途 |
HttpServlet |
抽象类,实现 HTTP 协议 |
所有 Web 应用的基类 |
GenericServlet |
通用协议 Servlet |
非 HTTP 协议场景 |
HttpServletRequestWrapper |
请求包装类 |
实现过滤、修改请求 |
HttpServletResponseWrapper |
响应包装类 |
实现过滤、修改响应 |
六、核心方法详解
1. HTTP 方法处理
HttpServlet 为不同 HTTP 方法提供了专用方法:
1
2
3
4
| protected void doGet(HttpServletRequest req, HttpServletResponse resp) // 处理 GET 请求
protected void doPost(HttpServletRequest req, HttpServletResponse resp) // 处理 POST 请求
protected void doPut(HttpServletRequest req, HttpServletResponse resp) // 处理 PUT 请求
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) // 处理 DELETE 请求
|
2. 关键方法功能
| 方法 |
功能 |
注意事项 |
init() |
初始化资源 |
避免长时间操作,阻塞容器启动 |
service() |
请求分发 |
通常不重写,由容器调用 |
doGet() |
处理 GET 请求 |
参数在 URL 中,长度有限 |
doPost() |
处理 POST 请求 |
适合表单提交,数据量大 |
destroy() |
释放资源 |
确保所有资源被正确关闭 |
3. 请求/响应处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| // 获取请求参数
String username = request.getParameter("username");
String[] hobbies = request.getParameterValues("hobby");
// 设置响应内容类型
response.setContentType("text/html;charset=UTF-8");
// 获取输出流
PrintWriter out = response.getWriter();
out.println("<h1>Hello, " + username + "!</h1>");
// 重定向
response.sendRedirect("success.html");
// 请求转发
request.getRequestDispatcher("result.jsp").forward(request, response);
// 设置 Cookie
Cookie userCookie = new Cookie("username", username);
userCookie.setMaxAge(3600); // 1小时
response.addCookie(userCookie);
// 获取 Session
HttpSession session = request.getSession();
session.setAttribute("user", user);
|
七、Servlet 配置方式
1. 传统方式:web.xml 配置
1
2
3
4
5
6
7
8
9
10
| <servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>com.example.LoginServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
|
2. 注解方式(Servlet 3.0+)
1
2
3
4
5
6
7
8
9
10
11
| @WebServlet(
name = "LoginServlet",
urlPatterns = {"/login", "/signin"},
loadOnStartup = 1,
initParams = {
@WebInitParam(name = "maxAttempts", value = "3")
}
)
public class LoginServlet extends HttpServlet {
// 实现代码
}
|
3. 编程式配置(Servlet 3.0+)
1
2
3
4
5
6
7
8
9
| public class MyServletRegistration implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> c, ServletContext ctx) {
ServletRegistration.Dynamic reg =
ctx.addServlet("DynamicServlet", new DynamicServlet());
reg.addMapping("/dynamic");
reg.setLoadOnStartup(2);
}
}
|
八、线程安全问题
问题根源
- Servlet 实例是单例的(Singleton)
- 多个请求共享同一个 Servlet 实例
- 成员变量在多线程环境下可能产生数据不一致
解决方案
- 避免使用实例变量
1
2
3
4
5
6
7
| // 不安全
private String username; // 实例变量
// 安全
protected void doGet(HttpServletRequest req, HttpServletResponse res) {
String username = req.getParameter("username"); // 局部变量
}
|
- 使用同步机制
1
2
3
4
5
6
7
| private final Object lock = new Object();
protected void doPost(HttpServletRequest req, HttpServletResponse res) {
synchronized(lock) {
// 临界区代码
}
}
|
- 使用线程安全对象
1
| private final ConcurrentHashMap<String, User> userCache = new ConcurrentHashMap<>();
|
- 使用 ThreadLocal
1
2
| private static final ThreadLocal<SimpleDateFormat> dateFormatHolder =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
|
九、过滤器与监听器
1. 过滤器(Filter)
- 作用:在请求到达 Servlet 前/后处理请求/响应
- 应用场景:编码转换、权限检查、日志记录、压缩响应
- 核心接口:
javax.servlet.Filter
- 示例:
1
2
3
4
5
6
7
8
| @WebFilter("/*")
public class CharacterEncodingFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
req.setCharacterEncoding("UTF-8");
res.setContentType("text/html;charset=UTF-8");
chain.doFilter(req, res); // 传递给下一个过滤器或Servlet
}
}
|
2. 监听器(Listener)
- 作用:监听 Web 应用中的关键事件
- 常见类型:
ServletContextListener:监听应用启动/销毁
HttpSessionListener:监听会话创建/销毁
ServletRequestListener:监听请求开始/结束
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| @WebListener
public class AppContextListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent sce) {
// 应用启动时初始化数据库连接池
DataSource ds = createDataSource();
sce.getServletContext().setAttribute("dataSource", ds);
}
public void contextDestroyed(ServletContextEvent sce) {
// 应用关闭时释放资源
DataSource ds = (DataSource)sce.getServletContext().getAttribute("dataSource");
if (ds != null) ds.close();
}
}
|
十、Servlet 3.0+ 新特性
1. 注解配置
- 消除 web.xml 配置负担
@WebServlet, @WebFilter, @WebListener 等注解
2. 异步处理
- 解决长轮询、实时通知等场景
- 释放请求线程,提高容器吞吐量
- 核心方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| @WebServlet(urlPatterns = "/async", asyncSupported = true)
public class AsyncServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse res) {
AsyncContext asyncCtx = req.startAsync();
asyncCtx.setTimeout(30000);
new Thread(() -> {
// 模拟耗时操作
try { Thread.sleep(10000); } catch (Exception e) {}
// 完成异步处理
asyncCtx.getResponse().getWriter().println("异步处理完成");
asyncCtx.complete();
}).start();
}
}
|
3. 文件上传 API
- 简化文件上传处理
- 内置对 multipart/form-data 的支持
1
2
3
4
5
6
7
8
9
10
| @MultipartConfig
public class FileUploadServlet extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse res) {
Part filePart = req.getPart("file");
String fileName = Paths.get(filePart.getSubmittedFileName()).getFileName().toString();
// 保存文件
filePart.write("/uploads/" + fileName);
}
}
|
4. 动态注册
- 运行时动态添加 Servlet、Filter、Listener
- 适合插件化架构
十一、实战示例:用户注册功能
1. 创建 Servlet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
| @WebServlet("/register")
public class RegisterServlet extends HttpServlet {
private UserService userService;
@Override
public void init() throws ServletException {
// 通常应该使用依赖注入,简化示例
this.userService = new UserServiceImpl();
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 1. 设置编码
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
// 2. 获取参数
String username = request.getParameter("username");
String password = request.getParameter("password");
String email = request.getParameter("email");
// 3. 验证输入
if (username == null || username.trim().isEmpty()) {
request.setAttribute("error", "用户名不能为空");
request.getRequestDispatcher("/register.jsp").forward(request, response);
return;
}
// 4. 处理业务
try {
User user = userService.register(username, password, email);
// 5. 保存到会话
HttpSession session = request.getSession();
session.setAttribute("currentUser", user);
// 6. 重定向到主页(防止表单重复提交)
response.sendRedirect(request.getContextPath() + "/home");
} catch (DuplicateUserException e) {
request.setAttribute("error", "用户名已存在");
request.getRequestDispatcher("/register.jsp").forward(request, response);
} catch (Exception e) {
request.setAttribute("error", "注册失败: " + e.getMessage());
request.getRequestDispatcher("/register.jsp").forward(request, response);
}
}
}
|
2. web.xml 配置(如使用传统方式)
1
2
3
4
5
6
7
8
| <servlet>
<servlet-name>RegisterServlet</servlet-name>
<servlet-class>com.example.servlet.RegisterServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>RegisterServlet</servlet-name>
<url-pattern>/register</url-pattern>
</servlet-mapping>
|
十二、最佳实践
1. 关注点分离
- Controller 层 (Servlet):只处理请求分发,不包含业务逻辑
- Service 层:实现核心业务逻辑
- DAO 层:负责数据访问
- View 层 (JSP/Thymeleaf):只负责显示
2. RESTful 设计
- 使用 HTTP 方法语义:GET(获取)、POST(创建)、PUT(更新)、DELETE(删除)
- 设计有意义的 URL:
/users, /users/123
- 使用状态码表示结果:200(OK)、201(Created)、404(Not Found)、500(Internal Error)
3. 异常处理
- 全局异常处理(使用过滤器或错误页面配置)
- 避免向客户端暴露内部错误详情
- 记录详细错误日志
4. 安全实践
- 防止 XSS:对输出进行编码
- 防止 CSRF:使用 token 验证
- 防止 SQL 注入:使用预编译语句
- 敏感操作验证权限
十三、现代 Web 开发中的定位
传统应用
- 与 JSP 配合构建 MVC 架构
- 适合中后台管理系统
现代应用
- API 服务:作为 REST API 的控制器
- 微服务:在 Spring Boot 等框架中作为基础组件
- 前后端分离:提供 JSON 数据,前端使用 Vue/React 等框架
框架演进
- Servlet → Spring Web MVC → Spring Boot → Spring WebFlux
- 虽然框架在演进,但底层仍基于 Servlet API
- 了解 Servlet 有助于理解上层框架的工作原理
总结
Servlet 作为 Java Web 开发的核心技术,是理解整个 Java Web 生态的基础。通过掌握其工作原理、生命周期和最佳实践,您将能够:
- 构建高性能、可维护的 Web 应用
- 理解上层框架(如 Spring MVC)的工作原理
- 解决复杂的 Web 开发问题
- 设计符合行业标准的架构模式
提示:在实际开发中,很少直接使用原始 Servlet API 构建应用,而是使用 Spring Boot 等框架简化开发。但深入理解 Servlet 对于解决问题、性能优化和架构设计至关重要。
建议学习路径:Servlet 基础 → Filter/Listener → JSP/EL/JSTL → JDBC 数据库连接 → MVC 架构 → Spring Boot