JavaWeb学习笔记

文章目录

  • XML
    • [XML 基本语法](#XML 基本语法)
    • [DOM4J 进行XML解析](#DOM4J 进行XML解析)
      • [DOM4J API](#DOM4J API)
  • Tomcat10
    • [WEB 服务器原理](#WEB 服务器原理)
    • [WEB 服务器软件](#WEB 服务器软件)
    • [Tomcat 服务器相关](#Tomcat 服务器相关)
      • [Tomcat 服务器的配置和使用](#Tomcat 服务器的配置和使用)
      • [解决 Tomcat 乱码](#解决 Tomcat 乱码)
      • [Tomcat 目录](#Tomcat 目录)
    • WEB项目的标准结构
    • [WEB 项目部署的方式](#WEB 项目部署的方式)
    • [IDEA 中开发并部署运行WEB项目](#IDEA 中开发并部署运行WEB项目)
  • [HTTP 协议](#HTTP 协议)
  • Servlet
    • [Servlet 简介](#Servlet 简介)
    • [Servlet 开发流程](#Servlet 开发流程)
    • [Servlet 注解方式配置](#Servlet 注解方式配置)
    • [Servlet 生命周期](#Servlet 生命周期)
    • [Servlet 继承结构](#Servlet 继承结构)
      • [Servlet 顶级接口](#Servlet 顶级接口)
      • [GenericServlet 抽象模板类](#GenericServlet 抽象模板类)
      • [HTTPServlet 抽象类](#HTTPServlet 抽象类)
      • 我们需要重写的方法
    • [ServletConfig 和 ServletContext](#ServletConfig 和 ServletContext)
      • ServletConfig
        • [使用 `ServletConfig`](#使用 ServletConfig)
        • [使用 `init-param`](#使用 init-param)
      • ServletContext
        • [获取 ServletContext 对象](#获取 ServletContext 对象)
        • [ServletContext 获取配置参数](#ServletContext 获取配置参数)
        • [ServletContext 获得路径问题](#ServletContext 获得路径问题)
        • [ServletContext 域相关](#ServletContext 域相关)
    • HTTPServletRequet
    • HTTPServletResponse
    • 请求转发和响应重定向
    • [Web 乱码问题](#Web 乱码问题)
        • [HTML 乱码问题](#HTML 乱码问题)
        • [Tomcat 控制台乱码](#Tomcat 控制台乱码)
        • [GET 请求乱码问题](#GET 请求乱码问题)
        • [POST 方式请求乱码](#POST 方式请求乱码)
        • 响应乱码
    • [web 路径问题](#web 路径问题)
      • 前端路径问题
      • [重定向 和 请求转发中的路径问题](#重定向 和 请求转发中的路径问题)
      • 总结
    • [MVC 架构模式](#MVC 架构模式)
    • [MVC 项目演示](#MVC 项目演示)
  • 会话
    • 概述
    • Cookie
      • [Cookie 概念](#Cookie 概念)
      • [Cookie 使用](#Cookie 使用)
      • [Cookie 的时效性](#Cookie 的时效性)
      • [Cookie 的提交路径](#Cookie 的提交路径)
    • Session
      • [HTTPSession 概述](#HTTPSession 概述)
      • [HttpSession 的使用](#HttpSession 的使用)
      • [HttpSession 时效性](#HttpSession 时效性)
    • 三大域对象
  • 过滤器
  • 监听器
    • 概述
    • 监听器的六个主要接口
      • [application 域监听器](#application 域监听器)
      • [Session 域监听器](#Session 域监听器)
      • [request 域监听器](#request 域监听器)
    • [Session 域的两个特殊监听器](#Session 域的两个特殊监听器)

XML

XMLEXtensible Markup Language 的缩写,翻译过来就是可扩展标记语言。所以很明显,XMLHTML 一样都是标记语言,也就是说它们的基本语法都是标签。

XML 基本语法


  • XML文档声明 这部分基本上就是固定格式,要注意的是文档声明一定要从第一行第一列开始写

<?xml version="1.0" encoding="UTF-8"?>


  • 根标签
    • 根标签有且只能有一个

  • 标签关闭

    • 双标签:开始标签和结束标签必须成对出现。比如:<name></name>

    • 单标签:单标签要在标签内关闭。比如:<br/>


  • 标签嵌套
    • 可以嵌套,但是不能交叉嵌套。
xml 复制代码
<root>
    <!--这里就是交叉嵌套-->
    <element1>Content of element1 <element2>Content of element2</element1></element2>
</root>

  • 标签名、属性名建议使用小写字母

  • 属性
    • 属性必须有值
    • 属性值必须加引号,单双都行

  • XML 约束(了解)
    • 将来我们主要就是根据XML约束中的规定来编写XML配置文件,而且会在我们编写XML的时候根据约束来提示我们编写, 而XML约束主要包括DTD和Schema两种。

      • DTD
      • Schema

例子

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<students>
    <student>
        <name>张三</name>
        <age>18</age>
    </student>
    <student>
        <name>李四</name>
        <age>20</age>
    </student>
</students>

DOM4J 进行XML解析


使用步骤

  1. 导入jar 包 dom4j.jar
  2. 创建解析器对象 (SAXReader)
  3. 解析xml 获得 Document 对象
  4. 获取根节点 RootElement
  5. 获取根节点下的子节点

DOM4J API

  • 创建 SAXReader对象
java 复制代码
SAXReader saxReader = new SAXReader()
  • 解析 XML 获取 Document 对象:需要传入要解析的 XML 字节输入流
java 复制代码
Document document = reader.read(inputStream);
  • 获取文档的根标签
java 复制代码
Element rootElement = documen.getRootElement()
  • 获取标签的子标签
java 复制代码
//获取所有子标签
List<Element> sonElementList = rootElement.elements();
//获取指定标签名的子标签
List<Element> sonElementList = rootElement.elements("标签名");
  • 获取标签体内的文本
java 复制代码
String text = element.getText();
  • 获取标签的某个属性的值
java 复制代码
String value = element.attributeValue("属性名");

例子

java 复制代码
public class TestDom4j {
    @Test
    public void testRead() throws DocumentException {
        //读取jdbc.xml 配置文件,获得 doucment对象
        SAXReader saxReader = new SAXReader();
        //通过类加载器获得指向字节码根路径下的指定文件的输入流
        InputStream resourceAsStream = TestDom4j.class.getClassLoader().getResourceAsStream("jdbc.xml");
        //通过输入流获得配置文件,解析成一个DOM对象
        Document document = saxReader.read(resourceAsStream);
        //从 document 对象上获取配置文件中的信息
        /*
            Element 元素节点
            Attribute 属性节点
            Text 文本节点
         */
        Element rootElement = document.getRootElement();
        System.out.println(rootElement.getName());

        //获取元素下的子元素
        List<Element> elements = rootElement.elements();
        for(Element element : elements) {
            System.out.println("\t" +element.getName());
            //从元素上获取属性
            Attribute idAttribute = element.attribute("id");
            System.out.println("\t\t" + idAttribute.getName() + "=" + idAttribute.getValue());
            //继续读取子元素
            List<Element> eles = element.elements();
            for (Element ele : eles) {
                System.out.println("\t\t" + ele.getName() + ":" + ele.getText());
            }
        }

    }
}

Tomcat10

WEB 服务器原理


域名

ip 地址

  • 计算机在网络当中的一个身份证号。在同一个网络当中,IP地址是唯一的。
  • A计算机要想和B计算机通信,首先你需要知道B计算机的IP地址,有了IP地址才能建立连接。

端口号

  • 一个端口代表一个软件(一个端口代表一个应用,一个端口仅代表一个服务)。
  • 一个计算机当中有很多软件,每一个软件启动之后都有一个端口号。
  • 在同一个计算机上,端口号具有唯一性

URL

统一资源定位符(http://www.baidu.com

请求和响应

  • 请求和响应实际上说的是数据的流向不同。
  • 从Browser端发送数据到Server端,我们称为请求。英语单词:request
  • 从Server端向浏览器Browser端发送数据,我们称为响应。英语单词:response
  • B --> S (请求request)
  • S --> B (响应response)

Web 通信原理

  • 第一步:用户输入网址(URL)
  • 第二步:域名解析器进行域名解析:http://110.242.68.3:80/index.html
  • 第三步:浏览器软件在网络中搜索110.242.68.3这一台主机,直到找到这台主机。
  • 第四步:定位110.242.68.3这台主机上的服务器软件,因为是80端口,可以很轻松的定位到80端口对应的服务器软件。
  • 第五步:80端口对应的服务器软件得知浏览器想要的资源名是:index.html
  • 第六步:服务器软件找到index.html文件,并且将index.html文件中的内容直接输出响应到浏览器上。
  • 第七步:浏览器接收到来自服务器的代码(HTML CSS JS)
  • 第八步:浏览器渲染,执行HTML CSS JS代码,展示效果。

WEB 服务器软件


  • Tomcat(WEB服务器)
  • jetty(WEB服务器)
  • JBOSS(应用服务器)
  • WebLogic(应用服务器)
  • WebSphere(应用服务器)

应用服务器和 WEB 服务器的区别

  • 应用服务器实现了JavaEE的所有规范。(JavaEE有13个不同的规范。)
  • WEB服务器只实现了JavaEE中的 Servlet + JSP 两个核心的规范。
  • 通过这个讲解说明了:应用服务器是包含WEB服务器的。
  • 用过JBOSS服务器的同学应该很清楚,JBOSS中内嵌了一个Tomcat服务器。

Tomcat 服务器相关


下载

  • 安装版:需要安装,一般不考虑使用。
  • 解压版: 直接解压缩使用,我们使用的版本。

Tomcat 服务器的配置和使用

  • 必须利用 JAVA_HOME 配置 JDK path变量

  • bin 目录下有 startup.bat,通过它可以启动 Tomcat 服务器

    • xxx.bat 是 windows 操作系统专用的,bat 是批处理文件,这种文件可以编写大量的 windows 的 dos 命令,然后执行 bat 文件就相当于批量的执行 dos 文件

    • startup.sh 这个文件在 Linux 环境中使用,在 Linux 环境下能执行的是 shell 命令,大量的 shell 命令编写在 sh 文件当中,然后执行这个 shell 文件可以批量执行 shell 命令

    • 执行 startup.bat 命令,实际上最后执行的是: catalina.bat 文件

    • catalina.bat文件中有这样一行配置:MAINCLASS=org.apache.catalina.startup.Bootstrap (这个类就是main方法所在的类。)

    • tomcat 服务器就是 java 写的,既然是 java 写的,那么启动 Tomcat 服务器就是执行 main 方法 (catalina.bat 中的配置)


  • 配置 Tomcat 的 bin 路径到 path 变量 , 再把 Tomcat 根目录设成变量名为 CATALINA_HOME 的变量,然后写进 path 变量带上 bin 最后 dos 命令窗口中输入 startup.bat 启动 tomcat 服务器

  • 关闭Tomcat:stop (shutdown.bat文件重命名为stop.bat,为什么?原因是shutdown命令和windows中的关机命令冲突。所以修改一下。)

  • 测试 Tomcat 服务器

    • 打开浏览器再浏览器输入 URL

    • http://ip地址 : 端口号

    • 本机地址:127.0.0.1 或者 localhost ,端口号 8080

解决 Tomcat 乱码

apache-tomcat-10.1.28\conf\logging.properties文件中的内容修改如下:

java.util.logging.ConsoleHandler.encoding = GBK

Tomcat 目录


  • bin

    该目录下存放的是二进制可执行文件,如果是安装版,那么这个目录下会有两个exe文件:tomcat10.exe、tomcat10w.exe,前者是在控制台下启动Tomcat,后者是弹出GUI窗口启动Tomcat;如果是解压版,那么会有startup.bat和shutdown.bat文件,startup.bat用来启动Tomcat,但需要先配置JAVA_HOME环境变量才能启动,shutdawn.bat用来停止Tomcat;


  • conf:里面有四个重要文件

    • server.xml

      配置整个服务器信息。例如修改端口号。默认HTTP请求的端口号是:8080

    • tomcat-users.xml

      存储 tomcat用户 的文件,这里保存的是tomcat的用户名及密码,以及用户的角色信息。可以按着该文件中的注释信息添加tomcat用户,然后就可以在Tomcat主页中进入Tomcat Manager页面了;

    • web.xml

      部署描述符文件,这个文件中注册了很多MIME类型,即文档类型。这些MIME类型是客户端与服务器之间说明文档类型的,如用户请求一个html网页,那么服务器还会告诉客户端浏览器响应的文档是text/html类型的,这就是一个MIME类型。客户端浏览器通过这个MIME类型就知道如何处理它了。当然是在浏览器中显示这个html文件了。但如果服务器响应的是一个exe文件,那么浏览器就不可能显示它,而是应该弹出下载窗口才对。MIME就是用来说明文档的内容是什么类型的!

    • context.xml

      对所有应用的统一配置,通常我们不会去配置它。


  • lib

    Tomcat的类库,里面是一大堆jar文件。如果需要添加Tomcat依赖的jar文件,可以把它放到这个目录中,当然也可以把应用依赖的jar文件放到这个目录中,这个目录中的jar所有项目都可以共享之,但这样你的应用放到其他Tomcat下时就不能再共享这个目录下的jar包了,所以建议只把Tomcat需要的jar包放到这个目录下;


  • logs

    这个目录中都是日志文件,记录了Tomcat启动和关闭的信息,如果启动Tomcat时有错误,那么异常也会记录在日志文件中。【不用可以删掉】


  • temp

    存放Tomcat的临时文件,这个目录下的东西可以在停止Tomcat后删除!


  • webapps

    存放 web 项目的目录中,每个文件夹代表一个项目。如果该目录下已经存在一些目录,那么这些可能是 tomcat 自带的项目。"ROOT" 是一个特殊的项目,当在地址栏中输入 "http://127.0.0.1:8080" 且没有给出具体项目目录时,系统会自动对应到这个 ROOT 项目进行访问。


  • work

    运行时生成的文件,最终运行的文件都在这里。通过webapps中的项目生成的!可以把这个目录下的内容删除,再次运行时会生再次生成work目录。当客户端用户访问一个JSP文件时,Tomcat会通过JSP生成Java文件,然后再编译Java文件生成class文件,生成的java和class文件都会存放到这个目录下。【现在基本用不到了】


  • LICENSE:许可证。
  • NOTICE:说明文件。

注意:上下文路径不一定和项目的部署目录一样。上下文路径是可以改的

WEB项目的标准结构


图示

URL 的组成部分和项目中资源的对应关系

解释

  • app 应用根目录
    • static 非必要目录,约定俗成的名字,一般在此处放静态资源 ( css js img)
    • WEB-INF 必要目录,必须叫WEB-INF,受保护的资源目录,浏览器通过url不可以直接访问的目录
      • classes 必要目录, src下源代码,配置文件,编译后会在该目录下,web项目中如果没有源码,则该目录不会出现
      • lib 必要目录, 项目依赖的jar编译后会出现在该目录下,web项目要是没有依赖任何jar,则该目录不会出现
      • web.xml 必要文件, web项目的基本配置文件. 较新的版本中可以没有该文件,但是学习过程中还是需要该文件
    • index.html 非必要文件,index.html/index.htm/index.jsp为默认的欢迎页

WEB 项目部署的方式


  • 方式一

直接将编译好的项目放在webapps目录下

  • 方式二

将编译好的项目打成war包放在webapps目录下,tomcat启动后会自动解压war包(其实和第一种一样)

  • 方式三

可以将项目放在非webapps的其他目录下,在 tomcat 中通过配置文件指向 app 的实际磁盘路径

方式三操作步骤

  • 在磁盘的自定义目录上准备一个 app目录

  • 在tomcat的conf下创建Catalina/localhost目录,并在该目录下准备一个app.xml文件
xml 复制代码
<!-- 
	path: 项目的访问路径,也是项目的上下文路径,就是在浏览器中,输入的项目名称
    docBase: 项目在磁盘中的实际路径
 -->
<Context path="/app" docBase="D:\mywebapps\app" />

IDEA 中开发并部署运行WEB项目


第一步:IDEA关联本地 Tomcat

  • IDEA 关联本地 Tomcat

可以在创建项目前设置本地tomcat,也可以在打开某个项目的状态下找到settings

找到 Build,Execution,Eeployment下的Application Servers ,找到+号

选择Tomcat Server

选择tomcat的安装目录

关联完毕

第二步:IDEA 创建 WEB 工程

  • IDEA 创建 WEB 工程

推荐先创建一个空项目,这样可以在一个空项目下同时存在多个modules,不用后续来回切换之前的项目,当然也可以忽略此步直接创建web项目

检查项目的SDK,语法版本,以及项目编译后的输出目录

先创建一个普通的JAVA项目

检查各项信息是否填写有误

创建完毕后,为项目添加Tomcat依赖


选择modules,添加 framework support,新 IDEA 用搜索框搜索

选择Web Application 注意Version,勾选 Create web.xml

删除index.jsp ,替换为 index.html

处理配置文件

  • 在工程下创建resources目录,专门用于存放配置文件(都放在src下也行,单独存放可以尽量避免文件集中存放造成的混乱)
  • 标记目录为资源目录,不标记的话则该目录不参与编译

处理依赖 jar 包问题

  • 在WEB-INF下创建lib目录
  • 必须在WEB-INF下,且目录名必须叫lib!!!
  • 复制jar文件进入lib目录

将lib目录添加为当前项目的依赖,后续可以用maven统一解决


环境级别推荐选择module 级别,降低对其他项目的影响,name可以空着不写

查看当前项目有那些环境依赖

在此位置,可以通过-号解除依赖。有需不要的可以解除

第三步:IDEA部署-运行WEB项目

  • IDEA部署-运行WEB项目

检查idea是否识别modules为web项目并存在将项目构建成发布结构的配置

  • 就是检查工程目录下,web目录有没有特殊的识别标记
  • 以及artifacts下,有没有对应 _war_exploded,如果没有,就点击+号添加

点击向下箭头,出现 Edit Configurations选项

出现运行配置界面 , 点击+号,添加本地tomcat服务器

选择Deployment,通过+添加要部署到Tomcat中的artifact

applicationContext中是默认的项目上下文路径,也就是url中需要输入的路径,这里可以自己定义,可以和工程名称不一样,也可以不写,但是要保留 /,这个路径就是访问你项目目录的路径,就是第二张图里的 demp01_web01_war_exploded,这个目录也是放进 weapps 的目录,还构建成 out 前就是 web

点击apply 应用后,回到Server部分. After Launch是配置启动成功后,是否默认自动打开浏览器并输入URL中的地址,HTTP port是Http连接器目前占用的端口号

点击OK后,启动项目,访问测试

  • 绿色箭头是正常运行模式
  • "小虫子"是debug运行模式
  • 点击后,查看日志状态是否有异常
  • 浏览器自动打开并自动访问了index.html欢迎页

原理

工程结构和可以发布的项目结构之间的目录对应关系

IDEA部署并运行项目的原理

  • idea并没有直接进将编译好的项目放入tomcat的webapps中

  • idea根据关联的tomcat,创建了一个tomcat副本,将项目部署到了这个副本中

  • idea的tomcat副本路径可以启动 tomcat 看命令行中的 LINA_BASE 查看

  • idea的tomcat副本并不是一个完整的tomcat,副本里只是准备了和当前项目相关的配置文件而已

  • idea启动tomcat时,是让本地tomcat程序按照tomcat副本里的配置文件运行

  • idea的tomcat副本部署项目的模式是通过conf/Catalina/localhost/*.xml配置文件的形式实现项目部署的

目录原理

这个 demo01 就是 webapps 中的目录 我们在 Application context 中的目录就是访问它的,它在没构建成 out 前就是工程文件中的 web 目录

HTTP 协议

HTTP 简介


HTTP 超文本传输协议 (HTTP-Hyper Text transfer protocol),是一个属于应用层的面向对象的协议。它是一种详细规定了浏览器和万维网服务器之间互相通信的规则 。通过因特网传送万维网文档的数据传送协议。客户端与服务端通信时传输的内容我们称之为报文HTTP协议就是规定报文的格式。HTTP就是一个通信规则,这个规则规定了客户端发送给服务器的报文格式,也规定了服务器发送给客户端的报文格式。实际我们要学习的就是这两种报文 。客户端发送给服务器的称为"请求报文**",服务器发送给客户端的称为"响应报文"。**

HTTP 协议的会话方式

  • 浏览器与服务器之间通信要经历四个步骤
    • 浏览器与WEB服务器的连接过程是短暂的,每次连接只处理一个请求和响应。对每一个页面的访问,浏览器与WEB服务器都要建立一次单独的连接。
    • 浏览器到WEB服务器之间的所有通讯都是完全独立分开的请求和响应对。

HTTP 1.0 和 1.1 的区别

  • 在HTTP1.0版本中,浏览器请求一个带有图片的网页,会由于下载图片而与服务器之间开启一个新的连接;但在HTTP1.1版本中,允许浏览器在拿到当前请求对应的全部资源后再断开连接,提高了效率。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

浏览器抓包工具

  • 几乎所有的PC端浏览器都支持了F12开发者工具,只不过不同的浏览器工具显示的窗口有差异

请求和响应报文


报文的格式

主体上分为报文首部和报文主体,中间空行隔开

报文部首部可以继续细分为 "行" 和 "头" 【请求行就是行,字段就是头】

请求报文


客户端发给服务端的报文

格式

  • 请求首行(请求行): 请求方式GET/POST请求资源路径请求的协议和版本
  • 请求头信息(请求头)
  • 空行
  • 请求体【POST 请求才往请求体放数据】

浏览器 f12 网络下查看请求数据包
第一个红框就是请求行,第二个红框就是请求头请求体要在响应中看

FROM 表单发送 GET 请求的特点
  • 由于请求参数在请求首行中已经携带了,所以没有请求体,也没有请求空行
  • 请求参数拼接在url地址中,地址栏可见[url?name1=value1&name2=value2],不安全
  • 由于参数在地址栏中携带,所以由大小限制[地址栏数据大小一般限制为4k],只能携带纯文本
  • get请求参数只能上传文本数据
  • 没有请求体。所以封装和解析都快,效率高, 浏览器默认提交的请求都是get请求比如:地址栏输入回车,超链接,表单默认的提交方式

GET 请求行组成部分

  • 请求方式 GET
  • 访问服务器的资源路径?参数1=值1&参数2=值2 ... ...
  • 协议及版本 HTTP/1.1
http 复制代码

GET请求报文结构详细

  • 请求行
    • 请求方式 GET
    • 访问服务器的资源路径?参数1=值1&参数2=值2 ... ...
    • 协议及版本 HTTP/1.1
http 复制代码
GET /05_web_tomcat/login_success.html?username=admin&password=123213 HTTP/1.1
  • 请求头
http 复制代码
-主机虚拟地址
Host: localhost:8080   
-长连接
Connection: keep-alive 
-请求协议的自动升级[http的请求,服务器却是https的,浏览器自动会将请求协议升级为https的]
Upgrade-Insecure-Requests: 1  
- 用户系统信息
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36
- 浏览器支持的文件类型
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
- 当前页面的上一个页面的路径[当前页面通过哪个页面跳转过来的]:   可以通过此路径跳转回上一个页面, 广告计费,防止盗链
Referer: http://localhost:8080/05_web_tomcat/login.html
- 浏览器支持的压缩格式
Accept-Encoding: gzip, deflate, br
- 浏览器支持的语言
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
  • 请求空行

  • 请求体

    • GET请求数据不放在请求体
FROM 表单中 POST 请求的特点
  • POST 请求有请求体,而 GET 请求没有请求体
  • post请求数据在请求体中携带,请求体数据大小没有限制,可以用来上传所有内容[文件、文本]
  • 只能使用post请求上传文件
  • post请求报文多了和请求体相关的配置[请求头]
  • 地址栏参数不可见,相对安全
  • post效率比get低

POST请求报文结构

  • 请求行
    • 请求方式:POST
    • 访问服务器的资源路径
    • 协议及版本号
http 复制代码
POST /05_web_tomcat/login_success.html HTTP/1.1
  • 请求头
http 复制代码
Host: localhost:8080
Connection: keep-alive
Content-Length: 31     -请求体内容的长度
Cache-Control: max-age=0  -无缓存
Origin: http://localhost:8080
Upgrade-Insecure-Requests: 1  -协议的自动升级
Content-Type: application/x-www-form-urlencoded   -请求体内容类型[服务器根据类型解析请求体参数]
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: http://localhost:8080/05_web_tomcat/login.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Cookie:JSESSIONID-
  • 请求空行

  • 请求体:浏览器提交个提交给服务器的数据

http 复制代码
username=admin&password=1232131

响应报文


服务端发给客户端的报文

格式

  • 响应首行(响应行): 协议/版本 状态码 状态码描述
  • 响应头信息(响应头)
  • 空行
  • 响应体
    第一行就是响应行,后面都是响应头

响应报文详细

  • 响应行
    • 协议及版本
    • 响应状态码
    • 状态描述 (缺省)
HTTP 复制代码
HTTP/1.1 200 OK
说明:响应协议为HTTP1.1,响应状态码为200,表示请求成功; 
  • 响应头
HTTP 复制代码
Server: Apache-Coyote/1.1   服务器的版本信息
Accept-Ranges: bytes
ETag: W/"157-1534126125811"
Last-Modified: Mon, 13 Aug 2018 02:08:45 GMT
Content-Type: text/html    响应体数据的类型[浏览器根据类型解析响应体数据]
Content-Length: 157   响应体内容的字节数
Date: Mon, 13 Aug 2018 02:47:57 GMT  响应的时间,这可能会有8小时的时区差
  • 响应体
html 复制代码
<!--需要浏览器解析使用的内容[如果响应的是html页面,最终响应体内容会被浏览器显示到页面中]-->

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
  </head>
  <body>
    恭喜你,登录成功了...
  </body>
</html>

常见状态码


常见状态码

  • 200: 请求成功,浏览器会把响应体内容(通常是html)显示在浏览器中;
  • 302: 重定向,当响应码为302时,表示服务器要求浏览器重新再发一个请求,服务器会发送一个响应头Location指定新请求的URL地址;
  • 304: 使用了本地缓存
  • 404: 请求的资源没有找到,说明客户端错误的请求了不存在的资源;
  • 405: 请求的方式不允许
  • 500: 请求资源找到了,但服务器内部出现了错误;

其他的状态码

状态码 状态码英文描述 中文含义
1**
100 Continue 继续。客户端应继续其请求
101 Switching Protocols 切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到HTTP的新版本协议
2**
200 OK 请求成功。一般用于GET与POST请求
201 Created 已创建。成功请求并创建了新的资源
202 Accepted 已接受。已经接受请求,但未处理完成
203 Non-Authoritative Information 非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本
204 No Content 无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档
205 Reset Content 重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域
206 Partial Content 部分内容。服务器成功处理了部分GET请求
3**
300 Multiple Choices 多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择
301 Moved Permanently 永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替
302 Found 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI
303 See Other 查看其它地址。与301类似。使用GET和POST请求查看
304 Not Modified 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源
305 Use Proxy 使用代理。所请求的资源必须通过代理访问
306 Unused 已经被废弃的HTTP状态码
307 Temporary Redirect 临时重定向。与302类似。使用GET请求重定向
4**
400 Bad Request 客户端请求的语法错误,服务器无法理解
401 Unauthorized 请求要求用户的身份认证
402 Payment Required 保留,将来使用
403 Forbidden 服务器理解请求客户端的请求,但是拒绝执行此请求
404 Not Found 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面
405 Method Not Allowed 客户端请求中的方法被禁止
406 Not Acceptable 服务器无法根据客户端请求的内容特性完成请求
407 Proxy Authentication Required 请求要求代理的身份认证,与401类似,但请求者应当使用代理进行授权
408 Request Time-out 服务器等待客户端发送的请求时间过长,超时
409 Conflict 服务器完成客户端的 PUT 请求时可能返回此代码,服务器处理请求时发生了冲突
410 Gone 客户端请求的资源已经不存在。410不同于404,如果资源以前有现在被永久删除了可使用410代码,网站设计人员可通过301代码指定资源的新位置
411 Length Required 服务器无法处理客户端发送的不带Content-Length的请求信息
412 Precondition Failed 客户端请求信息的先决条件错误
413 Request Entity Too Large 由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个Retry-After的响应信息
414 Request-URI Too Large 请求的URI过长(URI通常为网址),服务器无法处理
415 Unsupported Media Type 服务器无法处理请求附带的媒体格式
416 Requested range not satisfiable 客户端请求的范围无效
417 Expectation Failed 服务器无法满足Expect的请求头信息
5**
500 Internal Server Error 服务器内部错误,无法完成请求
501 Not Implemented 服务器不支持请求的功能,无法完成请求
502 Bad Gateway 作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应
503 Service Unavailable 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中
504 Gateway Time-out 充当网关或代理的服务器,未及时从远端服务器获取请求
505 HTTP Version not supported 服务器不支持请求的HTTP协议的版本,无法完成处理

Servlet

Servlet 简介


Servlet (server applet) 是运行在服务端(tomcat)的Java小程序,是sun公司提供一套定义动态资源规范; 从代码层面上来讲 Servlet 就是一个接口。Tomcat实现了这个接口

作用

用来接收、处理客户端请求、响应给浏览器的动态资源。在整个Web应用中,Servlet主要负责接收处理请求、协同调度功能以及响应数据。我们可以把Servlet称为Web应用中的控制器

大概流程

Servlet 开发流程


目标

校验注册时,用户名是否被占用,通过客户端向 Servlet 发送氢气球,携带 username,如果用户名是 atguigu,则向客户端响应 NO,如果是其他响应 YES

开发过程

  • 配置一个 web 类型的 module

  • 开发一个 UserServlet

    • 自定义一个类,继承 HttpServlet
    • 重写 service 方法,该方法主要就是用于处理用户请求的服务方法
    • HttpSerevletRequest 代表请求对象,是有请求报文经过 tomcat 转换而来的,通过该对象可以获取请求中的信息
    • HttpServletResponse 代表响应对象,该对象会被 tomcat 转换为响应的报文,通过该对象可以设置响应中的信息
    • Servlet 对象的生命周期(创建,初始化,处理服务,销毁)由 tomcat 管理,无序我们自己 new
    • HttpServletRequest HttpServletResponse 两个对象也是由 tomcat 负责转换,在调用 service 方法时传给我们
java 复制代码
public class UserServlet  extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取请求中的参数
        String username = req.getParameter("username");
        if("atguigu".equals(username)){
            //通过响应对象响应信息
            resp.getWriter().write("NO");
        }else{
            resp.getWriter().write("YES");
        }

    }
}
  • web.xmlUseServlet 配置请求的映射路径
    • Servlet并不是文件系统中实际存在的文件或者目录,所以为了能够请求到该资源,我们需要为其配置映射路径
    • servlet的请求映射路径配置在web.xml中
    • servlet-name作为servlet的别名,可以自己随意定义,见名知意就好
    • url-pattern标签用于定义Servlet的请求映射路径
    • 一个servlet可以对应多个不同的url-pattern
    • 多个servlet不能使用相同的url-pattern
    • url-pattern中可以使用一些通配写法
      • / 表示通配所有资源,不包括jsp文件
      • /* 表示通配所有资源,包括jsp文件
      • /a/* 匹配所有以a前缀的映射路径
      • *.action 匹配所有以action为后缀的映射路径
xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0">

    <servlet>
        <!--给UserServlet起一个别名-->
        <servlet-name>userServlet</servlet-name>
        <servlet-class>com.atguigu.servlet.UserServlet</servlet-class>
    </servlet>


    <servlet-mapping>
        <!--关联别名和映射路径-->
        <servlet-name>userServlet</servlet-name>
        <!--可以为一个Servlet匹配多个不同的映射路径,但是不同的Servlet不能使用相同的url-pattern-->
        <url-pattern>/userServlet</url-pattern>
       <!-- <url-pattern>/userServlet2</url-pattern>-->
        <!--
            /        表示通配所有资源,不包括jsp文件
            /*       表示通配所有资源,包括jsp文件
            /a/*     匹配所有以a前缀的映射路径
            *.action 匹配所有以action为后缀的映射路径
        -->
       <!-- <url-pattern>/*</url-pattern>-->
    </servlet-mapping>

</web-app>
  • 开发一个 form 表单,向 servlet 发送一个 get 请求并携带 username 参数
html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="userServlet">
        请输入用户名:<input type="text" name="username" /> <br>
        <input type="submit" value="校验">
    </form>
</body>
</html>
  • 启动项目,访问 index.html 提交表单测试
    • 使用debug模式运行测试

映射关系图

servlet-api jar 包导入问题

我们不单独建一个 lib 放 servlet-api 而是直接 project structure 中导入。

   是因为我们如果单独建一个 lib 放 serlvet jar 包, 再打包成 out 的时候会生成,你移入 webapps 中的时候,官方已经有了没必要
   所以我们只需要在编码的时候使用 servlet jar ,在 projefct stucture 中导入 scope 作用域为 Provided 时就是编码的时候使用,构建成 out 的时候不生成

Content-Type响应头问题

MIME 类型响应头 媒体类型,文件类型,响应的数据类型

MIME 类型用于告诉客户端响应数据是什么类型的数据,客户端以此类型决定用什么方式解析响应体

请求文件的时候,如果找不到这个类型的文件,就会把这个文件通过 io 流,放到 web.xml 中的defaultServlet 中的 response

Servlet 注解方式配置


@WebServlet 注解源码

java 复制代码
package jakarta.servlet.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @since Servlet 3.0
 */
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebServlet {

    /**
     * The name of the servlet
     * 相当于 servlet-name
     * @return the name of the servlet
     */
    String name() default "";

    /**
     * The URL patterns of the servlet
     * 如果只配置一个url-pattern ,则通过该属性即可,和urlPatterns属性互斥
     * @return the URL patterns of the servlet
     */
    String[] value() default {};

    /**
     * The URL patterns of the servlet
     * 如果要配置多个url-pattern ,需要通过该属性,和value属性互斥
     * @return the URL patterns of the servlet
     */
    String[] urlPatterns() default {};

    /**
     * The load-on-startup order of the servlet
     * 配置Servlet是否在项目加载时实例化
     * @return the load-on-startup order of the servlet
     */
    int loadOnStartup() default -1;

    /**
     * The init parameters of the servlet
     * 配置初始化参数
     * @return the init parameters of the servlet
     */
    WebInitParam[] initParams() default {};

    /**
     * Declares whether the servlet supports asynchronous operation mode.
     *
     * @return {@code true} if the servlet supports asynchronous operation mode
     * @see jakarta.servlet.ServletRequest#startAsync
     * @see jakarta.servlet.ServletRequest#startAsync( jakarta.servlet.ServletRequest,jakarta.servlet.ServletResponse)
     */
    boolean asyncSupported() default false;

    /**
     * The small-icon of the servlet
     *
     * @return the small-icon of the servlet
     */
    String smallIcon() default "";

    /**
     * The large-icon of the servlet
     *
     * @return the large-icon of the servlet
     */
    String largeIcon() default "";

    /**
     * The description of the servlet
     *
     * @return the description of the servlet
     */
    String description() default "";

    /**
     * The display name of the servlet
     *
     * @return the display name of the servlet
     */
    String displayName() default "";

}

使用举例

java 复制代码
@WebServlet(
        name = "userServlet",
        value = "/user",  //有 urlPatterns 就不能有 value
        urlPatterns = {"/userServlet1","/userServlet2","/userServlet"},
    	//配置初始化参数
        initParams = {@WebInitParam(name = "encoding",value = "UTF-8")},
        loadOnStartup = 6
)


public class UserServlet  extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String encoding = getServletConfig().getInitParameter("encoding");
        System.out.println(encoding);
        // 获取请求中的参数
        String username = req.getParameter("username");
        if("atguigu".equals(username)){
            //通过响应对象响应信息
            resp.getWriter().write("NO");
        }else{
            resp.getWriter().write("YES");
        }
    }
}

Servlet 生命周期


  • Servlet对象Servlet容器创建的,生命周期方法都是由容器 (目前我们使用的是Tomcat) 调用的。

  • 不建议在 service 方法中修改成员变量,因为 ServletTomcat 中式单例的,Servlet 的成员变量在多个线程栈中式共享的。如果修改了可能引发线程安全问题

生命周期 对应方法 执行时机 执行次数
构造对象 构造器 第一次请求或者容器启动 1
初始化 init() 构造完毕后 1
处理服务 service(HttpServletRequest req,HttpServletResponse resp) 每次请求 多次
销毁 destory() 容器关闭 1

load-on-startup 问题

  • 注解 中配置或者在 xml 中配置
  • 默认是 -1 表示第一次请求时加载
  • 整数就是应用启动时就加载

如果配置的是正整数则表示容器在启动时就要实例化Servlet。不建议配置 1 到 5 因为,因为 Tomcatweb.xml 文件中配置过了 1 到 5。数字表示的是实例化的顺序

Servlet 继承结构


Servlet 顶级接口

java 复制代码
1 顶级的 Servlet 接口
public interface Servlet {

    //初始化方法, 构造完毕后, 由 Tomcat 自动调用完成初始化
    void init(ServletConfig var1) throws ServletException;

    //获得 ServletConfig 对象的方法
    ServletConfig getServletConfig();

  	//接受用户请求, 向用户相应信息的方法
   	void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    //返回 Servlet 字符串形式描述信息的方法
    String getServletInfo();

    // Servlet 在回收前, 由 Tomcat 调用的销毁方法, 往往用于做资源的释放工作
    void destroy();
}

GenericServlet 抽象模板类

java 复制代码
2 抽象的类 GenericServlet  侧重出了 service 方法意外的其他方法的基础处理
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
    public void destroy() {
        //将抽象方法, 重写为普通方法, 在方法内部没有任何的实现代码
        //平庸实现
    }

    //tomcat 在调用 init 方法时, 会读取配置信息进入一个 ServiceConfig 对象并将该对象传入 init 方法
      public void init(ServletConfig config) throws ServletException {
          //将 config 对象存储为当前的属性
          this.config = config;
          //调用无参的 init()
          this.init();
      }

      //重载的初始化方法, 我们重写初始化方法时对应的方法, 因为如果我们不重写的话, 要自己处理 config 这些东西很麻烦
      public void init() throws ServletException {

      }

      //返回 ServletConfig 的方法
      public ServletConfig getServletConfig() {
          return this.config;
      }

      //再次抽象声明 service 方法
      public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException {

      }
}

HTTPServlet 抽象类

java 复制代码
3 HttpServlet 抽象类
public abstract class HttpServlet extends GenerricServlet {
     @Override
     public service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
         HttpServletRequest request;
         HttpServletResponse response;
        //参数的父转子 调用重载的 service 方法
         try {
             request = (HttpServletRequest)req;
             response = (HttpServletResponse)res;
        } catch(ClassCastException var6) {
             throww new ServletException(lString.getString("http.non_htto"));
         }

        //调用重载的 service
         this.service(request, response);
     }
}

HttpServlet 抽象类中的 this.service 调用的 service

java 复制代码
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取请求的方式
        String method = req.getMethod(); //GET POST PUT DELETE  OPTIONS......
        long lastModified;

        //根据请求方式,  调用 do...方法
        if (method.equals("GET")) {
            this.doGet(req, resp);
        } else if (method.equals("HEAD")) {
            this.doHead(req, resp);
        } else if (method.equals("POST")) {
            this.doPost(req, resp);
        } else if (method.equals("PUT")) {
            this.doPut(req, resp);
        } else if (method.equals("DELETE")) {
            this.doDelete(req, resp);
        } else if (method.equals("OPTIONS")) {
            this.doOptions(req, resp);
        } else if (method.equals("TRACE")) {
            this.doTrace(req, resp);
        } else {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[]{method};
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(501, errMsg);
        }

}

我们需要重写的方法

  • 继承 HTTpServlet 后, 要么重写 service 方法, 要么重写 doGet/doPost方法
  • 部分程序员推荐在 servlet 中重写 do*** 方法处理请求 理由: service 方法中可能做了一些处理,如果我们直接重写 service 的话父类 service 方法处理的功能就失效的
  • 如果 doGet 和 doPost 方法中, 我们定义的代码都一样, 可以让一个方法直接调用另一个方法就行
  • 后续使用了 SpringMVC 框架后, 我们无序继承 HttpServlet, 处理请求的方法也无需 do*** service

继承关系图解

ServletConfig 和 ServletContext


ServletConfig

  • ServletConfig 是为 Servlet 提供初始配置参数的一种对象,每个 Servlet 都有自己独立唯一的 ServletConfig 对象。就是各自 Servletweb.xml 中的配置信息

  • 容器会为每个 Servlet实例化一个ServletConfig对象,并通过 Servlet 生命周期的 init 方法传入给Servlet 作为属性

使用 ServletConfig

ServletConfig 接口

java 复制代码
public interface ServletConfig {
    String getServletName();
    ServletContext getServletContext();
    String getInitParameter(String var1);
    Enumeration<String> getInitParameterNames();
}

获取 ServletConfig

HttpServlet 继承了 GenericServlet 。而 GenericServlet 中有 getServletConfig 方法我们自己的方法又继承了 HttpServlet 所以 GenericServlet 中的 getServletConfig 方法可以在 HttpServlet 中使用

java 复制代码
ServletConfig servletConfig = getServletConfig();
方法名 作用
getServletName() 获取<servlet-name>HelloServlet</servlet-name>定义的Servlet名称
getServletContext() 通过 servletConfig 获取 ServletContext 对象
getInitParameter() 获取配置Servlet时设置的『初始化参数』,根据名字获取值
getInitParameterNames() 获取所有初始化参数名组成的Enumeration对象

Enumeration

方法名 作用
hasMoreElements() 判断有没有下一个参数 如果有返回true 如果没有返回 false
nextElement 1.取出下一个元素 2.向下移动游标
使用 init-param
xml 复制代码
<servlet>
        <servlet-name>servlet2</servlet-name>
        <servlet-class>com.atmangfu.servlet.Servlet2</servlet-class>
        <!--配置servlet的初始参数-->
        <init-param>
            <param-name>keya</param-name>
            <param-value>value2</param-value>
        </init-param>
        <init-param>
            <param-name>keyb</param-name>
            <param-value>value2</param-value>
        </init-param>
</servlet>
java 复制代码
public class Servlet2 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletConfig servletConfig = getServletConfig();
        //获取初始时配置信息即可
        //根据参数名获取参数值
        String keya = servletConfig.getInitParameter("keya");
        System.out.println("keya:" + keya);

        //获取所有的参数的名字
        //hasMoreElements 判断有没有下一个参数 如果有返回true 如果没有返回 false
        //nextElement     1.取出下一个元素  2.向下移动游标
        Enumeration<String> initParameterNames = servletConfig.getInitParameterNames();
        while (initParameterNames.hasMoreElements()) {
            String pname = initParameterNames.nextElement();
            System.out.println(pname + "=" + getInitParameter(pname));
        }
    }
}

ServletContext


  • ServletContext 对象有称呼为上下文对象,或者叫应用域对象
  • 容器会为每个app创建一个独立的唯一的 ServletContext 对象,可以理解为 web.xml
  • ServletContext 对象为所有的 Servlet 所共享
  • ServletContext 可以为所有的 Servlet 提供初始配置参数
获取 ServletContext 对象
  • 第一种:先获取 ServletConfig 对象,再获取 SerletContext 对象
  • 第二种:直接调用 getServletContext()
  • 第三种:通过 request 调用 getServletContext()
java 复制代码
ServletContext servletContext1 = servletConfig.getServletContext();
ServletContext servletContext2 = req.getServletContext();
ServletContext servletContext3 = getServletContext();
ServletContext 获取配置参数
  • 配置ServletContext参数
xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0">

    <context-param>
        <param-name>paramA</param-name>
        <param-value>valueA</param-value>
    </context-param>
    <context-param>
        <param-name>paramB</param-name>
        <param-value>valueB</param-value>
    </context-param>
</web-app>
  • 在 Servlet 中获取 ServletContext 并获取参数
方法名 作用
public String getInitParameter(String name) 通过上下文初始化参数的 name 获取 value
public Enumeration<String> getInitParameterNames() 返回上下文初始化参数的 name 的集合
java 复制代码
public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       
        // 从ServletContext中获取为所有的Servlet准备的参数
        ServletContext servletContext = this.getServletContext();
        String valueA = servletContext.getInitParameter("paramA");
        System.out.println("paramA:"+valueA);
        // 获取所有参数名
        Enumeration<String> initParameterNames = servletContext.getInitParameterNames();
        // 迭代并获取参数名
        while (initParameterNames.hasMoreElements()) {
            String paramaterName = initParameterNames.nextElement();
            System.out.println(paramaterName+":"+servletContext.getInitParameter(paramaterName));
        }
    }
}
ServletContext 获得路径问题

获取资源的真实路径
java 复制代码
//获取一个指向项目部署位置下的某个文件/目录的磁盘真实路径
//E:\note\JavaWeb\code\web-all\out\artifacts\demo03_servletconfig_servletContext_war_exploded\upload
String realPath = servletContext.getRealPath("资源在web目录中的路径")
  • 这里的 demo3 就是项目部署后位置下的路径,相当于未构建前的 web 目录
获取资源的上下文路径
java 复制代码
String contextPath = servletContext.getContextPath();
  • 上下文路径
ServletContext 域相关

主要作用就是跨 Servlet 传输数据

  • 域对象:一些用于存储数据和传递数据的对象,传递数据不同的范围,我们称之为不同的域,不同的域对象代表不同的域,共享数据的范围也不同。
  • ServletContext 代表应用,所以ServletContext域也叫作应用域,是 webapp 中最大的域,可以在本应用内实现数据的共享和传递
  • webapp中的三大域对象:分别是应用域,会话域,请求域
API 功能解释
void setAttribute(String key,Object value); 向域中存储/修改数据
Object getAttribute(String key); 获得域中的数据
void removeAttribute(String key); 移除域中的数据
java 复制代码
@WebServlet(urlPatterns = "/servlet03")
public class Servlet3 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext servletContext = getServletContext();
        //获取一个指向项目部署位置下的某个文件/目录的磁盘真实路径API
        String path = servletContext.getRealPath("upload");
        System.out.println(path);
        FileOutputStream fos = new FileOutputStream(path + "/a.txt");

        //获得项目部署的上下文路径 项目的访问路径
        //后续我们会学习在项目中使用相对和绝对路径找目标资源
        //   servlet1  /上下文路径/servlet1
        String contextPath = servletContext.getContextPath();
        System.out.println(contextPath);

        //作为域对象一定会有的 API
        //void setAttribute(String key, Object value); 向域中存储/修改数据
        //Object getAttribute(String kye); 获得域中的数据
        //void removeAttribute(String key); 移除域中的数据
        // key 相同 value 会覆盖
        servletContext.setAttribute("ka", "va");
        servletContext.setAttribute("ka", "vaa");
        String attribute = (String)servletContext.getAttribute("ka");
        servletContext.removeAttribute("ka");

    }
}

HTTPServletRequet


HttpServletRequest 是一个接口,其父接口是 ServletRequest

HttpServletRequest 是 Tomcat 将请求报文转换封装而来的对象,在 Tomcat 调用 service 方法时传入

HttpServletRequest 代表客户端发来的请求,所有请求中的信息都可以通过该对象获得

常见 API

  • 获取请求行信息相关(方式,请求的url,协议及版本)
API 功能解释
StringBuffer getRequestURL(); 获取客户端请求的URL
String getRequestURI(); 获取客户端请求项目中的具体资源
int getServerPort(); 获取客户端发送请求时的端口
int getLocalPort(); 获取本应用在所在容器的端口
int getRemotePort(); 获取客户端程序的端口
String getScheme(); 获取请求协议
String getProtocol(); 获取请求协议及版本号
String getMethod(); 获取请求方式
  • 获得请求头信息相关
API 功能解释
String getHeader(String headerName); 根据头名称获取请求头
Enumeration<String> getHeaderNames(); 获取所有的请求头名字
String getContentType(); 获取content-type请求头
  • 获得请求参数相关
API 功能解释
String getParameter(String parameterName); 根据请求参数名获取请求单个参数值 【就是 发送的 key 和 vlaue】
String[] getParameterValues(String parameterName); 根据请求参数名获取请求多个参数值数组 【就是 发送的 key 和 vlaue】
Enumeration<String> getParameterNames(); 获取所有请求参数名
Map<String, String[]> getParameterMap(); 获取所有请求参数的键值对集合
BufferedReader getReader() throws IOException; 获取读取请求体的字符输入流
ServletInputStream getInputStream() throws IOException; 获取读取请求体的字节输入流
int getContentLength(); 获得请求体长度的字节数
  • 其他API
API 功能解释
String getServletPath(); 获取请求的Servlet的映射路径
ServletContext getServletContext(); 获取``ServletContext`对象
Cookie[] getCookies(); 获取请求中的所有cookie
HttpSession getSession(); 获取Session对象
void setCharacterEncoding(String encoding) ; 设置请求体字符集

HTTPServletResponse

  • HttpServletResponse是一个接口,其父接口是``ServletResponse`
  • HttpServletResponse是Tomcat预先创建的,在Tomcat调用service方法时传入
  • HttpServletResponse代表对客户端的响应,该对象会被转换成响应的报文发送给客户端,通过该对象我们可以设置响应信息

常用API

  • 设置响应行相关
API 功能解释
void setStatus(int code); 设置响应状态码【故意设置响应码】
  • 设置响应体相关
API 功能解释
PrintWriter getWriter() throws IOException; 获得向响应体放入信息的字符输出流 【JSON】
ServletOutputStream getOutputStream() throws IOException; 获得向响应体放入信息的字节输出流 【文件】
void setContentLength(int length); 设置响应体的字节长度,其实就是在设置content-length响应头

其他API

API 功能解释
void sendError(int code, String message) throws IOException; 向客户端响应错误信息的方法,需要指定响应码和响应信息
void addCookie(Cookie cookie); 向响应体中增加cookie
void setCharacterEncoding(String encoding); 设置响应体字符集

请求转发和响应重定向

请求转发

特点

  • 请求转发通过 HttpServletRequest 对象获取请求转发器实现 RequestDispatcher requestDispatcher = req.getRequestDispatcher("servletB");
  • 请求转发是服务器内部的行为,对客户端是屏蔽的
  • 客户端只发送了一次请求, 客户端地址栏不变
  • 服务端只产生了一对请求和响应对象, 这一对请求和响应对象会继续传递给下一个资源
  • 因为全程只有一个HttpServletRequset对象,所以请求参数可以传递,请求域中的数据也可以传递
  • 请求转发可以转发给其他Servlet动态资源,也可以转发给一些静态资源以实现页面跳转
  • 请求转发可以转发给WEB-INF下受保护的资源
  • 请求转发不能转发到本项目以外的外部资源

例子

  • ServeltA
java 复制代码
@WebServlet("/servletA")
public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //  获取请求转发器
        //  转发给servlet  ok
        RequestDispatcher  requestDispatcher = req.getRequestDispatcher("servletB");
        //  转发给一个视图资源 ok
        //RequestDispatcher requestDispatcher = req.getRequestDispatcher("welcome.html");
        //  转发给WEB-INF下的资源  ok
        //RequestDispatcher requestDispatcher = req.getRequestDispatcher("WEB-INF/views/view1.html");
        //  转发给外部资源   no
        //RequestDispatcher requestDispatcher = req.getRequestDispatcher("http://www.atguigu.com");
        //  获取请求参数
        String username = req.getParameter("username");
        System.out.println(username);
        //  向请求域中添加数据
        req.setAttribute("reqKey","requestMessage");
        //  做出转发动作
        requestDispatcher.forward(req,resp);
    }
}
  • ServeltB
java 复制代码
@WebServlet("/servletB")
public class ServletB extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取请求参数
        String username = req.getParameter("username");
        System.out.println(username);
        // 获取请求域中的数据
        String reqMessage = (String)req.getAttribute("reqKey");
        System.out.println(reqMessage);
        // 做出响应
        resp.getWriter().write("servletB response");        
    }
}

响应重定向

特点

  • 响应重定向通过 HttpServletResponse对象的sendRedirect方法实现

  • 响应重定向是服务端通过302响应码和路径,告诉客户端自己去找其他资源,是在服务端提示下的客户端的行为 就是重定向后,响应状态码为 302

  • 客户端至少发送了两次请求, 客户端地址栏是要变化的

  • 服务端产生了多对请求和响应对象,且请求和响应对象不会传递给下一个资源

  • 因为全程产生了多个HttpServletRequset对象,所以请求参数不可以传递,请求域中的数据也不可以传递

  • 重定向可以是其他Servlet动态资源,也可以是一些静态资源以实现页面跳转

  • 重定向不可以到给WEB-INF下受保护的资源

  • 重定向可以到本项目以外的外部资源

例子

  • ServletA
java 复制代码
@WebServlet("/servletA")
public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //  获取请求参数
        String username = req.getParameter("username");
        System.out.println(username);
        //  向请求域中添加数据
        req.setAttribute("reqKey","requestMessage");
        //  响应重定向
        // 重定向到servlet动态资源 OK
        resp.sendRedirect("servletB");
        // 重定向到视图静态资源 OK
        //resp.sendRedirect("welcome.html");
        // 重定向到WEB-INF下的资源 NO
        //resp.sendRedirect("WEB-INF/views/view1");
        // 重定向到外部资源
        //resp.sendRedirect("http://www.atguigu.com");
    }
}
  • ServletB
java 复制代码
@WebServlet("/servletB")
public class ServletB extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取请求参数
        String username = req.getParameter("username");
        System.out.println(username);
        // 获取请求域中的数据
        String reqMessage = (String)req.getAttribute("reqKey");
        System.out.println(reqMessage);
        // 做出响应
        resp.getWriter().write("servletB response");

    }
}

同样能够实现页面跳转,优先使用响应重定向

Web 乱码问题


乱码产生的根本原因

  • 数据的编码和解码使用的不是同一个字符集
  • 使用了不支持某个语言文字的字符集
HTML 乱码问题
  • 查看当前文件的字符集
  • 查看项目字符集 配置,将Global Encoding 全局字符集,Project Encoding 项目字符集, Properties Files 属性配置文件字符集设置为UTF-8
  • 当前视图文件的字符集通过 来告知浏览器通过什么字符集来解析当前文件
HTML 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    中文
</body>
</html>
Tomcat 控制台乱码
  • 在tomcat10.1.7这个版本中,修改 tomcat/conf/logging.properties 中修改
  • sout 乱码问题,设置JVM加载.class文件时使用UTF-8字符集
GET 请求乱码问题

原因

  • GET方式提交参数的方式是将参数放到URL后面,如果使用的不是UTF-8,那么会对参数进行URL编码处理
  • HTML中的 影响了GET方式提交参数的URL编码
  • tomcat10.1.7的URI编码默认为 UTF-8
  • 当GET方式提交的参数URL编码和tomcat10.1.7默认的URI编码不一致时,就会出现乱码

乱码演示

  • **浏览器解析的文档的 **
  • GET方式提交时,会对数据进行URL编码处理 ,是将GBK 转码为 "百分号码"
  • tomcat10.1.7 默认使用UTF-8对URI进行解析,造成前后端使用的字符集不一致,出现乱码
  • 解决方式

  • 方式1 :设置GET方式提交的编码和Tomcat10.1.7的URI默认解析编码一致即可 (推荐)

  • 方式2 : 设置Tomcat10.1.7的URI解析字符集和GET请求发送时所使用URL转码时的字符集一致即可,修改conf/server.xml中 Connecter 添加 URIEncoding="..." (不推荐)
POST 方式请求乱码

原因

  • POST请求将参数放在请求体中进行发送
  • 请求体使用的字符集受到了 的影响
  • Tomcat10.1.7 默认使用UTF-8字符集对请求体进行解析
  • 如果请求体的URL转码和Tomcat的请求体解析编码不一致,就容易出现乱码

乱码演示

  • POST请求请求体受到了 的影响
  • 请求体中,将GBK数据进行 URL编码
  • 后端默认使用UTF-8解析请求体,出现字符集不一致,导致乱码

解决方式

  • 方式1 : 请求时,使用UTF-8字符集提交请求体 (推荐)
  • 方式2 : 后端在获取参数前,设置解析请求体使用的字符集和请求发送时使用的字符集一致 (不推荐)
响应乱码

原因

  • 在Tomcat10.1.7中,向响应体中放入的数据默认使用了工程编码 UTF-8
  • 浏览器在接收响应信息时,使用了不同的字符集或者是不支持中文的字符集就会出现乱码

乱码演示

  • 服务端通过response对象向响应体添加数据
  • 浏览器接收数据解析乱码

解决

  • 方式1 : 手动设定浏览器对本次响应体解析时使用的字符集(不推荐)

    • edge和 chrome浏览器没有提供直接的比较方便的入口,不方便
  • 方式2: 后端通过设置响应体的字符集和浏览器解析响应体的默认字符集一致(不推荐)

  • 方式3: 通过设置content-type响应头,告诉浏览器以指定的字符集解析响应体(推荐)
    • 前端用...字符集解析
    • 后端也可以用 setCharacterEncoding 设置对应的字符集

web 路径问题


前端路径问题

  • 相对路径
java 复制代码
语法: 不以 / 开头
      ./表示当前目录
      ../表示当前目录所在的目录
缺点: 目标资源路径收到当前路径的影响,不同的位置,相对路径写法不同
  • 绝对路径
java 复制代码
     始终以固定的路径作为出发点去找目标资源 和当前资源的所在路径没有关系
     语法:以 / 开头
          不同的项目中, 固定的路径出发点可能不一致, 可以测试一下 http://localhost:8080/ 作为出发点
     优点:目标资源路径的写法不会受到当前资源路径的影响,不同的位置,绝对路径写法一致
     缺点:绝对路径要补充项目的上下文 项目上下文是可以发生改变的
     通过 head>base>href属性,定义相对路径公共前缀,通过公共前缀把一个相对路径转换为绝对路径[不完美]
    <head>     
      <base href="/demo05/">
    </head>     
    <img src="static/img/Image_1720860683567.jpg">
    这里的 static 前面会自动加上 /demo05/     
  • 相对原理原理

就是和当前的上下文路径拼接到一起,可以理解为 WEB 目录【除了 WEB-INF 里面的要通过请求转发访问】,用相对路径记得用 .../ 消去多余路径

  • 绝对路径原理

http://localhost:8080 作为出发点 我们加上 / + 上下文路径就行

重定向 和 请求转发中的路径问题

原理

  • 重定向的地址
    • 直接拼接到上下文路径 【相对路径】
    • 用 / 直接 表示 http://localhost:8080 我们直接加上 / + 上下文路径 + ServeltB 路径就行
  • 请求转发
    • 相对路径原理一致
    • 请求转发 / 代表路径是 http://localhost:8080/demo05/ 我们不用加上下文路径,直接 ServletB路径就行`
  • ServeltA
java 复制代码
@WebServlet("/x/y/z/serveltA")
public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("serveltA");
        //重定向到 ServletB
        /*
         * 1. 相对路径写法
         *      和前端的相对路径规则一致:当前相对路径是 /x/y/z/sereltA
         *
         * 2. 绝对路径写法
         *      / 就是 http://localhost:8080 为出发点
         *
         */
        //resp.sendRedirect("../../../serveltB");
        resp.sendRedirect("/demo05/serveltB");

        /*请求转发到 ServeltB*/
        /*
         * 1. 相对路径写法一致
         * 2. 绝对路径
         *      请求转发的绝对路径是不需要添加项目上下文的
         *      请求转发 / 代表路径是 http://localhost:8080/demo05/
         */
    }
}
  • servletB
java 复制代码
@WebServlet("/serveltB")
public class ServletB extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("servletB");
    }
}

总结

总之我们用相对路径要观察,路径拼接到上下文路径后面的问题,很麻烦

我们可以用绝对路径,除了请求转发路径直接 / + 要转发到路径,其他的都是 / + 上下文路径 + 要访问的路径。但不是最好。

缺省项目上下文路径

设置为 所有的绝对路径中就不必填写项目的上下文了。直接 / 开头即可。以后我们所有路径都 / + 要访问的路径 就行

MVC 架构模式


MVC(Model View Controller)是软件工程中的一种**软件架构模式,它把软件系统分为 模型 视图 控制器**三个基本部分。用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。

  • M:Model 模型层,具体功能如下

    1. 存放和数据库对象的实体类以及一些用于存储非数据库表完整相关的VO对象
    2. 存放一些对数据进行逻辑运算操作的的一些业务处理代码
  • V:View 视图层,具体功能如下

    1. 存放一些视图文件相关的代码 html css js等
    2. 在前后端分离的项目中,后端已经没有视图文件,该层次已经衍化成独立的前端项目
  • C:Controller 控制层,具体功能如下

    1. 接收客户端请求,获得请求数据
    2. 将准备好的数据响应给客户端

MVC模式下,项目中常见的包

  • M:

    1. 实体类包(pojo /entity /bean) 专门存放和数据库对应的实体类和一些VO对象
    2. 数据库访问包(dao/mapper) 专门存放对数据库不同表格CURD方法封装的一些类
    3. 服务包(service) 专门存放对数据进行业务逻辑运算的一些类
  • C:

    1. 控制层包(controller)
  • V:

    1. web目录下的视图资源 html css js img 等
    2. 前端工程化后,在后端项目中已经不存在了

非前后端分离 MVC

前后端分离的 MVC

MVC 项目演示


  • resources:配置文件
  • util:工具类
  • test:测试类
  • pojo:数据库实体类 - Model 层
  • Dao:针对数据库表格的 CRUD - Model 层
  • service:逻辑都写在 service 里面 - Model 层
  • controller:前后端交互 - Controller 层

Resources 配置文件

properties 复制代码
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/schedule_system
username=root
password=Ting123321
initialSize=5
maxActive=10
maxWait=1000

Util 工具类

  • JDBCUtil:从数据库连接池获取连接,用完归还连接
  • MD5Util:用来个密码加密

JDBCUtil

java 复制代码
package com.atmangfu.schedule.util;


import com.alibaba.druid.pool.DruidDataSourceFactory;
import lombok.Getter;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

public class JDBCUtil {
    private static ThreadLocal<Connection> threadLocal =new ThreadLocal<>();

    /*1 向外提供连接池的方法*/
    @Getter
    private static DataSource dataSource;
    // 初始化连接池
    static{
        // 可以帮助我们读取.properties配置文件
        Properties properties =new Properties();
        InputStream resourceAsStream = JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
        try {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        try {
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }


    }

    /*2 向外提供连接的方法*/
    public static Connection getConnection(){
        Connection connection = threadLocal.get();
        if (null == connection) {
            try {
                connection = dataSource.getConnection();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
            threadLocal.set(connection);
        }

        return connection;
    }


    /*定义一个归还连接的方法 (解除和ThreadLocal之间的关联关系) */
    public static void releaseConnection(){
        Connection connection = threadLocal.get();
        if (null != connection) {
            threadLocal.remove();
            // 把连接设置回自动提交的连接
            try {
                connection.setAutoCommit(true);
                // 自动归还到连接池
                connection.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

MD5Util

java 复制代码
import java.security.NoSuchAlgorithmException;
public final class MD5Util {
    public static String encrypt(String strSrc) {
        try {
            char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
                    '9', 'a', 'b', 'c', 'd', 'e', 'f' };
            byte[] bytes = strSrc.getBytes();
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(bytes);
            bytes = md.digest();
            int j = bytes.length;
            char[] chars = new char[j * 2];
            int k = 0;
            for (int i = 0; i < bytes.length; i++) {
                byte b = bytes[i];
                chars[k++] = hexChars[b >>> 4 & 0xf];
                chars[k++] = hexChars[b & 0xf];
            }
            return new String(chars);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            throw new RuntimeException("MD5加密出错!!!");
        }
    }
}

Pojo 包

每张表对应一个类

SyscheduleDaoimp

java 复制代码
ackage com.atmangfu.schedule.dao.impl;

import com.atmangfu.schedule.dao.BaseDao;
import com.atmangfu.schedule.dao.SysScheduleDao;
import com.atmangfu.schedule.pojo.Syschedule;

import java.util.List;


public class SyscheduleDaoimp extends BaseDao implements SysScheduleDao {
    @Override
    public int addSchedule(Syschedule schedule) {
        String sql = "insert into sys_schedule values(DEFAULT,?,?,?)";
        int rows = baseUpdate(sql, schedule.getUid(), schedule.getTitle(), schedule.getCompleted());
        return rows;
    }

    @Override
    public List<Syschedule> findAll() {
        String sql = "select sid,uid,title,completed from sys_schedule";
        List<Syschedule> scheduleList = baseQuery(Syschedule.class, sql);
        return scheduleList;
    }
}

SysUserDaoimp类

java 复制代码
package com.atmangfu.schedule.dao.impl;

import com.atmangfu.schedule.dao.BaseDao;
import com.atmangfu.schedule.dao.SysUserDao;
import com.atmangfu.schedule.pojo.SysUser;


public class SysUserDaoimp extends BaseDao implements SysUserDao {
    @Override
    public int addSysUser(SysUser sysUser) {
        String sql = "insert int sys_user values(DEFAULT, ?,?)";
        return baseUpdate(sql, sysUser.getUsername(), sysUser.getUserPwd());

    }
}

Dao 包

每张表对应一个 CRUD 类。

  • BaseDao 用于存放所有 CRUD 的操作
  • 两张表用接口制定规范,然后在 Impl 中集成 BaseDao 具体实现。

BaseDao

java 复制代码
package com.atmangfu.schedule.dao;



import com.atmangfu.schedule.util.JDBCUtil;

import java.lang.reflect.Field;
import java.sql.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

public class BaseDao {
    // 公共的查询方法  返回的是单个对象
    public <T> T baseQueryObject(Class<T> clazz, String sql, Object ... args) {
        T t = null;
        Connection connection = JDBCUtil.getConnection();
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        int rows = 0;
        try {
            // 准备语句对象
            preparedStatement = connection.prepareStatement(sql);
            // 设置语句上的参数
            for (int i = 0; i < args.length; i++) {
                preparedStatement.setObject(i + 1, args[i]);
            }

            // 执行 查询
            resultSet = preparedStatement.executeQuery();
            if (resultSet.next()) {
                t = (T) resultSet.getObject(1);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != resultSet) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (null != preparedStatement) {
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }

            }
            JDBCUtil.releaseConnection();
        }
        return t;
    }

    // 公共的查询方法  返回的是对象的集合
    public <T> List<T> baseQuery(Class clazz, String sql, Object ... args){
        List<T> list =new ArrayList<>();
        Connection connection = JDBCUtil.getConnection();
        PreparedStatement preparedStatement=null;
        ResultSet resultSet =null;
        int rows = 0;
        try {
            // 准备语句对象
            preparedStatement = connection.prepareStatement(sql);
            // 设置语句上的参数
            for (int i = 0; i < args.length; i++) {
                preparedStatement.setObject(i+1,args[i]);
            }

            // 执行 查询
            resultSet = preparedStatement.executeQuery();

            ResultSetMetaData metaData = resultSet.getMetaData();
            int columnCount = metaData.getColumnCount();

            // 将结果集通过反射封装成实体类对象
            while (resultSet.next()) {
                // 使用反射实例化对象
                Object obj =clazz.getDeclaredConstructor().newInstance();

                for (int i = 1; i <= columnCount; i++) {
                    String columnName = metaData.getColumnLabel(i);
                    Object value = resultSet.getObject(columnName);
                    // 处理datetime类型字段和java.util.Data转换问题
                    if(value.getClass().equals(LocalDateTime.class)){
                        value= Timestamp.valueOf((LocalDateTime) value);
                    }
                    Field field = clazz.getDeclaredField(columnName);
                    field.setAccessible(true);
                    field.set(obj,value);
                }

                list.add((T)obj);
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null !=resultSet) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
            if (null != preparedStatement) {
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
            JDBCUtil.releaseConnection();
        }
        return list;
    }

    // 通用的增删改方法
    public int baseUpdate(String sql,Object ... args) {
        // 获取连接
        Connection connection = JDBCUtil.getConnection();
        PreparedStatement preparedStatement=null;
        int rows = 0;
        try {
            // 准备语句对象
            preparedStatement = connection.prepareStatement(sql);
            // 设置语句上的参数
            for (int i = 0; i < args.length; i++) {
                preparedStatement.setObject(i+1,args[i]);
            }

            // 执行 增删改 executeUpdate
            rows = preparedStatement.executeUpdate();
            // 释放资源(可选)


        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            if (null != preparedStatement) {
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }

            }
            JDBCUtil.releaseConnection();
        }
        // 返回的是影响数据库记录数
        return rows;
    }
}

SyscheduleDao 接口和实现类

java 复制代码
package com.atmangfu.schedule.dao;


import com.atmangfu.schedule.pojo.Syschedule;

import java.util.List;


public interface SyscheduleDao {
    /**
     * 该方法用于向数据库中增加一条日程记录
     * @param syschedule 日程数据以 SysSchedule 实体类头对形参入参
     * @return 返回影响数据库记录的行数,行数为 0 说明增加失败, 行数大于 0 说明增加成功
     */
    int addSchedule(Syschedule syschedule);

    /**
     * 查询所有用户的所有日程
     * @return 将所有日程放入一个: List<SysSchedule>集合中返回
     */
    List<Syschedule> findAll();


}
java 复制代码
package com.atmangfu.schedule.dao.impl;

import com.atmangfu.schedule.dao.BaseDao;
import com.atmangfu.schedule.dao.SyscheduleDao;
import com.atmangfu.schedule.pojo.Syschedule;

import java.util.List;

public class SyscheduleDaoimp extends BaseDao implements SyscheduleDao {
    @Override
    public int addSchedule(Syschedule schedule) {
        String sql = "insert into sys_schedule values(DEFAULT,?,?,?)";
        int rows = baseUpdate(sql, schedule.getUid(), schedule.getTitle(), schedule.getCompleted());
        return rows;
    }

    @Override
    public List<Syschedule> findAll() {
        String sql = "select sid,uid,title,completed from sys_schedule";
        List<Syschedule> scheduleList = baseQuery(Syschedule.class, sql);
        return scheduleList;
    }
}

SysUserDao 接口和实现类

java 复制代码
package com.atmangfu.schedule.dao;

import com.atmangfu.schedule.pojo.SysUser;

/*
 * Data access Object 数据访问对象
 * 该类中定义针对表格的 CRUD 的方法
 * DAO层一般需要定义接口和实现类
 */

public interface SysUserDao {

    /**
     * 向数据库中增加一条用户记录的方法
     * @param sysUser 要增加的记录的 username 和 user_pwd 字段以 SysUser 实体类对象的形式接收
     * @return 增加成功返回 1 增加失败返回 0
     */
    int addSysUser(SysUser sysUser);
}
java 复制代码
package com.atmangfu.schedule.dao.impl;

import com.atmangfu.schedule.dao.BaseDao;
import com.atmangfu.schedule.dao.SysUserDao;
import com.atmangfu.schedule.pojo.SysUser;



public class SysUserDaoimp extends BaseDao implements SysUserDao {
    @Override
    public int addSysUser(SysUser sysUser) {
        String sql = "insert int sys_user values(DEFAULT, ?,?)";
        return baseUpdate(sql, sysUser.getUsername(), sysUser.getUserPwd());

    }
}

Controller 包

因为 两个 Sys 要多写一层路径来区分调用的逻辑,所以多了一个 BaseController 用来执行反射。两个 Sys 集成 BaseController。不用一个个都写反射

BaseController

java 复制代码
package com.atmangfu.schedule.controller;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.lang.reflect.Method;


public class BaseController extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        String requestURI = req.getRequestURI();
        String[] split = requestURI.split("/");
        String methodName =split[split.length-1];
        // 通过反射获取要执行的方法
        Class clazz = this.getClass();
        try {
            Method method = clazz.getDeclaredMethod(methodName,HttpServletRequest.class,HttpServletResponse.class);
            // 设置方法可以访问
            method.setAccessible(true);
            // 通过反射执行代码
            method.invoke(this,req,resp);
        } catch (Exception e) {
            e.printStackTrace();

        }
    }
}

SysScheduleController

java 复制代码
package com.atmangfu.schedule.controller;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.lang.reflect.Method;


/*
 * 多写一层路径用于区分调用的逻辑
 * 增加日程的请求 /schedule/add
 * 查询日程的请求 /schedule/find
 * 修改日程的请求 /schedule/update
 * 删除日程的请求 /schedule/remove
 * ......
 *
 *
 *
 *
 */
@WebServlet("/schedule/*")
public class SysScheduleController extends BaseController {

    protected void add(HttpServletRequest req, HttpServletResponse resp) {
        System.out.println("add");
    }

    protected void find(HttpServletRequest req, HttpServletResponse resp) {
        System.out.println("find");
    }

    protected void update(HttpServletRequest req, HttpServletResponse resp) {
        System.out.println("update");
    }

    protected void remove(HttpServletRequest req, HttpServletResponse resp) {
        System.out.println("remove");
    }

}

SysUserController

java 复制代码
package com.atmangfu.schedule.controller;

import com.atmangfu.schedule.pojo.SysUser;
import com.atmangfu.schedule.service.SysUserService;
import com.atmangfu.schedule.service.impl.SysUserServiceImpl;
import com.atmangfu.schedule.util.MD5Util;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;



@WebServlet("/user/*")
public class SysUserController  extends BaseController {

    private SysUserService userService = new SysUserServiceImpl();


    /**
     * 接收用户登录请求, 完成登录业务的业务接口 (不是 java 那个接口)
     * @param req
     * @param resp
     */
    protected void login(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        //1 接收用户名和密码
        String username = req.getParameter("username");
        String userPwd = req.getParameter("userPwd");

        //2 调用服务层方法,根据用户查询用户信息
        SysUser loginUser = userService.findByUsername(username);
        if (null == loginUser) {
            //跳转到用户名有误提示页
            resp.sendRedirect("/loginUsernameError.html");
        } else if ( !MD5Util.encrypt(userPwd).equals(loginUser.getUserPwd()))  {
            //3 判断密码是否匹配
            //跳转到密码由误界面
            resp.sendRedirect("/loginUserPwdError.html");
        } else {
            //4 跳转到首页
            resp.sendRedirect("/showSchedule.html");
        }




    }


    /**
     * 接收用户注册请求的业务处理方法( 业务接口 不是java中的interface  )
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void regist(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1 接收客户端提交的参数
        String username = req.getParameter("username");
        String userPwd = req.getParameter("userPwd");

        // 2 调用服务层方法,完成注册功能
        //将参数放入一个SysUser对象中, 在调用regist方法时传入
        SysUser sysUser = new SysUser(null, username, userPwd);
        int rows = userService.regist(sysUser);

        // 3 根据注册结果(成功  失败) 做页面跳转
        if(rows > 0){
            resp.sendRedirect("/registSuccess.html");
        }else{
            resp.sendRedirect("/registFail.html");
        }
    }
}

Servlce 包

SysUserService 接口和实体类

java 复制代码
package com.atmangfu.schedule.service;

import com.atmangfu.schedule.pojo.SysUser;

/**
 * 该接口定义了以 sys_user 表格为核心的业务处理功能
 */
public interface SysUserService {

    /**
     * 注册用户的方法
     * @param registUser 要注册的用户名和明文密码 以 SysUser 对象的形式接接收
     * @return 注册成功返回 1, 注册失败返回 0
     */
    int regist(SysUser registUser);

    /**
     * 根据用户名获得完整用户信息的方法
     * @param username 要查询的用户名
     * @return 如果找到返回 SysUser 对象, 找不到返回 null
     */
    SysUser findByUsername(String username);
}
java 复制代码
package com.atmangfu.schedule.service.impl;

import com.atmangfu.schedule.dao.SysUserDao;
import com.atmangfu.schedule.dao.impl.SysUserDaoimp;
import com.atmangfu.schedule.pojo.SysUser;
import com.atmangfu.schedule.service.SysUserService;
import com.atmangfu.schedule.util.MD5Util;


public class SysUserServiceImpl implements SysUserService {

    private SysUserDao userDao = new SysUserDaoimp();

    @Override
    public int regist(SysUser sysUser) {
        //将用户的明文密码转为密文密码
        sysUser.setUserPwd(MD5Util.encrypt(sysUser.getUserPwd()));

        //调用 DAO 层的方法 将 sysUser 信息存入数据库
        return userDao.addSysUser(sysUser);
    }


    @Override
    public SysUser findByUsername(String username) {
        return userDao.finByUsername(username);
    }
}

SyscheduleService 接口和实体类

java 复制代码
package com.atmangfu.schedule.service;


public interface SyscheduleService {
}
java 复制代码
package com.atmangfu.schedule.service.impl;

import com.atmangfu.schedule.service.SyscheduleService;


public class SyscheduleServiceImpl implements SyscheduleService {
}

前端

login
html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>

        .ht{
            text-align: center;
            color: cadetblue;
            font-family: 幼圆;
        }
        .tab{
            width: 500px;
            border: 5px solid cadetblue;
            margin: 0px auto;
            border-radius: 5px;
            font-family: 幼圆;
        }
        .ltr td{
            border: 1px solid  powderblue;

        }
        .ipt{
            border: 0px;
            width: 50%;

        }
        .btn1{
            border: 2px solid powderblue;
            border-radius: 4px;
            width:60px;
            background-color: antiquewhite;

        }
        #usernameMsg , #userPwdMsg {
            color: rgb(230, 87, 51);
        }

        .buttonContainer{
            text-align: center;
        }
    </style>
    <script>
        // 检验用户名格式是否合法的函数
        function checkUsername(){
            // 定义正则表示字符串的规则
            var  usernameReg= /^[a-zA-Z0-9]{5,10}$/
            // 获得用户在页面上输入的信息
            var usernameInput =document.getElementById("usernameInput")
            var username = usernameInput.value
            // 获得格式提示的框
            var usernameMsg =document.getElementById("usernameMsg")
            // 格式有误时,返回false,在页面上提示
            if(!usernameReg.test(username)){
                usernameMsg.innerText="用户名格式有误"
                return false
            }
            // 格式OK,返回true 在页面上提示OK
            usernameMsg.innerText="OK"
            return true

        }

        // 检验密码格式是否合法的函数
        function checkUserPwd(){
            // 定义正则表示字符串的规则
            var  userPwdReg= /^[0-9]{6}$/
            // 获得用户在页面上输入的信息
            var userPwdInput =document.getElementById("userPwdInput")
            var userPwd = userPwdInput.value
            // 获得格式提示的框
            var userPwdMsg =document.getElementById("userPwdMsg")
            // 格式有误时,返回false,在页面上提示
            if(!userPwdReg.test(userPwd)){
                userPwdMsg.innerText="密码必须是6位数字"
                return false
            }
            // 格式OK,返回true 在页面上提示OK
            userPwdMsg.innerText="OK"
            return true

        }

        // 表单在提交时,校验用户名和密码格式,格式OK才会提交
        function checkForm(){
            var flag1 =checkUsername()
            var flag2 =checkUserPwd()

            return flag1&&flag2
        }


    </script>


</head>
<body>
<h1 class="ht">欢迎使用日程管理系统</h1>
<h3 class="ht">请登录</h3>
<form method="post" action="/user/login" onsubmit="return checkForm()">
    <table class="tab" cellspacing="0px">
        <tr class="ltr">
            <td>请输入账号</td>
            <td>
                <input class="ipt" type="text" id="usernameInput" name="username" onblur="checkUsername()">
                <span id="usernameMsg"></span>
            </td>
        </tr>
        <tr class="ltr">
            <td>请输入密码</td>
            <td>
                <input class="ipt" type="password" id="userPwdInput"  name="userPwd" onblur="checkUserPwd()">
                <span id="userPwdMsg"></span>
            </td>
        </tr>
        <tr class="ltr">
            <td colspan="2" class="buttonContainer">
                <input class="btn1" type="submit" value="登录">
                <input class="btn1" type="reset" value="重置">
                <button class="btn1"><a href="regist.html">去注册</a></button>
            </td>
        </tr>
    </table>
</form>
</body>
</html>
loginUsernameError
html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>登录失败,账户有误,请<a href="/login.html">重写的登录</a> </h1>
</body>
</html>

loginUserPwdError.html

java 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>登录失败,密码有误,请<a href="/login.html">重写的登录</a> </h1>
</body>
</html>
regist
java 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>

        .ht{
            text-align: center;
            color: cadetblue;
            font-family: 幼圆;
        }
        .tab{
            width: 500px;
            border: 5px solid cadetblue;
            margin: 0px auto;
            border-radius: 5px;
            font-family: 幼圆;
        }
        .ltr td{
            border: 1px solid  powderblue;

        }
        .ipt{
            border: 0px;
            width: 50%;

        }
        .btn1{
            border: 2px solid powderblue;
            border-radius: 4px;
            width:60px;
            background-color: antiquewhite;

        }

        .msg {
            color: gold;
        }

        .buttonContainer{
            text-align: center;
        }
    </style>

    <script>
        function checkUsername(){
            var usernameReg = /^[a-zA-Z0-9]{5,10}$/
            var usernameInput = document.getElementById("usernameInput")
            var username = usernameInput.value
            var usernameMsg = document.getElementById("usernameMsg")
            if(!usernameReg.test(username)){
                usernameMsg.innerText="格式有误"
                return false
            }
            usernameMsg.innerText="OK"
            return true
        }


        function checkUserPwd(){
            var userPwdReg = /^\d{6}$/
            var userPwdInput = document.getElementById("userPwdInput")
            var userPwd = userPwdInput.value
            var userPwdMsg = document.getElementById("userPwdMsg")
            if(!userPwdReg.test(userPwd)){
                userPwdMsg.innerText="格式有误"
                return false
            }
            userPwdMsg.innerText="OK"
            return true
        }


        function checkReUserPwd(){
            var userPwdReg = /^\d{6}$/
            // 再次输入的密码的格式
            var reUserPwdInput = document.getElementById("reUserPwdInput")
            var reUserPwd = reUserPwdInput.value
            var reUserPwdMsg = document.getElementById("reUserPwdMsg")
            if(!userPwdReg.test(reUserPwd)){
                reUserPwdMsg.innerText="格式有误"
                return false
            }
            // 获得上次密码,对比两次密码是否一致
            var userPwdInput = document.getElementById("userPwdInput")
            var userPwd = userPwdInput.value
            if(reUserPwd != userPwd){
                reUserPwdMsg.innerText="两次密码不一致"
                return false
            }
            reUserPwdMsg.innerText="OK"
            return true
        }


        function checkForm(){
            var flag1 = checkUsername()
            var flag2 = checkUserPwd()
            var flag3 = checkReUserPwd()

            return flag1 && flag2 && flag3
        }


    </script>



</head>
<body>
<h1 class="ht">欢迎使用日程管理系统</h1>
<h3 class="ht">请注册</h3>
<form method="post" action="/user/regist" onsubmit="return checkForm()">
    <table class="tab" cellspacing="0px">
        <tr class="ltr">
            <td>请输入账号</td>
            <td>
                <input class="ipt" id="usernameInput" type="text" name="username" onblur="checkUsername()">
                <span id="usernameMsg" class="msg"></span>
            </td>
        </tr>
        <tr class="ltr">
            <td>请输入密码</td>
            <td>
                <input class="ipt" id="userPwdInput" type="password" name="userPwd" onblur="checkUserPwd()">
                <span id="userPwdMsg" class="msg"></span>
            </td>
        </tr>
        <tr class="ltr">
            <td>确认密码</td>
            <td>
                <input class="ipt" id="reUserPwdInput" type="password" onblur="checkReUserPwd()">
                <span id="reUserPwdMsg" class="msg"></span>
            </td>
        </tr>
        <tr class="ltr">
            <td colspan="2" class="buttonContainer">
                <input class="btn1" type="submit" value="注册">
                <input class="btn1" type="reset" value="重置">
                <button class="btn1"><a  href="login.html">去登录</a></button>
            </td>
        </tr>
    </table>

</form>
</body>
</html>
registFail
html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>注册失败,用户名已经被占用,请<a href="/regist.html">重新注册</a> </h1>
</body>
</html>
registSuccess
java 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>注册成功,请立刻去登录吧<a href="/login.html">登录</a> </h1>
</body>
</html>
showSchedule
html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>以下是你的所有日程</h1>
    ... ...
</body>
</html>

会话

概述


无状态

  • 无状态是指在一个通信系统(这里以 HTTP 协议为例)中,每个请求都是独立的,不依赖于之前的请求或响应。
  • 就好比每次通信都是一次全新的对话,没有记忆之前交互内容的能力。

有状态

  • 就是和无状态反过来,可以记录浏览器的历史信息

会话管理实现的手段

  • cookie 是在客户端保留少量数据的技术,主要通过响应头向客户端响应一些客户端要保留的信息
  • session 是在服务端保留更多数据的技术,主要通过HttpSession对象保存一些和客户端相关的信息

cookie 是一种客户端会话技术,cookie 由服务端产生,它是服务器存放在浏览器的一小份数据,浏览器以后每次访问该服务器的时候都会将这小份数据携带到服务器去。

步骤

  • 服务端创建cookie,将cookie放入响应对象中,Tomcat容器将cookie转化为set-cookie响应头,响应给客户端
  • 客户端在收到cookie的响应头时,在下次请求该服务的资源时,会以cookie请求头的形式携带之前收到的Cookie
  • cookie是一种键值对格式的数据,从tomcat8.5开始可以保存中文,但是不推荐
  • 由于cookie是存储于客户端的数据,比较容易暴露,一般不存储一些敏感或者影响安全的数据

应用场景

  • 记录用户名
    • 当我们在用户名的输入框中输入完用户名后,浏览器记录用户名,下一次再访问登录页面时,用户名自动填充到用户名的输入框.
  • 保存电影播放进度
    • 在网页上播放电影的时候,如果中途退出浏览器了,下载再打开浏览器播放同一部电影的时候,会自动跳转到上次退出时候的进度,因为在播放的时候会将播放进度保存到cookie

存 Cookie

  • 第一步:用 new Cookie 创造 cookie 对象
  • 第二步:将 cookieresq.addCookie 放进 response

获取 Cookie

  • 第一步 :用 Cookie[] cookies = req.getCookies() 取出请求中的 cookie

举例

java 复制代码
//servletA向响应中增加Cookie

@WebServlet("/servletA")
public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 创建Cookie
        Cookie cookie1 =new Cookie("c1","c1_message");
        Cookie cookie2 =new Cookie("c2","c2_message");
        // 将cookie放入响应对象
        resp.addCookie(cookie1);
        resp.addCookie(cookie2);
    }
}
java 复制代码
//servletB 从请求中读取Cookie

@WebServlet("/servletB")
public class ServletB extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取请求中的cookie
        Cookie[] cookies = req.getCookies();
        //迭代cookies数组
        if (null != cookies && cookies.length!= 0) {
            for (Cookie cookie : cookies) {
                System.out.println(cookie.getName()+":"+cookie.getValue());
            }
        }
    }
}

默认情况下Cookie的有效期是一次会话范围内,我们可以通过cookie的setMaxAge()方法让Cookie持久化保存到浏览器上

  • 会话级Cookie

    • 服务器端并没有明确指定Cookie的存在时间
    • 在浏览器端,Cookie数据存在于内存中
    • 只要浏览器还开着,Cookie数据就一直都在
    • 浏览器关闭,内存中的Cookie数据就会被释放
  • 持久化Cookie

    • 服务器端明确设置了Cookie的存在时间
    • 在浏览器端,Cookie数据会被保存到硬盘上
    • Cookie在硬盘上存在的时间根据服务器端限定的时间来管控,不受浏览器关闭的影响
    • 持久化Cookie到达了预设的时间会被释放

使用

cookie.setMaxAge(int expiry) 参数单位是秒,表示cookie的持久化时间,如果设置参数为0,表示将浏览器中保存的该cookie删除

例子

java 复制代码
//servletA设置一个Cookie为持久化cookie

@WebServlet("/servletA")
public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 创建Cookie
        Cookie cookie1 =new Cookie("c1","c1_message");
        cookie1.setMaxAge(60);
        Cookie cookie2 =new Cookie("c2","c2_message");
        // 将cookie放入响应对象
        resp.addCookie(cookie1);
        resp.addCookie(cookie2);
    }
}
java 复制代码
//servletB接收Cookie,浏览器中间发生一次重启再请求servletB测试

@WebServlet("/servletB")
public class ServletB extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取请求中的cookie
        Cookie[] cookies = req.getCookies();
        //迭代cookies数组
        if (null != cookies && cookies.length!= 0) {
            for (Cookie cookie : cookies) {
                System.out.println(cookie.getName()+":"+cookie.getValue());
            }
        }
    }
}

访问互联网资源时不能每次都需要把所有Cookie带上。访问不同的资源时,可以携带不同的cookie,我们可以通过cookiesetPath(String path)cookie的路径进行设置

举例

java 复制代码
//从ServletA中获取cookie

public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 创建Cookie
        Cookie cookie1 =new Cookie("c1","c1_message");
        // 设置cookie的提交路径
        cookie1.setPath("/web03_war_exploded/servletB");
        Cookie cookie2 =new Cookie("c2","c2_message");
        // 将cookie放入响应对象
        resp.addCookie(cookie1);
        resp.addCookie(cookie2);
    }
}

向ServletB请求时携带携带了 c1

向其他资源请求时就不携带c1了

Session

HTTPSession 概述

HttpSession是一种保留更多信息在服务端的一种技术,服务器会为每一个客户端开辟一块内存空间,即session对象. 客户端在发送请求时,都可以使用自己的session. 这样服务端就可以通过session来记录某个客户端的状态了

步骤

  • 服务端在为客户端创建session时,会同时将session对象的id,即JSESSIONIDcookie的形式放入响应对象
  • 后端创建完session后,客户端会收到一个特殊的cookie,叫做JSESSIONID
  • 客户端下一次请求时携带JSESSIONID,后端收到后,根据JSESSIONID找到对应的session对象
  • 通过该机制,服务端通过session就可以存储一些专门针对某个客户端的信息了
  • session也是域对象(后续详细讲解)


应用场景

  • 记录用户的登录状态
    • 用户登录后,将用户的账号等敏感信息存入session
  • 记录用户操作的历史
    • 例如记录用户的访问痕迹,用户的购物车信息等临时性的信息

HttpSession 的使用

  • 获取 Session 对象HttpSession session = req.getSession();

  • 获取 Session 的 IDString jSessionId = session.getId();

  • 判断session是不是新创建的sessionboolean isNew = session.isNew();

  • 向session对象中存入数据session.setAttribute("username",username);

举例

JAVA 复制代码
//定义ServletA,将用户名存入session

@WebServlet("/servletA")
public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取请求中的参数
        String username = req.getParameter("username");
        // 获取session对象
        //判断请求中有没有一个特殊的 cookie JSESSIONID
        //有:根据 JESSIONID 找对应 session 对象
        //没有:创建一个 session 返回,并且向 response 对象中存放一个 JESSIONID
        HttpSession session = req.getSession();
         // 获取Session的ID
        String jSessionId = session.getId();
        System.out.println(jSessionId);
        // 判断session是不是新创建的session
        boolean isNew = session.isNew();
        System.out.println(isNew);
        // 向session对象中存入数据
        session.setAttribute("username",username);

    }
}

响应中收到了一个JSESSIONID的cookie

java 复制代码
//定义其他Servlet,从session中读取用户名

@WebServlet("/servletB")
public class ServletB extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取session对象
        HttpSession session = req.getSession();
         // 获取Session的ID
        String jSessionId = session.getId();
        System.out.println(jSessionId);
        // 判断session是不是新创建的session
        boolean isNew = session.isNew();
        System.out.println(isNew);
        // 从session中取出数据
        String username = (String)session.getAttribute("username");
        System.out.println(username);
    }
}

请求中携带了一个JSESSIONID的cookie

GetSeesion 方法处理逻辑

HttpSession 时效性

  • 为什么要设置session的时效

    • 用户量很大之后,Session对象相应的也要创建很多。如果一味创建不释放,那么服务器端的内存迟早要被耗尽。
    • 客户端关闭行为无法被服务端直接侦测,或者客户端较长时间不操作也经常出现,类似这些的情况,就需要对session的时限进行设置了
  • 默认的session最大闲置时间(两次使用同一个session中的间隔时间) 在tomcat/conf/web.xml配置为30分钟

  • 我们可以自己在当前项目的web.xml对最大闲置时间进行重新设定
  • 也可以通过HttpSession的API 对最大闲置时间进行设定
java 复制代码
// 设置最大闲置时间
session.setMaxInactiveInterval(60);
  • 也可以直接让session失效
java 复制代码
// 直接让session失效
session.invalidate();

三大域对象

概述


域对象: 一些用于存储数据和传递数据的对象,传递数据不同的范围,我们称之为不同的域,不同的域对象代表不同的域,共享数据的范围也不同

  • web项目中,我们一定要熟练使用的域对象分别是 请求域,会话域,应用域
  • 请求域对象是HttpServletRequest ,传递数据的范围是一次请求之内及请求转发
  • 会话域对象是HttpSession,传递数据的范围是一次会话之内,可以跨多个请求
  • 应用域对象是ServletContext,传递数据的范围是本应用之内,可以跨多个会话

生活举例: 热水器摆放位置不同,使用的范围就不同

  • 摆在张三工位下,就只有张三一个人能用
  • 摆在办公室的公共区,办公室内的所有人都可以用
  • 摆在楼层的走廊区,该楼层的所有人都可以用

域对象图解

  • 请求域
  • 会话域
  • 应用域
  • 所有域

域对象使用

API 功能
void setAttribute(String name,String value) 向域对象中添加/修改数据
Object getAttribute(String name); 从域对象中获取数据
removeAttribute(String name); 移除域对象中的数据

例子

java 复制代码
//ServletA向三大域中放入数据

@WebServlet("/servletA")
public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 向请求域中放入数据
        req.setAttribute("request","request-message");
        //req.getRequestDispatcher("servletB").forward(req,resp);
        // 向会话域中放入数据
        HttpSession session = req.getSession();
        session.setAttribute("session","session-message");
        // 向应用域中放入数据
        ServletContext application = getServletContext();
        application.setAttribute("application","application-message");

    }
}
java 复制代码
//ServletB从三大于中取出数据

@WebServlet("/servletB")
public class ServletB extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 从请求域中获取数据
        String reqMessage =(String)req.getAttribute("request");
        System.out.println(reqMessage);
        
        // 从会话域中获取数据
        HttpSession session = req.getSession();
        String sessionMessage =(String)session.getAttribute("session");
        System.out.println(sessionMessage);
        // 从应用域中获取数据
        ServletContext application = getServletContext();
        String applicationMessage =(String)application.getAttribute("application");
        System.out.println(applicationMessage);
    }
}

注意

  • 请求转发时,请求域可以传递数据 请求域内一般放本次请求业务有关的数据,如:查询到的所有的部门信息
  • 同一个会话内,不用请求转发,会话域可以传递数据 会话域内一般放本次会话的客户端有关的数据,如:当前客户端登录的用户
  • 同一个APP内,不同的客户端,应用域可以传递数据 应用域内一般放本程序应用有关的数据 如:Spring框架的IOC容器

过滤器

概述


Filter, 即过滤器,是JAVAEE技术规范之一, 作用目标资源的请求进行过滤的一套技术规范,是Java Web项目中最为实用的技术之一

  • Filter接口定义了过滤器的开发规范,所有的过滤器都要实现该接口
  • Filter 的 doFilter 方法可以控制请求是否继续, 如果放行, 则请求继续,如果拒绝,则请求到此为止,由过滤器本身做出响应
  • Filter 不仅可以对请求做出过滤,也可以在目标资源做出响应前,对响应再次进行处理
  • Filter 是GOF中责任链模式的典型案例

图示

开发中应用的场景

  • 日志的记录
  • 性能的分析
  • 乱码的处理
  • 事务的控制
  • 登录的控制
  • 跨域的处理
  • ... ...

过滤器使用

  • 先通过 注解 或者 xml 方式配置过滤器
  • 实现 filter 重写 dofilter
  • 写过滤条件
API 目标
default public void init(FilterConfig filterConfig) 初始化方法,由容器调用并传入初始配置信息filterConfig 对象
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 过滤方法,核心方法,过滤请求,决定是否放行,响应之前的其他处理等都在该方法中
default public void destroy() 销毁方法,容器在回收过滤器对象之前调用的方法
FilterConfig getInitParameter(String ) 获取初始化参数通过 k 获得 v。可以通过 init 方法中的 config 形参 获取初始化参数
JAVA 复制代码
/**
 * 日志过滤器 记录请求的历史 将日志打印到控制台
 *
 * 1. 实现 Fileter 接口
 * 2. 重写过滤方法
 * 3. 配置过滤器 web.xml, 注解
 */
public class LoggingFilter implements Filter {

    /*
    过滤请求和响应的方法
        1. 请求到达目标资源前,要经过该方法
        2. 该方法有能力控制请求是否连续向后到达目标资源 可以在该方法内直接向客户端做响应处理
        3. 请求到达目标资源后,响应之前,还会经过该方法
     */

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
         /*
         * 1. 请求到达目标资源之前的功能代码
         *      判断是否登录
         *      校验权限是否满足
         *
         * 2. 放行代码
         *
         * 3. 响应之前 HttpServletResponse 转换为响应报文之前 的功能代码
         */

        //请求到达目标资源之前的代码
        System.out.println("loggingFilter befor doFilter invoked");

        //放行
        filterChain.doFilter(servletRequest, servletResponse);

        //响应之前的功能代码
        System.out.println("logging after filterChain.doFilter invoked");
    }
}	

说明

说明

  • doFilter方法中的请求和响应对象是以父接口的形式声明的,实际传入的实参就是HttpServletRequestHttpServletResponse子接口级别的,可以安全强转
  • filterChain.doFilter(request,response); 这行代码的功能是放行请求,如果没有这一行代码,则请求到此为止
  • filterChain.doFilter(request,response);在放行时需要传入requestresponse,意味着请求和响应对象要继续传递给后续的资源,这里没有产生新的request和response对象

配置过滤器有以及过滤器的过滤范围

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0">

    <!--配置filter,并为filter起别名-->
   <filter>
       <filter-name>loggingFilter</filter-name>
       <filter-class>com.atguigu.filters.LoggingFilter</filter-class>
   </filter>
    
    <!--为别名对应的filter配置要过滤的目标资源-->
    <filter-mapping>
        <filter-name>loggingFilter</filter-name>
        <!--通过映射路径确定过滤资源-->
        <url-pattern>/servletA</url-pattern>
        <!--通过后缀名确定过滤资源-->
        <url-pattern>*.html</url-pattern>
        <!--通过servlet别名确定过滤资源-->
        <servlet-name>servletBName</servlet-name>

    </filter-mapping>
</web-app>

说明

  • filter-mapping标签中定义了过滤器对那些资源进行过滤
  • 子标签url-pattern通过映射路径确定过滤范围
    • /servletA 精确匹配,表示对servletA资源的请求进行过滤
    • *.html 表示对以.html结尾的路径进行过滤
    • /* 表示对所有资源进行过滤
    • 一个filter-mapping下可以配置多个 url-pattern
  • 子标签servlet-name通过servlet别名确定对那些servlet进行过滤
    • 使用该标签确定目标资源的前提是servlet已经起了别名
    • 一个filter-mapping下可以定义多个servlet-name
    • 一个filter-mapping下,servlet-nameurl-pattern子标签可以同时存在

过滤器链的使用


一个web项目中,可以同时定义多个过滤器,多个过滤器对同一个资源进行过滤时,工作位置有先后,整体形成一个工作链,称之为过滤器链

  • 过滤器链中的过滤器的顺序由 filter-mapping 顺序决定
  • 每个过滤器过滤的范围不同,针对同一个资源来说,过滤器链中的过滤器个数可能是不同的
  • 如果某个Filter是使用 ServletName 进行匹配规则的配置,那么这个Filter 执行的优先级要更低

图示

过滤器的生命周期


过滤器作为web项目的组件之一,和Servlet的生命周期类似,略有不同,没有servletload-on-startup 的配置,默认就是系统启动立刻构造

阶段 对应方法 执行时机 执行次数
创建对象 构造器 web应用启动时 1
初始化方法 void init(FilterConfig filterConfig) 构造完毕 1
过滤请求 void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) 每次请求 多次
销毁 default void destroy() web应用关闭时 1次

举例

java 复制代码
package com.atguigu.filters;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebServlet;

import java.io.IOException;


@WebServlet("/*")
public class LifeCycleFilter implements Filter {
    public LifeCycleFilter(){
        System.out.println("LifeCycleFilter constructor method invoked");
    }
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("LifeCycleFilter init method invoked");
        
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("LifeCycleFilter doFilter method invoked");
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {
        System.out.println("LifeCycleFilter destory method invoked");
    }
}

注解方式配置过滤器


  • valueurlpattern 使用和 配置 servlet 一样
java 复制代码
package jakarta.servlet.annotation;

import jakarta.servlet.DispatcherType;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebFilter {
    String description() default "";

    String displayName() default "";

    WebInitParam[] initParams() default {};

    String filterName() default "";

    String smallIcon() default "";

    String largeIcon() default "";

    String[] servletNames() default {};

    String[] value() default {};

    String[] urlPatterns() default {};

    DispatcherType[] dispatcherTypes() default {DispatcherType.REQUEST};

    boolean asyncSupported() default false;
}

一个比较完整的 Filter 的 XML 配置

xml 复制代码
<!--配置filter,并为filter起别名-->
<filter>
    <filter-name>loggingFilter</filter-name>
    <filter-class>com.atguigu.filters.LoggingFilter</filter-class>
    <!--配置filter的初始参数-->
    <init-param>
        <param-name>dateTimePattern</param-name>
        <param-value>yyyy-MM-dd HH:mm:ss</param-value>
    </init-param>
</filter>
<!--为别名对应的filter配置要过滤的目标资源-->
<filter-mapping>
    <filter-name>loggingFilter</filter-name>
    <!--通过映射路径确定过滤资源-->
    <url-pattern>/servletA</url-pattern>
    <!--通过后缀名确定过滤资源-->
    <url-pattern>*.html</url-pattern>
    <!--通过servlet别名确定过滤资源-->
    <servlet-name>servletBName</servlet-name>
</filter-mapping>

注解方式

java 复制代码
@WebFilter(
        filterName = "loggingFilter",
        initParams = {@WebInitParam(name="dateTimePattern",value="yyyy-MM-dd HH:mm:ss")},
        urlPatterns = {"/servletA","*.html"},
        servletNames = {"servletBName"}
)
public class LoggingFilter  implements Filter {
    private SimpleDateFormat dateFormat ;

    /*init初始化方法,通过filterConfig获取初始化参数
    * init方法中,可以用于定义一些其他初始化功能代码
    * */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 获取初始参数
        String dateTimePattern = filterConfig.getInitParameter("dateTimePattern");
        // 初始化成员变量
        dateFormat=new SimpleDateFormat(dateTimePattern);
    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 参数父转子
        HttpServletRequest request =(HttpServletRequest)  servletRequest;
        HttpServletResponse  response =(HttpServletResponse)  servletResponse;
        // 拼接日志文本
        String requestURI = request.getRequestURI();
        String time = dateFormat.format(new Date());
        String beforeLogging =requestURI+"在"+time+"被请求了";
        // 打印日志
        System.out.println(beforeLogging);
        // 获取系统时间
        long t1 = System.currentTimeMillis();
        // 放行请求
        filterChain.doFilter(request,response);
        // 获取系统时间
        long t2 = System.currentTimeMillis();
        String afterLogging =requestURI+"在"+time+"的请求耗时:"+(t2-t1)+"毫秒";
        // 打印日志
        System.out.println(afterLogging);

    }
}

监听器

概述


监听器:专门用于对域对象对象身上发生的事件或状态改变进行监听和相应处理的对象

  • 监听器是GOF设计模式中,观察者模式的典型案例

  • 观察者模式: 当被观察的对象发生某些改变时, 观察者自动采取对应的行动的一种设计模式

  • 监听器使用的感受类似JS中的事件,被观察的对象发生某些情况时,自动触发代码的执行

  • 监听器并不监听web项目中的所有组件,仅仅是对三大域对象做相关的事件监听

监听器分类

web中定义八个监听器接口作为监听器的规范,这八个接口按照不同的标准可以形成不同的分类

按监听的对象划分

  • application域监听器 ServletContextListenerServletContextAttributeListener
  • session域监听器 HttpSessionListener HttpSessionAttributeListenerHttpSessionBindingListenerHttpSessionActivationListener
  • request域监听器 ServletRequestListener ServletRequestAttributeListener

按监听的事件分

  • 域对象的创建和销毁监听器 ServletContextListener , HttpSessionListener, ServletRequestListener
  • 域对象数据增删改事件监听器 ServletContextAttributeListener, HttpSessionAttributeListener , ServletRequestAttributeListener
  • 其他监听器 HttpSessionBindingListener, HttpSessionActivationListener

监听器的六个主要接口


application 域监听器

ServetContextListener

ServletContextListener 监听 ServletContext 对象的创建与销毁

方法名 作用
contextInitialized(ServletContextEvent sce) ServletContext创建时调用
contextDestroyed(ServletContextEvent sce) ServletContext销毁时调用

注意

ServletContextEvent 对象代表从ServletContext 对象身上捕获到的事件,通过这个事件对象我们可以用 getServletContext() 获取到 ServletContext 对象.

**ServletContextAttributeListener **

ServletContextAttributeListener 监听 ServletContext 中属性的添加、移除和修改

方法名 作用
attributeAdded(ServletContextAttributeEvent scab) ServletContext中添加属性时调用
attributeRemoved(ServletContextAttributeEvent scab) ServletContext中移除属性时调用
attributeReplaced(ServletContextAttributeEvent scab) ServletContext中的属性被修改时调用

注意

ServletContextAttributeEvent 对象代表属性变化事件,它包含的方法如下:

方法名 作用
getName() 获取修改或添加的属性名
getValue() 获取被修改或添加前的属性值
getServletContext() 获取 ServletContext 对象

举例

  • 定义监听器
java 复制代码
package com.atguigu.listeners;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebListener;


@WebListener
public class ApplicationListener implements ServletContextListener , ServletContextAttributeListener {
    // 监听初始化
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext application = sce.getServletContext();
        System.out.println("application"+application.hashCode()+" initialized");
    }
    // 监听销毁
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        ServletContext application = sce.getServletContext();
        System.out.println("application"+application.hashCode()+" destroyed");
    }

    // 监听数据增加
    @Override
    public void attributeAdded(ServletContextAttributeEvent scae) {
        String name = scae.getName();
        Object value = scae.getValue();
        ServletContext application = scae.getServletContext();
        System.out.println("application"+application.hashCode()+" add:"+name+"="+value);
    }

    // 监听数据移除
    @Override
    public void attributeRemoved(ServletContextAttributeEvent scae) {
        String name = scae.getName();
        Object value = scae.getValue();
        ServletContext application = scae.getServletContext();
        System.out.println("application"+application.hashCode()+" remove:"+name+"="+value);
    }
    // 监听数据修改
    @Override
    public void attributeReplaced(ServletContextAttributeEvent scae) {
        String name = scae.getName();
        Object value = scae.getValue();
        ServletContext application = scae.getServletContext();
        Object newValue = application.getAttribute(name);
        System.out.println("application"+application.hashCode()+" change:"+name+"="+value+" to "+newValue);
    }

}
  • 定义触发监听器的代码
java 复制代码
// ServletA用于向application域中放入数据
@WebServlet(urlPatterns = "/servletA",name = "servletAName")
public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 向application域中放入数据
        ServletContext application = this.getServletContext();
        application.setAttribute("k1","v1");
        application.setAttribute("k2","v2");
    }
}


// ServletB用于向application域中修改和移除数据
@WebServlet(urlPatterns = "/servletB", name = "servletBName")
public class ServletB extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext appliation  = getServletContext();
        //  修改application域中的数据
        appliation.setAttribute("k1","value1");
        //  删除application域中的数据
        appliation.removeAttribute("k2");
    }
}

