Servlet学习笔记

文章目录

一、Servlet简介

  • Servlet 是 Server Applet 的缩写,译为“服务器端小程序”,是一种使用 Java 语言来开发动态网站的技术

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

  • Servlet 是 Sun 公司推出的一种基于 Java 的动态网站开发技术。编写 Servlet 代码需要遵循 Java 语法,一个 Servlet 程序其实就是一个按照 Servlet 规范编写的 Java 类。Servlet 程序需要先编译成字节码文件(.class文件),然后再部署到服务器运行。

1、Servlet是一种规范

  • 严格来说,Servlet 只是一套 Java Web 开发的规范,或者说是一套 Java Web 开发的技术标准。只有规范并不能做任何事情,必须要有人去实现它。所谓实现 Servlet 规范,就是真正编写代码去实现 Servlet 规范提到的各种功能,包括类、方法、属性等。

  • Servlet 规范是开放的,除了 Sun 公司,其它公司也可以实现 Servlet 规范,目前常见的实现了 Servlet 规范的产品包括 Tomcat、Weblogic、Jetty、Jboss、WebSphere 等,它们都被称为“Servlet 容器”。Servlet 容器用来管理程序员编写的 Servlet 类。

2、Servlet接口

  • 所有的 Servlet 功能都是通过一个名为Servlet的接口(Interface)向外暴露的,编写 Servlet 代码,可以从实现 Servlet 接口开始,就像下面这样:
public class ServletDemo implements Servlet{
    //TODO;
}
  • 直接实现 Servlet 接口比较麻烦,需要实现很多方法,所以 Servlet 规范又提供了两个抽象类,分别是 GenericServlet 类和 HttpServlet 类,它们都实现了 Servlet 接口的很多常用功能。和 GenericServlet 类相比,HttpServlet 类更加方便,所以实际开发中一般都继承自 HttpServlet 类。

3、JSP(详见JSP笔记)

  • Servlet 是第一代 Java Web 开发技术,它将 HTML 代码以字符串的形式向外输出,编写 HTML 文档就是在拼接字符串,非常麻烦,所以 Java 官方又推出了第二代 Web 开发技术——JSP。
  • JSP 才是现代化的 Web 开发技术,它允许 HTML 代码和 JSP 代码分离,让程序员能够在 HTML 文档中直接嵌入 JSP 代码。
  • JSP 依赖于 Servlet,用户访问 JSP 页面时,JSP 代码会被翻译成 Servlet 代码,最终,HTML 代码还是以字符串的形式向外输出的。
  • JSP是对Servlet的进一步封装

4、Applet

  • Java Servlet 是“服务器端小程序”,运行在服务器上,用来开发动态网站;

  • Java Applet 是“客户端小程序”,一般被嵌入到 HTML 页面,运行在支持 Java 的浏览器中。

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

二、Servlet容器

部署动态网站一般需要Web服务器的支持:

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

在部署Servlet网站时,同样需要一种类似的软件,例如 Tomcat、Jboss、Jetty、WebLogic 等,但是它们通常被称为“容器”,而不是“服务器”

1、Web服务器

1.1 简介

  • Web 服务器是一种对外提供 Web 服务的软件,它可以接收浏览器的 HTTP 请求,并将处理结果返回给浏览器。
  • 我们通常所说的 Web 服务器,比如 Apache、Nginx、IIS 等,它们的功能往往都比较单一,只能提供 http(s) 服务,让用户访问静态资源(HTML 文档、图片、CSS 文件、JavaScript 文件等),它们不能执行任何编程语言,也不能访问数据库,更不能让用户注册和登录。
  • 也就是说,如果只有 Web 服务器,那您只能部署静态网站,不能部署动态网站。要想部署动态网站,必须要有编程语言运行环境(运行时,Runtime)和数据库管理系统的支持。

运行环境(运行时):

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

数据库:

  • Web服务器不自带数据库,数据库是一款独立的软件;要想实现用户注册、发布文章、提交评论等功能,就必须通过数据库,比如MYSQL、Oracle、SQL Server等。

总结:

  • 部署动态网站至少需要三个组件,分别是Web服务器、脚本语言(运行环境)和数据库

1.2 Servlet容器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2MeqDCpf-1631159929110)(…/servlet/img/Http请求.png)]

1.2.1 为什么需要Servlet容器
  • Servlet是基于Java语言的,运行Servlet必然少不了JRE的支持,它负责解析和执行字节码(.class文件)。然而JRE只包含了Java虚拟机(JVM)、Java核心类库和一些辅助性文件,它并不支持Servlet规范。想要运行Servlet代码,还需要一种额外的部件,以支持Servlet规范,实现了Servlet接口和一些基础类,这就是Servlet容器。
1.2.2 什么是Servlet容器
  • Servlet容器就是Servlet代码的运行环境,它除了实现Servlet规范定义的各种接口和类,为Servlet的运行提供底层支持,还需要管理由用户编写的Servlet类,比如实例化类、调用方法、销毁类等。
  • 我们自己编写的Servlet类没有main()函数,不能独立运行,只能作为一个模块载入到Servlet容器,然后由Servlet容器来实例化,并调用其中的方法。
  • 一个动态页面对应一个Servlet类,开发一个动态页面就是编写一个Servlet类,当用户请求到达时,Servlet容器会根据配置文件(web.xml)来决定调用哪个类。

**流程图再理解:**Web服务器是整个动态网站的"大门",用户的HTTP请求首先到达Web服务器,Web服务器判断该请求是静态资源还是动态资源:如果是静态资源就直接返回(比如下载一个文件,直接返回需要的文件即可);如果是动态资源将无法处理,必须将该请求转发给Servlet容器。

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

:为了简化部署流程。Web容器往往也会自带Web服务器模块,提供基本的HTTP服务,所以可以不用再安装Apache、IIS、Nginx等传统意义上的服务器,只需要安装一款Web容器,就能部署Servlet网站了。因此,上面的流程图也可以是如下这样:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f3GQWkeA-1631159929112)(…/servlet/img/Web容器流程图.png)]

1.2.4 Servlet容器总结
  • Servlet容器就是Servlet程序的运行环境,它主要包含以下几个功能:
    • 实现Servlet规范定义的各种接口和类,为Servlet的运行提供底层支持
    • 提供HTTP服务,相当于一个简化的服务器(提供了基本的服务器功能,不用另装服务器软件)
    • 可以仅使用Web容器做部署,也可以采用Web容器+Web服务器的方式部署网站
  • 用户发出的请求首先会到达Web服务器,如果请求的是静态资源(如下载文件等)那么服务器可以直接返回该资源。如果是动态资源,则需要交给Web容器处理,Web容器处理完再将处理结果交给Web服务器,Web服务器将处理结果封装后再返回给用户

三、Servlet三种创建方法

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

所以,如果需要创建个动态网页,首先需要创建一个Servlet类

Servlet类实现了javax.servlet.Servlet接口。Servlet接口有两个抽象实现类,

分别是GenericServletHttpServlet

Servlet、GenericServlet、HttpServlet的关系

MyServlet是自己定义的Servlet类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hRSaYhUD-1631159929113)(…/…/Servlet、GenericServlet、HttpServlet的关系.png)]

1、实现javax.servlet.Servlet接口,重写其全部方法

1.1 Servlet接口

  • Servlet是ServletAPI的核心接口,所有的Servlet类都直接或间接地实现了这一接口。

常用方法

  • void init(ServletConfig config)
Servlet实例化之后,由Servlet容器调用,用来初始化Servlet对象。该方法只能被调用一次。参数config用来向Servlet传递配置信息。
  • void service(ServletRequest req, ServletResponce res)
Servlet容器调用该方法处理客户端请求
  • void destory()
服务器关闭、重启或者Servlet对象被移除时,由Servlet容器调用,负责释放Servlet对象占用的资源
  • ServletConfig getServletConfig()
该方法用来获取ServletConfig对象,该对象中包含了Servlet的初始化参数
  • String getServletInfo()
该方法用于获取Servlet的信息,例如作者、版本、版权等

代码实例:

import javax.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;

public class MyServlet implements Servlet{
   //只能被调用一次的初始化方法
    @Override
    public void init(ServletConfig servletConfig) throws ServletException{
    }
    //返回ServletConfig对象,包含了Servlet的初始化参数
    @Override
    public ServletConfig getServletConfig(){
        return null;
    }
    //每次请求,都会调用一次service()方法
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException,IOException{
        //设置字符集
        serlvetResponse.setContentType("text/html;charset=UTF-8");
        //使用PrintWriter.write()方法向前台输出内容
        PrintWriter writer = servletResponse.getWriter();
        writer.write("这是输出内容");
        writer.close();
    }
    //返回关于Servlet的信息,例如作者、版本、版权等
    @Override
    public String getServletInfo(){
        return null;
    }
    //Servlet被销毁时的操作
    @Override
    public void destory(){
    }
}

2、继承javax.serlvet.GenericServlet抽象类,重写service()方法

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

常用方法:

  • String getInitParameter(String name)
返回名字为name的初始化参数的值,初始化参数在web.xml中配置。如果参数不存在,则返回null
  • Enumeration getInitParameterNames()
返回Servlet所有初始化参数的名字的枚举集合,若Servlet没有初始化参数,返回一个空的枚举集合
  • ServletContext getServletContext()
