Servlet

文章参考:http://c.biancheng.net/servlet2/

2022/11/16 14:44 我这里需要重申几个学习方面的原则和观点,学习某些知识点并不是一定说现在我一定要把它们理解透彻之类,特别是JAVA这个框架实在是有太多太多的知识点需要了解了,因此我在学习这些知识点的时候大多数只是在看的过程中理解并做下笔记即可,并不会说向学习C++一样一个知识点一个知识点的琢磨着进行,总而言之针对不同的时间节点以及所处环境和目标学习知识,我们应当是动态的调整我们的学习方法,最大化我们的学习效率;

一、前言

Servlet 是 Server Applet 的简称,译作“服务器端小程序”,它是一种基于 Java 技术的 Web 组件,运行在服务器端,由 Servlet 容器管理,用来生成动态的 Web 内容,是一种使用Java语言来开发动态网站的技术;

简单来说Server Applet实际上就是一个按照 Servlet 规范编写的 Java 类,它具有平台独立性,可以被编译成字节码,移植到任何支持 Java 技术的服务器中运行;

Servlet 可以使用所有的 Java API,换句话说就是,Java 能做的事情,Servlet 都能做;

使用 Servlet,可以获取用户通过网页表单提交的数据、访问数据库,还可以创建动态网页;

在 Servlet 出现之前,CGI(通用网关接口,详情参考CGI(通用网关接口)_百度百科 (baidu.com))脚本语言是最常用服务器端编程语言,然而 CGI 程序存在一些缺点,例如:编写困难、对用户请求响应时间较长、以进程的方式运行导致性能受限等。Servlet 是 SUN 公司作为 CGI 技术的替代品而推出的技术,与 CGI 技术相比,Servlet 具有以下优势:

  • 性能更好:Servlet 中,每个请求由一个轻量级的 Java 线程处理,效率更高,性能更好;
  • 可移植性好: 基于Java,具有平台独立性,可移植到任何支持 Java 的服务器中运行;
  • 功能更加强大:Servlet 能够直接和 Web 服务器交互,并能够在各个程序之间共享数据,使得数据库连接池之类的功能很容易实现;
  • 编写方便:Servlet 提供了大量的实用工具例程,例如自动地解析和解码 HTML 表单数据、读取和设置 HTTP 头、处理 Cookie、跟踪会话状态等。

Servlet 是 CGI 技术的替代品,直接使用 Servlet 开发依旧十分繁琐,因此 SUN 公司又推出了JSP技术。JSP 对 Servlet 再次进行了封装,JSP 经过编译后依然是 Servlet;

Servlet 是 Java 语言中编写 Web 服务器扩展功能的重要技术,同时它也是 JSP 技术的底层运行基础,学习 Java Web 开发,首先就要掌握 Servlet 技术;

1.Servlet概述

Java 是一种功能强大的通用型编程语言,可以处理 HTTP 请求,可以访问数据库,可以生成 HTML 代码,您完全可以使用原生 Java 来开发动态网站。但是,使用原生 Java 开发动态网站非常麻烦,需要自己解析 HTTP 请求的报头,需要自己分析用户的请求参数,需要自己加载数据库组件……种种原因导致使用原生 Java 开发动态网站几乎是一件不能被接受的事情。正是基于这种原因,Java 官方后来推出了 Servlet 技术,它对开发动态网站需要使用的原生 Java API 进行了封装,形成了一套新的 API,称为 Servlet API;

严格来说,Servlet只是一套Java Web的开发规范/技术标准;

只有规范并不能做任何事情,必须要有人去实现它,所谓实现 Servlet 规范,就是真正编写代码去实现 Servlet 规范提到的各种功能,包括类、方法、属性;

1.1 Servlet接口

所有的 Servlet 功能都是通过一个名为Servlet的接口(Interface)向外暴露的,编写 Servlet 代码(实现Servlet类),可以从实现 Servlet 接口开始

1
2
3
public class ServletDemo implements Servlet {
//TODO:
}

直接实现 Servlet 接口比较麻烦,需要实现很多方法,所以 Servlet 规范又提供了两个抽象类,分别是 GenericServlet 类和 HttpServlet 类,它们都实现了 Servlet 接口的很多常用功能。和 GenericServlet 类相比,HttpServlet 类更加方便,所以实际开发中一般都继承自 HttpServlet ;

1.2 JSP

Servlet 是第一代 Java Web 开发技术,它将 HTML 代码以字符串的形式向外输出,编写 HTML 文档的过程就是拼接字符串的过程,非常麻烦,所以 Java 官方又推出了第二代 Web 开发技术 —— JSP;

JSP 是现代化的 Web 开发技术,它允许 HTML 代码和 JSP 代码分离,让程序员能够在 HTML 文档中直接嵌入 JSP 代码

直接使用Servlet开发动态网站并不是很明智的选择,开发者一般都选择JSP,但是前面也说过JSP依赖于Servlet,即用户访问 JSP 页面时,JSP 代码会被翻译成 Servlet 代码,最终,HTML 代码还是以字符串的形式向外输出的;实际上JSP就是在Servlet的基础上做进一步的封装,要想学习JSP一定要先学会Servlet;

1.3 Applet

尽管Servlet的全称是Servlet applet,但是Applet和Servlet不是同一个概念:

  • Java Servlet 是“服务器端小程序”,运行在服务器上,用来开发动态网站;
  • Java Applet 是“客户端小程序”,一般被嵌入到 HTML 页面,运行在支持 Java 的浏览器中;