Session 域监听器

HttpSessionListener

HttpSessionListener 监听HttpSession对象的创建与销毁

方法名 作用
sessionCreated(HttpSessionEvent hse) HttpSession对象创建时调用
sessionDestroyed(HttpSessionEvent hse) HttpSession对象销毁时调用

注意

HttpSessionEvent对象代表从HttpSession对象身上捕获到的事件,通过这个事件对象我们可以通过 getSession() 获取到触发事件的 HttpSession对象。

HttpSessionBindingEvent

代表属性变化事件,

方法名 作用
getName() 获取修改或添加的属性名
getValue() 获取被修改或添加前的属性值
getSession() 获取触发事件的 HttpSession 对象

举例

  • 定义监听器
java 复制代码
package com.atguigu.listeners;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebListener;
import jakarta.servlet.http.*;


@WebListener
public class SessionListener implements HttpSessionListener, HttpSessionAttributeListener {
    // 监听session创建
    @Override
    public void sessionCreated(HttpSessionEvent se) {
        HttpSession session = se.getSession();
        System.out.println("session"+session.hashCode()+" created");
    }

    // 监听session销毁
    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        HttpSession session = se.getSession();
        System.out.println("session"+session.hashCode()+" destroyed");
    }
    // 监听数据增加
    @Override
    public void attributeAdded(HttpSessionBindingEvent se) {
        String name = se.getName();
        Object value = se.getValue();
        HttpSession session = se.getSession();
        System.out.println("session"+session.hashCode()+" add:"+name+"="+value);
    }
    // 监听数据移除
    @Override
    public void attributeRemoved(HttpSessionBindingEvent se) {
        String name = se.getName();
        Object value = se.getValue();
        HttpSession session = se.getSession();
        System.out.println("session"+session.hashCode()+" remove:"+name+"="+value);
    }
    // 监听数据修改
    @Override
    public void attributeReplaced(HttpSessionBindingEvent se) {
        String name = se.getName();
        Object value = se.getValue();
        HttpSession session = se.getSession();
        Object newValue = session.getAttribute(name);
        System.out.println("session"+session.hashCode()+" change:"+name+"="+value+" to "+newValue);
    }

}
  • 定义触发监听器的代码