返回Servlet上下文对象的引用
  • String getServletName()
返回此Servlet示例的名称

示例代码:

import javax.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;

public class Myservlet extends GenericServlet{
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException,IOException{
        //设置字符集
        servletResponse.setContentType("text/html;charset=UTF-8");
        //使用PrintWriter.write()方法向前台页面输出内容
        PrintWriter writer = servletResponse.getWriter();
        writer.wtrte("这是输出内容");
        writer.close();
    }
}

3、继承javax.servlet.httpServlet抽象类,重写doGet()和doPost()方法

  • httpServlet继承了GenericServlet抽象类,用于开发基于HTTP协议的Servlet程序。由于Servlet主要用来处理HTTP请求和响应,通常情况下,编写的Servlet类都继承子HttpServlet类。

  • 在HTTP/1.1协议中共定义了7中请求方式,即GET、POST、HEAD、PUT、DELETE、TRACE和OPTIONS。

  • HttpServlet针对这7中请求方式定义了7中方法,doGet()、doPost()、doPut()、doHead()、doDelete()、doTrace()、doOptions()。

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

示例代码:

import javax.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyServlet extends HttpServlet{
    public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException,IOException{
        //使用PrintWriter.write()方法向前台页面输出内容
        res.setContentType("text/html;charset=UTF-8");
        PrintWriter writer = res.getWriter();
        writer.write("调用Get方法");
        writer.close();
    }
    public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException{
        PrintWriter writer = res.getWriter();
        writer.write("调用Post方法");
        writer.close();
        doGet(req, res);
    }
}

4、总结

上面演示了三种创建 Servlet 的方式,那么在实际开发中,我们究竟该选择哪一种呢?下面我们就来分析和对比一下。

1) Servlet 接口

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

2) GenericServlet 类

GenericServlet 抽象类实现了 Servlet 接口,并对 Servlet 接口中除 service() 方法外的其它四个方法进行了简单实现。通过继承 GenericServlet 创建 Servlet,只需要重写 service() 方法即可,大大减少了创建 Servlet 的工作量。

Generic 是“通用”的意思,正如其名,GenericServlet 是一个通用的 Servlet 类,并没有针对某种场景进行特殊处理,尤其是 HTTP 协议,我们必须手动分析和封装 HTPP 协议的请求信息和响应信息。

3) HttpServlet 类

HttpServlet 是 GenericServlet 的子类,它在 GenericServlet 的基础上专门针对 HTPP 协议进行了处理。HttpServlet 为 HTTP 协议的每种请求方式都提供了对应的方法,名字为 doXxx(),例如:

  • 处理 GET 请求的方法为 doGet();
  • 处理 POST 请求的方法为 doPost()。

正如其名,HttpServlet 就是专为 HTTP 协议而量身打造的 Servlet 类。

在互联网上,人们都是通过 HTTP 协议来访问动态网页的,其中使用最频繁的就是 GET 方式和 POST 方式,因此,我们通常基于 HttpServlet 来创建 Servlet 类,这样就省去了处理 HTTP 请求的过程。

四、Servlet的部署和访问

  • Servlet 没有 main() 方法,不能独立运行,但它可以作为 JavaWeb 应用的一个组件被部署到 Servlet 容器中,由容器来实例化和调用 Servlet 的方法,例如:doGet() 、doPost() 等。

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 应用中的资源。

webapps目录结构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hrtT3SlH-1631159929115)(…/…/webapps目录结构.png)]

2、部署

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

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

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

2.1 引入javax.servlet包

  • 由于 Servlet 是 JavaEE 下的技术标准,不是 JDK 的组成部分,所以在编译 Servlet 前,需要先引入 servlet-api.jar 包(在 Apache Tomcat 安装目录下的 lib 文件夹中提供了此 jar 包)。

  • 使用命令set classpath可以将 servlet-api.jar 引入,该命令的语法如下,其中 path 表示引入 jar 包的路径。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-98K2mkff-1631159929116)(img/引入javax.servlet包.png)]

2.2 编译Servlet

1)在命令提示符中使用 cd命令进入 MyServlet.java 所在的目录。

2)使用 javac -encoding UTF-8 -d . MyServlet.java命令进行编译,若没有报错,则编译成功。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tREavMsX-1631159929117)(img/编译Servlet.png)]

其中:

  • javac :编译命令;
  • -encoding UTF-8:用于指定编译源文件时的字符编码,这里指定为 UTF-8;
  • javac -d:带包编译 ;
  • .:表示当前位置。

2.3 创建目录结构

在完成对 Servlet 的编译后,下一步依照 JavaWeb 应用的固定目录结构,在 Tomcat 中为该 Servlet 创建目录。

  • 在 Tomcat 的 webapps 目录中创建 servletDemo(web 应用的名字,由自己定义)文件夹;
  • 在 servletDemo 文件夹内创建 WEB-INF 文件夹;
  • 在 WEB-INF 内创建 classes 文件夹;
  • 在 WEB-INF 内创建配置文件 web.xml(可以复制 \webapps\ROOT\WEB-INF 中的 web.xml 使用)。

2.4 将Servlet移动到Tomcat目录中

  • 将编译好的字节码和目录移动到 Tomcat\webapps\servletDemo\WEB-INF\classes 目录下。

2.5 配置web.xml

对 webapps\servletDemo\WEB-INF 目录的 web.xml 中进行配置,具体配置代码如下。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
   http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0" metadata-complete="true">
    <servlet>
        <servlet-name>MyServlet</servlet-name>
        <servlet-class>net.biancheng.www.MyServlet</servlet-class>
    </servlet>
   <servlet-mapping>
        <servlet-name>MyServlet</servlet-name>
        <url-pattern>/MyServlet</url-pattern>
    </servlet-mapping>
</web-app>

web.xml 中各元素含义及用法如下:

  • : 根元素。
  • :用于注册 Servlet,即给 Servlet 起一个独一无二的名字。
  • 包含两个主要的子元素 和 ,分别用于指定 Servlet 的名称和 Servlet 的完整限定名(包名+类名)。
  • :用于定义 Servlet 与 URL 之间的映射。
  • 包含两个子元素 和 ,分别用于指定 Servlet 的名称和虚拟路径。

2.6 访问

进入 Tomcat\bin 目录下双击 startup.bat,启动 Tomcat。

Tomcat 启动成功后,在地址栏中输入“http://localhost:8080/servletDemo/MyServlet”,访问 MyServlet。

五、@WebServlet注解

  • 在 Servlet 中,web.xml 扮演的角色十分的重要,它可以将所有的 Servlet 的配置集中进行管理,但是若项目中 Servelt 数量较多时,web.xml 的配置会变得十分的冗长。这种情况下,注解(Annotation)就是一种更好的选择。
  • 与 XML 不同,注解不需要依赖于配置文件,它可以直接在类中使用,其配置只对当前类有效,这样就避免了集中管理造成的配置冗长问题。那么 Servelt 支持注解吗?

5.1 @WebServlet注解的属性

@WebServlet用于讲一个类声明为Servlet,该注解会在部署时被容器处理,容器根据其具体的属性配置将响应的类部署为Servlet。如下:

属性名类型标签描述是否必需
nameString指定 Servlet 的 name 属性。 如果没有显式指定,则取值为该 Servlet 的完全限定名,即包名+类名。
valueString[ ]该属性等价于 urlPatterns 属性,两者不能同时指定。 如果同时指定,通常是忽略 value 的取值。
urlPatternsString[ ]指定一组 Servlet 的 URL 匹配模式。
loadOnStartupint指定 Servlet 的加载顺序。
initParamsWebInitParam[ ]指定一组 Servlet 初始化参数。
asyncSupportedboolean声明 Servlet 是否支持异步操作模式。
descriptionString指定该 Servlet 的描述信息。
displayNameString指定该 Servlet 的显示名。

5.2 @WebServlet注解的使用

5.2.1.启用注解的支持

  • web.xml 的顶层标签中有一个属性:metadata-complete,该属性用于指定当前web.xml是否是完全的。若该属性设置为true,则容器在部署时将只依赖web.xml,忽略所有的注解。若设置为false或者不设置,则表示启用注解支持。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         id="WebApp_ID" metadata-complete="false" version="4.0">
</web-app>

5.2.2 使用@WebServlet注解

  • @WebServlet 属于类级别的注解,标注在继承了 HttpServlet 的类之上。常用的写法是将 Servlet 的相对请求路径(即 value)直接写在注解内,如下所示。
@WebServlet(urlPatterns="/MyServlet")

示例代码:

  • 如果 @WebServlet 中需要设置多个属性,则属性之间必须使用逗号隔开
package net.biancheng.www;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@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") })
public class MyServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
    }
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
    }
}

注:

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

5.3 @WebServlet注解和web.xml的优缺点

使用 web.xml 或 @WebServlet 注解都可以配置 Servlet, 两者各有优缺点。

@WebServlet 注解配置 Servlet

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

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

web.xml 配置文件配置 Servlet

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

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

六、Servlet生命周期

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

6.1 初始化阶段

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