Applet 和 Servlet 都是基于 Java 的一种技术,功能都非常强大,但是 Applet 开发步骤繁杂,而且只能在安装 Java 虚拟机(JVM)的计算机上运行,现在已经被 JavaScript 全面替代,几乎没有人再学习 Applet;

更多关于Applet和Servlet的区别参考:Java中Applet和Servlet之间的区别是什么-java教程-PHP中文网

2.服务器&容器

Web服务器是一种对外提供Web服务的软件,它可以接收浏览器的HTTP请求并将处理结果返回给浏览器(实际上就是我们学习前端的时候成天说的服务器),部署动态网站一般需要Web服务器的支持:

  • 运行 PHP 网站一般选择 Apache 或者 Nginx;
  • 运行 ASP/ASP.NET 网站一般选择 IIS;
  • 运行 Python 网站一般选择内置的 WSGI 服务器模块——wsgiref;

However,在部署Servlet网站的时候需要一种类似的软件,我们称之为“容器”而不是“服务器”,包括 Tomcat、Weblogic、Jetty、Jboss、WebSphere 等,它们都是实现Servlet规范的产品;

2.1 服务器

常见的Web服务器如Apache、Nginx、IIS 等,它们的功能往往都比较单一,只能提供 http(s) 服务,让用户访问静态资源(HTML 文档、图片、CSS 文件、JavaScript 文件等),它们不能执行任何编程语言,也不能访问数据库,更不能让用户注册和登录;

这就意味着假如只有Web服务器,开发者只能部署静态网站不能部署动态网站,因为想要部署动态网站必须要有运行时(Runtime)和数据库管理系统的支持;


关于运行时、运行时库、运行时环境的区别:(1条消息) 什么是运行时Runtime、运行时库Runtime Library、运行时环境Runtime environment_软件工程小施同学的博客-CSDN博客_运行时


2.1.1 运行环境

运行环境也被称为运行时,习惯将各种支持脚本语言运行的部件统称为运行环境,或者运行时(Runtime);

我们知道开发网站使用的编程语言一般都是脚本语言(比如PHP、ASP、Python),开发者在部署网站时都是将源代码直接扔到服务器上,然而源代码自己并不能运行,必须要有解释器的支持;当用户访问动态页面时,解释器负责分析、编译和执行源代码,然后得到处理结果;

解释器实际上是执行脚本语言的核心部件,除此之外还有其他辅助性部件如垃圾回收器、标准库等;

2.1.2 数据库

Web 服务器不带数据库,编程语言也不带数据库,数据库是一款独立的软件;要想实现用户注册、发布文章、提交评论等功能,就必须安装一款数据库,比如 MySQL、Oracle、SQL Server 等;

综上所述,部署动态网站至少需要三个组件:Web服务器、脚本语言运行时和数据库,例如,部署 PHP 网站一般选择「Apache + PHP 运行时 + MySQL」的组合

2.2 容器

Servlet既然是基于Java语言,因此必然少不了JRE的支持,但是JRE只包含了 Java 虚拟机(JVM)、Java 核心类库和一些辅助性性文件,它并不支持 Servlet 规范;

要想运行 Servlet 代码,还需要一种额外的部件,该部件必须支持 Servlet 规范,实现了 Servlet 接口和一些基础类,这种部件就是 Servlet 容器;

之所以称之为容器,实际上和生活中的容器是类似的,生活中的容器用来装水、粮食,Servlet中的容器用来装类、对象;

Servlet 容器就是 Servlet 代码的运行环境(运行时),它除了实现 Servlet 规范定义的各种接口和类,为 Servlet 的运行提供底层支持,还需要管理由用户编写的 Servlet 类,比如实例化类(创建对象)、调用方法、销毁类等;

自定义的Servlet类需要使用Servlet容器来管理,这是因为自定义的Servlet类没有main函数不能独立运行,只能作为一个模板载入到Servlet容器,由Servlet容器来实例化并调用其中的方法;

一个动态页面对应一个Servlet类,因此开发一个动态页面就是编写一个Servlet类,当用户请求到达时,Servlet 容器会根据配置文件(web.xml)来决定调用哪个类;

因此具有Servlet容器在整个HTTP请求流程中的位置如下

基本的工作流程为:用户的 HTTP 请求首先到达 Web 服务器,Web 服务器判断该请求是静态资源还是动态资源:如果是静态资源就直接返回,此时相当于用户下载了一个服务器上的文件;如果是动态资源将无法处理,必须将该请求转发给 Servlet 容器;

Servlet 容器接收到请求以后,会根据配置文件(web.xml)找到对应的 Servlet 类,将它加载并实例化,然后调用其中的方法来处理用户请求;处理结束后,Servlet 容器将处理结果再转交给 Web 服务器,由 Web 服务器将处理结果进行封装,以 HTTP 响应的形式发送给最终的用户;


常用的Web容器有Tomcat、Jboss、Jetty、WebLogic 等,其中 Tomcat 由 Java 官方提供(初学者常用);

实际上为了简化部署流程,Web容器往往会自带Web服务器模块以提供基本的HTTP服务(当然Servlet自带的Web服务器模块可能并没有传统的Web服务器强大,但是对初学者来说足够,实际开发过程中选择的往往是将传统的Web服务器和Servlet容器组合共同完成HTTP请求),因此可以不再额外安装Apache、IIS、Nginx 等传统意义上的服务器,仅仅只需要Web容器就能够部署一个Servlet网站;

正因如此,有些地方会将Tomcat称为Web服务器,有些地方也将其称之为Web容器,这两种说法实际上都有一定的道理;