java 复制代码
// servletA用于创建session并向session中放数据
@WebServlet(urlPatterns = "/servletA",name = "servletAName")
public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 创建session,并向session中放入数据
        HttpSession session = req.getSession();

        session.setAttribute("k1","v1");
        session.setAttribute("k2","v2");
    }
}


// servletB用于修改删除session中的数据并手动让session不可用
@WebServlet(urlPatterns = "/servletB", name = "servletBName")
public class ServletB extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession();
        //  修改session域中的数据
        session.setAttribute("k1","value1");
        //  删除session域中的数据
        session.removeAttribute("k2");
        // 手动让session不可用
        session.invalidate();
    }
}

request 域监听器

ServletRequestListener

ServletRequestListener 监听 ServletRequest对象的创建与销毁

方法名 作用
requestInitialized(ServletRequestEvent sre) ServletRequest对象创建时调用
requestDestroyed(ServletRequestEvent sre) ServletRequest对象销毁时调用

注意

ServletRequestEvent 对象代表从HttpServletRequest 对象身上捕获到的事件,通过这个事件对象我们可以通过 getServletRequest() 获取到触发事件的 HttpServletRequest对象。另外还可以通过 getServletContext() 获取到当前Web应用的 ServletContext 对象。

ServletRequestAttributeListener

