愉快的 Servlet 之旅 —— 两万字详解 Servlet

文章目录

  • 一、初识 Servlet
    • 1.1 Servlet 是什么
    • 1.2 Servlet 的主要工作
    • 1.3 Servlet 和 Tomcat 的关系
  • 二、第一个 Servlet 程序
    • 2.1 创建 Maven 项目
    • 2.2 引入依赖
    • 2.3 创建目录
    • 2.4 编写代码
    • 2.5 打包程序
    • 2.6 部署程序
    • 2.7 验证程序
  • 三、Smart Tomcat 部署程序
    • 3.1 安装 Smart Tomcat 插件
    • 3.2 配置 Smart Tomcat 插件
  • 四、访问出错的原因
    • 4.1 出现404
    • 4.2 出现405
    • 4.3 出现500
    • 4.4 出现“无法访问此网站”
  • 五、Servlet 运行原理和生命周期
    • 5.1 运行原理
    • 5.2 生命周期
  • 六、Servlet API
    • 6.1 HttpServlet 类
    • 6.2 HttpServletRequest 接口
      • 6.2.1 核心方法
      • 6.2.2 获取请求信息
      • 6.2.3 获取 GET 请求中的参数
      • 6.2.4 通过 form 表单获取 POST 请求中的参数
      • 6.2.5 通过 JSON 获取 POST 请求中的参数
    • 6.3 HttpServletResponse 接口
      • 6.3.1 核心方法
      • 6.3.2 设置状态码
      • 6.3.3 设置自动刷新
      • 6.3.4 设置重定向
  • 七、Cookie 和 Session
    • 7.1 什么是 Cookie 和 Session
    • 7.2 深入理解会话机制
    • 7.3 实现用户登录案例
  • 八、上传文件操作
    • 8.1 核心方法
    • 8.2 提交图片到服务器


一、初识 Servlet

1.1 Servlet 是什么

Servlet 是 Java 编写的服务端程序,用于处理客户端(通常是浏览器)发送的请求,并向客户端返回响应。它是Java EE 规范的一部分,提供了一种在服务器上动态生成内容的方式。

Servlet 是基于Java 的实现的,因此具有跨平台性,可以在不同的操作系统上运行。它遵循 Java 的安全性和可靠性,也能充分利用 Java 语言的优势,如面向对象编程和丰富的类库。

Servlet 主要用于开发 Web应用程序,如网站、Web服务、Web应用程序后端等。Tomcat 容器会在接收到请求的时候将其传递给相应的Servlet 类进行处理。同时,Servlet 可以根据请求参数、会话状态、数据库查询等动态生成HTML、XML、JSON等内容,然后将响应发送给客户端。

总而言之,Servlet 是一种实现动态页面的技术,是一组 Tomcat 提供给开发人员的 API,帮助开发人员能够简单高效的开发 Web 程序。

1.2 Servlet 的主要工作

Servlet 在 Java Web 开发中主要负责以下工作:

  1. 注册 Servlet 类并处理 HTTP 请求:

    • Servlet 允许程序员注册一个 Java 类,并在 Tomcat(或其他Java Servlet容器)收到特定的 HTTP 请求时执行该类中的代码。通过在web.xml 文件或使用注解来配置 Servlet 映射,将 Servlet 与特定的URLURL关联起来。
    • 当客户端发送匹配的 HTTP 请求时,Servlet 容器将实例化并调用相应的 Servlet 来处理请求。开发者可以在 Servlet 中编写业务逻辑,从请求中获取参数、执行数据库操作、生成动态内容等,并生成HTTP响应返回给客户端。
  2. 解析 HTTP 请求:

    • Servlet 帮助程序员解析 HTTP 请求,将客户端发送的 HTTP 请求从字符串解析成一个 HttpRequest 对象。HttpRequest 对象提供了各种方法,使得开发者能够轻松地获取 HTTP 请求的方法(GET、POST等)、请求头、请求参数、会话信息等。
  3. 构造 HTTP 响应:

    • Servlet 帮助程序员构造HTTP响应,使得开发者只需设置指定的 HttpResponse 对象的属性字段,Servlet 会自动根据 HTTP 协议的方式构造出一个 HTTP 响应字符串,并通过 Socket 写回给客户端。
    • HttpResponse 对象提供了设置响应状态码、响应头、响应内容等的方法,开发者可以根据业务需求进行设置,Servlet 会将设置的响应信息返回给客户端。

总的来说,Servlet 作为 Java 的服务器端技术,帮助开发者处理 HTTP 请求和响应,允许注册和执行特定的 Java 类,解析 HTTP 请求,并根据设置的 HttpResponse 对象构造 HTTP 响应。这使得 Java 开发者能够轻松地开发动态、交互式的 Web 应用程序。

1.3 Servlet 和 Tomcat 的关系

Servlet 和 Tomcat 之间有着密切的关系,可以说它们是一对密不可分的组合,以下是对它们之间关系的解释:

  1. Tomcat 是 Servlet 的容器,它提供了 Servlet 的执行环境和支持,使得 Servlet 能够在 Tomcat 服务器上运行
  2. 当在 Tomcat 上部署一个 Web 应用程序时,Tomcat 会负责加载和初始化其中的 Servlet 对象,并在接收到 HTTP 请求时调用相应的Servlet 对象来处理请求和生成响应
  3. Tomcat 还提供了管理和监控 Web 应用程序的功能,可以通过 Tomcat 的管理界面来管理已部署的 Servlet 和 Web 应用程序。