将Web容器替代Web服务器之后就有了如下的结构图

关于Servlet容器的总结,Servlet 容器就是 Servlet 程序的运行环境,它主要包含以下几个功能:

  • 实现 Servlet 规范定义的各种接口和类,为 Servlet 的运行提供底层支持;
  • 管理用户编写的 Servlet 类,以及实例化以后的对象;
  • 提供 HTTP 服务,相当于一个简化的服务器;

3.Tomcat

搭建好了Java环境之后,只需要再安装一款Web容器就可以运行Servlet代码,这里选择最常用的免费开源Web容器Tomcat,安装教程参考Tomcat下载和安装(图解) (biancheng.net)

关于Tomcat安装成功后(这里我和教程一样都是选择的解压版),会出现如下目录树,关于每个目录的作用参考Tomcat目录结构 (biancheng.net)

二、Servlet基础

1.Servlet类创建

基本思想:在 Servlet 中,一个动态网页对应一个 Servlet 类,可以通过 web.xml 配置文件将 URL 路径和 Servlet 类对应起来。访问一个动态网页的过程,实际上是将对应的 Servlet 类加载、实例化并调用相关方法的过程;网页上显示的内容,就是通过 Servlet 类中的某些方法向浏览器输出的 HTML 语句;

因此使用Servlet创建动态网页的第一步就是创建Servlet类(是不是很熟悉?因为在我们之前的概述中简单介绍过这个概念);

Servlet 规范的最顶层是一个名为 javax.servlet.Servlet 的接口,所有的 Servlet 类都要直接或者间接地实现该接口。直接实现 Servlet 接口不太方便,所以 Servlet 又内置了两个 Servlet 接口的实现类(抽象类),分别为 GenericServlet 和 HttpServlet,因此,创建 Servlet 类有如下三种方式:

  1. 实现 javax.servlet.Servlet 接口,重写其全部方法;
  2. 继承 javax.servlet.GenericServlet 抽象类,重写 service() 方法;
  3. 继承 javax.servlet.http.HttpServlet 抽象类,重写 doGet() 或 doPost() 方法;

Servlet接口、GenericServlet类和HttpServlet类的关系如下(MyServlet类是自定义类)

由上图可以直观的看出它们的关系:

  1. GenericServlet 是实现了 Servlet 接口的抽象类;
  2. HttpServlet 是 GenericServlet 的子类,具有 GenericServlet 的一切特性;
  3. Servlet 程序(MyServlet 类)是一个实现了 Servlet 接口的 Java 类;

1.1 Servlet接口

javax.servlet.Servlet 是 Servlet API 的核心接口,所有的 Servlet 类都必须直接或间接地实现这一接口,Servlet 接口中定义了如下 5 个方法

1.2 GenericServlet抽象类

javax.servlet.GenericServlet 实现了 Servlet 接口,并提供了除 service() 方法以外的其他四个方法的简单实现。通过继承 GenericServlet 类创建 Servlet ,只需要重写 service() 方法即可,大大减少了创建 Servlet 的工作量;

GenericServlet 类中还提供了以下方法,用来获取 Servlet 的配置信息

1.3 HttpServlet 抽象类

javax.servlet.http.HttpServlet 继承了 GenericServlet 抽象类,用于开发基于 HTTP 协议的 Servlet 程序。由于 Servlet 主要用来处理 HTTP 的请求和响应,所以通常情况下,编写的 Servlet 类都继承自 HttpServlet;

在 HTTP/1.1 协议中共定义了 7 种请求方式,即 GET、POST、HEAD、PUT、DELETE、TRACE 和 OPTIONS;HttpServlet 针对这 7 种请求方式分别定义了 7 种方法,即 doGet()、doPost()、doHead()、doPut()、doDelete()、doTrace() 和 doOptions();

HttpServlet 重写了 service() 方法,该方法会先获取客户端的请求方式,然后根据请求方式调用对应 doXxx 方法;

由于我们使用的请求方式主要是 GET 和 POST,所以通过继承 HttpServlet 类创建 Servlet 时,只需要重写 doGet 或者 doPost 方法;


上述三种创建Servlet(实现Servlet接口)的方式,我们这里做一个简单的归纳总结

(1)Servlet接口

通过实现 Servlet 接口创建 Servlet 类,需要重写其全部的方法,比较繁琐,所以我们很少使用该方法创建 Servlet;

(2)GenericServlet 类

GenericServlet 抽象类实现了 Servlet 接口,并对 Servlet 接口中除 service() 方法外的其它四个方法进行了简单实现。通过继承 GenericServlet 创建 Servlet,只需要重写 service() 方法即可,大大减少了创建 Servlet 的工作量。Generic 是“通用”的意思,正如其名,GenericServlet 是一个通用的 Servlet 类,并没有针对某种场景进行特殊处理,尤其是 HTTP 协议,我们必须手动分析和封装 HTTP 协议的请求信息和响应信息;

(3)HttpServlet 类

HttpServlet 是 GenericServlet 的子类,它在 GenericServlet 的基础上专门针对 HTPP 协议进行了处理,HttpServlet 为 HTTP 协议的每种请求方式都提供了对应的方法;

HttpServlet 就是专为 HTTP 协议而量身打造的 Servlet 类;

2.Servlet的部署

Servlet没有main函数,因此不能独立运行,需要将其作为JavaWeb应用的一个组件部署到Servlet组件中,通过容器来实例化和调用Servlet方法;

2.1 JavaWeb应用