6.1.1 加载和实例化Servlet

  • Servlet 容器负责加载和实例化 Servlet。当容器启动或首次请求某个 Servlet 时,容器会读取 web.xml 或 @WebServlet 中的配置信息,对指定的 Servlet 进行加载。加载成功后,容器会通过反射对 Servlet 进行实例化。
  • 因为 Servlet 容器是通过 Java 的反射 API 来创建 Servlet 实例的,需要调用 Servlet 的默认构造方法(default constructor,即不带参数的构造方法),所以在编写 Servlet 类时,不能只提供一个带参数的构造方法。

6.1.2 调用init()方法进行初始化

  • 初始化的目的:让 Servlet 实例在处理请求之前完成一些初始化工作,例如建立数据库连接,获取配置信息等。
  • 初始化期间,Servlet 实例可以通过 ServletConfig 对象获取在 web.xml 或者 @WebServlet 中配置的初始化参数。

6.2 运行时阶段

  • 运行时阶段是 Servlet 生命周期中最重要的阶段。Servlet 容器接收到来自客户端请求时,容器会针对该请求分别创建一个 ServletRequst 对象和 ServletResponse 对象,将它们以参数的形式传入 service() 方法内,并调用该方法对请求进行处理。
  • 在 service() 方法中,Servlet 通过 ServletRequst 对象获取客户端的相关信息和请求信息。在请求处理完成后,通过 ServletResponse 对象将响应信息进行包装,返回给客户端。当 Servlet 容器将响应信息返回给客户端后,ServletRequst 对象与 ServletResponse 对象就会被销毁。
  • 在 Servlet 的整个生命周期内,对于 Servlet 的每一次请求,Servlet 容器都会调用一次 service() 方法,并创建新的 ServletRequest 和 ServletResponse 对象。即 service() 方法在 Servlet 的整个生命周期中会被调用多次。

6.3 销毁阶段

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

6.4 Servlet生命周期执行流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ETAL07J8-1631159929118)(…/…/Servlet生命周期流程.png)]

七、load-on-startup元素(写在xml文件的Servlet标签中)

  • load-on-startup 是 web.xml 中的一个节点,是 servlet 元素的子元素,用来标记 Servlet 容器启动时是否初始化当前 Servlet,以及当前 Servlet 的初始化顺序

  • load-on-startup元素取值规则如下:

    它的取值必须是一个整数;
    当值小于0或者没有指定时,则表示容器在该Servlet被首次请求时才会被加载;
    当值大于0或等于0时,表示容器在启动时就加载并初始化该Servlet,取值越小,优先级越高;
    当取值相同时,容器会自行选择顺序进行加载;
    

    注:@WebServlet注解的loadOnStartup属性与web.xml中的load-on-startup元素对应,取值的规则和含义相同

八、Servlet虚拟路径映射

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

  • Servlet虚拟路径映射可被分为两类:

    • 单一映射
    • 多重映射

8.1 单一映射

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

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

  • 使用 web.xml 实现单一映射;

  • 使用 @WebServlet 实现单一映射。

8.1.1 web.xml实现单一映射

如下代码:

<?Xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         id="WebApp_ID" metadata-complete="false" version="4.0">
    <servlet>													<!-- 用于注册Servlet-->
        <servlet-name>MyServlet</servlet-name>					<!--Servlet名称 -->
        <servlet-class>net.biancheng.www.MyServlet</servlet-class>		<!--Servlet的全类名 -->
    </servlet>
    <servlet-mapping>								<!--定义Servlet与虚拟路径之间的映射 -->
        <servlet-name>MyServlet</servlet-name>				<!--Servlet名称 -->
        <url-pattern>/myServlet</url-pattern>				<!--Servlet的虚拟路径 -->
    </servlet-mapping>
</web-app>

8.1.2 @WebServlet实现单一映射

如下代码:

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/MyServlet")		//指定了Servlet的虚拟路径
或者 
@WebServlet(urlPatterns="/MyServlet")		//使用urlPattern实现单一映射
public class MyServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    }
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

8.2 Servlet多重映射

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

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

  • 配置多个元素
  • 配置多个元素
  • 在@WebServlet的urlPattern属性中使用字符串数组

8.2.1配置多个元素

  • Servlet 2.5 规范之前, 元素只允许包含一个 子元素,若要实现 Servet 的多重映射,只能通过配置多个 元素实现

xml代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
    id="WebApp_ID" metadata-complete="false" version="4.0">
    <servlet>
        <servlet-name>MyServlet</servlet-name>
        <servlet-class>net.biancheng.www.MyServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>	
        <servlet-name>MyServlet</servlet-name>
        <url-pattern>/myServlet</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>MyServlet</servlet-name>
        <url-pattern>/myServlet2</url-pattern>
    </servlet-mapping>
</web-app>

如上代码: /myServlet和/myServlet都可以访问到myServlet页面

8.2.2 配置多个元素

  • 从 Servlet 2.5 开始, 元素可以包含多个 子元素,每个 代表一个虚拟路径的映射规则。因此,通过在一个 元素中配置多个 子元素,也可以实现 Servlet 的多重映射。

如下代码:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
    id="WebApp_ID" metadata-complete="false" version="4.0">
    <servlet>
        <servlet-name>MyServlet</servlet-name>
        <servlet-class>net.biancheng.www.MyServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>MyServlet</servlet-name>
        <url-pattern>/myServlet</url-pattern>
        <url-pattern>/myServlet3</url-pattern>
    </servlet-mapping>
</web-app>

如上代码: /myServlet和/myServlet3都可以访问到myServlet页面

8.2.3 在@WebServlet的urlPattern属性中使用字符串数组

  • Servlet 3.0 增加了对 @WebServlet 注解的支持,我们可以在 urlPatterns 属性中,以字符串数组的形式指定一组映射规则来实现 Servlet 的多重映射。
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(
        urlPatterns = { "/myServlet", "/myServlet4" })
public class MyServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    private int initCount = 0;
    private int httpCount = 0;
    private int destoryCount = 0;
    @Override
    public void destroy() {
        destoryCount++;
        super.destroy();
        // 向控制台输出destory方法被调用次数
        System.out.println(
                "**********************************destroy方法:" + destoryCount + "*******************************");
    }
    @Override
    public void init() throws ServletException {
        initCount++;
        super.init();
        // 向控制台输出init方法被调用次数
        System.out.println("init方法:" + initCount);
    }
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        httpCount++;
        // 控制台输出doGet方法次数
        System.out.println("doGet方法:" + httpCount);
        // 设置返回页面格式与字符集
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter writer = resp.getWriter();
        // 向页面输出
        writer.write("初始化次数:" + initCount + "<br/>" + "处理请求次数:" + httpCount + "<br/>" + "销毁次数:" + destoryCount);
        writer.close();
    }
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
    }
}

在注解中配置多个虚拟路径,用字符串数组的形式实现。

九、Servlet虚拟路径匹配规则

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

  • 以 servletDemo 为例,若 URL 为“http://localhost:8080/servletDemo/myServlet”,其应用上下文是 servletDemo,容器会将“http://localhost:8080/servletDemo”去掉,使用剩余的“/myServlet”与 Servlet 虚拟路径进行匹配。

9.1 匹配规则

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

  • 完全路径匹配

  • 目录匹配

  • 扩展名匹配

  • 缺省匹配(默认匹配)

匹配规则使用规则虚拟路径可访问的URL
完全路径匹配 (精确匹配)/开始,不能包含通配符*。 必须完全匹配/myServlet /user/myServlet /product/index.actionhttp://localhost:8080/servletDemo/myServlet http://localhost:8080/servletDemo/user/myServlet http://localhost:8080/servletDemo/product/index.action
目录匹配/字符开头,并以/*结尾的字符串。 用于路径匹配/user/* /*http://localhost:8080/servletDemo/user/aaa http://localhost:8080/servletDemo/aa
扩展名匹配以通配符*.开头的字符串。 用于扩展名匹配*.do *.action *.jsphttp://localhost:8080/servletDemo/user.do http://localhost:8080/servletDemo/myServlet.action http://localhost:8080/servletDemo/bb.jsp
缺省匹配(默认匹配)映射路径为/,表示这个 Servlet 为当前应用的缺省 Servlet 或默认 Servlet,默认处理无法匹配到虚拟路径的请求。/可以匹配任意请求 URL

9.2 匹配优先级

  • Servlet虚拟路径的匹配优先级顺序为:全路径匹配(精确匹配)>目录匹配>扩展名匹配>缺省匹配(默认匹配)
  • Servlet容器会从优先级高的虚拟路径开始匹配,匹配成功以后就会立刻将请求交给相应的Servlet处理,不会再关注其他虚拟路径是否匹配成功

十、ServletConfig接口

  • Servlet 容器初始化 Servlet 时,会为这个 Servlet 创建一个 ServletConfig 对象,并将 ServletConfig 对象作为参数传递给 Servlet 。通过 ServletConfig 对象即可获得当前 Servlet 的初始化参数信息。
  • 一个Web应用中可以存在多个ServletConfig对象,一个Servlet只能对应一个ServletConfig对,即Servlet的初始化参数仅对当前Servlet有效

10.1 获得Servlet对象

10.1.1 直接从带参的init()方法中提取

public class ServletConfigDemo extends HttpServlet {
    private ServletConfig servletConfig;
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取Servlet得名字
        this.servletConfig.getServletName();
    }
    @Override
    public void init(ServletConfig config) throws ServletException {
        //从带参init方法中,提取ServletConfig对象
        this.servletConfig = config;
    }
}

10.1.2 调用GenericServlet提供的getSerletConfig()方法获得

//调用 GenericServlet 提供的 getServletConfig 方法获得 ServletConfig 对象
ServletConfig servletConfig = this.getServletConfig();

10.2 ServletConfig接口(方法)

返回值类型方法功能描述
StringgetInitParameter(String name)根据初始化参数名 name,返回对应的初始化参数值。
EnumerationgetInitParameterNames()返回 Servlet 所有的初始化参数名的枚举集合,如果该 Servlet 没有初始化参数,则返回一个空的集合。
ServletContextgetServletContext()返回一个代表当前 Web 应用的 ServletContext 对象。
StringgetServletName()返回 Servlet 的名字,即 web.xml 中 元素的值。

10.3 配置Servlet初始化参数

10.3.1 使用web.xml配置初始化参数

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
    id="WebApp_ID" metadata-complete="false" version="4.0">
    <servlet>
        <servlet-name>MyServlet</servlet-name>
        <servlet-class>net.biancheng.www.MyServlet</servlet-class>
        <!-- Servlet 初始化参数 -->
        <init-param>
            <param-name>name</param-name>
            <param-value>value</param-value>
        </init-param>
        <!-- Servlet 初始化参数 -->
        <init-param>
            <param-name>URL</param-name>
            <param-value>www.biancheng.net</param-value>
        </init-param>
    </servlet>
</web-app>

10.3.2 使用@WebServlet配置初始化参数

import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(urlPatterns = {"/MyServlet"}, initParams = {@WebInitParam(name = "name", value = "编程帮"),
        @WebInitParam(name = "URL", value = "www.biancheng.net")})
public class MyServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

10.4 获取Servlet初始化参数

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
*
* @author 编程帮 www.biancheng.net
* 获取Servlet的初始化参数
*
*/
@WebServlet(urlPatterns = { "/ReadConfigServlet" }, initParams = { @WebInitParam(name = "name", value = "编程帮"),
        @WebInitParam(name = "URL", value = "www.biancheng.net") })