总结而言,Servlet 是 Java 编写的服务器端程序,而 Tomcat 是一个能够执行和管理 Servlet 的容器。它们共同组成了Java Web 应用程序的基础架构,使得开发者能够构建动态、交互式的 Web 应用程序。

二、第一个 Servlet 程序

2.1 创建 Maven 项目

Maven是什么:

Maven 项目是基于 Apache Maven 构建工具的 Java 项目。Maven 是一个流行的项目管理和构建工具,旨在简化和标准化 Java 项目的构建过程。通过 Maven,开发者可以更轻松地管理项目依赖、构建项目、运行单元测试、生成项目文档等。

一个典型的 Maven 项目通常包含以下几个重要部分:

  1. pom,xml文件pom.xml是 Maven 项目的核心配置文件,它位于项目的根目录下。pom.xml文件用于描述项目的元数据信息(如项目的名称、版本、描述等),定义项目的依赖关系(所需的第三方库和插件),配置项目的构建过程(编译、打包等),以及其他项目相关配置。

  2. src目录:该目录是项目的源代码和资源文件的根目录。通常包含两个子目录:

    • src/main:包含主要的源代码和资源文件;
    • src/test:包含单元测试代码和资源。
  3. target目录:该目录是 Maven 构建生成的输出目录,其中包含构建后的项目生成的输出文件,如编译后的类文件、打包后的JAR文件和WAR文件。

  4. Maven 生命周期和插件:Maven 定义了一套标准的生命周期和插件,用于在构建过程中执行不同的任务。开发者可以通过配置pom.xml文件来定义项目在不同构建阶段执行的任务,如编译、测试、打包、部署等。

使用 IDEA 创建 Maven 项目:

  1. 在 IDEA 中新建一个项目,在 Build System选项中选择 Maven,注意项目路径不要出现中文或其他特殊符号。

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第1张图片

  1. 出现以下界面,说明创建成功

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第2张图片

2.2 引入依赖

当 Maven 项目创建完成后,会自动生成一个 pom.xml文件,我们需要在 pom.xml 文件中引入 Servlet API 所依赖的 JAR 包。

  1. 在中央仓库(https://mvnrepository.com)中搜索 servlet,一般第一个结果就是我们所需要的 API。

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第3张图片
2. 选择版本,这里我选择的是3.1.0版本。

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第4张图片

关于 Servlet 和 Tomcat 版本的匹配问题:

  • 可以在 https://tomcat.apache.org/whichversion.html 上查询版本对应关系。
  • 因为我的 Tomcat 版本是8.5 的,因此我就选择使用 Servlet API 3.1.0:
    愉快的 Servlet 之旅 —— 两万字详解 Servlet_第5张图片
  1. 将中央仓库提供的Maven xml复制粘贴到pom.xml文件中

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第6张图片
注意要新增一个 dependencies标签,将其复制到此标签中:
愉快的 Servlet 之旅 —— 两万字详解 Servlet_第7张图片

2.3 创建目录

当创建完 Maven 项目后,IDEA 会自动帮我们生成如下的目录结构:
愉快的 Servlet 之旅 —— 两万字详解 Servlet_第8张图片
这些目录中:

  • src 表示源代码所在的目录;
  • main/java 表示源代码的根目录,后续创建 .java 文件就放到这个目录中;
  • main/resources 表示项目的一些资源文件所在的目录;
  • test/java 表示测试代码的根目录。

有了这些目录还不够,我们还需要创建以下新的目录和文件。

1. 创建 webapp 目录

main目录下,创建一个与 java 目录并列的 webapp 目录,该目录用于存放 Web 应用程序的 Web 资源,例如 HTML 页面、JSP文件、CSS样式、JavaScript脚本等

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第9张图片

2. 创建 WEB-INF目录
webapp 目录下,创建一个名为 WEB-INF 的目录。该目录是 Web 应用程序中的特殊目录,用于存放 Web 应用程序的配置和受保护的资源

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第10张图片

3. 创建 web.xml 文件

WEB-INF 目录下,创建一个名为 web.xml 的文件,该文件是 Java Web 应用程序的部署描述符,用于配置 Servlet、Filter、Listener 等 Web 组件以及其他应用程序相关的配置信息
愉快的 Servlet 之旅 —— 两万字详解 Servlet_第11张图片

4. 编写 web.xml 文件

web.xml 文件中拷贝以下代码:

DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
    <display-name>Archetype Created Web Applicationdisplay-name>
web-app>

2.4 编写代码

java 目录中创建一个 HelloServlet 类,代码如下:

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // super.doGet(req, resp);
        System.out.println("hello world");
        resp.getWriter().write("hello world");
    }
}

对这段代码的解释:

  • 创建一个继承自 HttpServletHelloServlet 类;
  • 在这个类上方加上 @WebServlet("/hello") 的注解,用于指定 Servlet 的 URL 映射。在这个示例中,Servlet 将被映射到 URL 路径为/hello,当客户端发起对/hello的 HTTP 请求时,HelloServlet 将被调用。
  • 重写 doGet 方法doGet 的参数有两个,分别表示收到的 HTTP 请求和要构造的 HTTP 响应。这个方法会在 Tomcat 收到 GET 请求时触发被 Tomcat 自动调用
  • HttpServletRequest 表示 HTTP 请求,Tomcat 按照 HTTP 请求的格式把字符串格式的请求转成了一个 HttpServletRequest 对象,后续想获取请求中的信息(比如方法、urlheaderbody 等)都是通过这个对象来获取。
  • HttpServletResponse 表示 HTTP 响应,需要程序员在代码中把这个对象构造好(如构造响应的状态码、headerbody 等)。
  • resp.getWriter() 会获取到一个 Writer 字符流对象,通过这个流对象就可以写入一些数据,写入的数据会被构造成一个 HTTP 响应的 body 部分,Tomcat 会把整个响应转成字符串,通过 socket 写回给客户端。