JavaWeb 应用由一组 Servlet/JSP、HTML 文件、相关 Java 类、以及其他的资源组成,它可以在由各种供应商提供的 Servlet 容器中运行;

由 JavaWeb 应用的定义可知, Servlet 是 JavaWeb 应用的一个组件;

为了让 Servlet 容器顺利地找到 JavaWeb 应用的各个组件,Servlet 规范规定,JavaWeb 应用必须采用固定的目录结构,即每种组件在 JavaWeb 应用中都有固定的存放目录;

以 Tomcat 为例,通常将 JavaWeb 应用存放到 Tomcat 的 webapps 目录下。在 webapps 下,每一个子目录都是一个独立的 Web 应用,子目录的名字就是 Web 应用的名字,也被称为 Web 应用的上下文根,用户可以通过这个上下文根来访问 JavaWeb 应用中的资源;

2.2 部署

在Tomcat中部署JavaWeb应用最快捷的方式就是直接将 JavaWeb 应用的所有文件复制到 Tomcat 的 /webapps 目录下,在默认情况下,Tomcat 会自动加载 webapps 目录下的 JavaWeb 应用,并把它发布到名为 localhost 的虚拟主机中;

Tomcat 既可以运行采用开放式目录结构(只编译不打包)的 Web 应用,也可以运行 Web 应用的打包文件(WAR 文件)。在开发阶段,为了方便程序调试,通常采用开放式的目录结构部署 JavaWeb 应用。在开发完成进入产品发布阶段时,就应该将整个应用打包成 WAR 文件再进行部署;

即使采用 WAR 文件的形式发布,Tomcat 启动时也会将 WAR 文件自动展开为开放式的目录结构;

具体如何编译Servlet并将其部署到Tomcat上的步骤以及如何访问部署好的Servlet参考

关于Tomcat出现乱码,解决方法参考(1条消息) Tomcat首次登录 执行startup.bat 出现乱码_luoluo95的博客-CSDN博客_startup.bat打开乱码

3.Servlet注解

在 Servlet 中,web.xml 扮演的角色十分的重要,它可以将所有的 Servlet 的配置集中进行管理,但是若项目中 Servelt 数量较多时,web.xml 的配置会变得十分的冗长。这种情况下,注解(Annotation)就是一种更好的选择;

注解可以直接在类中使用,其对Servlet所作的配置只对当前类有效,有效避免了集中管理造成的配置冗长问题;

Servlet在3.0版本之后增加了注解的支持以简化Servlet的配置,例如@WebServlet、@WebInitParm 、@WebFilter 和 @WebLitener 等,正是因为这些注解的出现使得web.xml不再是必选项;

3.1 @WebServlet

@WebServlet 用于将一个类声明为 Servlet,该注解会在部署时被容器处理,容器根据其具体的属性配置将相应的类部署为 Servlet,该注解具有下表给出的一些常用属性

@WebServlet 属于类级别的注解,标注在继承了 HttpServlet 的类之上,常用的写法是将 Servlet 的相对请求路径(即 value)直接写在注解内;

如果 @WebServlet 中需要设置多个属性,则属性之间必须使用逗号隔开;

1
2
3
4
@WebServlet(asyncSupported = true, name = "myServlet", description = "name描述", loadOnStartup = 1, urlPatterns = {
"/MyServlet", "/*" }, initParams = {
@WebInitParam(name = "编程帮", value = "www.biancheng.net", description = "init参数1"),
@WebInitParam(name = "京东", value = "www.jd.com", description = "init参数2") })

注意事项:

  • 通过实现 Serlvet 接口或继承 GenericServlet 创建的 Servlet 类无法使用 @WebServlet 注解;
  • 使用 @WebServlet 注解配置的 Servlet 类,不要在 web.xml 文件中再次配置该 Servlet 相关属性。若同时使用 web.xml 与 @WebServlet 配置同一 Servlet 类,则 web.xml 中 < servlet-name > 的值与注解中 name 取值不能相同(如果name不同那还是一个类吗??),否则容器会忽略注解中的配置;

3.2 启用注解

web.xml 的顶层标签 < web-app > 中有一个属性:metadata-complete,该属性用于指定当前 web.xml 是否是完全的。若该属性设置为 true,则容器在部署时将只依赖 web.xml,忽略所有的注解。若不配置该属性,或者将其设置为 false,则表示启用注解支持;

由于 metadata-complete 属性的默认值是 false,即默认启用 Servlet 注解支持,所以默认情况下,使用Servlet注解时,不必创建 web.xml 文件;


最后针对使用web.xml和@WebServlet注解配置Servlet做一个总结,使用 web.xml 或 @WebServlet 注解都可以配置 Servlet,两者各有优缺点;

@WebServlet 注解配置 Servlet

优点:@WebServlet 直接在 Servlet 类中使用,代码量少,配置简单。每个类只关注自身业务逻辑,与其他 Servlet 类互不干扰,适合多人同时开发;

缺点:Servlet 较多时,每个 Servlet 的配置分布在各自的类中,不便于查找和修改;

web.xml 配置文件配置 Servlet

优点:集中管理 Servlet 的配置,便于查找和修改;

缺点:代码较繁琐,可读性不强,不易于理解;

4.Servlet生命周期

生命周期是指事物从创建到毁灭的过程,在这个过程中,必定会有一些与生命周期息息相关的事件,这些事件会在生命周期中的某个特定时刻发生;

Servlet 也有生命周期,Servlet 的生命周期就是Servlet对象从创建到销毁的过程,Servlet 的生命周期由 Servlet 容器管理,主要分为以下 3 个阶段:

  1. 初始化阶段
  2. 运行时阶段
  3. 销毁阶段

