【JavaWeb】三大组件之——Servlet

一、什么是Servlet

ServletServer Applet的简称,是用 Java 编写的服务器端程序。Servlet 被部署在 Web 服务器(如 Tomcat)中,由服务器负责管理并调用,用于处理客户端发送的请求并返回响应。

Servlet 的作用可以概括为三个核心步骤:

  1. 接收请求数据
  2. 处理请求
  3. 完成响应

💡 简单来说: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{// 处理请求的核心方法}
  • 特点:每次请求都会执行
  • 过程
    1. 容器为每个请求创建ServletRequestServletResponse对象
    2. 调用service()方法处理请求
    3. 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
nameString指定 Servlet 的名称<servlet-name>
valueString[]等价于urlPatterns<url-pattern>
urlPatternsString[]指定 URL 匹配模式<url-pattern>
loadOnStartupint指定启动加载顺序<load-on-startup>
initParamsWebInitParam[]指定初始化参数<init-param>
asyncSupportedboolean是否支持异步操作<async-supported>
descriptionStringServlet 描述信息<description>
displayNameStringServlet 显示名称<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 线程安全最佳实践

建议说明
避免使用实例变量尽量使用局部变量存储请求相关数据
使用线程安全类ConcurrentHashMapAtomicInteger
同步关键代码必要时使用synchronizedLock
使用请求域存储数据通过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 两者对比

特性ServletConfigServletContext
作用范围单个 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("密&nbsp;&nbsp;码:<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)

如果本文对你有帮助,欢迎点赞 👍、收藏 ⭐、关注 💖,你的支持是我持续创作的动力!