虽然这几行代码很少,但是缺包含了大量的信息:

  1. 在 Servlet 程序的代码中,不需要自己编写 main 方法作为程序的入口了,因为 main 方法已经包含在 Tomcat 里面了,我们自己编写的代码会在合适的时候被 Tomcat 所调用
  2. 我们写的代码并不是一个完整的程序,而是 Tomcat 这个程序的一小部分逻辑,如果要想在合适的时候正确的被 Tomcat 调用,需要满足一下三个条件
    1)创建的类需要继承自 HttpServlet 类;
    2)这个类需要使用 @WebServlet 注解关联上一个 HTTP 的路径;
    3) 这个类需要实现 doXXX 方法。

2.5 打包程序

  1. 在 IDEA 中可以使用 Maven 进行打包,一般在 IDEA 右侧就可以看到这个 Maven 窗口,如果看不到的话,可以通过 View -> View -> Tool Window -> Maven 打开。

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第12张图片

  1. 展开 Lifecycle,双击 package 即可进行打包

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第13张图片

当出现如下信息,则表示打包成功:
愉快的 Servlet 之旅 —— 两万字详解 Servlet_第14张图片

打包成功后,可以看到在 target 目录下,生成了一个 JAR 包:

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第15张图片

但是,这样的 JAR 包并不是我们所需要的,Tomcat 需要识别的是另一种 WAR 包的形式。并且这个包的名字也太复杂了,我们希望有一个简单的名字。

  1. 修改 pom.xml配置文件,以生成 WAR 包和修改 WAR 包名称

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第16张图片

  • pom.xml 中新增一个 packing 标签,表示打包的方式是打一个 WAR 包;
  • 再新增一个 build 标签,内置一个 finalName 标签,表示打出的 WAR 包的名字是 hello_servlet
  1. 重新使用 Maven 进行打包

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第17张图片

2.6 部署程序

将上面打包形成的 WAR 包文件复制到 Tomcat 的 webapps目录下:
愉快的 Servlet 之旅 —— 两万字详解 Servlet_第18张图片
运行 Tomcat,会将其自动解压,此时便部署完成。

2.7 验证程序

此时通过浏览器访问 http://127.0.0.1:8080/hello_servlet/hello,就可以看到以下结果了:
愉快的 Servlet 之旅 —— 两万字详解 Servlet_第19张图片

此时,我们的第一个 Servlet 程序就已经完成了,如果看到这里,你也会相信这就是学习到目前为止的最麻烦的一个hello world程序了吧。

三、Smart Tomcat 部署程序

以上的手动打包,然后拷贝到 Tomcat 的过程显得非常繁琐,但是 IDEA 上为我们提供了更加方便的打包部署程序的插件。“Smart Tomcat” 插件就是是为了简化和改进在 IntelliJ IDEA 中使用 Apache Tomcat 的体验而开发的。使用 “Smart Tomcat” 插件,可以更方便地配置和管理 Tomcat 服务器,轻松部署和调试 Java Web 应用程序。

3.1 安装 Smart Tomcat 插件

在 IDEA 上打开:File -> Settings -> plugins,然后搜索 Smart Tomcat,然后按照重启 IDEA 即可。
愉快的 Servlet 之旅 —— 两万字详解 Servlet_第20张图片

3.2 配置 Smart Tomcat 插件

  1. 在 IDEA 右上角点击 “Current File”

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第21张图片

  1. 然后点击 “Edit Configurations”

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第22张图片

  1. 点击 “Add new run configurations”

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第23张图片

  1. 选择 “Smart Tomcat”

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第24张图片

  1. 然后进行如下配置

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第25张图片

  1. 当配置完成后,我们会发现开始的 “Current File” 位置就变成了 “run”,然后点击旁边的绿色三角形,就能够自动运行 Tomcat 并部署我们的程序了。
  2. 运行


此时 Tomcat 日志就会输出在 IDEA 的控制台中,并且不会出现乱码。

  1. 访问服务器

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第26张图片

四、访问出错的原因

4.1 出现404

404 表示用户访问的资源不存在,大概率是 URL 的路径写的不正确。

错误示例一:少写了 “Context Path”,直接通过 “/hello” 访问服务器

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第27张图片

错误示例二:少写了 “Servlet Path”,直接通过 “/hello_servlet” 访问服务器

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第28张图片

4.2 出现405

405 表示对应的 HTTP 请求方法没有实现。

例如,现在将HelloServlet类中重写的 doGet方法注释掉,然后重启 Tomcat 再次访问服务器

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第29张图片

4.3 出现500

出现 500 的原因往往是 Servlet 代码中抛出异常导致的。

例如,修改 HelloServlet 中的 doGet 方法如下,然后重启 Tomcat 并访问服务器。

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String s = null;
    resp.getWriter().write(s);
}

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第30张图片
此时出现了空指针异常,表明服务器内部出错,因此返回的响应码为 500。

4.4 出现“无法访问此网站”

出现这种情况一般就是没有启动 Tomcat 或者启动 Tomcat 服务器失败。

比如,当 “Servlet Path” 写错的时候,就会出现启动 Tomcat 服务器失败。

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第31张图片

此时,启动 Tomcat,就会出错:

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第32张图片
访问服务器就会出现 “无法访问此网站”
愉快的 Servlet 之旅 —— 两万字详解 Servlet_第33张图片