Servlet 生命周期流程如下图所示

在 Servlet 的整个生命周期中,创建 Servlet 实例、init() 方法和 destory() 方法都只执行一次;初始化完成后,Servlet 容器会将该实例保存在内存中,通过调用它的 service() 方法,为接收到的请求服;

4.1 初始化阶段

Servlet 初始化是其生命周期的第一个阶段,也是其它阶段的基础,只有完成了初始化,Servlet 才能处理来自客户端的请求;

Servlet 初始化阶段分为 2 步:

  1. 加载和实例化 Servlet;
  2. 调用 init() 方法进行初始化;

初始化期间,Servlet 实例可以通过 ServletConfig 对象获取在 web.xml 或者 @WebServlet 中配置的初始化参数;

4.1.1 加载和实例化

Servlet 容器负责加载和实例化 Servlet;

当容器启动或首次请求某个 Servlet 时,容器会读取 web.xml 或 @WebServlet 中的配置信息,对指定的 Servlet 进行加载,加载成功后,容器会通过反射对 Servlet 进行实例化

因为 Servlet 容器是通过 Java 的反射 API 来创建 Servlet 实例的,需要调用 Servlet 的默认构造方法(default constructor,即不带参数的构造方法),所以在编写 Servlet 类时,不能只提供一个带参数的构造方法;

4.1.2 初始化

加载和实例化完成后,Servlet 容器调用 init() 方法初始化 Servlet 实例;

初始化的目的是让 Servlet 实例在处理请求之前完成一些初始化工作,例如建立数据库连接,获取配置信息等;

在 Servlet 的整个生命周期内,init() 方法只能被调用一次;

4.2 运行时阶段

运行时阶段是 Servlet 生命周期中最重要的阶段,Servlet 容器接收到来自客户端请求时,容器会针对该请求分别创建一个 ServletRequst 对象和 ServletResponse 对象,将它们以参数的形式传入 service() 方法内,并调用该方法对请求进行处理;

  • Servlet 通过 ServletRequst 对象获取客户端的相关信息和请求信息;
  • 在请求处理完成后,通过 ServletResponse 对象将响应信息进行包装,返回给客户端;
  • Servlet 容器将响应信息返回给客户端后,ServletRequst 对象与 ServletResponse 对象就会被销毁;

执行 service() 方法前,init() 方法必须已成功执行;

在 Servlet 的整个生命周期内,对于 Servlet 的每一次请求,Servlet 容器都会调用一次 service() 方法,并创建新的 ServletRequest 和 ServletResponse 对象,即 service() 方法在 Servlet 的整个生命周期中会被调用多次;

4.3 销毁阶段

当 Servlet 容器关闭、重启或移除 Servlet 实例时,容器就会调用 destory() 方法,释放该实例使用的资源,例如:关闭数据库连接,关闭文件的输入流和输出流等,随后该实例被 Java 的垃圾收集器回收;

对于每个 Servlet 实例来说,destory() 方法只能被调用一次;

5.虚拟路径

5.1 路径映射

客户端通过URL地址来访问Web服务器中的资源,Servlet程序若想被外界访问,就必须被映射到一个URL地址上;很多时候,该URL地址和Servlet程序的物理路径(在硬盘上的存储位置)并不一致,因此它被称为虚拟路径;Servlet程序的物理地址与虚拟路径的对应关系就叫做 Servlet 虚拟路径映射;

Servlet 虚拟路径映射可以被分为 2 类:

  1. 单一映射
  2. 多重映射

5.1.1 单一映射

Servelt 单一映射是指一个 Servlet 只被映射到一个虚拟路径上;

Servlet 单一映射的实现方式有 2 种:

  1. 使用 web.xml 实现单一映射;
    • 在 web.xml 文件中,使用 < servlet > 和 < servlet-mapping > 元素实现 Servlet 单一映射;
      • < servlet > 元素用于注册 Servlet,即给 Servlet 起一个独一无二的名字;< servlet > 包含两个主要的子元素 < servlet-name > 和 < servlet-class >,分别用于指定 Servlet 的名称和 Servlet 的完整限定名(包名+类名);
      • < servlet-mapping > 元素用于定义 Servlet 与虚拟路径之间的映射;< servlet-mapping > 包含两个子元素 < servlet-name > 和 < url-pattern >,分别用于指定 Servlet 的名称和虚拟路径;
  2. 使用 @WebServlet 实现单一映射;
    • 在 @WebServlet 注解中,一般使用 value 属性实现 Servlet 单一映射,也可以使用 urlPatterns 属性实现 Servlet 单一映射;

5.1.2 多重映射

Servlet 的多重映射是指一个 Servlet 可以被映射到多个虚拟路径上,此时,客户端可以通过多个路径实现对同一个 Servlet 的访问;

Servlet 多重映射的实现方式有 3 种:

  1. 配置多个 < servlet-mapping > 元素;
    • Servlet 2.5 规范之前,< servlet-mapping > 元素只允许包含一个 < url-pattern > 子元素,若要实现 Servet 的多重映射,只能通过配置多个 < servlet-mapping > 元素实现;
  2. 配置多个 < url-pattern > 子元素;
    • 从 Servlet 2.5 开始,< servlet-mapping > 元素可以包含多个 < url-pattern > 子元素,每个 < url-pattern > 代表一个虚拟路径的映射规则。因此,通过在一个 < servlet-mapping > 元素中配置多个 < url-pattern > 子元素,也可以实现 Servlet 的多重映射;
  3. 在 @WebServlet 的 urlPatterns 属性中使用字符串数组;
    • Servlet 3.0 增加了对 @WebServlet 注解的支持,我们可以在 urlPatterns 属性中,以字符串数组的形式指定一组映射规则来实现 Servlet 的多重映射;