ServletRequestAttributeListener 监听 ServletRequest 中属性的添加、移除和修改

方法名 作用
attributeAdded(ServletRequestAttributeEvent srae) ServletRequest 中添加属性时调用
attributeRemoved(ServletRequestAttributeEvent srae) ServletRequest 中移除属性时调用
attributeReplaced(ServletRequestAttributeEvent srae) ServletRequest 中的属性被修改时调用

注意

ServletRequestAttributeEvent 对象代表属性变化事件,它包含的方法如下:

方法名 作用
getName() 获取修改或添加的属性名
getValue() 获取被修改或添加的属性值
getServletRequest () 获取触发事件的 ServletRequest 对象

举例

  • 定义监听器
java 复制代码
package com.atguigu.listeners;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebListener;


@WebListener
public class RequestListener implements ServletRequestListener , ServletRequestAttributeListener {
    // 监听初始化
    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        ServletRequest request = sre.getServletRequest();
        System.out.println("request"+request.hashCode()+" initialized");
    }

    // 监听销毁
    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        ServletRequest request = sre.getServletRequest();
        System.out.println("request"+request.hashCode()+" destoryed");
    }


    // 监听数据增加
    @Override
    public void attributeAdded(ServletRequestAttributeEvent srae) {
        String name = srae.getName();
        Object value = srae.getValue();
        ServletRequest request = srae.getServletRequest();
        System.out.println("request"+request.hashCode()+" add:"+name+"="+value);
    }

    //  监听数据移除
    @Override
    public void attributeRemoved(ServletRequestAttributeEvent srae) {
        String name = srae.getName();
        Object value = srae.getValue();
        ServletRequest request = srae.getServletRequest();
        System.out.println("request"+request.hashCode()+" remove:"+name+"="+value);
    }
    // 监听数据修改
    @Override
    public void attributeReplaced(ServletRequestAttributeEvent srae) {
        String name = srae.getName();
        Object value = srae.getValue();
        ServletRequest request = srae.getServletRequest();
        Object newValue = request.getAttribute(name);
        System.out.println("request"+request.hashCode()+" change:"+name+"="+value+" to "+newValue);
    }
}
  • 定义触发监听器的代码