五、Servlet 运行原理和生命周期

5.1 运行原理

Servlet 是 Java 中用于处理 Web 请求和响应的组件。它运行在 Web 服务器内部,通过 Web 容器(如Apache Tomcat)来管理其生命周期和请求调度。

Servlet运行的主要原理如下:

  1. Web容器初始化:当 Web 服务器启动时,Web 容器会加载并初始化 Servlet 。在初始化过程中,Web 容器会创建 Servlet 实例,并调用其 init() 方法。在这个方法中,Servlet 可以进行一些初始化操作,例如加载配置信息、建立数据库连接等。

  2. 接收HTTP请求:当用户在浏览器中访问一个URL时,Web服务器接收到HTTP`请求。

  3. 请求分发:Web 容器根据请求的 URL 和部署描述符(web.xml)中的配置信息来确定应该由哪个 Servlet 来处理该请求。

  4. 调用 Servlet 的 service() 方法:一旦确定了要由哪个 Servlet 处理请求,Web 容器会在 Servlet 实例上调用 service() 方法,并将请求和响应对象作为参数传递给它。

  5. Servlet 处理请求:在 service() 方法中,Servlet 根据 HTTP 请求类型(GET、POST、PUT等)来执行相应的逻辑,处理请求并生成响应。

  6. 返回 HTTP 响应:处理完请求后,Servlet 会将生成的 HTTP 响应写回到响应对象中。

  7. 销毁:在 Web 服务器关闭时或 Servlet 不再需要时,Web 容器会调用 Servlet 的 destroy() 方法,使其有机会进行一些资源释放和清理工作。

5.2 生命周期

Servlet 的生命周期主要包括以下三个阶段:

  1. 初始化(Initialization)

    • 在 Servlet 容器启动时,会读取部署描述符(web.xml 或注解配置)并加载 Servlet 类。
    • 创建 Servlet 实例并调用其 init(ServletConfig config) 方法来进行初始化。
    • init() 方法只会在 Servlet 第一次被加载时调用一次,用于执行一些初始化操作,比如加载配置信息、建立数据库连接等。
  2. 请求处理(Request Handling)

    • 一旦 Servlet 初始化完成,它就处于可服务状态,可以处理客户端的请求。
    • 当客户端发送请求到服务器并匹配到该 Servlet 处理请求时,Servlet 容器会在同一个 Servlet 实例上调用 service(ServletRequest request, ServletResponse response) 方法。
    • service() 方法根据请求类型(GET、POST、PUT 等)来调用相应的 doXXX() 方法(例如 doGet()doPost()doPut() 等)来处理请求。
  3. 销毁(Destruction)

    • 当 Servlet 容器关闭时或者 Servlet 不再被需要时,它将销毁 Servlet 实例。
    • 在销毁之前,Servlet 容器会调用 Servlet 的 destroy() 方法来进行一些资源释放和清理操作。
    • destroy() 方法只会在 Servlet 卸载之前调用一次,用于执行一些善后工作,比如关闭数据库连接、释放资源等。

整个 Servlet 生命周期如下图所示:

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第34张图片

注意:

  • Servlet 容器负责管理 Servlet 的生命周期,开发者无需手动创建和销毁 Servlet 实例。
  • 在整个生命周期中,Servlet 可能被实例化多次,但 init()destroy() 方法只会调用一次。
  • 在处理请求期间,每个请求都会在单独的线程上调用 service() 方法,并且多个请求可以并发地访问同一个 Servlet 实例,因此开发者应确保 Servlet 的线程安全性。

六、Servlet API

Servlet API 是用于开发 Java Servlet 程序的一组接口和类的集合。它提供了一种标准化的方式来处理 Web 请求和响应,使开发人员能够编写基于 Java 的 Web 应用程序。

Servlet API 位于 Java EE 规范中,开发人员可以通过 Java Servlet 规范来了解这些接口和类的详细信息。在 Java 中,Servlet API 通常由 javax.servlet 包和 javax.servlet.http 包中的类和接口组成

下面是 Servlet API 中一些核心的类和接口以及它们的简要说明:

类/接口 说明
javax.servlet.Servlet Servlet接口,所有Servlet类都必须实现该接口。
javax.servlet.GenericServlet Servlet抽象类,实现了Servlet接口的大部分方法。
javax.servlet.http.HttpServlet HttpServlet类,用于处理HTTP相关的请求和响应。
javax.servlet.ServletConfig 接口,用于获取Servlet的初始化参数。
javax.servlet.ServletContext 接口,用于在整个Web应用程序中共享信息。
javax.servlet.ServletException 异常类,Servlet可能抛出的异常的基类。
javax.servlet.ServletRequest 接口,用于表示客户端请求的对象。
javax.servlet.ServletResponse 接口,用于表示Servlet的响应对象。
javax.servlet.http.HttpServletRequest 表示HTTP请求的接口,扩展自ServletRequest。
javax.servlet.http.HttpServletResponse 表示HTTP响应的接口,扩展自ServletResponse。
javax.servlet.http.HttpSession 用于在客户端和服务器之间维护状态的接口。
javax.servlet.RequestDispatcher 接口,用于将请求转发到其他资源(Servlet、JSP等)。
javax.servlet.Filter 过滤器接口,用于在请求和响应之间执行过滤操作。
javax.servlet.FilterChain 过滤器链,用于在多个过滤器之间传递请求和响应。

6.1 HttpServlet 类

HttpServlet 是 Java Servlet API 中的一个抽象类,它扩展了 GenericServlet 类,并为处理基于 HTTP 协议的请求和响应提供了更方便的方法。HttpServlet 是编写基于 HTTP 的 Servlet 的常用基类

以下是 HttpServlet 类中常用的方法:

方法 说明
void init(ServletConfig config) 在 Servlet 实例被创建后立即调用,用于初始化该 Servlet。
void destroy() 在 Servlet 容器正常关闭时调用,用于释放资源和清理操作。
void doGet(HttpServletRequest request, HttpServletResponse response) 处理 HTTP GET 请求。
void doPost(HttpServletRequest request, HttpServletResponse response) 处理 HTTP POST 请求。
void doPut(HttpServletRequest request, HttpServletResponse response) 处理 HTTP PUT 请求。
void doDelete(HttpServletRequest request, HttpServletResponse response) 处理 HTTP DELETE 请求。
void service(HttpServletRequest request, HttpServletResponse response) 根据请求的 HTTP 方法调用相应的 doXXX 方法进行处理。
ServletConfig getServletConfig() 获取当前 Servlet 的配置信息。
String getServletInfo() 获取 Servlet 的描述信息。
String getInitParameter(String name) 获取指定名称的初始化参数值。
Enumeration getInitParameterNames() 获取所有初始化参数名称的枚举。
ServletContext getServletContext() 获取 Servlet 上下文对象,用于访问 Servlet 环境的信息。
String getServletName() 获取 Servlet 的名称。
void log(String msg) 记录日志消息到 Servlet 容器的日志系统。
void log(String msg, Throwable throwable) 记录异常信息到 Servlet 容器的日志系统。
void setInitParameter(String name, String value) 设置初始化参数的值。
boolean isAsyncSupported() 检查 Servlet 是否支持异步处理。
void startAsync() 启动异步处理。
void startAsync(ServletRequest request, ServletResponse response) 以指定请求和响应启动异步处理。

以下是 HttpServlet 类的一些重要特点:

  1. 继承关系HttpServlet 继承自 GenericServlet,因此它拥有 GenericServlet 中的一些通用功能,但它专门用于处理 HTTP 请求和响应。

  2. 提供 HTTP 请求处理方法HttpServlet 提供了针对不同 HTTP 请求方法(GET、POST、PUT、DELETE 等)的处理方法,这些方法可以被子类重写来处理特定的请求类型。

  3. 默认实现HttpServlet 中这些 HTTP 请求处理方法默认实现是返回 HTTP 状态码 405(Method Not Allowed),表示不支持对应的请求方法。因此,子类需要根据需要重写这些方法以提供自定义的请求处理逻辑

  4. 获取请求信息HttpServletRequest 参数提供了访问客户端 HTTP 请求信息的方法,如获取请求 URL、请求参数、请求头、会话信息等。

  5. 设置响应信息HttpServletResponse 参数提供了设置 HTTP 响应内容、响应状态码、响应头等的方法。

由于 HttpServlet 是一个抽象类,不能直接实例化,开发者需要编写具体的子类来继承 HttpServlet,然后实现相应的请求处理方法,根据业务逻辑处理不同类型的 HTTP 请求,并生成相应的 HTTP 响应

6.2 HttpServletRequest 接口

HttpServletRequest 是Java Servlet API 中的一个接口,用于表示客户端发起的 HTTP 请求。它提供了访问客户端请求信息的方法,开发者可以通过该接口获取请求的各种属性、参数、头信息等。该接口继承自 ServletRequest接口,专门用于处理 HTTP 请求。

6.2.1 核心方法

以下是一些常用的 HttpServletRequest 接口的方法:

1. 获取请求信息

方法 说明
String getMethod() 获取 HTTP 请求方法,例如 GET、POST、PUT 等。
String getRequestURI() 获取请求的 URI(Uniform Resource Identifier)部分,即去除协议、主机、端口号后的路径部分。
String getQueryString() 获取请求的查询字符串,即 URL 中 “?” 后面的部分。
String getProtocol() 获取请求使用的协议,如 “HTTP/1.1”。
String getContextPath() 获取应用程序的上下文路径(Context Path),即应用程序的根路径。

2. 获取请求参数

方法 说明
String getParameter(String name) 获取指定名称的请求参数值。
Enumeration getParameterNames() 获取所有请求参数名称的枚举。
String[] getParameterValues(String name) 获取指定名称的请求参数值数组,用于处理同名参数。

3. 获取请求头信息

方法 说明
String getHeader(String name) 获取指定名称的请求头信息。
Enumeration getHeaderNames() 获取所有请求头名称的枚举。

4. 获取会话信息

方法 说明
HttpSession getSession() 获取与该请求关联的 HttpSession 对象,如果没有会话,则创建一个新的会话。
HttpSession getSession(boolean create) 根据指定的 create 参数决定是否创建新的会话。true代表创建,false代表不创建。

5. 获取客户端信息

方法 说明
String getRemoteAddr() 获取客户端的 IP 地址。
String getRemoteHost() 获取客户端的主机名。
int getRemotePort() 获取客户端连接的端口号。

6. 其他常用方法

方法 说明
Cookie[] getCookies() 获取客户端发送的所有 Cookie。
Locale getLocale() 获取客户端的首选语言环境。
Enumeration getLocales() 获取客户端支持的所有语言环境的枚举。

通过使用 HttpServletRequest 接口提供的这些方法,开发者可以在 Servlet 中获取请求的各种信息,从而更好地处理和响应客户端的请求。

6.2.2 获取请求信息

创建一个 ShowHeaderServlet 类, 用于获取请求信息并返回响应。它可以响应 HTTP GET 请求,并将请求的一些信息以及请求头信息返回到客户端。

@WebServlet("/show")
public class ShowHeaderServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf8");
        StringBuilder respBody = new StringBuilder();
        respBody.append(req.getProtocol());
        respBody.append("
"
); respBody.append(req.getMethod()); respBody.append("
"
); respBody.append(req.getRequestURL()); respBody.append("
"
); respBody.append(req.getContextPath()); respBody.append("
"
); respBody.append(req.getQueryString()); respBody.append("
"
); respBody.append("

headers:

"); Enumeration<String> headerNames = req.getHeaderNames(); while (headerNames.hasMoreElements()){ String headerName = headerNames.nextElement(); respBody.append(headerName).append(": ").append(req.getHeader(headerName)); respBody.append("
"
); } resp.getWriter().write(respBody.toString()); } }

访问服务器,可以发现响应内容如下:

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第35张图片

6.2.3 获取 GET 请求中的参数

创建一个 GetParameterServlet 类用于获取 GET 请求中的参数。

GET 方法的查询字符串说明:

  • 在 HTTP 请求中,GET 方法通过将参数附加到 URL 的查询字符串(Query String)部分来传递数据查询字符串是在 URL 的问号(?)后面的部分,用于向服务器传递键值对参数。它的格式通常为: http://example.com/path/to/resource?key1=value1&key2=value2&key3=value3...

代码如下:

@WebServlet("/getParameter")
public class GetParameterServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 期待收到的请求:http://localhost:8080/hello_servlet/getParameter?studentId=10&classId=20
        resp.setContentType("text/html; charset=utf8");

        String studentId = req.getParameter("studentId");
        String classId = req.getParameter("classId");
        resp.getWriter().write("studentId: " + studentId + ", classId: " + classId);
    }
}

访问服务器结果:
愉快的 Servlet 之旅 —— 两万字详解 Servlet_第36张图片

6.2.4 通过 form 表单获取 POST 请求中的参数

1. 首先约定前后端的交互接口

POST /postParameter

2. 前端创建页面testPost.html,里面创建一个 form 表单

    <form action="postParameter" method="post">
        <input type="text" name="studentId">
        <input type="text" name="classId">
        <input type="submit" value="提交">
    form>

3. 后端创建一个 PostParameterServlet 类,用于接收 form表单提交的数据

@WebServlet("/postParameter")
public class PostParameterServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf8");

        String studentId = req.getParameter("studentId");
        String classId = req.getParameter("classId");
        resp.getWriter().write("studentId: " + studentId + ", classId: " + classId);
    }
}

4. 访问服务器页面,然后提交参数

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第37张图片
5. 提交数据后会自动跳转至响应页面
愉快的 Servlet 之旅 —— 两万字详解 Servlet_第38张图片
6. 通过 Fiddler工具抓包,可以发现通过 POST 方法传递的参数在 body

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第39张图片

6.2.5 通过 JSON 获取 POST 请求中的参数

1. 约定前后端交互接口

POST /jsonPostParameter

2. 后端获取 body 并通过 Jackson 解析 JSON 字符串

在Java代码中,需要引入 Jackson 这个库,完成对 JSON 的解析工作:

  1. 在中央仓库中搜索 jackson,选择 “JackSon Databind”:
    愉快的 Servlet 之旅 —— 两万字详解 Servlet_第40张图片
  2. 把中央仓库中的依赖拷贝到 pom.xml文件中,形如:
    愉快的 Servlet 之旅 —— 两万字详解 Servlet_第41张图片

创建一个JsonPostServlet 类:

class Student {
    public String studentId;
    public String classId;
}

@WebServlet("/jsonPostParameter")
public class JsonPostParameterServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        Student student = objectMapper.readValue(req.getInputStream(), Student.class);
        resp.getWriter().write("studentId: " + student.studentId + ", classId: " + student.classId);
    }
}

其中,Student类用于储存 JSON 解析出来的数据。

3. 启动服务器,然后通过 Postman 工具发送 JSON 格式的数据

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第42张图片
4. 服务器响应内容
愉快的 Servlet 之旅 —— 两万字详解 Servlet_第43张图片

6.3 HttpServletResponse 接口

HttpServletResponse 也是 Java Servlet API 中的一个接口,它表示用于发送 HTTP 响应给客户端的对象。通过这个接口,Servlet 可以设置响应的内容、响应头、状态码等,以便向客户端发送适当的响应。

6.3.1 核心方法

下面是 HttpServletResponse 接口的核心方法及其说明:

1. 设置响应头和状态码

方法 说明
void setStatus(int sc) 设置响应的状态码。
void setStatus(int sc, String sm) 设置响应的状态码和描述信息。
int getStatus() 获取响应的状态码。
String getHeader(String name) 获取指定名称的响应头的值。
Collection getHeaderNames() 获取所有响应头名称的集合。
Collection getHeaders(String name) 获取指定名称的响应头的所有值的集合。
void setHeader(String name, String value) 设置指定名称的响应头的值。
void addHeader(String name, String value) 添加指定名称的响应头的值(可以多次调用)。
void setIntHeader(String name, int value) 设置指定名称的整数响应头的值。
void addIntHeader(String name, int value) 添加指定名称的整数响应头的值(可以多次调用)。

2. 设置响应内容

方法 说明
PrintWriter getWriter() 获取用于发送字符文本的 PrintWriter 对象。
ServletOutputStream getOutputStream() 获取用于发送二进制数据的 ServletOutputStream 对象。
void setContentLength(int len) 设置响应内容的长度。
void setContentType(String type) 设置响应内容的 MIME 类型。
String getContentType() 获取响应内容的 MIME 类型。
void setCharacterEncoding(String charset) 设置响应内容的字符编码。

3. 缓存控制

方法 说明
void setDateHeader(String name, long date) 设置指定名称的日期型响应头的值。
void addDateHeader(String name, long date) 添加指定名称的日期型响应头的值(可以多次调用)。
void setExpires(long expires) 设置响应的过期时间。
void addCookie(Cookie cookie) 添加一个 Cookie 到响应中。

4. 重定向和错误处理

方法 说明
void sendRedirect(String location) 重定向到另一个 URL
void sendError(int sc) 发送一个错误状态码。
void sendError(int sc, String msg) 发送一个错误状态码和描述信息。
void reset() 重置响应对象,清除所有设置的响应数据。

5. 其他方法

方法 说明
void setBufferSize(int size) 设置响应缓冲区的大小。
int getBufferSize() 获取响应缓冲区的大小。
void flushBuffer() 强制将缓冲区中的数据发送到客户端。
boolean isCommitted() 检查是否已经提交响应。
void resetBuffer() 重置响应缓冲区。
void setLocale(Locale loc) 设置响应内容的区域设置。

这些方法提供了处理 Servlet 响应的各种操作和设置,开发人员可以根据需要使用这些方法来构建定制化的 HTTP 响应。

6.3.2 设置状态码

创建一个 StutasServlet类,用于接收一个状态码参数,然后在响应中设置该状态码:

@WebServlet("/status")
public class StatusServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 期待收到请求:http://localhost:8080/hello_servlet/status?status=200
        String statusStr = req.getParameter("status");
        int status = Integer.parseInt(statusStr);
        resp.setStatus(status);
        resp.getWriter().write("status: " + status);
    }
}

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第44张图片
通过 Fiddler 工具抓包查看设置的响应状态码:
愉快的 Servlet 之旅 —— 两万字详解 Servlet_第45张图片

6.3.3 设置自动刷新

创建一个 AutoRefreshServlet 类,当浏览器访问时会每隔一秒自动刷新一次页面,并显示当前的时间戳。

@WebServlet("/autoRefresh")
public class AutoRefreshServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setHeader("refresh", String.valueOf(1));
        Timestamp timestamp = new Timestamp(System.currentTimeMillis());
        resp.getWriter().write("timestamp: " + timestamp.toString());
    }
}

其中,通过 HTTP 响应报头中的 Refresh 字段,可以控制浏览器自动刷新的时机。
愉快的 Servlet 之旅 —— 两万字详解 Servlet_第46张图片

6.3.4 设置重定向

创建一个RedirectServlet类,返回一个重定向 HTTP 请求,自动跳转到另一个页面。

@WebServlet("/redirect")
public class RedirectServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.sendRedirect("https://csdn.net");
    }
}

启动程序,通过 URL: http://localhost:8080/hello_servlet/redirect访问,可以看到页面自动跳转到 CSDN 主页了。

通过 Fiddler 抓包可以看到,响应设置了 302 重定向状态码,重定向的位置就是 https://csdn.net
愉快的 Servlet 之旅 —— 两万字详解 Servlet_第47张图片

七、Cookie 和 Session

7.1 什么是 Cookie 和 Session

Cookie:

  • Cookie 是一种在客户端(浏览器)存储小型数据的机制,以便在客户端和服务器之间传递数据。
  • 服务器可以在 HTTP 响应头中通过 Set-Cookie 标头发送一个或多个 Cookie 到客户端,客户端会将这些 Cookie 存储在本地。
  • 之后,当客户端再次请求同一个服务器,它会将之前存储的 Cookie 自动包含在请求头中的 Cookie 标头内,从而将相关数据传递给服务器。

Session:

  • Session 是一种服务器端存储数据的机制,用于跟踪用户在网站上的状态和活动
  • 当用户首次访问网站时,服务器会为每个用户创建一个唯一的会话标识(通常是一个长随机字符串),并将其存储在 Cookie 中,发送到客户端。
  • 之后,客户端的每个请求都会自动包含会话标识,服务器通过会话标识识别用户,并在服务器端的会话存储中保存用户的状态和数据

7.2 深入理解会话机制

服务器在同一时刻一般都会收到很多请求,因此就需要区分清楚哪一个请求是属于哪一个用户(客户端)的,所有就要求服务器与每一个用户都维持一个会话,并且这个会话中包含了这个用户的唯一标识以及与用户信息的对应关系

因此,会话的本质就是一个 “哈希表”,其中的key就是对用户的唯一标识(sessionId),value就是客户端信息(可以根据需求灵活设置)。

  • sessionId 是由服务器生成的一个 “唯一性字符串”,用来唯一标识用户的身份信息。
  • 从 Session 机制的角度来看,这个唯一性字符串称为 sessionId,但是站在整个登录流程中看待,也可以把这个唯一性字符串称为 token
  • sessionIdtoken 可以理解成是同一个东西在不同视角的不同叫法。
  • 例如:愉快的 Servlet 之旅 —— 两万字详解 Servlet_第48张图片

服务端与客户端维持会话的基本流程:

  1. 当用户登录的时候,服务器会在 Session 中新增一条新的记录,并把生成的 sessionId 通过 Set-Cookie字段返回给客户端,然后将其保存在浏览器中。
  2. 客户端后续再向服务端发起请求的时候,都会在请求中将 Cookie 中的 sessionId 通过 Cookie 字段发送给服务器。
  3. 服务器在收到用户请求后,会根据请求中的 sessionId 在 Session 中找到与这个用户对应的用户信息,维持与该用户的会话,并处理请求任务。

另外,Servlet 中的 Session 默认是保存到内存中的,那么就意味着:如果重启服务器则 Session 数据就会消失。为了避免这个问题,可以使用其他方式存储 Session 数据,如数据库或分布式缓存。

7.3 实现用户登录案例

在用户登录案例中,通常的流程如下:

  1. 用户在登录页面输入用户名和密码,点击登录按钮提交表单。
  2. 服务器接收表单提交的数据,验证用户身份。
  3. 如果验证成功,服务器创建一个 Session,将用户信息存储在其中,并生成一个会话标识(Session ID)。
  4. 服务器通过 Set-Cookie 响应头将会话标识发送给客户端,存储在客户端的 Cookie 中。
  5. 客户端在后续的每个请求中自动包含会话标识的 Cookie,以便服务器识别用户。
  6. 服务器通过会话标识从会话存储中取出用户信息,识别用户身份,实现用户的登录状态。

以下是简单的登录案例:

这个代码中主要是通过 HttpSession 类完成,并不需要我们手动操作 Cookie 对象。

  1. 创建 LoginServlet 类,用于实现登录逻辑。
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf8");
        resp.setContentType("text/html; charset=utf8");

        // 1. 获取输入的用户名和密码
        String username = req.getParameter("username");
        String password = req.getParameter("password");

        // 2. 判断用户名和密码是否合法
        if(!username.equals("admin") || !password.equals("123456")){
            resp.getWriter().write("用户名或密码错误!");
            return;
        }

        // 3. 登录成功,设置 Session
        HttpSession session = req.getSession(true); // Session 不存在则创建
        session.setAttribute("username", username);
        session.setAttribute("loginCount", 0);

        // 4. 重定向到 index
        resp.sendRedirect("index");
    }
}
  1. 创建 IndexServlet 类,用于判断用户是否登录,如果登录则显示用户信息。
@WebServlet("/index")
public class IndexServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf8");
        resp.setContentType("text/html; charset=utf8");

        // 1. 获取 Session,判断用户是否登录
        HttpSession session = req.getSession(false);  // Session 不存在也不创建
        if(null == session){
            // 用户没有登录,重定向到 login.html
            resp.sendRedirect("login.html");
            return;
        }

        // 2. 已经登录,获取 Session 中保存的用户名和登录次数
        String username = (String)session.getAttribute("username");
        Integer loginCount = (Integer) session.getAttribute("loginCount");

        loginCount += 1;
        session.setAttribute("loginCount", loginCount);

        // 3. 展示用户信息到页面上
        StringBuilder respBody = new StringBuilder();
        respBody.append(String.format("
用户名:%s
"
, username)); respBody.append(String.format("
loginCount: %d
"
, loginCount)); resp.getWriter().write(respBody.toString()); } }
  1. webapp目录下创建 login.html,用于提交登录用户名和密码。
<form action="login" method="post">
    <input type="text" name="username">
    <input type="password" name="password">
    <input type="submit" value="登录">
form>
  1. 运行程序

访问 http://localhost:8080/hello_servlet/index,由于没有登录,跳转至 login.html

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第49张图片

正确输入用户名和密码:

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第50张图片

成功登录,跳转至 index,成功显示 Session 中设置的用户信息:

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第51张图片

八、上传文件操作

文件上传是 Web 开发中常见的操作,Java Servlet 提供了处理文件上传的支持。在 Java Servlet 中,文件上传通常使用 javax.servlet.http.Part 类或 javax.servlet.http.HttpServletRequest 的相关方法来实现。

8.1 核心方法

HttpServletRequest 接口相关方法:

方法 说明
Part getPart(String name) 获取请求中给定的 name 文件
Collection getParts() 获取所有文件

Part类相关方法:

方法 说明
String getSubmittedFileName() 获取提交的文件名
String getContentType() 获取提交的文件类型
long getSize() 获取文件的大小
void write(String path) 把提交的文件数据写入磁盘文件

8.2 提交图片到服务器

1. 在 webapp目录下,创建 upload.html用于提交图片文件。

<body>
    <form action="upload" enctype="multipart/form-data" method="post">
        <input type="file" name="image">
        <input type="submit" value="提交图片">
    form>
body>

上传文件一般通过 POST 请求的 form 表单实现,并且在 form 标签中要加上 multipart/form-data 字段。

2. 创建 UploadServlet类,用于处理客户端提交图片的请求。

@WebServlet("/upload")
@MultipartConfig
public class UploadServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1. 获取图片文件
        Part image = req.getPart("image");
        // 2. 打印图片信息
        System.out.println("图片名称:" + image.getSubmittedFileName());
        System.out.println("图片类型:" + image.getContentType());
        System.out.println("图片大小:" + image.getSize());

        // 3. 将图片保存到磁盘
        image.write("D:/image/tomcat.jpg");

        resp.getWriter().write("upload OK!");
    }
}

注意事项:

  1. 需要给 UploadServlet 加上 @MultipartConfig 注解,否则服务器代码无法使用 getPart 方法。
  2. getPart 的参数需要和 form 表单中 input 标签的 name 属性对应。
  3. 客户端一次可以提交多个文件(使用多个 input 标签),此时服务器可以通过 getParts 获取所有的 Part 对象。

3. 运行程序,并提交一张图片。

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第52张图片

此时发现 IDEA 控制台打印的信息:

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第53张图片

D盘的 image 目录下多了一张图片:

愉快的 Servlet 之旅 —— 两万字详解 Servlet_第54张图片

你可能感兴趣的:(Java进阶,servlet)