5.2 路径匹配

当 Servlet 容器接收到请求后,容器会将请求的 URL 减去当前应用的上下文路径,使用剩余的字符串作为映射 URL 与 Servelt 虚拟路径进行匹配,匹配成功后将请求交给相应的 Servlet 进行处理;

5.2.1 匹配规则

Servlet 虚拟路径匹配规则有以下 4 种:

  1. 完全路径匹配
  2. 目录匹配
  3. 扩展名匹配
  4. 缺省匹配(默认匹配)

注意:目录匹配和扩展名匹配无法混合使用;

5.2.2 匹配优先级

Servlet 虚拟路径的匹配优先级顺序为:全路径匹配(精确匹配)> 目录匹配 > 扩展名匹配 > 缺省匹配(默认匹配);

Servlet 容器会从优先级高的虚拟路径开始匹配,匹配成功后就会立刻将请求交给相应的 Servlet 进行处理,不会再关注其他虚拟路径是否匹配成功;

6.Servlet接口

6.1 ServletConfig

Servlet 容器初始化 Servlet 时,会为这个 Servlet 创建一个 ServletConfig 对象,并将 ServletConfig 对象作为参数传递给 Servlet ;

通过 ServletConfig 对象即可获得当前 Servlet 的初始化参数信息;

一个 Web 应用中可以存在多个 ServletConfig 对象,但是一个 Servlet 只能对应一个 ServletConfig 对象,即 Servlet 的初始化参数仅对当前 Servlet 有效;

6.2 ServletContext

Servlet 容器启动时,会为每个 Web 应用(webapps 下的每个目录都是一个 Web 应用)创建一个唯一的 ServletContext 对象,该对象一般被称为“Servlet 上下文”;

ServletContext 对象的生命周期从 Servlet 容器启动时开始,到容器关闭或应用被卸载时结束;

一个Web 应用中的所有 Servlet 共享同一个 ServletContext 对象,不同 Servlet 之间可以通过 ServletContext 对象实现数据通讯,因此 ServletContext 对象也被称为 Context 域对象;

域对象是服务器在内存上创建的存储空间,该空间用于不同动态资源(例如 Servlet、JSP)之间传递与共享数据;

6.3 HttpServletRequest

Servlet 处理 HTTP 请求的流程如下

  1. Servlet 容器接收到来自客户端的 HTTP 请求后,容器会针对该请求分别创建一个 HttpServletRequest 对象和 HttpServletReponse 对象;
  2. 容器将 HttpServletRequest 对象和 HttpServletReponse 对象以参数的形式传入 service() 方法内,并调用该方法;
  3. 在 service() 方法中 Servlet 通过 HttpServletRequest 对象获取客户端信息以及请求的相关信息;
  4. 对 HTTP 请求进行处理;
  5. 请求处理完成后,将响应信息封装到 HttpServletReponse 对象中;
  6. Servlet 容器将响应信息返回给客户端;
  7. 当 Servlet 容器将响应信息返回给客户端后,HttpServletRequest 对象和 HttpServletReponse 对象被销毁;

HttpServletRequest 对象用于封装 HTTP 请求信息,HttpServletReponse 对象用于封装 HTTP 响应信息;


HttpServletRequest 接口继承自 ServletRequest 接口,HttpServletRequest 对象专门用于封装 HTTP 请求消息,简称 request 对象;

HTTP 请求消息分为请求行、请求消息头和请求消息体三部分,所以 HttpServletRequest 接口中定义了获取请求行、请求头和请求消息体的相关方法;

  • 获取请求行信息:HTTP 请求的请求行中包含请求方法、请求资源名、请求路径等信息,HttpServletRequest 接口定义了一系列获取请求行信息的方法

  • 获取请求头信息:浏览器发送请求时,需要通过请求头向服务器传递一些附加信息,例如客户端可以接收的数据类型、压缩方式、语言等。为了获取请求头中的信息, HttpServletRequest 接口定义了一系列用于获取 HTTP 请求头字段的方法

  • 获取form表单数据:实际开发中,我们经常需要获取用户提交的表单数据,例如用户名和密码等。为了方便获取表单中的请求参数,ServletRequest 定义了一系列获取请求参数的方法;

6.4 HttpServletResponse

在 Servlet API 中,定义了一个 HttpServletResponse 接口,它继承自 ServletResponse 接口。HttpServletResponse 对象专门用来封装 HTTP 响应消息,简称 response 对象;

Servlet 容器会针对每次请求创建一个 response 对象,并把它作为参数传递给 Servlet 的 service 方法。Servlet 处理请求后,会将响应信息封装到 response 对象中,并由容器解析后返回给客户端;

由于 HTTP 响应消息由响应行、响应头、消息体三部分组成,所以 HttpServletResponse 接口中定义了向客户端发送响应状态码、响应头、响应体的方法;

  • 响应行相关的方法:当 Servlet 返回响应消息时,需要在响应消息中设置状态码。因此,HttpServletResponse 接口定义了发送状态码的方法;

  • 响应头相关的方法:HttpServletResponse 接口中定义了一系列设置 HTTP 响应头字段的方法;

  • 响应体相关的方法:由于在 HTTP 响应消息中,大量的数据都是通过响应消息体传递的。因此 ServletResponse 遵循以 I/O 流传递大量数据的设计理念,在发送响应消息体时,定义了两个与输出流相关的方法;