java 复制代码
//  servletA向请求域中放数据
@WebServlet(urlPatterns = "/servletA",name = "servletAName")
public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 向request中增加数据
        req.setAttribute("k1","v1");
        req.setAttribute("k2","v2");
        // 请求转发
        req.getRequestDispatcher("servletB").forward(req,resp);
    }
}

// servletB修改删除域中的数据
@WebServlet(urlPatterns = "/servletB", name = "servletBName")
public class ServletB extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //  修改request域中的数据
        req.setAttribute("k1","value1");
        //  删除session域中的数据
        req.removeAttribute("k2");

    }
}

Session 域的两个特殊监听器


Session 绑定监听器

HttpSessionBindingListener

HttpSessionBindingListener 监听当前监听器对象在Session域中的增加与移除。【就是这个对象放到 Session 里或从 Session 移除】

方法名 作用
valueBound(HttpSessionBindingEvent event) 该类的实例被放到Session域中时调用
valueUnbound(HttpSessionBindingEvent event) 该类的实例从Session中移除时调用

注意

HttpSessionBindingEvent 对象代表属性变化事件,它包含的方法如下:

方法名 作用
getName() 获取当前事件涉及的属性名
getValue() 获取当前事件涉及的属性值
getSession() 获取触发事件的 HttpSession 对象

