一、Servlet 概述1.1 JavaWeb 的三大组件JavaWeb 开发中有三大核心组件它们是构建 Java Web 应用的基石组件一Servlet作用处理客户端请求生成动态响应内容。是 JavaWeb 最基础的组件必须 100% 掌握。组件二Filter作用在请求到达 Servlet 之前或响应返回客户端之后对请求和响应进行预处理和后处理。例如字符编码过滤、登录校验、日志记录等。组件三Listener作用基于观察者模式监听 Web 应用中的事件如 ServletContext 创建、销毁Session 创建、销毁属性变化等在事件发生时执行特定的业务逻辑。1.2 Servlet 的作用Servlet即 Server Let意为“服务器端的小程序”。它的核心作用就是处理用户请求。当客户端浏览器发出一个 HTTP 请求时Tomcat或其他 Servlet 容器会根据请求的 URL 找到对应的 Servlet 来处理这个请求。具体场景举例用户发出登录请求 → 由 LoginServlet 处理用户发出注册请求 → 由 RegisterServlet 处理用户发出查询商品列表请求 → 由 ProductListServlet 处理也就是说Servlet 是 MVC 模式中的 Controller控制器层负责接收请求、调用业务逻辑、返回响应。1.3 Servlet 与 Tomcat 的关系很多初学者会混淆 Servlet 和 Tomcat 的关系这里做一个清晰说明Tomcat 是一个 Servlet 容器也叫 Web 服务器它的主要职责是管理 Servlet 的生命周期创建、初始化、调用、销毁将客户端的 HTTP 请求解析并转发给对应的 Servlet接收 Servlet 的响应并返回给客户端而 Servlet 是我们开发者编写的 Java 类它实现了 javax.servlet.Servlet 接口用来处理具体的业务逻辑。Tomcat 负责“管”Servlet 负责“做”。Tomcat 是舞台Servlet 是演员。二、第一个 Servlet2.1 Servlet 接口详解所有 Servlet 都必须实现 javax.servlet.Servlet 接口。该接口定义了以下 5 个方法其中前三个是生命周期方法方法一void init(ServletConfig config)当 Tomcat 创建 Servlet 实例后会立即调用 init() 方法。该方法在整个生命周期中只被调用一次。用于执行 Servlet 的初始化工作例如加载配置文件、建立数据库连接等。方法二void service(ServletRequest request, ServletResponse response)每次客户端请求到达时Tomcat 都会调用 service() 方法。该方法会被多次调用每来一次请求就调用一次。用于处理请求并生成响应是 Servlet 的核心业务方法。方法三void destroy()当 Tomcat 要销毁 Servlet 实例时通常是在服务器关闭或应用卸载时会先调用 destroy() 方法。该方法在整个生命周期中只被调用一次。用于释放资源例如关闭数据库连接、停止后台线程等。方法四ServletConfig getServletConfig()返回当前 Servlet 的配置信息对象。通常在 init() 方法中保存传入的 ServletConfig 对象然后在本方法中返回。方法五String getServletInfo()返回一个描述当前 Servlet 的字符串基本用不上。2.2 创建第一个 Servlet第一步编写 Servlet 实现类package cn.tx.servlet; import javax.servlet.*; import java.io.IOException; public class Servlet1 implements Servlet { Override public void init(ServletConfig servletConfig) throws ServletException { System.out.println(Servlet 初始化完成); } Override public ServletConfig getServletConfig() { return null; } Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { System.out.println(处理请求中...); servletResponse.getWriter().write(Hello Servlet!); } Override public String getServletInfo() { return null; } Override public void destroy() { System.out.println(Servlet 即将销毁); } }第二步在 web.xml 中配置 Servlet?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd version3.1 !-- 声明 Servlet -- servlet servlet-namehello/servlet-name servlet-classcn.tx.servlet.Servlet1/servlet-class /servlet !-- 映射 Servlet 的访问路径 -- servlet-mapping servlet-namehello/servlet-name url-pattern/hello/url-pattern /servlet-mapping /web-app配置说明servlet-name为 Servlet 起一个别名在项目内必须唯一。servlet-classServlet 类的全限定名包名 类名。url-pattern客户端访问该 Servlet 的 URL 路径相对于项目上下文路径。2.3 web.xml 配置详解web.xml 是 JavaWeb 应用的部署描述符位于 WEB-INF 目录下。它告诉 Tomcat 这个 Web 应用有哪些 Servlet、Filter、Listener以及它们的映射关系。重要规则servlet-name 在 servlet 和 servlet-mapping 中必须完全一致。url-pattern 必须以 / 开头除非是扩展名匹配。一个 Servlet 可以有多个 url-pattern即多个路径映射到同一个 Servlet。三、Servlet 生命周期3.1 生命周期三阶段Servlet 的生命周期由 Tomcat 管理包含三个阶段阶段一实例化与初始化时机第一次访问该 Servlet 时或服务器启动时若配置了 load-on-startup。动作Tomcat 通过反射创建 Servlet 实例 → 调用 init() 方法。次数整个生命周期中只执行一次。阶段二服务时机每次客户端请求到达时。动作Tomcat 调用 Servlet 的 service() 方法。次数每来一次请求就执行一次可执行多次。阶段三销毁时机服务器关闭或 Web 应用被卸载时。动作Tomcat 先调用 destroy() 方法再销毁 Servlet 实例。次数整个生命周期中只执行一次。3.2 生命周期完整演示代码为了清晰地观察生命周期方法被调用的时机我们可以编写如下测试代码package cn.tx.servlet; import javax.servlet.*; import java.io.IOException; public class LifecycleServlet implements Servlet { public LifecycleServlet() { System.out.println(1. 构造方法被调用实例化); } Override public void init(ServletConfig servletConfig) throws ServletException { System.out.println(2. init() 方法被调用初始化); } Override public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException { System.out.println(3. service() 方法被调用处理请求); response.getWriter().write(生命周期测试成功); } Override public void destroy() { System.out.println(4. destroy() 方法被调用销毁前); } Override public ServletConfig getServletConfig() { return null; } Override public String getServletInfo() { return null; } }部署运行后首次访问该 Servlet 时控制台输出1. 构造方法被调用实例化2. init() 方法被调用初始化3. service() 方法被调用处理请求再次访问时只输出3. service() 方法被调用处理请求关闭服务器时输出4. destroy() 方法被调用销毁前重要结论Servlet 是单例的一个 Servlet 类在 Tomcat 中只有一个实例。多个线程可能同时访问同一个 Servlet 的 service() 方法因此 Servlet 是线程不安全的。四、HttpServlet4.1 为什么需要 HttpServlet我们已经看到实现 Servlet 接口需要实现 5 个方法其中 service() 方法的参数是 ServletRequest 和 ServletResponse没有提供专门针对 HTTP 协议的方法如 doGet、doPost。而现在的 Web 应用都是基于 HTTP 协议的所以需要针对 HTTP 协议做一层封装。继承关系如下Servlet接口↑GenericServlet抽象类—— 实现了 Servlet 接口提供了通用的 servlet 功能↑HttpServlet抽象类—— 继承自 GenericServlet专门处理 HTTP 请求HttpServlet 为我们提供了以下便利自动将 ServletRequest 和 ServletResponse 转换为 HttpServletRequest 和 HttpServletResponse。提供了 doGet()、doPost()、doPut()、doDelete() 等方法分别对应不同的 HTTP 请求方法。开发者只需继承 HttpServlet重写需要处理的方法即可无需再实现 service()。4.2 HTTP 请求方法概述HTTP 协议定义了多种请求方法常用的有以下几种GET请求获取指定资源常用于查询操作如搜索、查看详情。参数在 URL 中可见有长度限制。POST向服务器提交数据常用于表单提交、上传文件等。参数在请求体中安全性相对较好。PUT更新指定资源用于修改数据。DELETE删除指定资源。HEAD类似 GET但只返回响应头不返回响应体用于检查资源是否存在。OPTIONS查询服务器支持的请求方法。TRACE用于诊断回显服务器收到的请求。CONNECT用于代理传输如 HTTPS。在实际开发中90% 以上的场景只需要 GET 和 POST 两种方法。4.3 创建 HttpServlet 的两种方式方式一手动创建类继承 HttpServletpackage cn.tx.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class Servlet2 extends HttpServlet { Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.getWriter().write(处理 GET 请求); } Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // POST 请求默认调用 doGet也可以单独实现 doGet(request, response); } }然后在 web.xml 中配置servlet servlet-namehello2/servlet-name servlet-classcn.tx.servlet.Servlet2/servlet-class /servlet servlet-mapping servlet-namehello2/servlet-name url-pattern/hello2/url-pattern /servlet-mapping方式二使用 IDEA 快捷创建在 IDEA 中右键项目 → New → Servlet填写类名和包名IDEA 会自动生成继承 HttpServlet 的代码框架并在 web.xml 中自动注册。开发者只需在 doGet/doPost 中编写业务逻辑即可。4.4 load-on-startup 配置创建顺序默认情况下Servlet 在第一次被访问时才创建实例。但有些 Servlet 需要在服务器启动时就完成初始化例如数据库连接池的初始化、定时任务调度等可以通过 load-on-startup 实现。配置方式servlet servlet-namehello/servlet-name servlet-classcn.tx.servlet.Servlet1/servlet-class load-on-startup1/load-on-startup /servlet/servletload-on-startup 的值是一个正整数表示加载顺序值越小优先级越高越先被加载。如果有多个 Servlet 都配置了该元素Tomcat 会按照值从小到大依次创建。值从 0 开始通常使用 1、2、3...。配置了 load-on-startup 的 Servlet在 Tomcat 启动时就会创建实例并调用 init() 方法而不需要等待第一次请求。五、ServletConfig5.1 ServletConfig 概述ServletConfig 对象对应 web.xml 中某个 servlet 元素的配置信息。每个 Servlet 都有自己的 ServletConfig 对象。ServletConfig 的主要作用获取当前 Servlet 在 web.xml 中配置的名称servlet-name。获取当前 Servlet 的初始化参数init-param。获取 ServletContext 对象。ServletConfig 对象由 Tomcat 创建通过 init() 方法传递给 Servlet。我们需要在 init() 方法中保存它以便后续使用。5.2 配置初始化参数在 web.xml 的 servlet 元素中可以添加多个 init-param 子元素来配置初始化参数servlet servlet-namehello/servlet-name servlet-classcn.tx.servlet.Servlet1/servlet-class !-- 初始化参数 -- init-param param-nameusername/param-name param-valueroot/param-value /init-param init-param param-namepassword/param-name param-valuetxjava/param-value /init-param load-on-startup1/load-on-startup /servlet注意init-param 必须写在 servlet 元素内部不能写在 servlet-mapping 中。5.3 获取初始化参数代码示例在 Servlet 的 init() 方法中通过 ServletConfig 对象获取参数Override public void init(ServletConfig config) throws ServletException { // 获取单个参数 String username config.getInitParameter(username); System.out.println(username: username); // 获取所有参数名称Enumeration 枚举类型 EnumerationString parameterNames config.getInitParameterNames(); while (parameterNames.hasMoreElements()) { String paramName parameterNames.nextElement(); String paramValue config.getInitParameter(paramName); System.out.println(paramName : paramValue); } }在 HttpServlet 的子类中可以直接调用 getServletConfig() 方法获取 ServletConfig 对象无需在 init() 中保存。六、Servlet 路径映射6.1 URL-Pattern 三种匹配方式url-pattern 的配置有三种方式每种方式的语法和适用场景不同方式一完全路径匹配以 / 开头表示一个精确的路径。示例/hello、/user/login、/admin/user/list特点只能匹配一个固定的路径精确度最高。方式二目录匹配以 / 开头以 /* 结尾表示匹配该目录下的所有路径。示例/admin/*、/user/*、/*特点匹配指定目录下的所有请求适用于批量路由。方式三扩展名匹配不以 / 开头以 .* 开头表示匹配具有特定扩展名的请求。示例*.do、*.action、*.jsp特点匹配所有以指定扩展名结尾的请求。6.2 匹配优先级规则当一个请求 URL 同时匹配多个 url-pattern 时Tomcat 按照以下优先级选择优先级从高到低第 1 优先级完全路径匹配精确匹配第 2 优先级目录匹配越长越精确越优先第 3 优先级扩展名匹配6.3 常见错误与经典案例错误示例url-pattern/*.do/url-pattern ← 这是错误的目录匹配和扩展名匹配不能混用。经典案例解析假设有以下 4 个 Servlet 映射Servlet11 → /abc/*Servlet22 → /*Servlet33 → /abcServlet44 → *.do问题 1访问 /abc/a.html匹配结果Servlet11/abc/*和 Servlet22/*都匹配但 /abc/* 更精确所以 Servlet11 响应。问题 2访问 /abc匹配结果Servlet11/abc/*和 Servlet33/abc都匹配但完全路径匹配优先级最高所以 Servlet33 响应。问题 3访问 /abc/a.do匹配结果Servlet11/abc/*和 Servlet44*.do都匹配但目录匹配优先级高于扩展名匹配所以 Servlet11 响应。问题 4访问 /a.do匹配结果Servlet22/*和 Servlet44*.do都匹配目录匹配优先级高于扩展名匹配所以 Servlet22 响应。问题 5访问 /xxx/yyy/a.do匹配结果Servlet22/*和 Servlet44*.do都匹配目录匹配优先级更高所以 Servlet22 响应。总结精确匹配 长目录匹配 短目录匹配 扩展名匹配。七、相对路径与绝对路径7.1 相对路径相对路径是相对于当前资源所在的路径通过 .当前目录和 ..上级目录来定位目标资源。示例当前页面路径http://localhost:8080/day5/user/list.html目标资源../index.html → 实际指向 http://localhost:8080/day5/index.html相对路径的缺点根据当前路径不同写法也会不同容易出错不推荐在 Web 开发中使用。7.2 绝对路径绝对路径是指从根目录开始的完整路径。方式一带协议的完整路径跨网站http://localhost:8080/day5/hello方式二以 / 开头的路径同一站点内/day5/hello7.3 客户端路径与服务器端路径的区别在 Web 开发中 / 在不同场景下有不同含义客户端路径浏览器地址栏、HTML 中的 href、src 等/ 代表服务器的根路径即 http://localhost:8080/。示例a href/day5/hello 实际访问 http://localhost:8080/day5/hello服务器端路径Servlet 跳转、请求转发、包含等/ 代表 Web 应用的根路径即 http://localhost:8080/项目名/。示例request.getRequestDispatcher(/hello) 转发到当前项目下的 /hello重要结论客户端使用时绝对路径需要带上项目上下文名称。服务器端使用时绝对路径不需要带项目上下文名称Tomcat 会自动补全。八、ServletContext8.1 ServletContext 概述ServletContext 是一个全局的、所有用户共享的存储空间。它的生命周期与 Web 应用一致当 Tomcat 启动 Web 应用时创建当 Web 应用被卸载或 Tomcat 关闭时销毁。重要特性一个 Web 应用只有一个 ServletContext 对象。所有 Servlet 共享同一个 ServletContext 对象。可以用来实现 Servlet 之间的数据通信。被称为 context 域对象可以存储键值对数据。8.2 ServletContext API 详解主要方法Object getAttribute(String name)功能获取绑定在 context 中的属性值。void setAttribute(String name, Object object)功能向 context 中绑定属性键值对。String getContextPath()功能获取当前 Web 应用的上下文路径即项目名。String getInitParameter(String name)功能获取 Web 应用的全局初始化参数。String getRealPath(String path)功能将虚拟路径转换为文件系统中的真实物理路径。InputStream getResourceAsStream(String path)功能获取指定路径资源的输入流。8.3 获取全局初始化参数在 web.xml 中配置全局参数context-param 是 web-app 的直接子元素context-param param-namecompany/param-name param-value达内教育/param-value /context-param在 Servlet 中获取ServletContext servletContext getServletContext(); String company servletContext.getInitParameter(company); System.out.println(company: company);System.out.println(company: company);注意context-param 与 init-param 的区别context-param 是全局的所有 Servlet 都能获取到。init-param 是单个 Servlet 私有的只能被当前 Servlet 获取。8.4 实现数据共享统计网站访问次数使用 ServletContext 实现所有用户共享的访问计数器Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取 ServletContext 对象 ServletContext servletContext getServletContext(); // 从 context 中获取访问次数 Integer visitNums (Integer) servletContext.getAttribute(visitNums); // 如果为空说明是第一次访问初始化为 0 visitNums (visitNums null) ? 0 : visitNums; // 次数加 1 并保存回 context visitNums; servletContext.setAttribute(visitNums, visitNums); // 响应给客户端 response.setCharacterEncoding(UTF-8); response.getWriter().write(当前网站访问人次: visitNums); }每次用户访问该 Servlet计数器都会加 1且所有用户看到的是同一个计数结果。8.5 读取资源文件的三种方式在 JavaWeb 项目中经常需要读取配置文件如 .properties 文件以下是三种常用的读取方式方式一使用 ServletContext.getRealPath() 获取绝对路径后再读取ServletContext servletContext getServletContext(); // 参数是相对于 Web 应用根目录的路径 String path servletContext.getRealPath(tx.properties); System.out.println(绝对路径: path); try (InputStream is new FileInputStream(path)) { Properties p new Properties(); p.load(is); String username p.getProperty(username); System.out.println(username: username); } catch (Exception e) { e.printStackTrace(); }方式二使用 ServletContext.getResourceAsStream() 直接获取流ServletContext servletContext getServletContext(); // 第一个 / 代表 Web 应用的根目录 try (InputStream is servletContext.getResourceAsStream(/WEB-INF/tx1.properties)) { Properties p new Properties(); p.load(is); String username p.getProperty(username); System.out.println(username: username); } catch (Exception e) { e.printStackTrace(); }方式三使用类加载器读取 classpath 下的资源// 使用当前类的类加载器读取 classes 目录下的资源 try (InputStream is this.getClass().getClassLoader() .getResourceAsStream(tx2.properties)) { Properties p new Properties(); p.load(is); String username p.getProperty(username); System.out.println(username: username); } catch (Exception e) { e.printStackTrace(); }三种方式对比方式一需要先获取绝对路径再创建流步骤稍多。方式二直接获取流更简洁推荐用于 Web 项目中读取资源。方式三不依赖 ServletContext通用性最强任何 Java 类都可以使用。