注意:getOutputStream() 和 getWriter() 方法互相排斥,不可同时使用,否则会发生 IllegalStateException 异常;


重定向

重定向属于客户端行为,服务器在收到客户端请求后,会通知客户端浏览器重新向另外一个 URL 发送请求,这称为请求重定向,本质上是两次 HTTP 请求,对应两个 request 对象和两个 response 对象;

重定向的流程如下

  1. 用户在浏览器中输入 URL,请求访问服务器端的 Web 资源;
  2. 服务器端的 Web 资源返回一个状态码为 302 的响应信息,该响应的含义为:通知浏览器再次发送请求,访问另一个 Web 资源(在响应信息中提供了另一个资源的 URL);
  3. 当浏览器接收到响应后,立即自动访问另一个指定的 Web 资源;
  4. 另一 Web 资源将请求处理完成后,由容器把响应信息返回给浏览器进行展示;

转发和重定向的都能够实现页面的跳转,但是两者存在一定区别

HttpServletResponse 接口中的 sendRedirect() 方法用于实现重定向

6.5 RequestDispatcher

RequestDispatcher 对象由 Servlet 容器创建,用于封装由路径所标识的 Web 资源,利用 RequestDispatcher 对象可以把请求转发给其他的 Web 资源;

RequestDispatcher 接口中提供了以下方法


请求转发

Web 应用在处理客户端的请求时,经常需要多个 Web 资源共同协作才能生成响应结果。但由于 Serlvet 对象无法直接调用其他 Servlet 的 service() 方法,所以Servlet提供了请求转发的解决方案;

请求转发属于服务器行为,容器接收请求后,Servlet 会先对请求做一些预处理,然后将请求传递给其他 Web 资源,来完成包括生成响应在内的后续工作;

请求转发的工作原理是在 Servlet 中,使用RequestDispatcher的forward()方法将当前请求转发给其他的 Web 资源进行处理;

请求转发具有以下特点:

  1. 请求转发不支持跨域访问,只能跳转到当前应用中的资源;
  2. 请求转发之后,浏览器地址栏中的 URL 不会发生变化,因此浏览器不知道在服务器内部发生了转发行为,更无法得知转发的次数;
  3. 参与请求转发的 Web 资源之间共享同一 request 对象和 response 对象;
  4. 由于 forward() 方法会先清空 response 缓冲区,因此只有转发到最后一个 Web 资源时,生成的响应才会被发送到客户端;

请求转发需要和request域对象配合使用,以实现动态资源之间的数据传递;

Context 域对象和 request 域对象对比,具有以下 4 点差异:
(1)生命周期不同
Context 域对象的生命周期从容器启动开始,到容器关闭或者 Web 应用被移除时结束;
request 域对象的生命周期从客户端向容器发送请求开始,到对这次请求做出响应后结束;

(2)作用域不同
Context 域对象对整个 Web 应用内的所有Servlet都有效;
request 域对象只对本次请求涉及的 Servlet 有效;

(3)Web 应用中数量不同
整个 Web 应用中只有一个 Context 域对象;
由于 Servlet 能处理多个请求,因此 Web 应用中的每个 Servlet 实例都可以有多个 request 域对象;

(4)实现数据共享的方式不同
Context 域对象可以独立完成动态资源之间的数据共享;

7.Web技术

我们知道HTTP(超文本传输协议)是一个基于请求与响应模式的无状态协议,无状态主要指的是:

  1. 协议对于事务处理没有记忆能力,服务器不能自动维护用户的上下文信息,无法保存用户状态;
  2. 每次请求都是独立的,不会受到前面请求的影响,也不会影响后面的请求;

通常情况下,用户通过浏览器访问 Web 应用时,服务器都需要保存和跟踪用户的状态;由于 HTTP 协议是无协议的,无法保存和跟踪用户状态,所以需要有方案来解决,它就是会话技术;

会话:从打开浏览器访问某个网站,到关闭浏览器的过程,称为一次会话;

会话技术:会话技术是指在会话中,帮助服务器记录用户状态和数据的技术;

常用的会话技术分为两种:

  1. Cookie :客户端会话技术
  2. Session :服务端会话技术

Cookie 属于客户端会话技术,它是服务器发送给浏览器的小段文本信息,存储在客户端浏览器的内存中或硬盘上;当浏览器保存了 Cookie 后,每次访问服务器,都会在 HTTP 请求头中将这个 Cookie 回传给服务器;Cookie分为两种:

  1. 会话级别 Cookie(默认):Cookie 保存到浏览器的内存中,浏览器关闭则 Cookie 失效;
  2. 持久的 Cookie:Cookie 以文本文件的形式保存到硬盘上;

Cookie是基于HTTP协议实现的,其工作流程如下

  1. 客户端浏览器访问服务器时,服务器通过在 HTTP 响应中增加 Set-Cookie 字段,将数据信息发送给浏览器;
  2. 浏览器将 Cookie 保存在内存中或硬盘上;
  3. 再次请求该服务器时,浏览器通过在 HTTP 请求消息中增加 Cookie 请求头字段,将 Cookie 回传给 Web 服务器,服务器根据 Cookie 信息跟踪客户端的状态;