public class ReadConfigServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter writer = response.getWriter();
        // 获取ServletConfig对象
        ServletConfig config = getServletConfig();
        // 获取servletName
        String servletName = config.getServletName();
        // 返回 servlet 的初始化参数的名称的集合
        Enumeration<String> initParameterNames = config.getInitParameterNames();
        // 遍历集合获取初始化参数名称
        while (initParameterNames.hasMoreElements()) {
            // 获取初始化参数名称
            String initParamName = initParameterNames.nextElement();
            // 获取相应的初始参数的值
            String initParamValue = config.getInitParameter(initParamName);
            // 向页面输出
            writer.write(initParamName + "  :  " + initParamValue + "<br/>");
        }
        // 关闭流
        writer.close();
    }
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

十一、ServletContext接口

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

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

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

11.1 获得ServletContext对象

11.1.1 通过GenericServlet提供的getServletContext() 方法获取

ServletContext servletcontext = this.getServletContext();

11.1.2 通过ServletConfig提供的getServletContext()方法获取

ServletContext servletContext = this.getServletConfig().getServletContext();

11.1.3 通过 HttpSession 提供的 getServletContext() 方法

ServletContext servletContext = req.getSession().getServletContext();

11.1.3 通过 HttpServletRequest 提供的 getServletContext() 方法

ServletContext servletContext = req.getServletContext();

11.2 ServletContext的应用

11.2.1 获取上下文初始化参数

1)设置上下文初始化参数

  • 通过web.xml中的元素可以为web应用设置一些全局的初始化参数,这些参数被称为上下文初始化参数
  • 与 Servlet 的初始化参数不同,应用中的所有 Servlet 都共享同一个上下文初始化参数。在 Web 应用的整个生命周期中,上下文初始化参数会一直存在,并且可以随时被任意一个 Servlet 访问。

如下代码:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="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" metadata-complete="false">
    <!--设置全局初始化参数 -->
    <context-param>
        <param-name>name</param-name>
        <param-value>编程帮</param-value>
    </context-param>
    <context-param>
        <param-name>url</param-name>
        <param-value>www.biancheng.net</param-value>
    </context-param>
</web-app>

2)调用接口中方法获取初始化参数

  • Servlet 容器启动时,会为容器内每个 Web 应用创建一个 ServletContext 对象,并将 元素中的上下文初始化参数以键值对的形式存入该对象中,因此我们可以通过 ServletContext 的相关方法获取到这些初始化参数

    返回值类型方法描述
    StringgetInitParameter(String name)根据初始化参数名 name,返回对应的初始化参数值。
    EnumerationgetInitParameterNames()返回 Web 应用所有上下文初始化参数名的枚举集合,如果该 Web 应用没有上下文初始化参数,则返回一个空的枚举集合。

代码示例:

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/ReadContextServlet")
public class ReadContextServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter writer = response.getWriter();
        // 调用httpServlet父类GenericServlet的getServletContext方法获取ServletContext对象
        ServletContext context = super.getServletContext();
        // 返回 context 上下文初始化参数的名称
        Enumeration<String> initParameterNames = context.getInitParameterNames();
        while (initParameterNames.hasMoreElements()) {
            // 获取初始化参数名称
            String initParamName = initParameterNames.nextElement();
            // 获取相应的初始参数的值
            String initParamValue = context.getInitParameter(initParamName);
            // 向页面输出
            writer.write(initParamName + "  :  " + initParamValue + "<br/>");
        }
        // 关闭流
        writer.close();
    }
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

11.2.2 实现Servlet之间的数据通讯

  • 在 Servlet 中,调用 ServletContext 接口的 setAttribute() 方法可以创建一些属性,这些属性被存放在 ServletContext 对象中。应用中所有 Servlet 都可以对这些属性进行访问和操作,通过它们可以实现应用内不同 Servlet 之间的数据通讯。

相关方法:

返回值类型方法描述
voidsetAttribute(String name, Object object)把一个 Java 对象与一个属性名绑定,并将它作为一个属性存放到 ServletContext 中。 参数 name 为属性名,参数 object 为属性值。
voidremoveAttribute(String name)从 ServletContext 中移除属性名为 name 的属性。
ObjectgetAttribute(String name)根据指定的属性名 name,返回 ServletContext 中对应的属性值。

ServletContext 属性与上下文初始化参数对比

  • 虽然 ServletContext 的属性与上下文初始化参数都是存放在 ServletContext 对象中,但它们是不同的。
不同点ServletContext 的属性上下文初始化参数
创建方式ServletContext 的属性通过调用 ServletContext 接口的 setAttribute() 方法创建上下文初始化参数通过 web.xml 使用 元素配置
可进行的操作ServletContext 的属性可以通过 ServletContext 接口的方法进行读取、新增、修改、移除等操作上下文初始化参数在容器启动后只能被读取,不能进行新增、修改和移除操作
生命周期ServletContext 中属性的生命周期从创建开始,到该属性被移除(remove)或者容器关闭结束上下文初始化参数的生命周期,从容器启动开始,到 Web 应用被卸载或容器关闭结束
作用使用 ServletContext 中的属性可以实现 Servlet 之间的数据通讯使用上下文初始化参数无法实现数据通讯

代码示例:

创建一个名称为 CountServlet 的 Servlet 类,代码如下。

import java.io.IOException;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
*
* @author 编程帮 www.biancheng.net
* 使用ServletContext 统计访问次数
*
*/
@WebServlet("/CountServlet")
public class CountServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    public void init() throws ServletException {
        // 获取ServletContext对象
        ServletContext context = getServletContext();
        // 初始化时,向ServletContext中设置count属性,初始值为0
        context.setAttribute("count", 0);
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 调用httpServlet父类GenericServlet的getServletContext方法获取ServletContext对象
        ServletContext context = super.getServletContext();
        // 获取count的值,自增
        Integer count = (Integer) context.getAttribute("count");
        // 存入到域对象中
        context.setAttribute("count", ++count);
        // 向页面输出内容
        response.setContentType("text/html;charset=UTF-8");
        response.getWriter().write("<h3>编程帮  www.biancheng.net 欢迎您</h3>");
    }
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

然后再创建一个名称为 ShowServlet 的 Servlet 类,代码如下。

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
*
* @author 编程帮 www.biancheng.net
* 使用ServletContext展示网站的访问次数
*
*/
@WebServlet("/ShowServlet")
public class ShowServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 获取ServletContext中存放的count属性(即页面的访问次数)
        Integer count = (Integer) getServletContext().getAttribute("count");
        // 向页面输出
        response.setContentType("text/html;charset=UTF-8");
        // 若CountServlet已被访问
        if (count != null) {
            response.getWriter().write("<h3>该网站一共被访问了" + count + "次</h3>");
        } else {
            // 若CountServlet未被访问,提示先访问CountServlet
            response.getWriter().write("<h3>请先访问 CountServlet</h3>");
        }
    }
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

