一、什么是Servlet
Servlet是Server Applet的简称,是用 Java 编写的服务器端程序。Servlet 被部署在 Web 服务器(如 Tomcat)中,由服务器负责管理并调用,用于处理客户端发送的请求并返回响应。
Servlet 的作用可以概括为三个核心步骤:
- 接收请求数据
- 处理请求
- 完成响应
💡 简单来说:Servlet 就是运行在服务器上的 Java 类,专门用来处理 HTTP 请求和响应。
二、Servlet 的核心特点
| 特点 | 说明 |
|---|---|
| 功能强大 | 基于 Java 语言编写,可调用 Java API,能处理各种 HTTP 请求 |
| 可移植 | Java 语言跨平台,Servlet 可在任何支持 Java 的服务器上运行 |
| 性能高效 | 采用单例多线程模型,首次实例化后驻留内存,后续请求复用实例 |
| 安全性高 | 继承 Java 安全框架,Servlet 容器提供额外安全机制 |
| 可扩展 | 面向对象设计,支持封装、继承、多态等特性 |
三、Servlet 的体系结构
Servlet 的框架由两个核心 Java 包组成:
javax.servlet:定义了所有 Servlet 类必须实现或扩展的通用接口和类javax.servlet.http:定义了采用 HTTP 协议通信的HttpServlet 类
3.1 核心接口与类的继承关系
Servlet (接口) │ ▼ GenericServlet (抽象类) │ ▼ HttpServlet (抽象类) │ ▼ 自定义 Servlet (如 MyServlet)3.2 Servlet 接口中的核心方法
| 方法声明 | 功能描述 |
|---|---|
void init(ServletConfig config) | Servlet 实例化后,容器调用该方法完成初始化工作 |
ServletConfig getServletConfig() | 获取 Servlet 对象的配置信息 |
String getServletInfo() | 返回 Servlet 的描述信息(作者、版本等) |
void service(ServletRequest req, ServletResponse res) | 负责响应用户请求,是 Servlet 的核心方法 |
void destroy() | 负责释放 Servlet 占用的资源 |
四、Servlet 的生命周期
Servlet 的生命周期由Web 容器(如 Tomcat)管理,分为以下四个阶段:
4.1 生命周期图解
4.2 各阶段详解
① 加载和实例化阶段
- 触发时机:
- 容器启动时(配置了
<load-on-startup>) - 客户端首次请求该 Servlet 时
- 容器启动时(配置了
- 过程:容器通过 Java 反射调用 Servlet 的无参构造方法创建实例
- 注意事项:编写 Servlet 时不要提供带参数的构造方法
② 初始化阶段(init)
publicvoidinit(ServletConfigconfig)throwsServletException{// 初始化代码:加载配置文件、建立数据库连接等super.init(config);}- 特点:只执行一次
- 用途:完成一些初始化工作,如加载配置文件、建立数据库连接、创建必要对象等
- 参数:
ServletConfig对象包含了 web.xml 或@WebServlet中配置的初始化参数
③ 服务阶段(service)
publicvoidservice(ServletRequestreq,ServletResponseres)throwsServletException,IOException{// 处理请求的核心方法}- 特点:每次请求都会执行
- 过程:
- 容器为每个请求创建
ServletRequest和ServletResponse对象 - 调用
service()方法处理请求 service()方法根据请求类型(GET/POST/PUT/DELETE 等)分派到doGet()、doPost()等方法
- 容器为每个请求创建
- 线程模型:Servlet 采用单例多线程模式,多个请求共享同一个 Servlet 实例,每个请求由独立线程处理
④ 销毁阶段(destroy)
publicvoiddestroy(){// 释放资源:关闭数据库连接、清理临时文件等super.destroy();}- 触发时机:服务器关闭、Web 应用被移除、Servlet 被卸载
- 特点:只执行一次
- 用途:释放 Servlet 占用的资源,如关闭数据库连接、清理临时文件等
4.3 生命周期执行流程图
五、Servlet 的配置方式
Servlet 的配置有两种方式:web.xml 配置和注解配置。
5.1 web.xml 配置方式(传统方式)
在WEB-INF/web.xml文件中进行配置:
<?xml version="1.0" encoding="UTF-8"?><web-appxmlns="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"><!-- Servlet 配置 --><servlet><!-- Servlet 名称,自定义 --><servlet-name>HelloServlet</servlet-name><!-- Servlet 类的全限定名 --><servlet-class>com.example.servlet.HelloServlet</servlet-class><!-- 初始化参数 --><init-param><param-name>encoding</param-name><param-value>UTF-8</param-value></init-param><!-- 启动时加载顺序,数字越小优先级越高 --><load-on-startup>1</load-on-startup></servlet><!-- Servlet 映射配置 --><servlet-mapping><servlet-name>HelloServlet</servlet-name><!-- 访问路径 --><url-pattern>/hello</url-pattern></servlet-mapping></web-app>5.2 注解配置方式(推荐)
使用@WebServlet注解,无需 web.xml 配置:
packagecom.example.servlet;importjavax.servlet.ServletException;importjavax.servlet.annotation.WebServlet;importjavax.servlet.http.HttpServlet;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;importjava.io.PrintWriter;// 使用 @WebServlet 注解配置@WebServlet(name="HelloServlet",// Servlet 名称urlPatterns="/hello",// URL 映射路径initParams={// 初始化参数@WebInitParam(name="encoding",value="UTF-8")},loadOnStartup=1// 启动加载顺序)publicclassHelloServletextendsHttpServlet{@OverrideprotectedvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{// 设置响应内容类型和编码resp.setContentType("text/html;charset=UTF-8");// 获取输出流PrintWriterout=resp.getWriter();out.println("<html>");out.println("<head><title>Hello Servlet</title></head>");out.println("<body>");out.println("<h1>Hello, Servlet!</h1>");out.println("<p>这是我的第一个 Servlet 程序</p>");out.println("</body>");out.println("</html>");}@OverrideprotectedvoiddoPost(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{// 设置请求编码,解决中文乱码req.setCharacterEncoding("UTF-8");// 获取表单参数Stringusername=req.getParameter("username");Stringpassword=req.getParameter("password");// 处理业务逻辑...// 转发到结果页面req.setAttribute("msg","登录成功!");req.getRequestDispatcher("/result.jsp").forward(req,resp);}}5.3 @WebServlet 注解属性详解
| 属性名 | 类型 | 功能描述 | 等价于 web.xml |
|---|---|---|---|
name | String | 指定 Servlet 的名称 | <servlet-name> |
value | String[] | 等价于urlPatterns | <url-pattern> |
urlPatterns | String[] | 指定 URL 匹配模式 | <url-pattern> |
loadOnStartup | int | 指定启动加载顺序 | <load-on-startup> |
initParams | WebInitParam[] | 指定初始化参数 | <init-param> |
asyncSupported | boolean | 是否支持异步操作 | <async-supported> |
description | String | Servlet 描述信息 | <description> |
displayName | String | Servlet 显示名称 | <display-name> |
六、Servlet 处理请求的流程
6.1 请求处理完整流程
6.2 HttpServlet 常用方法
| 方法 | 功能描述 |
|---|---|
doGet(HttpServletRequest, HttpServletResponse) | 处理 GET 请求 |
doPost(HttpServletRequest, HttpServletResponse) | 处理 POST 请求 |
doPut(HttpServletRequest, HttpServletResponse) | 处理 PUT 请求 |
doDelete(HttpServletRequest, HttpServletResponse) | 处理 DELETE 请求 |
doHead(HttpServletRequest, HttpServletResponse) | 处理 HEAD 请求 |
doOptions(HttpServletRequest, HttpServletResponse) | 处理 OPTIONS 请求 |
七、Servlet 线程安全问题
由于 Servlet 采用单例多线程模型,多个请求共享同一个实例,因此需要注意线程安全问题。
7.1 线程安全问题示例
@WebServlet("/unsafe")publicclassUnsafeServletextendsHttpServlet{// ❌ 错误:实例变量被多线程共享,存在线程安全问题privateintcount=0;@OverrideprotectedvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{count++;// 多个线程同时操作,结果不可预期resp.getWriter().println("Count: "+count);}}7.2 解决方案
@WebServlet("/safe")publicclassSafeServletextendsHttpServlet{// ✅ 正确:使用局部变量,每个线程独立@OverrideprotectedvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{intcount=0;// 局部变量,线程安全count++;resp.getWriter().println("Count: "+count);}// ✅ 正确:使用同步代码块(但会影响性能)privatefinalObjectlock=newObject();privateintsharedCount=0;protectedvoiddoPost(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{synchronized(lock){sharedCount++;resp.getWriter().println("Shared Count: "+sharedCount);}}}7.3 线程安全最佳实践
| 建议 | 说明 |
|---|---|
| 避免使用实例变量 | 尽量使用局部变量存储请求相关数据 |
| 使用线程安全类 | 如ConcurrentHashMap、AtomicInteger等 |
| 同步关键代码 | 必要时使用synchronized或Lock |
| 使用请求域存储数据 | 通过request.setAttribute()传递数据 |
八、ServletContext 与 ServletConfig
8.1 ServletConfig(单个 Servlet 配置)
ServletConfig对象代表当前 Servlet 的配置信息,每个 Servlet 独享一个ServletConfig。
@Overridepublicvoidinit(ServletConfigconfig)throwsServletException{super.init(config);// 获取初始化参数Stringencoding=config.getInitParameter("encoding");System.out.println("编码配置:"+encoding);// 获取 Servlet 名称StringservletName=config.getServletName();System.out.println("Servlet 名称:"+servletName);}8.2 ServletContext(全局应用配置)
ServletContext对象代表整个 Web 应用,所有 Servlet 共享同一个ServletContext。
@WebServlet("/context")publicclassContextServletextendsHttpServlet{@OverrideprotectedvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{// 获取 ServletContextServletContextcontext=getServletContext();// 获取全局初始化参数StringdbUrl=context.getInitParameter("dbUrl");// 存储全局属性(所有 Servlet 共享)context.setAttribute("appName","MyWebApp");// 获取真实路径StringrealPath=context.getRealPath("/upload");// 获取资源输入流InputStreamis=context.getResourceAsStream("/WEB-INF/config.properties");}}8.3 两者对比
| 特性 | ServletConfig | ServletContext |
|---|---|---|
| 作用范围 | 单个 Servlet | 整个 Web 应用 |
| 获取方式 | getServletConfig() | getServletContext() |
| 参数配置 | <init-param> | <context-param> |
| 数据共享 | 不共享 | 所有 Servlet 共享 |
九、请求转发与重定向
9.1 请求转发(Forward)
// 获取请求分发器RequestDispatcherdispatcher=req.getRequestDispatcher("/target.jsp");// 转发请求dispatcher.forward(req,resp);- 特点:服务器内部跳转,URL 不变,请求对象共享
- 适用场景:MVC 模式中的控制器转发到视图
9.2 重定向(Redirect)
// 发送重定向响应resp.sendRedirect("/target.jsp");// 或重定向到外部网站resp.sendRedirect("https://www.example.com");- 特点:客户端重新发起请求,URL 改变,请求对象不共享
- 适用场景:登录成功后跳转、跨域跳转
9.3 两者对比
| 特性 | 请求转发(Forward) | 重定向(Redirect) |
|---|---|---|
| 跳转方式 | 服务器内部 | 客户端重新请求 |
| URL 变化 | 不变 | 改变 |
| 请求对象 | 共享 | 不共享 |
| 范围 | 同一服务器内 | 可跨域 |
| 效率 | 高 | 低(两次请求) |
十、完整示例:用户登录 Servlet
packagecom.example.servlet;importjavax.servlet.ServletException;importjavax.servlet.annotation.WebServlet;importjavax.servlet.http.HttpServlet;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;importjava.io.PrintWriter;/** * 用户登录 Servlet 示例 */@WebServlet(name="LoginServlet",urlPatterns="/login")publicclassLoginServletextendsHttpServlet{@Overridepublicvoidinit()throwsServletException{System.out.println("LoginServlet 初始化...");}@OverrideprotectedvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{// 显示登录页面resp.setContentType("text/html;charset=UTF-8");PrintWriterout=resp.getWriter();out.println("<!DOCTYPE html>");out.println("<html><head><title>登录</title></head><body>");out.println("<h2>用户登录</h2>");out.println("<form action='/login' method='post'>");out.println("用户名:<input type='text' name='username'><br><br>");out.println("密 码:<input type='password' name='password'><br><br>");out.println("<input type='submit' value='登录'>");out.println("</form></body></html>");}@OverrideprotectedvoiddoPost(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{// 设置请求编码req.setCharacterEncoding("UTF-8");// 获取请求参数Stringusername=req.getParameter("username");Stringpassword=req.getParameter("password");// 简单的业务逻辑验证if("admin".equals(username)&&"123456".equals(password)){// 登录成功,重定向到首页req.getSession().setAttribute("user",username);resp.sendRedirect("/index.jsp");}else{// 登录失败,请求转发回登录页并显示错误信息req.setAttribute("error","用户名或密码错误!");req.getRequestDispatcher("/login.jsp").forward(req,resp);}}@Overridepublicvoiddestroy(){System.out.println("LoginServlet 销毁...");}}总结
本文详细介绍了 JavaWeb 三大组件之一的Servlet,包括:
| 知识点 | 核心内容 |
|---|---|
| Servlet 定义 | 运行在服务器端的 Java 程序,处理 HTTP 请求和响应 |
| 核心特点 | 功能强大、可移植、性能高效、安全性高、可扩展 |
| 体系结构 | Servlet → GenericServlet → HttpServlet → 自定义 Servlet |
| 生命周期 | 加载实例化 → init() → service() → destroy() |
| 配置方式 | web.xml 配置 和 @WebServlet 注解配置 |
| 线程安全 | 单例多线程模型,注意避免实例变量的线程安全问题 |
| 核心对象 | ServletRequest、ServletResponse、ServletConfig、ServletContext |
| 页面跳转 | 请求转发(Forward)和重定向(Redirect) |
如果本文对你有帮助,欢迎点赞 👍、收藏 ⭐、关注 💖,你的支持是我持续创作的动力!