使用 Cookie 开发时需要注意以下细节:

  • 一个 Cookie 只能标识一种信息,它至少包含一个名称(NAME)和一个值(VALUE);
  • 如果创建了一个 Cookie,并发送到浏览器,默认情况下它是一个会话级别的 Cookie,用户退出浏览器就被删除。如果希望将 Cookie 存到磁盘上,则需要调用 setMaxAge(int maxAge) 方法设置最大有效时间,以秒为单位;
  • 使用 setMaxAge(0) 手动删除 Cookie时,需要使用 setPath 方法指定 Cookie 的路径,且该路径必须与创建 Cookie 时的路径保持一致;

Cookie 虽然可以解决服务器跟踪用户状态的问题,但是它具有以下缺点:

  • 在 HTTP 请求中,Cookie 是明文传递的,容易泄露用户信息,安全性不高;
  • 浏览器可以禁用 Cookie,一旦被禁用,Cookie 将无法正常工作;
  • Cookie 对象中只能设置文本(字符串)信息;
  • 客户端浏览器保存 Cookie 的数量和长度是有限制的;

7.2 Servlet Session

Session 是服务器端会话技术,当浏览器访问 Web 服务器的资源时,服务器可以为每个用户浏览器创建一个 Session 对象,每个浏览器独占一个 Session 对象;

由于每个浏览器独占一个 Session,所以用户在访问服务器的资源时,可以把数据保存在各自的 Session 中。当用户再次访问该服务器中的其它资源时,其它资源可以从 Session 中取出数据,为用户服务;

Session 虽然属于服务端会话技术,但是它的实现离不开客户端浏览器和 Cookie 的支持,其工作原理如下

  1. 当客户端第一次请求会话对象时,服务器会创建一个 Session 对象,并为该 Session 对象分配一个唯一的 SessionID(用来标识这个 Session 对象);
  2. 服务器将 SessionID 以 Cookie(Cookie 名称为:“JSESSIONID”,值为 SessionID 的值)的形式发送给客户端浏览器;
  3. 客户端浏览器再次发送 HTTP 请求时,会将携带 SessionID 的 Cookie 随请求一起发送给服务器;
  4. 服务器从请求中读取 SessionID,然后根据 SessionID 找到对应的 Session 对象;

注意:

  • 流程中的 Cookie 是容器自动生成的,它的 maxAge 属性取值为 -1,表示仅当前浏览器有效;
  • 浏览器关闭时,对应的 Session 并没有失效,但此时与此 Session 对应的 Cookie 已失效,导致浏览器无法再通过 Cookie 获取服务器端的 Session 对象(Session 对象在服务器中驻留一段时间后没有被使用,就会被销毁,这个时间就是 Session 的过期时间);
  • 同一浏览器的不同窗口共享同一 Session 对象,但不同浏览器窗口之间不能共享 Session 对象;

Session 和 Cookie 都属于会话技术,都能帮助服务器保存和跟踪用户状态,但两者也存在差异

7.3 Servlet Filter

Servlet Filter 又称 Servlet 过滤器,它是在 Servlet 2.3 规范中定义的,能够对 Servlet 容器传给 Web 资源的 request 对象和 response 对象进行检查和修改;

Filter 不是 Servlet,不能直接访问,它本身也不能生成 request 对象和 response 对象,它只能为 Web 资源提供以下过滤功能:

  • 在 Web 资源被访问前,检查 request 对象,修改请求头和请求正文,或对请求进行预处理操作;
  • 将请求传递到下一个过滤器或目标资源(过滤器并不是必须要将请求传递到下一个过滤器或目标资源,它可以自行对请求进行处理,并发送响应给客户端,也可以将请求转发或重定向到其他的 Web 资源);
  • 在 Web 资源被访问后,检查 response 对象,修改响应头和响应正文;

Filter 是 Servlet 规范中最实用的技术,通过它可以对服务器管理的所有 Web 资源(例如 JSP、Servlet、静态 HTML 文件、静态图片等)进行拦截,从而实现一些特殊的功能,例如用户的权限控制、过滤敏感词、设置统一编码格式等;

Filter的工作流程如下

  1. 客户端请求访问容器内的 Web 资源;
  2. Servlet 容器接收请求,并针对本次请求分别创建一个 request 对象和 response 对象;
  3. 请求到达 Web 资源之前,先调用 Filter 的 doFilter() 方法,检查 request 对象,修改请求头和请求正文,或对请求进行预处理操作;
  4. 在 Filter 的 doFilter() 方法内,调用 FilterChain.doFilter() 方法,将请求传递给下一个过滤器或目标资源;
  5. 目标资源生成响应信息返回客户端之前,处理控制权会再次回到 Filter 的 doFilter() 方法,执行 FilterChain.doFilter() 后的语句,检查 response 对象,修改响应头和响应正文;
  6. 响应信息返回客户端;

在 Web 应用中,可以部署多个 Filter,若这些 Filter 都拦截同一目标资源,则它们就组成了一个 Filter 链(也称过滤器链)。过滤器链中的每个过滤器负责特定的操作和任务,客户端的请求在这些过滤器之间传递,直到传递给目标资源;

Filter 链的拦截过程如下图所示

请求资源时,过滤器链中的过滤器依次对请求进行处理,并将请求传递给下一个过滤器,直到最后将请求传递给目标资源。发送响应信息时,则按照相反的顺序对响应进行处理,直到将响应返回给客户端;

过滤器链中的任何一个 Filter 没有调用 FilterChain.doFilter() 方法,请求都不会到达目标资源;


Servlet
https://gintoki-jpg.github.io/2022/11/16/后端_Servlet/
作者
杨再俨
发布于
2022年11月16日
许可协议