11.2.3 读取Web应用下的资源文件

  • 在实际开发中,有时会需要读取 Web 应用中的一些资源文件,如配置文件和日志文件等。为此,ServletContext 接口定义了一些读取 Web 资源的方法 ,如下表。

    返回值类型方法方法描述
    SetgetResourcePaths(String path)返回一个 Set 集合,该集合中包含资源目录中的子目录和文件的名称。
    StringgetRealPath(String path)返回资源文件的真实路径(文件的绝对路径)。
    URLgetResource(String path)返回映射到资源文件的 URL 对象。
    InputStreamgetResourceAsStream(String path)返回映射到资源文件的 InputStream 输入流对象。

代码示例:

properties文件:

name=编程帮
url=www.biancheng.net
desc=编程帮,欢迎你

Servlet类:

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Properties;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
*
* @author 编程帮 www.biancheng.net
* 使用ServletContext肚读取资源文件
*
*/
@WebServlet("/ReadServlet")
public class ReadServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter writer = response.getWriter();
        // 获取相对路径中的输入流对象
        InputStream ins = getServletContext().getResourceAsStream("/WEB-INF/classes/db.properties");
        // 获取输入流
        Properties pro = new Properties();
        // 加载
        pro.load(ins);
        // 获取文件中的内容
        String name = pro.getProperty("name");
        String url = pro.getProperty("url");
        String desc = pro.getProperty("desc");
        writer.write("用户名:" + name + "<br/>" + "地址:" + url + "<br/>" + "描述:" + desc + "<br/>");
    }
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

十二、HttpServletRequest接口

  • 一般情况下,浏览器(客户端)通过 HTTP 协议来访问服务器的资源,Servlet 主要用来处理 HTTP 请求。

Servlet 处理 HTTP 请求的流程如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iBCLnaMs-1631159929119)(img/Servlet处理Http请求.png)]

流程:

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

12.1 HttpServletRequest接口

  • 在 Servlet API 中,定义了一个 HttpServletRequest 接口,它继承自 ServletRequest 接口。HttpServletRequest 对象专门用于封装 HTTP 请求消息,简称 request 对象。
  • HTTP 请求消息分为请求行、请求消息头和请求消息体三部分,所以 HttpServletRequest 接口中定义了获取请求行、请求头和请求消息体的相关方法。

12.1.1 获取请求行信息

  • HTTP 请求的请求行中包含请求方法、请求资源名、请求路径等信息,HttpServletRequest 接口定义了一系列获取请求行信息的方法,如下表。
返回值类型方法声明描述
StringgetMethod()该方法用于获取Http请求方式(如GET、POST)
StringgetRequestURI()该方法用于获取请求行中的资源名称部分,即位于URL的主机和端口之后,参数之前的部分
StringgetQueryString()该方法用于获取请求行中的参数部分,也就是URL中"?"以后的所有内容
StringgetContextPath()返回当前Servlet所在应用的名字(上下文)。对于默认(ROOT)中的Servlet,其返回值为空字符串
StringgetServletPath()获取当前Servlet所映射的路径
StringgetRemoteAddr()获取客户端的IP地址
StringgetRemoteHost()用于获取客户端的完整主机名,如果无法解析出客户机的完整主机名,则返回IP地址

代码示例:

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/RequestLine")
public class RequestLine extends HttpServlet {
    private static final long serialVersionUID = 1L;
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter writer = response.getWriter();
        writer.println("请求方式:" + request.getMethod() + "<br/>" +
                "客户端的 IP 地址:" + request.getRemoteAddr() + "<br/>" +
                "应用名字(上下文):" + request.getContextPath() + "<br/>" +
                "URI:" + request.getRequestURI() + "<br/>" +
                "请求字符串:" + request.getQueryString() + "<br/>" +
                "Servlet所映射的路径:" + request.getServletPath() + "<br/>" +
                "客户端的完整主机名:" + request.getRemoteHost() + "<br/>"
        );
    }
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}

12.1.2 获取请求头信息

  • 当浏览器发送请求时,需要通过请求头向服务器传递一些附加信息,例如客户端可以接收的数据类型、压缩方式、语言等。为了获取请求头中的信息, HttpServletRequest 接口定义了一系列用于获取 HTTP 请求头字段的方法,如下表所示。
返回值类型方法声明描述
StringgetHeader(String name)该方法用于获取一个指定头字段的值。 如果请求消息中包含多个指定名称的头字段,则该方法返回其中第一个头字段的值。
EnumerationgetHeaders(String name)该方法返回指定头字段的所有值的枚举集合, 在多数情况下,一个头字段名在请求消息中只出现一次,但有时可能会出现多次。
EnumerationgetHeaderNames()该方法返回请求头中所有头字段的枚举集合。
StringgetContentType()该方法用于获取 Content-Type 头字段的值。
intgetContentLength()该方法用于获取 Content-Length 头字段的值 。
StringgetCharacterEncoding()该方法用于返回请求消息的字符集编码 。

12.1.3 获取form表单的数据

  • 在实际开发中,我们经常需要获取用户提交的表单数据,例如用户名和密码等。为了方便获取表单中的请求参数,ServletRequest 定义了一系列获取请求参数的方法,如下表所示。
返回值类型方法声明功能描述
StringgetParameter(String name)返回指定参数名的参数值。
String [ ]getParameterValues (String name)以字符串数组的形式返回指定参数名的所有参数值(HTTP 请求中可以有多个相同参数名的参数)。
EnumerationgetParameterNames()以枚举集合的形式返回请求中所有参数名。
MapgetParameterMap()用于将请求中的所有参数名和参数值装入一个 Map 对象中返回。

12.2 中文乱码问题

  • 根据请求方式的不同,请求一般可以被分为两种:GET 请求和 POST 请求。这两种请求方式都可能会产生中文乱码问题,下面我们分别对它们产生乱码的原因及其解决方案进行介绍。

12.2.1 POST请求

乱码的原因:

  • POST 提交的数据在请求体中,其所使用的编码格式时页面一致(即 utf-8)。request 对象接收到数据之后,会将数据放到 request 缓冲区,缓冲区的默认字符集是 ISO-8859-1(该字符集不支持中文),两者使用的字符集不一致导致乱码。

解决方案:

  • 在获取请求参数之前设置 request 缓冲区字符集为 utf-8 ,代码如下。
//修改request缓冲区的字符集为UTF-8
request.setCharacterEncoding("utf-8");
// 获取用户名
String username = request.getParameter("username");

12.2.2 GET请求

乱码的原因:

  • Get 请求将请求数据附加到 URL 后面作为参数,浏览器发送文字时采用的编码格式与页面编码保持一致(utf-8)。如果 Tomcat 没有设置字符集,接收 URL 时默认使用 ISO-8859-1 进行解码,ISO-8859-1 不兼容中文,无法正确解码,导致出现乱码。

解决方案:

1、修改 tomcat/conf/server.xml 中的配置,代码如下。

<Connector port="80" protocol="HTTP/1.1"
         connectionTimeout="20000"
           redirectPort="8443" URIEncoding="UTF-8"/>

2、使用 URLEncoder 和 URLDecoder 进行编码和解码的操作(逆向编解码)。

//得到TOMCAT通过ISO8859-1解码的字符串
String username = request.getParameter("username");
//对字符串使用ISO8859-1进行编码,得到最初浏览器使用UTF-8编码的字符串
username = URLEncoder.encode(username, "ISO8859-1");
//将使用UTF-8编码的字符串使用UTF-8进行解码,得到正确的字符串
username = URLDecoder.decode(username, "UTF-8");

3、使用 String 的构造方法:String(byte[] bytes, String charset) ,对字节数组(bytes)按照指定的字符集(charset)进行解码,返回解码后的字符串,解决乱码问题(推荐使用)

//获取姓名
String username = request.getParameter("username");
//使用String的构造方法解决乱码的问题
username = new String(username.getBytes("ISO-8859-1"),"UTF-8");

十三、Servlet请求转发

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

13.1 请求转发

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

13.1.1 RequestDispatcher接口

  • javax.servlet 包中定义了一个 RequestDispatcher 接口,RequestDispatcher 对象由 Servlet 容器创建,用于封装由路径所标识的 Web 资源。利用 RequestDispatcher 对象可以把请求转发给其他的 Web 资源。

Servlet 可以通过 2 种方式获得 RequestDispatcher 对象:

  1. 调用 ServletContext 的 getRequestDispatcher(String path) 方法,参数 path 指定目标资源的路径,必须为绝对路径;
  2. 调用 ServletRequest 的 getRequestDispatcher(String path) 方法,参数 path 指定目标资源的路径,可以为绝对路径,也可以为相对路径。

RequestDispatcher 接口中提供了以下方法。

返回值类型方法功能描述
voidforward(ServletRequest request,ServletResponse response)用于将请求转发给另一个 Web 资源。该方法必须在响应提交给客户端之前被调用,否则将抛出 IllegalStateException 异常
voidinclude(ServletRequest request,ServletResponse response)用于将其他的资源作为当前响应内容包含进来

13.1.2 请求转发的工作原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7APeFmLj-1631159929120)(img/请求转发的工作原理.png)]

13.1.3 请求转发的特点

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

13.2 request域对象

  • request 是 Servlet 的三大域对象之一,它需要与请求转发配合使用,才可以实现动态资源间的数据传递。