举例

  • 定义监听器
java 复制代码
package com.atguigu.listeners;

import jakarta.servlet.http.HttpSession;
import jakarta.servlet.http.HttpSessionBindingEvent;
import jakarta.servlet.http.HttpSessionBindingListener;

public class MySessionBindingListener  implements HttpSessionBindingListener {
    //  监听绑定
    @Override
    public void valueBound(HttpSessionBindingEvent event) {
        HttpSession session = event.getSession();
        String name = event.getName();
        System.out.println("MySessionBindingListener"+this.hashCode()+" binding into session"+session.hashCode()+" with name "+name);
    }

    // 监听解除绑定
    @Override
    public void valueUnbound(HttpSessionBindingEvent event) {
        HttpSession session = event.getSession();
        String name = event.getName();
        System.out.println("MySessionBindingListener"+this.hashCode()+" unbond outof session"+session.hashCode()+" with name "+name);
    }
}
  • 定义触发监听器的代码
java 复制代码
@WebServlet(urlPatterns = "/servletA",name = "servletAName")
public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession();
        // 绑定监听器
        session.setAttribute("bindingListener",new MySessionBindingListener());
        // 解除绑定监听器
        session.removeAttribute("bindingListener");
    }
}

钝化监听器和活化监听器

HttpSessionActivationListener

