Java Servlet 从入门到精通
Java Servlet 从入门到精通:核心原理 + 实战案例 + 避坑指南
Servlet 是 Java Web 开发的基石,是连接客户端(浏览器)与服务器(如 Tomcat)的核心组件。无论是 Spring MVC、Struts 等框架,其底层都依赖 Servlet 技术。本文将从基础概念出发,深入剖析 Servlet 生命周期、核心 API,结合实战案例带你掌握 Servlet 开发,并总结生产环境中的避坑技巧。
一、什么是 Servlet?
1. 核心定义
Servlet(Server Applet)是运行在服务器端的 Java 程序,用于接收和响应客户端的 HTTP 请求,本质是一个 遵循 Servlet 规范的 Java 接口实现类。
2. 核心作用
接收客户端请求(GET/POST 等)
处理业务逻辑(如数据库交互、数据计算)
生成响应结果(HTML 页面、JSON 数据、文件流等)
与服务器容器(Tomcat、Jetty)交互,利用容器提供的资源(如 Session、Cookie)
3. 与 JSP、Spring MVC 的关系
JSP:本质是 Servlet 的简化版,JSP 页面会被容器编译为 Servlet 类执行
Spring MVC:基于 Servlet 封装的 MVC 框架,核心控制器 DispatcherServlet 本质是一个 Servlet
关系:Servlet → JSP → Spring MVC(从底层到上层框架的演进)
二、Servlet 核心基础
1. 核心接口与类
Servlet 规范的核心是 javax.servlet 包下的接口和抽象类,最常用的是 HttpServlet(继承自 GenericServlet,实现 Servlet 接口):
2. 生命周期(重点)
Servlet 的生命周期由 Web 容器(如 Tomcat)全程管理,分为 3 个核心阶段 + 2 个可选阶段:
(1)初始化阶段(init ())
触发时机:Servlet 第一次被访问时(默认),或 Web 应用启动时(配置 load-on-startup)
执行次数:仅执行 1 次(Servlet 是单例模式,多线程共享实例)
核心作用:初始化资源(如加载配置文件、创建数据库连接池)
代码示例:
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
// 初始化:获取配置参数
String dbUrl = config.getInitParameter("dbUrl");
System.out.println("Servlet 初始化,数据库地址:" + dbUrl);
}(2)服务阶段(service ())
触发时机:每次客户端请求该 Servlet 时
执行次数:每次请求执行 1 次(多线程并发执行,需注意线程安全)
核心作用:处理请求并生成响应
关键逻辑:HttpServlet 的 service() 方法会根据 HTTP 请求方式(GET/POST/PUT/DELETE),自动分发到对应的 doXxx() 方法(如 doGet()、doPost())
线程安全:Servlet 实例是单例,多线程共享成员变量,需避免在成员变量中存储请求相关数据(推荐使用局部变量,或通过 ThreadLocal 隔离线程数据)
(3)销毁阶段(destroy ())
触发时机:Web 应用停止(如 Tomcat 关闭)或 Servlet 被移除时
执行次数:仅执行 1 次
核心作用:释放资源(如关闭数据库连接、销毁线程池)
代码示例:
@Override
public void destroy() {
// 销毁:关闭数据库连接
System.out.println("Servlet 销毁,释放资源");
super.destroy();
}生命周期流程图
Web应用启动 → [可选] init() 初始化 → 客户端请求 → service() 分发 → doGet/doPost 处理 → 响应客户端 → 重复请求处理 → Web应用停止 → destroy() 销毁3. 配置方式(Servlet 3.0+ 两种方式)
Servlet 3.0 后支持 注解配置(推荐)和 XML 配置(传统方式),两种方式等效。
(1)注解配置(@WebServlet)
无需修改 web.xml,直接在 Servlet 类上添加注解:
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
// urlPatterns:访问路径(可配置多个),loadOnStartup=1 表示应用启动时初始化(默认-1,第一次访问时初始化)
@WebServlet(urlPatterns = {"/hello", "/hi"}, loadOnStartup = 1, initParams = {@WebInitParam(name = "dbUrl", value = "jdbc:mysql://localhost:3306/test")})
public class HelloServlet extends HttpServlet {
// 实现 doGet/doPost 方法
}(2)XML 配置(web.xml)
传统方式,适合复杂配置或低版本 Servlet:
<!-- web/WEB-INF/web.xml -->
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="4.0">
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.example.HelloServlet</servlet-class>
<!-- 初始化参数 -->
<init-param>
<param-name>dbUrl</param-name>
<param-value>jdbc:mysql://localhost:3306/test</param-value>
</init-param>
<!-- 应用启动时初始化(默认-1) -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<!-- 访问路径 -->
<url-pattern>/hello</url-pattern>
<url-pattern>/hi</url-pattern>
</servlet-mapping>
</web-app>三、实战案例:开发一个完整的 Servlet 应用
需求:实现一个用户登录功能
接收客户端提交的用户名和密码(POST 请求)
验证用户信息(模拟数据库查询)
登录成功:跳转至首页(带用户信息)
登录失败:返回登录页并提示错误
1. 环境准备
JDK 8+
构建工具:Maven/Gradle(本文用 Maven)
Web 容器:Tomcat 9+
IDE:IntelliJ IDEA/Eclipse
2. 项目结构
servlet-login-demo/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ ├── LoginServlet.java // 登录处理Servlet
│ │ │ └── HomeServlet.java // 首页Servlet
│ │ └── webapp/
│ │ ├── login.jsp // 登录页面
│ │ ├── home.jsp // 首页
│ │ └── WEB-INF/
│ │ └── web.xml // 可选(本文用注解)
│ └── pom.xml // Maven依赖3. 核心依赖(pom.xml)
<dependencies>
<!-- Servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope> <!-- 容器已提供,打包时不包含 -->
</dependency>
<!-- JSP API(可选,用于页面渲染) -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
<scope>provided</scope>
</dependency>
</dependencies>4. 登录页面(login.jsp)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>用户登录</title>
</head>
<body>
<h1>登录</h1>
<!-- 提交到 /login Servlet,POST 方式 -->
<form action="${pageContext.request.contextPath}/login" method="post">
用户名:<input type="text" name="username" required><br>
密码:<input type="password" name="password" required><br>
<input type="submit" value="登录">
<!-- 显示错误信息 -->
<span style="color: red;">${errorMsg}</span>
</form>
</body>
</html>5. 登录处理 Servlet(LoginServlet.java)
package com.example;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet(urlPatterns = "/login")
public class LoginServlet extends HttpServlet {
// 模拟数据库中的用户(实际开发中需连接数据库)
private static final String DB_USERNAME = "admin";
private static final String DB_PASSWORD = "123456";
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 1. 设置请求编码(解决中文乱码)
request.setCharacterEncoding("UTF-8");
// 2. 获取请求参数
String username = request.getParameter("username");
String password = request.getParameter("password");
// 3. 验证用户信息
if (DB_USERNAME.equals(username) && DB_PASSWORD.equals(password)) {
// 登录成功:创建 Session,存储用户信息
HttpSession session = request.getSession();
session.setAttribute("loginUser", username);
// 重定向到首页(避免表单重复提交)
response.sendRedirect(request.getContextPath() + "/home");
} else {
// 登录失败:回显错误信息,转发到登录页
request.setAttribute("errorMsg", "用户名或密码错误");
try {
request.getRequestDispatcher("/login.jsp").forward(request, response);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 处理 GET 请求(直接跳转到登录页)
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
try {
request.getRequestDispatcher("/login.jsp").forward(request, response);
} catch (Exception e) {
e.printStackTrace();
}
}
}6. 首页 Servlet(HomeServlet.java)
package com.example;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet(urlPatterns = "/home")
public class HomeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
// 1. 验证 Session(未登录则跳转到登录页)
HttpSession session = request.getSession(false); // 不创建新 Session
if (session == null || session.getAttribute("loginUser") == null) {
try {
response.sendRedirect(request.getContextPath() + "/login");
return;
} catch (IOException e) {
e.printStackTrace();
}
}
// 2. 转发到首页(传递用户信息)
try {
request.getRequestDispatcher("/home.jsp").forward(request, response);
} catch (Exception e) {
e.printStackTrace();
}
}
}7. 首页(home.jsp)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>首页</title>
</head>
<body>
<h1>欢迎您,${sessionScope.loginUser}!</h1>
<a href="${pageContext.request.contextPath}/login?action=logout">退出登录</a>
</body>
</html>8. 部署与运行
将项目打包为 WAR 包(Maven 执行 package 命令)
复制 WAR 包到 Tomcat 的 webapps 目录下
启动 Tomcat(运行 bin/startup.bat 或 startup.sh)
测试:输入用户名 admin、密码 123456,登录成功跳转首页;输入错误则提示错误信息