返回值类型方法描述
voidsetAttribute(String name, Object o)将 Java 对象与属性名绑定,并将它作为一个属性存放到 request 对象中。参数 name 为属性名,参数 object 为属性值。
ObjectgetAttribute(String name)根据属性名 name,返回 request 中对应的属性值。
voidremoveAttribute(String name)用于移除 request 对象中指定的属性。
EnumerationgetAttributeNames()用于返回 request 对象中的所有属性名的枚举集合。

13.2.1 request域对象和Context域对象的对比

1) 生命周期不同

Context 域对象的生命周期从容器启动开始,到容器关闭或者 Web 应用被移除时结束;

request 域对象的生命周期从客户端向容器发送请求开始,到对这次请求做出响应后结束。

2) 作用域不同

Context 域对象对整个 Web 应用内的所有Servlet都有效;

request 域对象只对本次请求涉及的 Servlet 有效。

3) Web 应用中数量不同

整个 Web 应用中只有一个 Context 域对象;

由于 Servlet 能处理多个请求,因此 Web 应用中的每个 Servlet 实例都可以有多个 request 域对象。

4) 实现数据共享的方式不同

Context 域对象可以独立完成动态资源之间的数据共享;

Request 域对象需要与请求转发配合使用才能实现动态资源之间的数据共享。

十四、 HttpServletResponse接口

  • 在 Servlet API 中,定义了一个 HttpServletResponse 接口,它继承自 ServletResponse 接口。HttpServletResponse 对象专门用来封装 HTTP 响应消息,简称 response 对象。
  • Servlet 容器会针对每次请求创建一个 response 对象,并把它作为参数传递给 Servlet 的 service 方法。Servlet 处理请求后,会将响应信息封装到 response 对象中,并由容器解析后返回给客户端。
  • 由于 HTTP 响应消息由响应行、响应头、消息体三部分组成,所以 HttpServletResponse 接口中定义了向客户端发送响应状态码、响应头、响应体的方法

14.1 响应行相关方法

返回值类型方法描述
voidsetStatus(int status)用于设置 HTTP 响应消息的状态码,并生成响应状态行。
voidsendError(int sc)用于发送表示错误信息的状态码。

14.2 响应头相关方法

返回值类型方法描述
voidaddHeader(String name,String value)用于增加响应头字段,其中,参数 name 用于指定响应头字段的名称,参数 value 用于指定响应头字段的值。
voidsetHeader (String name,String value)用于设置响应头字段,其中,参数 name 用于指定响应头字段的名称,参数 value 用于指定响应头字段的值。
voidaddIntHeader(String name,int value)用于增加值为 int 类型的响应头字段,其中,参数 name 用于指定响应头字段的名称,参数 value 用于指定响应头字段的值,类型为 int。
voidsetIntHeader(String name, int value)用于设置值为 int 类型的响应头字段,其中,参数 name 用于指定响应头字段的名称,参数 value 用于指定响应头字段的值,类型为 int。
voidsetContentType(String type)用于设置 Servlet 输出内容的 MIME 类型以及编码格式。
voidsetCharacterEncoding(String charset)用于设置输出内容使用的字符编码。

14.3 响应体相关方法

返回值类型方法描述
ServletOutputStreamgetOutputStream()用于获取字节输出流对象。
PrintWritergetWriter()用于获取字符输出流对象。

十五、Servlet重定向

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

15.1 重定向流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7y0PxstM-1631159929121)(img/Servlet重定向流程.png)]

说明:

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

15.2 转发和重定向的区别

区别转发重定向
浏览器地址栏 URL 是否发生改变
是否支持跨域跳转
请求与响应的次数一次请求和一次响应两次请求和两次响应
是否共享 request 对象和 response 对象
是否能通过 request 域对象传递数据
速度相对要快相对要慢
行为类型服务器行为客户端行为

15.3 重定向实现(response.sendRedirect()方法)

调用response.sendRedirect()方法实现重定向

  • 该方法向浏览器返回状态码为 302 的响应结果,让浏览器访问新的 URL。若指定的 URL 是相对路径,Servlet 容器会将相对路径转换为绝对路径。参数 location 表示重定向的URL。

十六、Servlet Cookie

  • HTTP(超文本传输协议)是一个基于请求与响应模式的无状态协议。
  • 无状态协议:
    • 1、协议对于事务处理没有记忆能力,服务器不能自动维护用户的上下文信息,无法保存用户状态;
    • 2、每次请求都是独立的,不会受到前面请求的影响,也不会影响后面的请求;
  • 当浏览器发送 HTTP 请求到服务器时,服务器会响应客户端的请求,但当同一个浏览器再次发送请求到该服务器时,服务器并不知道它就是刚才那个浏览器,即 HTTP 协议的请求无法保存用户状态。
  • 通常情况下,用户通过浏览器访问 Web 应用时,服务器都需要保存和跟踪用户的状态。例如,用户在某购物网站结算商品时,Web 服务器必须根据请求用户的身份,找到该用户所购买的商品。由于 HTTP 协议是无协议的,无法保存和跟踪用户状态,所以需要其他的方案来解决问此题,它就是会话技术。

16.1 会话技术

  • 从打开浏览器访问某个网站,到关闭浏览器的过程,称为一次会话。会话技术是指在会话中,帮助服务器记录用户状态和数据的技术;

16.1.1 Cookie(客户端会话技术)

1、Cookie分为两种:
  • 会话级别Cookie(默认):Cookie保存到浏览器的内存中,浏览器关闭则Cookie失效;
  • 持久的Cookie:Cookie以文本文件的形式保存到硬盘上;
2、Cookie工作流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-inIJw7zx-1631159929121)(img/Cookie工作流程.png)]

说明:

  • 客户端浏览器访问服务器时,服务器通过在HTTP响应中增加Set-Cookie字段,将数据信息发给浏览器;
  • 浏览器将Cookie保存在内存中或硬盘上;
  • 再次请求该服务器时,浏览器通过在HTTP请求信息中增加Cookie请求头字段,将Cookie回传给Web服务器。服
  • 务器根据Cookie信息跟踪客户端的状态;
3、Cookie API
  • javax.servlet.http包中定义了一个Cookie类
Cookie c = new Cookie("url","www.biancheng.net");
  • HttpServletResponse 接口和 HttpServletRequest 接口也都定义了与 Cookie 相关的方法,如下表所示。
方法描述所属接口
void addCookie(Cookie cookie)用于在响应头中增加一个相应的 Set-Cookie 头字段。javax.servlet.http.HttpServletResponse
Cookie[] getCookies()用于获取客户端提交的 Cookie。javax.servlet.http.HttpServletRequest
  • javax.servlet.http.Cookie 类中提供了一系列获取或者设置 Cookie 的方法,如下表
返回值类型方法描述
intgetMaxAge()用于获取指定 Cookie 的最大有效时间,以秒为单位。 默认情况下取值为 -1,表示该 Cookie 保留到浏览器关闭为止。
StringgetName()用于获取 Cookie 的名称。
StringgetPath()用于获取 Cookie 的有效路径。
booleangetSecure()如果浏览器只通过安全协议发送 Cookie,则返回 true;如果浏览器可以使用任何协议发送 Cookie,则返回 false。
StringgetValue()用于获取 Cookie 的值。
intgetVersion()用于获取 Cookie 遵守的协议版本。
voidsetMaxAge(int expiry)用于设置 Cookie 的最大有效时间,以秒为单位。 取值为正值时,表示 Cookie 在经过指定时间后过期。取值为负值时,表示 Cookie 不会被持久存储,在 Web 浏览器退出时删除。取值为 0 时,表示删除该 Cookie。
voidsetPath(String uri)用于指定 Cookie 的路径。
voidsetSecure(boolean flag)用于设置浏览器是否只能使用安全协议(如 HTTPS 或 SSL)发送 Cookie。
voidsetValue(String newValue)用于设置 Cookie 的值。
4、Cookie细节

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

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

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

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

16.1.2 Session(服务端会话技术)

  • Session 是服务器端会话技术。当浏览器访问 Web 服务器的资源时,服务器可以为每个用户浏览器创建一个 Session 对象,每个浏览器独占一个 Session 对象。
  • 由于每个浏览器独占一个 Session,所以用户在访问服务器的资源时,可以把数据保存在各自的 Session 中。当用户再次访问该服务器中的其它资源时,其它资源可以从 Session 中取出数据,为用户服务。

工作原理:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9g4LSRIy-1631159929122)(img/Session会话技术.jpg)]

说明:

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

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

不同点CookieSession
存储位置不同Cookie 将数据存放在客户端浏览器内存中或硬盘上。Session 将数据存储在服务器端。
大小和数量限制不同浏览器对 Cookie 的大小和数量有限制。Session 的大小和数量一般不受限制。
存放数据类型不同Cookie 中保存的是字符串。Session 中保存的是对象。
安全性不同Cookie 明文传递,安全性低,他人可以分析存放在本地的 Cookie 并进行 Cookie 欺骗。Session 存在服务器端,安全性较高。
对服务器造成的压力不同Cookie 保存在客户端,不占用服务器资源。Session 保存在服务端,每一个用户独占一个 Session。若并发访问的用户十分多,就会占用大量服务端资源。
跨域支持上不同Cookie 支持跨域名访问。Session 不支持跨域名访问。
2、Session API

Session 对象由服务器创建,通过 HttpServletRequest.getSession() 方法可以获得 HttpSession 对象,例如:

//获取session对象
HttpSession session=request.getSession();
3、操作Session对象的方法
返回值类型方法描述
longgetCreationTime()返回创建 Session 的时间。
StringgetId()返回获取 Seesion 的唯一的 ID。
longgetLastAccessedTime()返回客户端上一次发送与此 Session 关联的请求的时间。
intgetMaxInactiveInterval()返回在无任何操作的情况下,Session 失效的时间,以秒为单位。
ServletContextgetServletContext()返回 Session 所属的 ServletContext 对象。
voidinvalidate()使 Session 失效。
voidsetMaxInactiveInterval(int interval)指定在无任何操作的情况下,Session 失效的时间,以秒为单位。负数表示 Session 永远不会失效。
4、设置Session过期时间
  • 使用元素
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    <!--设置session的过期时间-->
    <session-config>
        <session-timeout>10</session-timeout>
    </session-config>
</web-app>

注:0或者负数表示为永不过期

  • 调用setMaxInactiveInterval()方法
//设置会话的过期时间
request.getSession().setMaxInactiveInterval(100);
5、Session生命周期

对象创建

Session 对象在容器第一次调用 request.getSession() 方法时创建。

  • 值得注意的是,当客户端访问的 Web 资源是 HTML,CSS,图片等静态资源时,服务器不会创建 Session 对象。

Session 对象销毁

Session 对象在如下 3 种情况下会被销毁:

  • Session 过期;
  • 调用 session.invalidate() 方法,手动销毁 Session;
  • 服务器关闭或者应用被卸载。
6、Session域对象
  • Session 对象也是一种域对象,它可以对属性进行操作,进而实现会话中请求之间的数据通讯和数据共享。

常用方法:

返回值类型方法描述
voidsetAttribute(String name, Object o)把一个 Java 对象与一个属性名绑定,并将它作为一个属性存放到 Session 对象中。 参数 name 为属性名,参数 object 为属性值。
ObjectgetAttribute(String name)根据指定的属性名 name,返回 Session 对象中对应的属性值。
voidremoveAttribute(String name)从 Session 对象中移除属性名为 name 的属性。
EnumerationgetAttributeNames()用于返回 Session 对象中的所有属性名的枚举集合。
  • Session 、request 以及 ServletContext 合称为 Servlet 的三大域对象,它们都能保存和传递数据,但是三者也存在许多差异,如下表。
不同requestSessionServletContext
类型javax.servlet.http.HttpServletRequestjavax.servlet.http.HttpSessionjavax.servlet.ServletContext
创建客户端向容器发送请求时创建。容器第一次调用 getSession() 方法时创建。Servlet 容器启动时创建。
销毁容器对这次请求做出响应后销毁。Session 销毁的时机: 关闭服务器或应用被卸载。Session 过期,默认为 30 分钟。手动调用 session.invalidate() 方法进行销毁。容器关闭或者 Web 应用被移除时销毁。
有效范围只对当前请求涉及的 Servlet 有效。Session 对本次会话期间的所有 Servlet 都有效。对整个 Web 应用内的所有 Servlet 有效。
数量Web 应用中的所有 Servlet 实例都可以有多个 request 对象。Web 应用中可以有多个 Session,多个 Servet 实例可以共享同一 Session 对象。在整个 Web 应用中只有一个 Context 对象。
数据共享每一次请求都是一个新的 request 对象。 通过和请求转发的配合使用可以实现一次请求中 Web 组件之间共享的数据。每一次会话都是一个新的 Session 对象。 通过 Session 域对象可以实现一次会话中的多个请求之间共享数据。在一个应用中有且只有一个 Context 对象,作用于整个 Web 应用,可以实现多次会话之间的数据共享。

十七、Servlet Filter

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

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

    • 在 Web 资源被访问前,检查 request 对象,修改请求头和请求正文,或对请求进行预处理操作。
    • 将请求传递到下一个过滤器或目标资源。
    • 在 Web 资源被访问后,检查 response 对象,修改响应头和响应正文。
  • Filter 是 Servlet 规范中最实用的技术,通过它可以对服务器管理的所有 Web 资源(例如 JSP、Servlet、静态 HTML 文件、静态图片等)进行拦截,从而实现一些特殊的功能,例如用户的权限控制、过滤敏感词、设置统一编码格式等。

17.1 Filter接口( javax.servlet.Filter)

17.1.1 接口方法

返回值类型方法功能描述
voidinit (FilterConfig filterConfig)该方法用于初始化过滤器。
voiddoFilter(ServletRequest request,SeivletResponse response, FilterChain chain)该方法完成实际的过滤操作,当客户端请求的 URL 与过滤器映射的 URL 匹配时,容器会先调用该方法对请求进行拦截。 参数 request 和 response 表示请求和响应对象。 参数 chain 代表当前 Filter 链对象,在该方法内部,调用 chain.doFilter() 方法,才能把请求交付给 Filter 链中的下一个 Filter 或者 Web 资源。
voiddestroy()该方法在销毁 Filter 对象之前被调用,用于释放被 Filter 对象占用的资源。

17.1.2 Filter实现流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sMZY4HC4-1631159929123)(img/Filter实现流程.png)]

说明:

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

17.2 Filter的生命周期

17.2.1 初始化阶段

Servlet 容器负责加载和实例化 Filter。容器启动时,读取 web.xml 或 @WebFilter 的配置信息对所有的过滤器进行加载和实例化。

加载和实例化完成后,Servlet 容器调用 init() 方法初始化 Filter 实例。在 Filter 的生命周期内, init() 方法只执行一次。

17.2.2 拦截和过滤阶段

该阶段是 Filter 生命周期中最重要的阶段。当客户端请求访问 Web 资源时,Servlet 容器会根据 web.xml 或 @WebFilter 的过滤规则进行检查。当客户端请求的 URL 与过滤器映射匹配时,容器将该请求的 request 对象、response 对象以及 FilterChain 对象以参数的形式传递给 Filter 的 doFilter() 方法,并调用该方法对请求/响应进行拦截和过滤。

17.2.3 销毁阶段

Filter 对象创建后会驻留在内存中,直到容器关闭或应用被移除时销毁。销毁 Filter 对象之前,容器会先调用 destory() 方法,释放过滤器占用的资源。在 Filter 的生命周期内,destory() 只执行一次。

17.3 注册与映射Filter

17.3.1 通过web.xml配置

<filter>
    <filter-name>myFilter</filter-name>
    <filter-class>net.biancheng.www.MyFilter</filter-class>
    <init-param>
        <param-name>name</param-name>
        <param-value>编程帮</param-value>
    </init-param>
    <init-param>
        <param-name>URL</param-name>
        <param-value>www.biancheng.net</param-value>
    </init-param>
</filter>

在 web.xml 中,通过使用 及其子元素映射 Filter,代码如下。

<filter-mapping>
    <filter-name>myFilter</filter-name>
    <url-pattern>/login</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
</filter-mapping>
<filter-mapping>
    <filter-name>myFilter</filter-name>
    <servlet-name>ServletDemo</servlet-name>
</filter-mapping>

以上元素说明如下:

  • 元素用于设置 Filter 负责拦截的资源。
  • 是 元素的子元素,用于设置 Filter 的注册名,该值必须在 元素的子元素 中声明过。
  • 是 元素的子元素,用于设置 Filter 拦截的请求路径。
  • 是 元素的子元素,用于设置 Filter 拦截的 Servlet 名称。
  • 是 元素的子元素,用于指定 Filter 拦截的资源被 Servlet 容器调用的方式,可以是 REQUEST、INCLUDE、FORWARD 和 ERROR 之一,默认 REQUEST。用户可以设置多个 子元素指定 Filter 对资源的多种调用方式进行拦截。

元素的取值及其意义:

  • REQUEST:当用户直接访问页面时,容器将会调用过滤器。如果目标资源是通过 RequestDispatcher 的 include() 或 forward() 方法访问,则该过滤器就不会被调用。
  • INCLUDE:如果目标资源通过 RequestDispatcher 的 include() 方法访问,则该过滤器将被调用。除此之外,该过滤器不会被调用。
  • FORWARD:如果目标资源通过 RequestDispatcher 的 forward() 方法访问,则该过滤器将被调用,除此之外,该过滤器不会被调用。
  • ERROR:如果目标资源通过声明式异常处理机制访问,则该过滤器将被调用。除此之外,过滤器不会被调用。

17.3.2 使用@WebFilter注解进行配置

@WebFilter 注解具有下表给出的一些常用属性。以下所有属性均为可选属性,但 value、urlPatterns、servletNames 三者必需至少包含一个,且 value 和 urlPatterns 不能共存,如果同时指定,通常忽略 value 的取值。

属性名类型描述
filterNameString指定过滤器的 name 属性,等价于 。
urlPatternsString[]指定过滤器的 URL 匹配模式。等价于 标签。
valueString[]该属性等价于 urlPatterns 属性,但是两者不能同时使用。
servletNamesString[]指定过滤器将应用于哪些 Servlet。取值是 @WebServlet 中 filterName 属性的取值,或者 web.xml 中 的取值。
dispatcherTypesDispatcherType指定过滤器拦截的资源被 Servlet 容器调用的方式。具体取值包括: ASYNC、ERROR、FORWARD、INCLUDE、REQUEST。
initParamsWebInitParam[]指定一组过滤器初始化参数,等价于 标签。
asyncSupportedboolean声明过滤器是否支持异步操作模式,等价于 标签。
descriptionString指定过滤器的描述信息,等价于 标签。
displayNameString指定过滤器的显示名,等价于 标签。