HttpSessionActivationListener 监听某个对象在Session中的序列化与反序列化。

方法名 作用
sessionWillPassivate(HttpSessionEvent se) 该类实例和Session一起钝化到硬盘时调用
sessionDidActivate(HttpSessionEvent se) 该类实例和Session一起活化到内存时调用

注意

HttpSessionEvent 对象代表事件对象,通过getSession()方法获取事件涉及的 HttpSession对象。

钝化和活化

  • session对象在服务端是以对象的形式存储于内存的,session过多,服务器的内存也是吃不消的
  • 而且一旦服务器发生重启,所有的session对象都将被清除,也就意味着session中存储的不同客户端的登录状态丢失
  • 为了分摊内存 压力并且为了保证session重启不丢失,我们可以设置将session进行钝化处理
  • 在关闭服务器前或者到达了设定时间时,对session进行序列化到磁盘,这种情况叫做session的钝化
  • 在服务器启动后或者再次获取某个session时,将磁盘上的session进行反序列化到内存,这种情况叫做session的活化

如何配置钝化

  • 在web目录下,添加 META-INF下创建Context.xml
  • 文件中配置钝化
xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<Context>
    <Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1">
        <Store className="org.apache.catalina.session.FileStore" directory="d:\mysession"></Store>
    </Manager>
</Context>
  • 请求servletA,获得session,并存入数据,然后重启服务器
java 复制代码
@WebServlet(urlPatterns = "/servletA",name = "servletAName")
public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession();
        // 添加数据
        session.setAttribute("k1","v1");
    }
}
  • 请求servletB获取session,获取重启前存入的数据
java 复制代码
@WebServlet(urlPatterns = "/servletB", name = "servletBName")
public class ServletB extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession();
        Object v1 = session.getAttribute("k1");
        System.out.println(v1);

    }
}

如何配置活化

  • 定义监听器
java 复制代码
package com.atguigu.listeners;

import jakarta.servlet.http.HttpSession;
import jakarta.servlet.http.HttpSessionActivationListener;
import jakarta.servlet.http.HttpSessionEvent;

import java.io.Serializable;

public class ActivationListener  implements HttpSessionActivationListener, Serializable {
    //  监听钝化
    @Override
    public void sessionWillPassivate(HttpSessionEvent se) {
        HttpSession session = se.getSession();
        System.out.println("session with JSESSIONID "+ session.getId()+" will passivate");
    }

    //  监听活化
    @Override
    public void sessionDidActivate(HttpSessionEvent se) {
        HttpSession session = se.getSession();
        System.out.println("session with JSESSIONID "+ session.getId()+" did activate");
    }
}
  • 定义触发监听器的代码
java 复制代码
@WebServlet(urlPatterns = "/servletA",name = "servletAName")
public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession();
        // 添加数据
        session.setAttribute("k1","v1");
        // 添加钝化活化监听器
        session.setAttribute("activationListener",new ActivationListener());
    }
}
相关推荐
Java程序员-小白4 分钟前
Spring Shell——快速构建终端应用,自定义终端命令
java·后端·spring
想做白天梦10 分钟前
LeetCode :150. 逆波兰表达式求值(含求后缀表达式和中缀转后缀表达式)
java·前端·算法
远望樱花兔13 分钟前
【d63】【Java】【力扣】141.训练计划III
java·开发语言·leetcode
过期动态14 分钟前
详解Python面向对象程序设计
开发语言·python·pycharm·django
九圣残炎15 分钟前
【从零开始的LeetCode-算法】3254. 长度为 K 的子数组的能量值 I
java·算法·leetcode
正在敲代码中35 分钟前
成绩排序c++
开发语言·c++·算法·函数·结构体排序
那你为何对我三笑留情44 分钟前
六、Spring Boot集成Spring Security之前后分离项目认证流程最佳方案
java·spring boot·分布式·后端·spring·spring security
土小帽软件测试1 小时前
jmeter基础01-2_环境准备-Mac系统安装jdk
java·测试工具·jmeter·macos·软件测试学习
gma9991 小时前
【Effective C++】阅读笔记3
c++·笔记
吃不胖的熊猫1 小时前
【LQB15_模拟】C风险对冲
c语言·开发语言