代码示例:

import java.io.IOException;
import java.util.logging.LogRecord;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
@WebFilter(
        dispatcherTypes = {
                DispatcherType.REQUEST,
                DispatcherType.FORWARD,
                DispatcherType.INCLUDE,
                DispatcherType.ERROR
        },
        asyncSupported = true,
        description = "过滤器4",
        urlPatterns = {"/login"},
        initParams = {
                @WebInitParam(name = "name", value = "编程帮", description = "name的描述")
        },
        servletNames = {"SuccessServlet"})
public class MyFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
    }
}

十八、FilterChain过滤器链(Servlet)

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

18.1 FilterChain接口

javax.servlet包中提供了一个FilterChain接口,改接口由容器实现。容器将其实例对象作为参数传入 Filter 对象的 doFilter() 方法中。Filter 对象可以使用 FilterChain 对象调用链中下一个 Filter 的 doFilter() 方法,若该 Filter 是链中最后一个过滤器,则调用目标资源的 service() 方法。FilterChain 接口中只有一个方法,如下表。

返回值类型方法描述
voiddoFilter(ServletRequest request ,ServletResponse response)使用该方法可以调用过滤器链中的下一个 Filter 的 doFilter() 方法,若该 Filter 是链中最后一个过滤器,则调用目标资源的 service() 方法。

18.2 Filter链的拦截过程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lW2NqJmJ-1631159929124)(img/Filter的拦截过程.png)]

  • 请求资源时,过滤器链中的过滤器依次对请求进行处理,并将请求传递给下一个过滤器,直到最后将请求传递给目标资源。发送响应信息时,则按照相反的顺序对响应进行处理,直到将响应返回给客户端。
  • 过滤器并不是必须要将请求传递到下一个过滤器或目标资源,它可以自行对请求进行处理,并发送响应给客户端,也可以将请求转发给其他的目标资源。

18.3 Filter链中的Filter的执行顺序

  • 通过 web.xml 配置的 Filter 过滤器,执行顺序由 标签的配置顺序决定。 靠前,则 Filter 先执行,靠后则后执行。通过修改 的顺序便可以修改 Filter 的执行顺序。
  • 通过 @WebFilter 注解配置的 Filter 过滤器,无法进行排序,若需要对 Filter 过滤器进行排序,建议使用 web.xml 进行配置。

十九、FilterConfig接口

  • Javax.Servet 包中提供了一个 FilterCofig 接口,它与 ServletConfig 接口相似,用于在过滤器初始化期间向其传递信息。

  • FilterConfig 接口由容器实现,容器将它作为参数传入过滤器的 init() 方法中。通过 filterConfig 对象就可以获得 Filter 的初始化参数。

  • 在 FilterConfig 接口中,定义了 4 个方法,如下表。

    • 返回值类型方法描述
      StringgetInitParameter(String name)根据初始化参数名 name,返回对应的初始化参数值。
      EnumerationgetInitParameterNames()返回过滤器的所有初始化参数名的枚举集合。
      ServletContextgetServletContext()返回 Servlet 上下文对象的引用。
      StringgetFilterName()返回过滤器的名称。

二十、Servlet监听器

  • 监听器 Listener 是一个实现特定接口的 Java 程序,这个程序专门用于监听另一个 Java 对象的方法调用或属性改变,当被监听对象发生上述事件后,监听器某个方法将立即自动执行。

监听器的相关概念:

  • 事件:方法调用、属性改变、状态改变等。
  • 事件源:被监听的对象( 例如:request、session、servletContext)。
  • 监听器:用于监听事件源对象 ,事件源对象状态的变化都会触发监听器。
  • 注册监听器:将监听器与事件源进行绑定。

20.1 监听器的分类

Servlet规范中定义了8个监听器接口,可以用于监听ServletContext、HttpSession和ServletRequest对象的生命周期和属性变化事件。开发Servlet监听器需要实现相应的监听器接口并重写接口中的方法;

20.1.1 监听对象创建和销毁的监听器

事件源监听器监听器描述创建和销毁方法调用时机
ServletContextServletContextListener用于监听 ServletContext 对象的创建与销毁过程void contextInitialized (ServletContextEvent sce)当创建 ServletContext 对象时
void contextDestroyed (ServletContextEvent sce)当销毁 ServletContext 对象时
HttpSessionHttpSessionListener用于监听 HttpSession 对象的创建和销毁过程void sessionCreated (HttpSessionEvent se)当创建 HttpSession 对象时
void sessionDestroyed (HttpSessionEvent se)当销毁 HttpSession 对象时
ServletRequestServletRequestListener用于监听 ServletRequest 对象的创建和销毁过程void requestInitialized (ServletRequestEvent sre)当创建 ServletRequest 对象时
void requestDestroyed (ServletRequestEvent sre)当销毁 ServletRequest 对象时

20.1.2 监听对象中属性变更的监听器

事件源监听器监听器描述方法调用时机
ServletContextServletContextAttributeListener用于监听 ServletContext 对象的属性新增、移除和替换public void attributeAdded (ServletContextAttributeEvent scae)当 ServletContext 对象中新增一个属性时
public void attributeRemoved (ServletContextAttributeEvent scae)当删除 ServletContext 对象中的一个属性时
public void attributeReplaced (ServletContextAttributeEvent scae)当 ServletContext 对象中的某个属性被替换时
HttpSessionHttpSessionAttributeListener用于监听 HttpSession 对象的属性新增、移除和替换public void attributeAdded (HttpSessionBindingEvent hsbe)当 HttpSession 对象中新增一个属性时
public void attributeRemoved (HttpSessionBindingEvent hsbe)当删除 HttpSession 对象中的一个属性时
public void attributeReplaced (HttpSessionBindingEvent hsbe)当 HttpSession 对象中的某个属性被替换时
HttpServletRequestServletRequestAttributeListener用于监听 HttpServletRequest 对象的属性新增、移除和替换public void attributeAdded (ServletRequestAttributeEvent srae)当 HttpServletRequest 对象中新增一个属性时
public void attributeRemoved (ServletRequestAttributeEvent srae)当删除 HttpServletRequest 对象中的一个属性时
public void attributeReplaced (ServletRequestAttributeEvent srae)当 HttpServletRequest 对象中的某个属性被替换时

20.1.3 监听HttpSession中的对象状态改变的监听器

事件源监听器监听器描述方法调用时机
HttpSessionHttpSessionBindingListener用于监听 JavaBean 对象绑定到 HttpSession 对象和从 HttpSession 对象解绑的事件void valueBound (HttpSessionBindingEvent event)当对象被绑定(添加)到 HttpSession 对象中时
void valueUnbound (HttpSessionBindingEvent event)当对象从 HttpSession 对象中解除绑定(移除)时
HttpSessionActivationListener用于监听 HttpSession 中对象活化和钝化的过程void sessionWillPassivate (HttpSessionBindingEvent event)当绑定到 HttpSession 对象中的对象将要随 HttpSession 对象被钝化之前
void sessionDidActive (HttpSessionBindingEvent event)当绑定到 HttpSession 对象中的对象将要随 HttpSession 对象被活化之后

20.2 注册监听器

20.2.1 在web.xml中注册监听器

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="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">
    <listener>
        <listener-class>net.biancheng.www.listener.MySessionListener</listener-class>
    </listener>
</web-app>

20.2.2 使用@WebListener注解注册监听器

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
/**
* 监听器实例代码
*
* @author 编程帮 www.biancheng.net
*/
@WebListener
public class MyListener implements ServletContextListener {
    public MyListener() {
    }
    public void contextDestroyed(ServletContextEvent sce) {
    }
    public void contextInitialized(ServletContextEvent sce) {
    }
}

| 用于监听 JavaBean 对象绑定到 HttpSession 对象和从 HttpSession 对象解绑的事件 | void valueBound (HttpSessionBindingEvent event) | 当对象被绑定(添加)到 HttpSession 对象中时 |
| void valueUnbound (HttpSessionBindingEvent event) | 当对象从 HttpSession 对象中解除绑定(移除)时 | | | |
| HttpSessionActivationListener | 用于监听 HttpSession 中对象活化和钝化的过程 | void sessionWillPassivate (HttpSessionBindingEvent event) | 当绑定到 HttpSession 对象中的对象将要随 HttpSession 对象被钝化之前 | |
| void sessionDidActive (HttpSessionBindingEvent event) | 当绑定到 HttpSession 对象中的对象将要随 HttpSession 对象被活化之后 | | | |

20.2 注册监听器

20.2.1 在web.xml中注册监听器

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="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">
    <listener>
        <listener-class>net.biancheng.www.listener.MySessionListener</listener-class>
    </listener>
</web-app>

20.2.2 使用@WebListener注解注册监听器

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
/**
* 监听器实例代码
*
* @author 编程帮 www.biancheng.net
*/
@WebListener
public class MyListener implements ServletContextListener {
    public MyListener() {
    }
    public void contextDestroyed(ServletContextEvent sce) {
    }
    public void contextInitialized(ServletContextEvent sce) {
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值