Skip to content
On this page

javaWeb

xml

XML 技术用于解决什么问题

● 解决程序间数据传输的问题:=> json 比如 qq 之间的数据传送,用 xml 格式来传送数据,具有良好的可读性,可维护性。

● xml 可以做配置文件 xml 文件做配置文件可以说非常的普遍,比如我们的 tomcat 服务器的server.xml ,web.xml

● xml 可以充当小型的数据库 => 程序自己的数据格式存放 xml 文件做小型数据库,也是不错的选择,我们程序中可能用到的数据,如果放在数据库中读取不合适(因为你要增加维护数据库工作),可以考虑直接用xm 来做小型数据库,而且直接读取文件显然要比读取数据库快

XML 语法

1、文档声明

2、元素

3、属性

4、注释

5、CDATA 区 、特殊字符

文档声明

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

1、XML 声明放在 XML 文档的第一行

2、XML 声明由以下几个部分组成:

3、version - -文档符合 XML1.0 规范,我们学习 1.0

4、encoding - -文档字符编码,比如"utf-8"

元素

  1. 每个 XML 文档必须有且只有一个根元素。

  2. 根元素是一个完全包括文档中其他所有元素的元素。

  3. 根元素的起始标记要放在所有其他元素的起始标记之前。

  4. 根元素的结束标记要放在所有其他元素的结束标记之后

  5. XML 元素指 XML 文件中出现的标签,一个标签分为开始标签和结束标签,一个标签有如下几种书写形式包含标签体:www.sohu.cn不含标签体的:<a></a>, 简写为:<a/>

  6. 一个标签中也可以嵌套若干子标签。但所有标签必须合理的嵌套,绝对不允许交叉嵌套

  7. 叫法 student 元素,节点,标签

xml
<?xml version="1.0" encoding="utf-8" ?>
<students>
  <student id="100">
    <name>jack</name>
    <age>10</age>
    <gender></gender>
  </student>
  <student id="200">
    <name>mary</name>
    <age>18</age>
    <gender></gender>
  </student>
  <school>清华大学</school>
  <city/>
</students>

XML 元素命名规则

  1. 区分大小写,例如,<P><p>是两个不同的标记。
  2. 不能以数字开头。
  3. 不能包含空格。
  4. 名称中间不能包含冒号(:)。
  5. 如果标签单词需要间隔,建议使用下划线 比如 <book_title> hello <book_title>

属性

  1. 属性值用双引号(")或单引号(')分隔(如果属性值中有',用"分隔;有",用'分隔)

  2. 一个元素可以有多个属性,它的基本格式为:<元素名 属性名="属性值">

  3. 特定的属性名称在同一个元素标记中只能出现一次

  4. 属性值不能包括& 字符

xml
<students>
  <student id="100">
    <name>jack</name>
    <age>10</age>
    <gender></gender>
  </student>
  <student id="200">
    <name>mary</name>
    <age>18</age>
    <gender></gender>
  </student>
</students>

注释

xml
1. <!--这是一个注释- ->
2. 注释内容中不要出现- -;
3. 不要把注释放在标记中间;错误写法 <Name <!--the name-->>TOM</Name>
4. 注释不能嵌套;
5. 可以在除标记以外的任何地方放注释

CDATA 节

有些内容不想让解析引擎执行,而是当作原始内容处理(即当做普通文本),可以使用 CDATA 包括起来,CDATA 节中的所有字符都会被当作简单文本,而不是XML 标记

xml
<?xml version="1.0" encoding="utf-8"?>
<!-- 老韩解读
  <![CDATA[
  这里可以把你输入的字符原样显示,不会解析 xml
  ]]>
-->
<students>
  <stduent id="01">
    <name>tom</name>
    <gender></gender>
    <age>18</age>
    <!-- 举例说明:
      下面是一段 js 的代码片段. 直接放在<code></code>标签间,语法错误使用 CDATA 节来处理即可. <script data-compress=strip>
      function h(obj){
      obj.style.behavior='url(#default#homepage)';
      var a = obj.setHomePage('//www.baidu.com/');
      }
      </script>
    -->
   </stduent>
   <stduent id="02">
    <name>scott</name>
    <gender></gender>
    <age>17</age>
    <code>
      <!--如果希望把某些字符串,当做普通文本,使用CDATA 包括-->
      <![CDATA[
         <script data-compress=strip>
          function h(obj){
            obj.style.behavior='url(#default#homepage)';
            var a = obj.setHomePage('//www.baidu.com/');
          }
      	</script>
      ]]>
		</code>
	</stduent>
</stduent>

转义字符

对于一些单个字符,若想显示其原始样式,也可以使用转义的形式予以处理

转义字符符号
&lt;<
&gt;>
&amp;&
&quot;"
&apos;'
xml
<students>
  <stduent id="01">
    <name>tom</name>
    <gender></gender>
    <age>18</age>
    <!-- 使用转义字符表示一些特殊字符
    <resume>年龄<100 &版权</resume>
    -->
    <resume>年龄&lt;10 &gt; &amp;</resume>
  </stduent>
  <stduent id="02">
    <name>scott</name>
    <gender></gender>
    <age>17</age>
  </stduent>
</students>

DOM4j

XML 解析技术原理

  1. 不管是 html 文件还是 xml 文件它们都是标记型文档,都可以使用w3c 组织制定的dom 技术来解析
  2. document 对象表示的是整个文档(可以是 html 文档,也可以是xml

XML 解析技术介绍

● 早期 JDK 为我们提供了两种 xml 解析技术 DOM 和Sax 简介

  1. dom 解析技术是 W3C 组织制定的,而所有的编程语言都对这个解析技术使用了自己语言的特点进行实现。 Java 对 dom 技术解析也做了实现
  2. sun 公司在 JDK5 版本对 dom 解析技术进行升级:SAX(Simple API for XML )SAX解析,它是以类似事件机制通过回调告诉用户当前正在解析的内容。是一行一行的读取 xml 文件进行解析的。不会创建大量的 dom 对象。所以它在解析xml 的时候,在性能上优于 Dom 解析
  3. 这两种技术已经过时,知道有这两种技术即可

● 第三方的 XML 解析技术

  1. jdom 在 dom 基础上进行了封装
  2. dom4j 又对 jdom 进行了封装。
  3. pull 主要用在 Android 手机开发,是在跟 sax 非常类似都是事件机制解析xml 文件

DOM4J 介绍

  1. Dom4j 是一个简单、灵活的开放源代码的库(用于解析/处理XML 文件)。Dom4j 是由早期开发 JDOM 的人分离出来而后独立开发的。
  2. 与 JDOM 不同的是,dom4j 使用接口和抽象基类,虽然 Dom4j 的API 相对要复杂一些,但它提供了比 JDOM 更好的灵活性。
  3. Dom4j 是一个非常优秀的 Java XML API,具有性能优异、功能强大和极易使用的特点。现在很多软件采用的 Dom4j。
  4. 使用 Dom4j 开发,需下载 dom4j 相应的 jar 文件

使用DOM4j获得 Document 对象

DOM4j 中,获得 Document 对象的方式有三种

● 开发 dom4j 要导入 dom4j 的包

1、读取 XML 文件,获得 document 对象

java
SAXReader reader = new SAXReader(); //创建一个解析器
Document document = reader.read(new File("src/input.xml"));//XML Document

2、解析 XML 形式的文本,得到 document 对象.

java
String text = "<members></members>";
Document document = DocumentHelper.parseText(text);

3、主动创建 document 对象.

java
Document document = DocumentHelper.createDocument(); //创建根节点
Element root = document.addElement("members");

使用 DOM4J 对 xml 文件进行增删改查

xml
<?xml version="1.0" encoding="utf-8"?>
<students>
  <student id="01">
    <name>小龙女</name>
    <gender></gender>
    <age>16</age>
    <resume>古墓派掌门人</resume>
  </student>
  <student id="02">
    <name>欧阳锋</name>
    <gender></gender>
    <age>18</age>
    <resume>白驼山,蛤蟆神功</resume>
  </student>
</students>
java
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.junit.jupiter.api.Test;

import java.io.File;
import java.io.FileOutputStream;
import java.util.List;

/**
 * @author 韩顺平
 * @version 1.0
 */
public class Dom4j_ {

    /**
     * 演示如何加载xml文件
     */
    @Test
    public void loadXML() throws DocumentException {
        //得到一个解析器
        SAXReader reader = new SAXReader();
        //老师的代码技巧->debug 看看document对象的属性
        //分析了document对象的底层结构
        Document document = reader.read(new File("src/students.xml"));
        System.out.println(document);

    }

    /**
     * 遍历所有的student信息
     */
    @Test
    public void listStus() throws DocumentException {
        //得到一个解析器
        SAXReader reader = new SAXReader();
        //老师的代码技巧->debug 看看document对象的属性
        //分析了document对象的底层结构
        Document document = reader.read(new File("src/students.xml"));

        //1. 得到rootElement, 你是OOP
        Element rootElement = document.getRootElement();
        //2. 得到rootElement的student Elements
        List<Element> students = rootElement.elements("student");
        //System.out.println(student.size());//2
        for (Element student : students) {//element就是Student元素/节点
            //获取Student元素 的name Element
            Element name = student.element("name");
            Element age = student.element("age");
            Element resume = student.element("resume");
            Element gender = student.element("gender");

            System.out.println("学生信息= " + name.getText() + " " + age.getText() +
                    " " + resume.getText() + " " + gender.getText());
        }

    }

    /**
     * 指定读取第一个学生的信息 就是 dom4j+xpath
     */
    @Test
    public void readOne() throws DocumentException {

        //得到一个解析器
        SAXReader reader = new SAXReader();
        //老师的代码技巧->debug 看看document对象的属性
        //分析了document对象的底层结构
        Document document = reader.read(new File("src/students.xml"));

        //1. 得到rootElement, 你是OOP
        Element rootElement = document.getRootElement();

        //2. 获取第一个学生
        Element student = (Element) rootElement.elements("student").get(1);
        //3. 输出该信息
        System.out.println("该学生的信息= " + student.element("name").getText() + " " +
                student.element("age").getText() + " " + student.element("resume").getText() +
                student.element("gender").getText());

        //4. 获取student元素的属性
        System.out.println("id= " + student.attributeValue("id"));
    }

    /**
     * 加元素(要求: 添加一个学生到xml中) [不要求,使用少,了解]
     * @throws Exception
     */
    @Test
    public void add() throws Exception {

        //1.得到解析器
        SAXReader saxReader = new SAXReader();
        //2.指定解析哪个xml文件
        Document document = saxReader.read(new File("src/students.xml"));


        //首先我们来创建一个学生节点对象
        Element newStu = DocumentHelper.createElement("student");
        Element newStu_name = DocumentHelper.createElement("name");
        //如何给元素添加属性
        newStu.addAttribute("id", "04");
        newStu_name.setText("宋江");
        //创建age元素
        Element newStu_age = DocumentHelper.createElement("age");
        newStu_age.setText("23");
        //创建resume元素
        Element newStu_intro = DocumentHelper.createElement("resume");
        newStu_intro.setText("梁山老大");

        //把三个子元素(节点)加到 newStu下
        newStu.add(newStu_name);
        newStu.add(newStu_age);
        newStu.add(newStu_intro);
        //再把newStu节点加到根元素
        document.getRootElement().add(newStu);
        //直接输出会出现中文乱码:
        OutputFormat output = OutputFormat.createPrettyPrint();
        output.setEncoding("utf-8");//输出的编码utf-8

        //把我们的xml文件更新
        // lets write to a file
        //new FileOutputStream(new File("src/myClass.xml"))
        //使用到io编程 FileOutputStream 就是文件字节输出流
        XMLWriter writer = new XMLWriter(
                new FileOutputStream(new File("src/students.xml")), output);
        writer.write(document);
        writer.close();

    }

    /**
     * //删除元素(要求:删除第一个学生) 使用少,了解
     * @throws Exception
     */
    @Test
    public void del() throws Exception {
        //1.得到解析器
        SAXReader saxReader = new SAXReader();
        //2.指定解析哪个xml文件
        Document document = saxReader.read(new File("src/students.xml"));
        //找到该元素第一个学生
        Element stu = (Element) document.getRootElement().elements("student").get(2);
        //删除元素
        stu.getParent().remove(stu);
//        //删除元素的某个属性
//        stu.remove(stu.attribute("id"));
        //更新xml
        //直接输出会出现中文乱码:
        OutputFormat output = OutputFormat.createPrettyPrint();
        output.setEncoding("utf-8");//输出的编码utf-8
        //把我们的xml文件更新
        XMLWriter writer = new XMLWriter(
                new FileOutputStream(new File("src/students.xml")), output);
        writer.write(document);
        writer.close();
        System.out.println("删除成功~");
    }


    /**
     * //更新元素(要求把所有学生的年龄+3) 使用少,了解
     * @throws Exception
     */
    @Test
    public void update() throws Exception {

        //1.得到解析器å
        SAXReader saxReader = new SAXReader();
        //2.指定解析哪个xml文件
        Document document = saxReader.read(new File("src/students.xml"));
        //得到所有学生的年龄
        List<Element> students = document.getRootElement().elements("student");
        //遍历, 所有的学生元素的age+3
        for (Element student : students) {
            //取出年龄
            Element age = student.element("age");
            age.setText((Integer.parseInt(age.getText()) + 3) + "");
        }

        //更新
        //直接输出会出现中文乱码:
        OutputFormat output = OutputFormat.createPrettyPrint();
        output.setEncoding("utf-8");//输出的编码utf-8

        //把我们的xml文件更新
        XMLWriter writer = new XMLWriter(
                new FileOutputStream(new File("src/students.xml")), output);
        writer.write(document);
        writer.close();
        System.out.println("更新成功~");
    }
}

Tomcat

WEB 开发介绍

  1. WEB,在英语中 web 表示网/网络资源(页面,图片,css,js)意思,它用于表示WEB服务器(主机)供浏览器访问的资源
  2. WEB 服务器(主机)上供外界访问的 Web 资源分为: - 静态 web 资源(如 html 页面):指 web 页面中供人们浏览的数据始终是不变。- 动态 web 资源,比如 Servlet(java)、PHP 等。
  3. 静态 web 资源开发技术 - Html、CSS,js 等
  4. 常用动态 web 资源开发技术: Servlet、SpringBoot、SpringMVC、PHP、ASP.NET 等

JavaWeb 开发技术栈图

image-20231127094112544

BS 开发

  1. B: browser(浏览器, 种类太多 ff, chrome, ie, edge,)
  2. S: Server(服务端, 考虑很多)

老韩对 BS 的解读

(1) 兼容性 , 因为浏览器的种类很多,发现你写的程序,在某个浏览器会出现问题,其它浏览器正常

(2) 安全性, 通常情况下,BS 安全性不如 CS 好控制

(3) 易用性, BS 好于 CS, 浏览器电脑有

(4) 扩展性, BS 相对统一,只需要写 Server

image-20231127094504620

CS 开发

  1. C: Client(客户端)
  2. S: Server(服务端)

JavaWeb 服务软件

学习 JavaWeb 开发,需要先安装 JavaWeb 服务软件【我们把安装了JavaWeb服务软件主机称为 Web 服务器/JavaWeb 服务器】,然后在 web 服务器中开发相应的web资源。[Javaweb 服务器,Mysql

学习 JavaWeb 开发,为什么必须要先装 WEB 服务软件?

答:需要安装, 理解 Tomcat 本质就是一个 Java 程序, 但是这个Java 程序可以处理来自浏览器的 HTTP 请求, 和我们前面讲的 java 网络 服务(多人聊天, Server)

手写简单 Web 服务程序

需求: 手写 MyWebServer.java , 让大家体验一下 JavaWeb 服务本质【先初步体会,后面还会深入 不急】

需要使用到 Java 基础(IO/网络)

java
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author 韩顺平
 * @version 1.0
 * 这是我们自己写的一个web服务, 可以返回hello.html给浏览器
 */
public class MyTomcat {
    public static void main(String[] args) throws IOException {

        //1.在9999端口监听
        ServerSocket serverSocket = new ServerSocket(9999);

        //如果serverSocket 没有关闭,就等待连接, 不停的等待
        while (!serverSocket.isClosed()) {
            System.out.println("=====我的web服务在 9999端口监听=====");
            //2. 等待浏览器/客户端连接, 得到socket
            //   该socket用于通信
            Socket socket = serverSocket.accept();

            //3. 通过socket 得到 输出流,[]
            OutputStream outputStream = socket.getOutputStream();
            //   返回给浏览器/客户端
            //4. 读取 hello.html 文件返回即可=> 如何读取文件内容
            //   得到文件输入流(字符输入流), 和 src/hello.html
            BufferedReader bufferedReader =
                    new BufferedReader(new FileReader("src/hello.html"));
            String buf = "";
            //   循环读取hello.html
            while ((buf = bufferedReader.readLine()) != null) {
                outputStream.write(buf.getBytes());
            }

            outputStream.close();
            socket.close();
        }
        serverSocket.close();


    }
}

常用 JavaWeb 服务软件

  1. Tomcat:由 Apache 组织提供的一种 Web 服务器,提供对jsp 和Servlet 的支持。它是一种轻量级的 javaWeb 容器(服务器),也是当前应用最广的JavaWeb 服务器(免费)。
  2. Jboss:是一个遵从 JavaEE 规范的、它支持所有的 JavaEE 规范(免费)。
  3. GlassFish: 由 Oracle 公司开发的一款 JavaWeb 服务器,是一款商业服务器,达到产品级质量(应用很少)。
  4. Resin:是 CAUCHO 公司的产品,是一个非常流行的服务器,对servlet 和JSP提供了良好的支持, 性能也比较优良(收费)。
  5. WebLogic【很猛】:是 Oracle 公司的产品,支持 JavaEE 规范,而且不断的完善以适应新的开发要求,适合大型项目(收费,用的不多,适合大公司)。

Tomcat 下载与安装

  1. Tomcat 官方站点:http://tomcat.apache.org/
  2. 获取 Tomcat 安装程序包 : tar.gz文件是Linux操作系统下的安装版本 , zip文件是Windows系统下的压缩版本
  3. 使用 zip 包安装 Tomcat : 找到你需要用的 Tomcat 版本对应的 zip 压缩包,解压到需要安装的目录即可,老师解压在 D:\program\apache-tomcat-8.0.50, 已经给小伙伴,放到网盘..
  4. which version https://tomcat.apache.org/whichversion.html ,可以看到Tomcat仍然是支持jsp 和 el

Tomcat 启动

  1. 双击 bin 目录下的 startup.bat 文件
  2. 输入 http://localhost:8080/,显示如下界面代表安装成功, 默认在8080 端口
  3. 注意,不要关闭黑窗口,关闭了,tomcat 服务就停止了.

在开发中,我们可以看一下哪些端口在监听 :netstat -anb (使用管理员权限)

Tomcat 启动故障排除

  1. 双击 startup.bat 文件,出现一个小黑窗口然后就没了,原因是因为没有配置好JAVA_HOME 环境变量,Tomcat 本质是一个 Java 程序,所以要 jdk, 会去根据 JAVA_HOME 使用指定jdk
  2. JAVA_HOME 必须全大写
  3. JAVA_HOME 中间必须是下划线
  4. JAVA_HOME 配置的路径只需要配置到 jdk 的安装目录即可。不需要带上bin目录
  5. 端口 8080 被占用 [查看端口 netstat -anb, 使用的非常多]
  6. 如果其它服务程序占用了 8080 端口,可以关闭该服务,或者修改Tomcat 服务的默认端口 8080 [后面讲]
  7. 配置 JAVA_HOME 环境变量

配置 JAVA_HOME 环境变量

image-20231127095500890

image-20231127095526302

Tomcat 目录结构

  1. server.xml 用于配置 tomcat 的基本设置(启动端口,关闭端口, 主机名)
  2. wex.xml 用于指定 tomcat 运行时配置(比如 servlet 等..)
  3. webapps 目录是存放 web 应用,就是网站

image-20231127095405160

catalina 启动 Tomcat

  1. 进入到 Tomcat 的 bin 目录下
  2. 执行命令: catalina run

停止 Tomcat

  1. 点击 tomcat 服务器窗口,直接点击的关闭按钮
  2. 进入 Tomcat 的 bin 目录下的 shutdown.bat 双击,就可以停止Tomcat 服务器(推荐)

修改 Tomcat 服务端口

  1. Tomcat 目录下的 conf 目录,修改 server.xml

image-20231127095846759

  1. 老 韩 说 明 : http://localhost , 默 认 是 访 问 80 端 口, 即http://localhost 等价http://localhost:80

Tomcat 服务中部署 WEB 应用

部署方式1:将 web 工程的目录拷贝到 Tomcat 的 webapps 目录下

  1. news Web工程(目前都是静态资源 html, 图片)

  2. 将该news目录/文件夹 拷贝到 Tomcat 的webapps目录下

  3. 浏览器输入: http://ip[域名]:port/news/子目录../文件名

部署方式2:通过配置文件来部署(只做介绍)

  1. 在Tomcat 下的 conf 目录\Catalina\localhost\ 下,配置文件,比如hsp.xml(提醒:知道Tomcat通过配置,可以把一个web应用,映射到指定的目录,可以解决磁盘空间分配

  2. 访问web工程: http://ip[域名]:port/hsp/index.html 就表示访问D:\album目录下的index.html

ROOT 的工程的访问

  1. 在浏览器地址栏中输入访问地址如下:http://ip[域名]:port,没有Web工程/应用名时,默认访问的是 ROOT 工程
  2. 在浏览器地址栏中输入的访问地址如下: http://ip[域名]:port/工程名/ ,没有资源名,默认访问 index.jsp 页面

浏览器访问 web 服务器文件 UML 时序图!!!

image-20231127100230615

开发 javaweb 工程 & 配置 TomCat &启动项目

  1. 热加载选项说明 老韩解读

    image-20231127100450749

    (1) on update action : 表示当我们更新操作是, Tomcat 会自动更新类和资源(当jsp/html文件修改时,可以生效, 但是如果你修改的 java 文件, 需要 Redepoly 才会生效)

    (2) on frame deactivation : 表示 IDEA 失去焦点(比如最小化), 也会导致jsp/html 发生更新 , 但是 java 修改了,还是需要 redeploy

  2. 端口修改

    这里修改的端口, 只会影响到当前的项目,而不是去修改 server.xml

    image-20231127100511741

  3. out 目录是什么 当 tomcat 启动时,会生成 out 目录,该目录就是 原项目资源的映射,我们浏览器访问的资源是 out 目录

  4. 当我们从外部拷贝资源到项目(图片, 文件, js , css 等), 如果出现404 不能访问错误, 解决方式 rebulid project -> 重启 Tomcat

JavaWeb 工程的目录介绍

image-20231127100642308

image-20231127100701739

动态 WEB 开发核心-Servlet

image-20231127100826668

什么是Servlet

Servlet 在开发动态 WEB 工程中,得到广泛的应用,掌握好Servlet 非常重要了, Servlet(基石)是 SpringMVC 的基础

Servlet(java 服务器小程序),它的特点:

  1. 他是由服务器端调用和执行的(一句话:是Tomcat解析和执行)
  2. 他是用java语言编写的, 本质就是Java类
  3. 他是按照Servlet规范开发的(除了tomcat->Servlet weblogic->Servlet)
  4. 功能强大,可以完成几乎所有的网站功能(在以前,我们老程员,使用Servlet开发网站) 技术栈要求高

Servlet 在 JavaWeb 项目位置

image-20231127101010412

Servlet 基本使用

  1. servlet3.0 前使用 web.xml , servlet3.0 版本以后(包括 3.0)支持注解,同时支持web.xml 配置
  2. 如何查看 servlet 版本[如图]

image-20231127101054041

  1. 讲解 SpringBoot 时,我们用注解方式, 从 ssm , springboot 后面全部使用注解
  2. 这专门讲 servlet, 为让大家更清晰知道 servlet 使用原理, 老师用配置方式(说明,原生的 Servlet 在项目中使用很少)
  3. 不管使用哪种方式,本质都一样

手动开发 Servlet

需求:

1、开发一个 HelloServlet

2、当浏览器 访问 http://localhost:8080/web 应用名/helloServlet 时,后台输出"hi HelloServelt"

步骤:

  1. 创建hspedu_servlet JavaWeb工程,并配置好Tomcat

  2. 添加servlet-api.jar(在tomcat/lib下) 到工程, 因为servlet.jar 不是jdk自带的, 要引入

  3. 在src 下 包 com.hspedu.servlet.HelloServlet.java ,并实现Servlet接口

java
package com.hspedu.servlet;

/**
 * @author 韩顺平
 * @version 1.0
 */

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

/**
 * 老韩解读
 * 1. 开发一个Servlet 需要 实现Servlet接口
 * 2. 实现Servlet接口的方法5个
 */
public class HelloServlet implements Servlet {


    private int count = 0; //属性

    /**
     * 1.初始化 servlet
     * 2.当创建HelloServlet 实例时,会调用init方法
     * 3. 该方法只会被调用一次
     * @param servletConfig
     * @throws ServletException
     */
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("init() 被调用");
    }

    /**
     * 返回ServletConfig 也就是返回Servlet的配置
     * @return
     */
    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    /**
     * 1. service方法处理浏览器的请求(包括get/post)
     * 2. 当浏览器每次请求Servlet时,就会调用一次service
     * 3. 当tomcat调用该方法时,会把http请求的数据封装成实现ServletRequest接口的request对象
     * 4. 通过servletRequest 对象,可以得到用户提交的数据
     * 5. servletResponse 对象可以用于返回数据给tomcat->浏览器
     * @param servletRequest
     * @param servletResponse
     * @throws ServletException
     * @throws IOException
     */
    @Override
    public void service(ServletRequest servletRequest,
                        ServletResponse servletResponse) throws ServletException, IOException {

        count++;
        //如果count的值,在不停的累计,说明HelloServlet是单例的
        System.out.println("hi HelloServlet~ count= " + count);
        //Tomcat每处理一次http请求,就生成一个新的线程
        System.out.println("当前线程id= " + Thread.currentThread().getId());

        //思考->从servletRequest对象来获取请求方式->
        //1. ServletRequest 没有得到提交方式的方法
        //2. ServletRequest 看看ServletRequest子接口有没有相关方法
        //3. 老师小技巧:ctrl+alt+b => 可以看到接口的子接口和实现子类
        //4. 把servletReqeust转成 HttpServletRequest引用
        //5. 仍然是Java基础的OOP
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        String method = httpServletRequest.getMethod();
        if("GET".equals(method)) {
            doGet(); //用doGet() 处理GET请求
        } else if("POST".equals(method)) {
            doPost(); //用doPost() 处理POST请求
        }

    }

    /**
     * 用于响应get请求的
     */
    public void doGet() {
        System.out.println("doGet() 被调用..");
    }

    /**
     * 用于响应post请求的
     */
    public void doPost() {
        System.out.println("doPost() 被调用..");
    }

    /**
     * 返回servlet信息,使用较少
     * @return
     */
    @Override
    public String getServletInfo() {
        return null;
    }

    /**
     * 1. 该方法是在servlet销毁时,被调用
     * 2. 只会调用一次
     */
    @Override
    public void destroy() {
        System.out.println("destroy() 被调用...");
    }
}
  1. 在web.xml配置HelloServlet,即:给HelloServlet 提供对外访问地址

image-20231127101624493

  1. 通过浏览器访问HelloServlet ,看是否正确(记住要redeploy[快] 或者restart[慢])

浏览器调用 Servlet 流程分析

image-20231127101723598

image-20231127101759090

Servlet 生命周期

  1. init()初始化阶段
  2. service()处理浏览器请求阶段
  3. destroy()终止阶段

image-20231127101850288

● 初始化阶段

Servlet 容器(比如: Tomcat)加载 Servlet,加载完成后,Servlet 容器会创建一个Servlet 实例并调用 init()方法,init()方法只会调用一次, Servlet 容器在下面的情况装载Servlet:

  1. Servlet 容器(Tomcat)启动时自动装载某些 servlet,实现这个需要在web.xml 文件中添加<load-on-startup> 1 </load-on-starup> 1 表示装载的顺序
  2. 在 Servlet 容器启动后,浏览器首次向 Servlet 发送请求(这个前面说过)
  3. Servlet 重新装载时(比如 tomcat 进行 redeploy【redeploy 会销毁所有的Servlet 实例】),浏览器再向 Servlet 发送请求的第 1 次

● 处理浏览器请求阶段(service 方法)

  1. 每收到一个 http 请求,服务器就会产生一个新的线程去处理[线程]
  2. 创建一个用于封装 HTTP 请求消息的 ServletRequest 对象和一个代表HTTP 响应消息的ServletResponse 对象
  3. 然后调用 Servlet 的 service()方法并将请求和响应对象作为参数传递进去

● 终止阶段 destory 方法(体现 Servlet 完整的生命周期)

当web 应用被终止,或者Servlet 容器终止运行,或者Servlet 类重新装载时,会调用destroy()方法 , 比如重启 tomcat ,或者 redeploy web 应用

GET 和 POST 请求的分发处理

java
/**
* 1. service 方法处理浏览器的请求(包括 get/post)
* 2. 当浏览器每次请求 Servlet 时,就会调用一次 service
* 3. 当 tomcat 调用该方法时,会把 http 请求的数据封装成实现ServletRequest 接口的 request 对象
* 4. 通过 servletRequest 对象,可以得到用户提交的数据* 5. servletResponse 对象可以用于返回数据给 tomcat->浏览器* @param servletRequest
* @param servletResponse
* @throws ServletException
* @throws IOException
*/
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException,
IOException {
  count++;
  //如果 count 的值,在不停的累计,说明 HelloServlet 是单例的System.out.println("hi HelloServlet~ count= " + count);
  //Tomcat 每处理一次 http 请求,就生成一个新的线程
  System.out.println("当前线程 id= " + Thread.currentThread().getId());
  //思考->从 servletRequest 对象来获取请求方式->
  //1. ServletRequest 没有得到提交方式的方法
  //2. ServletRequest 看看 ServletRequest 子接口有没有相关方法
  //3. 老师小技巧:ctrl+alt+b => 可以看到接口的子接口和实现子类
  //4. 把 servletReqeust 转成 HttpServletRequest 引用
  //5. 仍然是 Java 基础的 OOP
  HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
  String method = httpServletRequest.getMethod();
  if("GET".equals(method)) {
  	doGet(); //用 doGet() 处理 GET 请求
  } else if("POST".equals(method)) {
  	doPost(); //用 doPost() 处理 POST 请求
  }
}
/**
* 用于响应 get 请求的
*/
public void doGet() {
	System.out.println("doGet() 被调用..");
}
/**
* 用于响应 post 请求的
*/
public void doPost() {
	System.out.println("doPost() 被调用..");
}

通过继承 HttpServlet 开发 Servlet

在实际项目中,都是使用继承 HttpServlet 类开发 Servlet 程序,更加方便

image-20231127103356517

java
package com.hspedu.servlet;

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

/**
 * @author 韩顺平
 * @version 1.0
 */
public class HiServlet extends HttpServlet {

    //重写HttpServlet的doGet 和 doPost
    //alt +insert

    /**
     * 处理doGet请求
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("HiServlet doGet()...");
    }

    /**
     * 处理doPost
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("HiServlet doPost()...");
    }
}
xml
<!-- web.xml -->
<!-- 配置 HiServlet -->
<servlet>
  <servlet-name>HiServlet</servlet-name>
  <servlet-class>com.hspedu.servlet.HiServlet</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>HiServlet</servlet-name>
  <url-pattern>/hiServlet</url-pattern>
</servlet-mapping>

Servlet 注意事项和细节

  1. Servlet 是一个供其他 Java 程序(Servlet 引擎)调用的 Java 类,不能独立运行
  2. 针对浏览器的多次 Servlet 请求,通常情况下,服务器只会创建一个Servlet 实例对象,也就是说 Servlet 实例对象一旦创建,它就会驻留在内存中,为后续的其它请求服务,直至web 容器退出/或者 redeploy 该 web 应用,servlet 实例对象才会销毁【示意图】

image-20231127103639252

  1. 在 Servlet 的整个生命周期内,init 方法只被调用一次。而对每次请求都导致Servlet 引擎调用一次 servlet 的 service 方法。
  2. 对于每次访问请求,Servlet 引擎都会创建一个新的 HttpServletRequest 请求对象和一个新的 HttpServletResponse 响应对象,然后将这两个对象作为参数传递给它调用的Servlet的 service()方法,service 方法再根据请求方式分别调用 doXXX 方法
  3. 如果在元素中配置了一个元素,那么WEB 应用程序在启动时,就会装载并创建 Servlet 的实例对象、以及调用 Servlet 实例对象的init()方法, 老师聊聊(定时发送邮件的服务/自动启动->完成任务)

Servlet - 注解方式

java
@WebServlet(urlPatterns = {"/ok1", "/ok2"})
public class OkServlet extends HttpServlet {
  protected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
  	System.out.println("okServlet doPost");
  }
  protected void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
  	System.out.println("okServlet doGet");
  }
}

Servlet urlPattern 配置

  • 精确匹配

    配置路径 : @WebServlet("/ok/zs") 访问 servlet: localhost:8080/servlet/ok/zs

  • 目录匹配

    配置路径 : @WebServlet("/ok/*") 访问文件: localhost:8080/servlet/ok/aaa localhost:8080/servlet/ok/bbb

  • 扩展名匹配

    配置路径 : @WebServlet("*.action") 访问文件: localhost:8080/hsp/zs.action localhost:8080/hsp/ls.action

  • 任意匹配

    配置路径 : @WebServlet("/") @WebServlet("/*") 访问文件: localhost:8080/hsp/aaa localhost:8080/hsp/bbb localhost:8080/hsp/ccc

    / 和 /*的配置,会匹配所有的请求,这个比较麻烦,要避免

注意事项和使用细节

1、当 Servlet 配置了 "/", 会覆盖 tomcat 的 DefaultServlet, 当其他的utl-pattern都匹配不 上 时 , 都 会 走 这 个 Servlet, 这 样 可 以 拦 截 到其它静态资源, 比如D:\hspedu_javaweb_temp\hspedu_servlet\web\hi.html [举例] 查看:tomcat/conf/web.xml , 配置的 DefaultServlet The default servlet for all web applications, that serves static resources. 这个默认的 servlet 是处理静态资源的,一旦拦截,静态资源不能处理

xml
<servlet>
  <servlet-name>default</servlet-name>
  <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
  <init-param>
    <param-name>debug</param-name>
    <param-value>0</param-value>
   </init-param>
  <init-param>
    <param-name>listings</param-name>
    <param-value>false</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>
<!-- The mapping for the default servlet -->
<servlet-mapping>
  <servlet-name>default</servlet-name>
  <url-pattern>/</url-pattern>
</servlet-mapping>

2、当 Servelt 配置了 "/*", 表示可以匹配任意访问路径

3、提示: 建议不要使用 / 和 /* , 建议尽量使用精确匹配

4、优先级遵守: 精确路径 > 目录路径 > 扩展名路径 > /* > /

ServletConfig

  1. ServletConfig 类是为 Servlet 程序的配置信息的类
  2. Servlet 程序和 ServletConfig 对象都是由 Tomcat 负责创建
  3. Servlet 程序默认是第 1 次访问的时候创建,ServletConfig 在Servlet 程序创建时,就创建一个对应的 ServletConfig 对 象

ServletConfig 类能干什么

  1. 获取 Servlet 程序的 servlet-name 的值
  2. 获取初始化参数 init-param
  3. 获取 ServletContext 对象

ServletConfig 应用实例

  1. 在 web.xml 配置连接 mysql 的用户名和密码

  2. 在 DBServlet 执行 doGet()/doPost() 时,可以获取到 web.xml 配置的用户名和密码

    image-20231127105703547

    java
    package com.hspedu.servlet;
    
    import javax.servlet.ServletConfig;
    import javax.servlet.ServletContext;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.Serializable;
    import java.util.concurrent.ConcurrentHashMap;
    
    /**
     * @author 韩顺平
     * @version 1.0
     */
    public class DBServlet extends HttpServlet {
    
        /**
         * 老师梳理ServletConfig config 使用流程
         * 1. 当DBServlet对象初始化时, tomcat会同时创建一个 ServletConfig对象
         * 2. 这时如果DBServlet init() 方法中你调用 super.init(config);
         * 3. 调用 父类 GenericServlet
         * public void init(ServletConfig config) throws ServletException {
         * this.config = config;
         * this.init();
         * }
         * 这时就会把 Tomcat创建的 ServletConfig对象赋给 GenericServlet的属性 config
         * 4. 因此如果你重写init()方法,记住如果你想在其它方法通过 getServletConfig() 方法获取ServletConfig
         * , 则一定要记住 调用  super.init(config);
         * @param config
         * @throws ServletException
         */
    
        @Override
        public void init(ServletConfig config) throws ServletException {
            //ConcurrentHashMap, 是一个线程安全的容器.
            System.out.println("init" + config);
            super.init(config);
        }
    
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
            //在DBServlet 执行 doGet()/doPost() 时,可以获取到web.xml配置的用户名和密码
            //你是一个OOP程序员->现有的方法或对象来搞定
            //DBServlet的父类GenericServlet有getServletConfig()
            /**
             * 老韩解读
             * 1. getServletConfig() 方法是 GenericServlet
             * 2. 返回的 servletConfig对象是 GenericServlet private transient ServletConfig config;
             * 3. 当一个属性被 transient 修饰,表示该属性不会被串行化(有些重要信息,不希望保存到文件)
             */
            ServletConfig servletConfig = getServletConfig();
            System.out.println("doPost=" + servletConfig);
            String username = servletConfig.getInitParameter("username");
            String pwd = servletConfig.getInitParameter("pwd");
            System.out.println("初始化参数username= " + username);
            System.out.println("初始化参数pwd= " + pwd);
    
        }
    
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            doPost(request, response);
        }
    }

ServletContext

为什么需要 ServletContext ?

先看一个需求: 如果我们希望统计某个 web 应用的所有Servlet 被访问的次数,怎么办?

方案 1-DB

image-20231127105936178

方案 2-ServletContext

image-20231127110012954

ServletContext 基本介绍

  1. ServletContext 是一个接口,它表示 Servlet 上下文对象
  2. 一个 web 工程,只有一个 ServletContext 对象实例
  3. ServletContext 对象 是在 web 工程启动的时候创建,在 web 工程停止的时销毁
  4. ServletContext 对象可以通过 ServletConfig.getServletContext 方法获得对ServletContext对象的引用,也可以通过 this.getServletContext()来获得其对象的引用。
  5. 由于一个 WEB 应用中的所有 Servlet 共享同一个 ServletContext 对象,因此Servlet 对象之间可以通过 ServletContext 对象来实现多个 Servlet 间通讯。ServletContext 对象通常也被称之为域对象。【示意图】

image-20231127110125843

ServletContext 可以做什么

  1. 获取 web.xml 中配置的上下文参数 context-param [信息和整个web 应用相关,而不是属于某个 Servlet]
  2. 获取当前的工程路径,格式: /工程路径 =》 比如 /servlet
  3. 获 取 工 程 部 署 后 在 服 务 器 硬 盘 上 的绝对路径( 比如: D:\hspedu_javaweb\servlet\out\artifacts\servlet_war_exploded)
  4. 像 Map 一样存取数据, 多个 Servlet 共享数据

image-20231127110125843

应用实例 1-获取工程相关信息

● 需求如下:

  1. 获取 web.xml 中配置的上下文参数 context-param
  2. 获取当前的工程路径,格式: /工程路径
  3. 获取工程部署后在服务器硬盘上的绝对路径
java
package com.hspedu.servlet.servletcontext;

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

/**
 * @author 韩顺平
 * @version 1.0
 */
public class ServletContext_ extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取web.xml的context-parameter

        //1.获取到ServletContext对象
        ServletContext servletContext = getServletContext();
        //2. 获取website
        String website = servletContext.getInitParameter("website");
        String company = servletContext.getInitParameter("company");
        //3. 获取项目的工程路径
        String contextPath = servletContext.getContextPath();
        //4. 获取项目发布会,正在的工作路径
        //   /表示我们的项目(发布后)的 根路径 D:\hspedu_javaweb\servlet\out\artifacts\servlet_war_exploded
        String realPath = servletContext.getRealPath("/");
        System.out.println("项目路径= " + contextPath);// /servlet
        System.out.println("website= " + website);
        System.out.println("company= " + company);
        System.out.println("项目发布后的绝对路径= " + realPath);

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}

应用实例 2-简单的网站访问次数计数器

  1. 完成一个简单的网站访问次数计数器

  2. 使用 Chrome 访问 PayServlet, 每访问一次,就增加 1 访问次数,在后台输出,并将结果返回给浏览器显示

  3. 使用火狐访问 OrderServlet,每访问一次,就增加 1 访问次数,在后台输出,并将结果返回给浏览器显示

java
package com.hspedu.servlet.servletcontext;

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

/**
 * @author 韩顺平
 * @version 1.0
 */
public class PayServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        //获取到ServletContext对象
        ServletContext servletContext = getServletContext();
        // System.out.println("PayServlet servletContext= " +
        //         servletContext + " 运行类型=" + servletContext.getClass());
        //
        // //从servletContext获取 visit_count 属性 k-v
        // Object visit_count = servletContext.getAttribute("visit_count");
        // //判断visit_count是否为null
        // if (visit_count == null) {//说明是第1次访问网站
        //     servletContext.setAttribute("visit_count", 1);
        //     visit_count = 1;
        // } else { //是第二次或以后
        //     //取出visit_count属性的值+1
        //     visit_count = Integer.parseInt(visit_count + "") + 1;
        //     //放回到servletContext
        //     servletContext.setAttribute("visit_count", visit_count);
        // }
        Integer visit_count = WebUtils.visitCount(servletContext);
        //输出显示
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.print("<h1>新网站被访问的次数是" + visit_count + "</h1>");
        writer.flush();
        writer.close();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}
java
package com.hspedu.servlet.servletcontext;

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

/**
 * @author 韩顺平
 * @version 1.0
 */
public class OrderServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        //获取到ServletContext对象
        ServletContext servletContext = getServletContext();
        // System.out.println("OrderServlet servletContext= " +
        //         servletContext + " 运行类型=" + servletContext.getClass());

        // //从servletContext获取 visit_count 属性 k-v
        // Object visit_count = servletContext.getAttribute("visit_count");
        // //判断visit_count是否为null
        // if (visit_count == null) {//说明是第1次访问网站
        //     servletContext.setAttribute("visit_count", 1);
        //     visit_count = 1;
        // } else { //是第二次或以后
        //     //取出visit_count属性的值+1
        //     visit_count = Integer.parseInt(visit_count + "") + 1;
        //     //放回到servletContext
        //     servletContext.setAttribute("visit_count", visit_count);
        // }

        Integer visit_count = WebUtils.visitCount(servletContext);

        //输出显示
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.print("<h1>新网站被访问的次数是" + visit_count + "</h1>");
        writer.flush();
        writer.close();


    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}
xml
<servlet>
  <servlet-name>OrderServlet</servlet-name>
  <servlet-class>com.hspedu.servlet.servletcontext.OrderServlet</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>OrderServlet</servlet-name>
  <url-pattern>/orderServlet</url-pattern>
</servlet-mapping>
<servlet>
  <servlet-name>PayServlet</servlet-name>
  <servlet-class>com.hspedu.servlet.servletcontext.PayServlet</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>PayServlet</servlet-name>
  <url-pattern>/payServlet</url-pattern>
</servlet-mapping>
java
package com.hspedu.servlet.servletcontext;

import javax.servlet.ServletContext;

/**
 * @author 韩顺平
 * @version 1.0
 */
public class WebUtils {

    //这个方法就是对访问的次数累积,同时返回次数
    public static Integer visitCount(ServletContext servletContext) {
        //从servletContext获取 visit_count 属性 k-v
        Object visit_count = servletContext.getAttribute("visit_count");
        //判断visit_count是否为null
        if (visit_count == null) {//说明是第1次访问网站
            servletContext.setAttribute("visit_count", 1);
            visit_count = 1;
        } else { //是第二次或以后
            //取出visit_count属性的值+1
            visit_count = Integer.parseInt(visit_count + "") + 1;
            //放回到servletContext
            servletContext.setAttribute("visit_count", visit_count);
        }

        return Integer.parseInt(visit_count + "");
    }
}

HttpServletRequest

  1. HttpServletRequest 对象代表客户端的请求
  2. 当客户端/浏览器通过 HTTP 协议访问服务器时,HTTP 请求头中的所有信息都封装在这个对象中
  3. 通过这个对象的方法,可以获得客户端这些信息

image-20231127111039979

image-20231127111100518

HttpServletRequest 常用方法

  1. getRequestURI() 获取请求的资源路径 http://localhost:8080/servlet/loginServlet
  2. getRequestURL() 获 取 请 求 的 统 一 资 源 定位符(绝对路径)http://localhost:8080/servlet/loginServlet
  3. getRemoteHost() 获取客户端的 主机, getRemoteAddr()
  4. getHeader() 获取请求头
  5. getParameter() 获取请求的参数
  6. getParameterValues() 获取请求的参数(多个值的时候使用), 比如checkbox, 返回的数组
  7. getMethod() 获取请求的方式 GET 或 POST
  8. setAttribute(key, value); 设置域数据
  9. getAttribute(key); 获取域数据
  10. getRequestDispatcher() 获取请求转发对象, 请求转发的核心对象

HttpServletRequest 应用实例

需求: 说明: 在一个表单提交数据给 Servlet , 然后在 Servlet 通过HttpServletRequest对象获取相关数据

java
package com.hspedu.servlet.request;

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

/**
 * @author 韩顺平
 * @version 1.0
 */
public class HttpServletRequestMethods extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //这里我们使用request对象,获取表单提交的各种数据
        System.out.println("HttpServletRequestMethods doPost() 被调用..");

        /***********************************
         *  获取和http请求头相关信息
         ***********************************/

        System.out.println("请求的资源路径URI= " + request.getRequestURI());
        //http://主机/uri
        System.out.println("请求的统一资源定位符(绝对路径)URL= " + request.getRequestURL());
        System.out.println("请求的客户端ip 地址= " + request.getRemoteAddr());//本地就是127.0.01
        //思考题:如发现某个ip 在10s中,访问的次数超过 100次,就封ip
        //实现思路: 1用一个集合concurrentHashmap[ip:访问次数] 2[线程/定时扫描] 3 做成处理
        // 获取http请求头的信息,可以指定其他,比如 User-Agent , Host等待 老师就举一个例子
        System.out.println("http请求头HOST= " + request.getHeader("Host"));
        // 说明,如果我们希望得到请求的头的相关信息,可以使用request.getHeader("请求头字段")
        System.out.println("该请求的发起地址是= " + request.getHeader("Referer"));
        // 请获取访问网站的浏览器是什么?
        String userAgent = request.getHeader("User-Agent");
        System.out.println("User-Agent= " + userAgent);
        // 取出FireFox, 取出最后
        String[] s = userAgent.split(" ");
        System.out.println("浏览器=" + s[s.length - 1].split("\\/")[0]);
        //获取 Cookie
        // 	JSESSIONID=8CBBD23BDE01BAE6705E03C5C8916BD1

        String cookie = request.getHeader("Cookie");
        String JSESSIONID = cookie.split("=")[1];
        System.out.println("取出JSESSIONID= " + JSESSIONID);

        //课堂练习: 要求同学们取出  Windows NT 10.0  和 Win64
        // 主要是Get / Post
        System.out.println("http请求方式~= " + request.getMethod());
        /***********************************
         *  获取和请求参数相关信息, 注意要求在返回数据前,获取参数
         ***********************************/

        //解决接收参数的中文乱码问题, 老师提示,写在 getParameter前.
        request.setCharacterEncoding("utf-8");
        //1. 获取表单的数据[单个数据]
        //username=tom&pwd=&hobby=hsp&hobby=spls
        String username = request.getParameter("username");
        String pwd = request.getParameter("pwd");


        //2. 获取表单一组数据
        String[] hobbies = request.getParameterValues("hobby");
        System.out.println("username= " + username);
        System.out.println("pwd= " + pwd);
        //增强for循环的快捷键 iter->回车即可 , 能使用快捷键,就使用快捷键
        for (String hobby : hobbies) {
            System.out.println("hobby=" + hobby);
        }

        //推而广之, 如果是 单选 , 下拉框 等等. => 作业布置

        //返回接收到的信息, 给浏览器回显
        //本质就是在http响应头,加上 Content-Type: text/html;charset=utf-8
        //说 text/html 表示返回的数据类型,浏览器会根据这个类型来解析数据
        // text/plain 表示返回的数据,请浏览器使用文本方式解析
        // application/x-tar 表示返回的是文件,浏览器就会以下载文件的方式处理
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.print("<h1>提交的用户名= " + username + "</h1>");
        writer.flush();
        writer.close();

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}
xml
<servlet>
  <servlet-name>HttpServletRequestMethods</servlet-name>
  <servlet-class>com.hspedu.httpservletrequest.HttpServletRequestMethods</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>HttpServletRequestMethods</servlet-name>
  <url-pattern>/httpServletRequestMethods</url-pattern>
</servlet-mapping>

HttpServletRequest 注意事项和细节

  1. 获 取 doPost 参 数 中 文 乱 码 解 决 方 案 , 注 意 setCharacterEncoding("utf-8") 要写在request.getParameter()前。

    image-20231127111543426

  2. 注意:如果通过 PrintWriter writer, 有返回数据给浏览器,建议将获取参数代码写在writer.print() 之前,否则可能获取不到参数值(doPost)

  3. 处理 http 响应数据中文乱码问题

    image-20231127111635053

  4. 再次理解 Http 协议响应 Content-Type 的含义, 比如 text/plain application/x-tar

请求转发

  1. 实现请求转发:请求转发指一个 web 资源收到客户端请求后,通知服务器去调用另外一个 web 资源进行处理
  2. HttpServletRequest 对象(也叫 Request 对象)提供了一个 getRequestDispatcher 方法,该方法返回一个 RequestDispatcher 对象,调用这个对象的 forward 方法可以实现请求转发
  3. request 对象同时也是一个域对象,开发人员通过 request 对象在实现转发时,把数据通过 request 对象带给其它 web 资源处理 
    • setAttribute方法
    • getAttribute方法
    • removeAttribute方法
    • getAttributeNames方法

请求转发原理

image-20231127111835281

应用实例

java
package com.hspedu.servlet.request;

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

/**
 * @author 韩顺平
 * @version 1.0
 */
public class CheckServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("CheckServlet 被调用..");

        //根据用户名来确定该用户是什么身份
        String username = request.getParameter("username");
        //注意:如果是同一个request对象(请求转发),那么可以在不同的servlet中,是getParameter
        if ("tom".equals(username)) {
            //分配
            request.setAttribute("role", "管理员");
        } else {
            request.setAttribute("role", "普通用户");
        }

        //获取分发器
        //老韩解读  1. /manageServlet写的是 要转发的servlet的url
        //        2. / 会被解析成 /servlet
        //        3. forward(request, response) 表示把当前servlet的request对象和response对象,传递给下一个servlet使用

        RequestDispatcher requestDispatcher =
                request.getRequestDispatcher("/manageServlet");

        requestDispatcher.forward(request, response);


    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}
java
package com.hspedu.servlet.request;

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

/**
 * @author 韩顺平
 * @version 1.0
 */
public class ManageServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("ManageServlet 被调用..");

        String username = request.getParameter("username");
        String role = (String) request.getAttribute("role");

        //输出信息
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.print("用户名: " + username + "<br/>");
        writer.print("角色 : " + role);
        writer.flush();
        writer.close();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}

请求转发注意事项和细节

  1. 浏览器地址不会变化(地址会保留在第 1 个 servlet 的 url)

  2. 在同一次 HTTP 请求中,进行多次转发,仍然是一次 HTTP 请求

  3. 在同一次 HTTP 请求中,进行多次转发,多个 Servlet 可以共享request 域/对象的数据(因为始终是同一个 request 对象)

  4. 可以转发到 WEB-INF 目录下(后面做项目使用)

  5. 不能访问当前 WEB 工程外的资源

    image-20231127112053439

  6. 因为浏览器地址栏会停止在第一个 servlet ,如果你刷新页面,会再次发出请求(并且会带数据), 所以在支付页面情况下,不要使用请求转发,否则会造成重复支付[演示]

HttpServletResponse

  1. 每次 HTTP 请求,Tomcat 会创建一个 HttpServletResponse 对象传递给Servlet 程序去使用
  2. HttpServletRequest 表示请求过来的信息,HttpServletResponse 表示所有响应的信息,如果需要设置返回给客户端的信息,通过 HttpServletResponse 对象来进行设置即可

image-20231127112247029

向客户端返回数据的方法

image-20231127112319437

  1. 字节流 getOutputStream(); 常用于下载(处理二进制数据)
  2. 字符流 getWriter(); 常用于回传字符串
  3. (细节:)两个流同时只能使用一个。 使用了字节流,就不能再使用字符流,反之亦然,否则就会报错
java
public class ResponseServlet_ extends HttpServlet {
  protected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
    
  }
  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
  IOException {
    /* 老韩解读:
    1. setContentType 会设置服务器和客户端都用 utf-8 字符集,还设置了响应头2. setContentType 要在获取流对象(getWriter)之前调用才有效*/
    response.setContentType("text/html;charset=utf-8");
    PrintWriter writer = response.getWriter();
    writer.println("<h1>hello, world~</h1>");
    writer.println("<h1>hi, 韩顺平教育</h1>");
    writer.flush();
    writer.close();
  }
}

向客户端返回数据注意事项和细节

  1. 处理中文乱码问题-方案 1

    image-20231127112558197

  2. 处理中文乱码问题-方案 2

    image-20231127112629157

请求重定向

  1. 请求重定向指:一个 web 资源收到客户端请求后,通知客户端去访问另外一个web资源,这称之为请求重定向
  2. 请求重定向原理示意图

image-20231127112720952

请求重定向应用实例

需 求 : 演 示 请 求 重 定 向 的 使 用 当 访 问 DownServlet 下载文件,重定向到DownServletNew 下载文件

java
package com.hspedu.servlet.response;

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

/**
 * @author 韩顺平
 * @version 1.0
 */
public class DownServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        // System.out.println("DownServlet 被调用");
        // response.setContentType("application/x-tar;charset=utf-8");
        // PrintWriter writer = response.getWriter();
        // writer.print("hi");
        // writer.flush();
        // writer.close();

        //完成了自己业务
        //发出请求重定向-> DownServletNew
        //听老师解读
        //1. sendRedirect 本质就会 返回 302 状态码 Location: /servlet/downServletNew
        //2. 因此 302和 /servlet/downServletNew 是浏览器解析,而不是服务器
        //3. 浏览器在解析 /servlet/downServletNew => http://localhost:8080/servlet/downServletNew
        //4. 动态获取到application context
        String contextPath = getServletContext().getContextPath();
        System.out.println("contextPath= " + contextPath);
        //response.sendRedirect("/servlet/downServletNew");
        response.sendRedirect(contextPath + "/downServletNew");
        //response.sendRedirect("http://www.baidu.com");

        //第二种重定向的写法
        // System.out.println("第二种方式重定向...");
        // response.setStatus(302); //设置http响应的状态码
        // //设置http响应的 Location: /servlet/downServletNew
        // response.setHeader("Location", "/servlet/downServletNew");


    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request,response);
    }
}
java
package com.hspedu.servlet.response;

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

/**
 * @author 韩顺平
 * @version 1.0
 */
public class DownServletNew extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        System.out.println("DownServletNew 被调用");
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.print("ok");
        writer.flush();
        writer.close();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request,response);
    }
}

请求重定向注意事项和细节

  1. 最佳应用场景:网站迁移,比如原域名是 www.hsp.com 迁移到www.hsp.cn,但是百度抓取的还是原来网址.

  2. 浏览器地址会发生变化,本质是两次 http 请求.

  3. 不能共享 Request 域中的数据,本质是两次 http 请求,会生成两个HttpServletRequest对象

  4. 不能重定向到 /WEB-INF 下的资源

  5. 可以重定向到 Web 工程以外的资源, 比如 到 www.baidu.com【在前面的案例演示】

  6. 重定向有两种方式, 推荐使用第 1 种.

    java
    // 方式一
    response.sendRedirect("/servlet/servlet05")
      
    // 方式二
    response.setStatus(302)
    response.setHeader("Location","/servlet/servlet05")
  7. 动态获取到 application context

    java
    String contextPath = getServletContext().getContextPath();
    System.out.println("contextPath= " + contextPath);
    response.sendRedirect(contextPath + "/downServletNew");

手动实现 Tomcat 底层机制+ 自己设计Servlet

maven

image-20231127113524136

pom.xml

  1. dependency 表示依赖, 也就是我们这个项目需要依赖的jar 包
  2. groupId 和 artifactId 被统称为坐标, 是为了去定位这个项目/jar
  3. groupId: 一般是公司 比如 com.baidu , 这里是 avax.servlet
  4. artifactId 一般是项目名, 这里是 javax.servlet-api
  5. 这样的化就可以定位一个 jar 包
  6. version 表示你引入到我们项目的 jar 包的版本是 3.1.0
  7. scope: 表示作用域,也就是你引入的 jar 包的作用范围
  8. provided 表示在 tomcat 本身是有这个 jar 的,因此在编译,测试使用,但是在打包发布就不用要带上
  9. 在默认情况下, 引入的 jar 会到 中央仓库去下载 https://mvnrepository.com/
  10. 会下载到哪里到你指定的目录 C:\Users\Administrator.m2\repository
  11. 有时为了下载更快, 往往配置镜像,
  12. 在 默 认 的 路 径 下 拷 贝 一份setting.xml 到C:\Users\Administrator.m2\settings.xml
  13. 指定默认的阿里云镜像 [查看文章:maven配置镜像]
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>
  <dependencies>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
  </dependency>
</dependencies>

Tomcat整体结构分析

image-20231127114016422

基于 socket 开发服务端-流程

image-20231127114118726

阶段 1 - 编写自己 Tomcat, 能给浏览器返回 Hi, Hspedu

java
package com.hspedu.tomcat;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author 韩顺平
 * @version 1.0
 * 这是第一个版本的tomcat ,可以完成,接收浏览器的请求,并返回信息
 */
public class HspTomcatV1 {
    public static void main(String[] args) throws IOException {

        //1. 创建ServerSocket, 在 8080端口监听
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("=======mytomcat在8080端口监听======");
        while (!serverSocket.isClosed()) {

            //等待浏览器/客户端的连接
            //如果有连接来,就创建一个socket
            //这个socket就是服务端和浏览器端的连接/通道
            Socket socket = serverSocket.accept();

            //先接收浏览器发送的数据
            //inputStream 是字节流=> BufferedReader(字符流)
            //java基础 IO , 第19章
            InputStream inputStream = socket.getInputStream();
            BufferedReader bufferedReader =
                    new BufferedReader(new InputStreamReader(inputStream, "utf-8"));

            String mes = null;
            System.out.println("=======接收到浏览器发送的数据=======");
            //循环的读取
            while ((mes = bufferedReader.readLine()) != null) {
                //判断mes的长度是否为0
                if (mes.length() == 0) {
                    break;//退出while
                }
                System.out.println(mes);
            }

            //我们的tomcat会送-http响应方式
            OutputStream outputStream = socket.getOutputStream();
            //构建一个http响应的头
            //\r\n 表示换行
            //http响应体,需要前面有两个换行 \r\n\r\n
            String respHeader = "HTTP/1.1 200 OK\r\n" +
                    "Content-Type: text/html;charset=utf-8\r\n\r\n";
            String resp = respHeader + "hi, hspedu 韩顺平教育";

            System.out.println("========我们的tomcat 给浏览器会送的数据======");
            System.out.println(resp);

            outputStream.write(resp.getBytes());//将resp字符串以byte[] 方式返回

            outputStream.flush();
            outputStream.close();
            inputStream.close();
            socket.close();
            // //等会
            // inputStream.close();
            // socket.close();
        }
    }
}

阶段 2- 使用 BIO 线程模型,支持多线程

BIO 线程模型介绍

image-20231127114542364

分析示意图

image-20231127114616198

java
package com.hspedu.tomcat.handler;

/**
 * @author 韩顺平
 * @version 1.0
 */


import com.hspedu.tomcat.HspTomcatV3;
import com.hspedu.tomcat.http.HspRequest;
import com.hspedu.tomcat.http.HspResponse;

import com.hspedu.tomcat.servlet.HspHttpServlet;
import com.hspedu.tomcat.utils.WebUtils;


import java.io.*;
import java.net.Socket;

/**
 * 老师解读
 * 1. HspRequestHandler 对象是一个线程对象
 * 2. 处理一个http请求的
 */
public class HspRequestHandler implements Runnable {

    //定义Socket
    private Socket socket = null;

    public HspRequestHandler(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {

        //这里我们可以对客户端/浏览器进行IO编程/交互
        try {
            //InputStream inputStream = socket.getInputStream();

            // //把inputStream -> BufferedReader -> 方便进行按行读取
            // BufferedReader bufferedReader =
            //         new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
            //
            // //不同的线程在和浏览器和客户端交互
            // System.out.println("当前线程= " + Thread.currentThread().getName());
            //
            // System.out.println("=========hsptomcatv2 接收到的数据如下=========");
            // String mes = null;
            // //老韩解读: io - 网络 - 线程 - 反射 - 注解 - OOP [都会学会,也会学好]
            //
            // while ((mes = bufferedReader.readLine()) != null) {
            //     //如果长度为0 ""
            //     if (mes.length() == 0) {
            //         break; //退出
            //     }
            //     System.out.println(mes);
            // }

            //这里我们先死后活
            HspRequest hspRequest = new HspRequest(socket.getInputStream());
            // String num1 = hspRequest.getParameter("num1");
            // String num2 = hspRequest.getParameter("num2");
            // System.out.println("请求的参数num1= " + num1);
            // System.out.println("请求的参数num2= " + num2);
            // System.out.println("hspRequest= " + hspRequest);

            //这里我们可以同HspResponse对象,返回数据给浏览器/客户端
            HspResponse hspResponse = new HspResponse(socket.getOutputStream());

            //创建HspCalServlet对象-> 一会我们再用反射来构建对象
            // HspCalServlet hspCalServlet = new HspCalServlet();
            // hspCalServlet.doGet(hspRequest, hspResponse);

            //先说明一把实现思路->【停一下】 -> 如果你自己完成?10min
            //1. 得到 uri => 就是 servletUrlMapping 的 url-pattern
            String uri = hspRequest.getUri();



            //=====================新增业务逻辑==========
            //(1) 判断uri是什么资源 => 工具方法
            //(2) 如果是静态资源,就读取该资源,并返回给浏览器 content-type text/html
            //(3) 因为目前老师并没有起到tomcat, 不是一个标准的web项目
            //(4) 把读取的静态资源放到 target/classes/cal.html
            //过滤,拦截 , 权限等待 => Handler.... => 分发
            if(WebUtils.isHtml(uri)) {//就是静态页面
                String content = WebUtils.readHtml(uri.substring(1));
                content = HspResponse.respHeader + content;
                //得到outputstream , 返回信息(静态页面)给浏览器
                OutputStream outputStream = hspResponse.getOutputStream();
                outputStream.write(content.getBytes());
                outputStream.flush();
                outputStream.close();
                socket.close();
                return;
            }


            //有了filter机制,可以理解再调用servlet之前,先匹配filter
            //1. 根据request对象封装的uri
            //2. 到 filterUrlMapping 去匹配
            //3. 如果匹配上就调用 filterMapping 对应的filer对象doFilter()
            //4. 如果没有匹配上,就直接走我们后的servlet/jsp/html.

            String servletName = HspTomcatV3.servletUrlMapping.get(uri);
            if (servletName == null) {
                servletName = "";
            }
            //2. 通过uri->servletName->servlet的实例 , 真正的运行类型是其子类 HspCalServlet
            HspHttpServlet hspHttpServlet =
                    HspTomcatV3.servletMapping.get(servletName);
            //3. 调用service , 通过OOP的动态绑定机制,调用运行类型的 doGet/doPost
            if (hspHttpServlet != null) {//得到
                hspHttpServlet.service(hspRequest, hspResponse);
            } else {
                //没有这个servlet , 返回404的提示信息
                String resp = HspResponse.respHeader + "<h1>404 Not Found</h1>";
                OutputStream outputStream = hspResponse.getOutputStream();
                outputStream.write(resp.getBytes());
                outputStream.flush();
                outputStream.close();
            }


            // String resp = HspResponse.respHeader + "<h1>hspResponse 返回的信息 hi 你好<h1>";
            //
            // OutputStream outputStream = hspResponse.getOutputStream();
            // outputStream.write(resp.getBytes());
            // outputStream.flush();
            // outputStream.close();

            // //构建一下http响应头
            // //返回的http的响应体和响应头之间有两个换行 \r\n\r\n
            // String respHeader = "HTTP/1.1 200 OK\r\n" +
            //         "Content-Type: text/html;charset=utf-8\r\n\r\n";
            // String resp = respHeader + "<h1>hi hspedu 韩顺平教育</h1>";
            // System.out.println("========hsptomcatv2返回的数据是=========");
            // System.out.println(resp);
            // //返回数据给我们的浏览器/客户端-> 封装成http响应
            // OutputStream outputStream = socket.getOutputStream();
            //resp.getBytes() 是把字符串转成字节数组
            // outputStream.write(resp.getBytes());
            // outputStream.flush();
            // outputStream.close();
            // inputStream.close();
            socket.close();


        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //最后一定确保socket要关闭
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}
java
package com.hspedu.tomcat;

import com.hspedu.tomcat.handler.HspRequestHandler;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author 韩顺平
 * @version 1.0
 */
public class HspTomcatV2 {
    public static void main(String[] args) throws IOException {
        //在8080端口监听
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("=======hsptomcatV2 在8080监听=======");
        //只要 serverSocket没有关闭,就一直等待浏览器/客户端的连接
        while (!serverSocket.isClosed()) {
            //1. 接收到浏览器的连接后,如果成功,就会得到socket
            //2. 这个socket 就是 服务器和 浏览器的数据通道
            Socket socket = serverSocket.accept();
            //3. 创建一个线程对象,并且把socket给该线程
            //  这个是java线程基础
            HspRequestHandler hspRequestHandler =
                    new HspRequestHandler(socket);
            new Thread(hspRequestHandler).start();

        }
    }
}

阶段 3 - 处理servlet

servlet生命周期

image-20231127115154429

分析示意图

image-20231127115229639

代码实现
java
package com.hspedu.tomcat;

import com.hspedu.tomcat.handler.HspRequestHandler;
import com.hspedu.tomcat.servlet.HspHttpServlet;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import javax.servlet.Filter;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author 韩顺平
 * @version 1.0
 * 第3版的Tomcat, 实现通过xml+反射来初始化容器
 */
public class HspTomcatV3 {

    //1. 存放容器 servletMapping
    // -ConcurrentHashMap
    // -HashMap
    // key            - value
    // ServletName    对应的实例

    public static final ConcurrentHashMap<String, HspHttpServlet>
            servletMapping = new ConcurrentHashMap<>();


    //2容器 servletUrlMapping
    // -ConcurrentHashMap
    // -HashMap
    // key                    - value
    // url-pattern       ServletName

    public static final ConcurrentHashMap<String, String>
            servletUrlMapping = new ConcurrentHashMap<>();


    //你可以这里理解session, tomcat还维护一个容器
    public static final ConcurrentHashMap<String, HttpSession>
            sessionMapping = new ConcurrentHashMap<>();
    

    //你可以这里理解filter, tomcat还维护了filter的容器
    public static final ConcurrentHashMap<String, String>
            filterUrlMapping = new ConcurrentHashMap<>();

    public static final ConcurrentHashMap<String, Filter>
            filterMapping = new ConcurrentHashMap<>();
    //变强..
    public static void main(String[] args) {
        HspTomcatV3 hspTomcatV3 = new HspTomcatV3();
        hspTomcatV3.init();
        //启动hsptomcat容器
        hspTomcatV3.run();
    }


    //启动HspTomcatV3容器
    public void run() {

        try {
            ServerSocket serverSocket = new ServerSocket(8080);
            System.out.println("=====hsptomcatv3在8080监听======");
            while (!serverSocket.isClosed()) {
                Socket socket = serverSocket.accept();
                HspRequestHandler hspRequestHandler =
                        new HspRequestHandler(socket);
                new Thread(hspRequestHandler).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    //直接对两个容器进行初始化
    public void init() {
        //读取web.xml => dom4j =>
        //得到web.xml文件的路径 => 拷贝一份.
        String path = HspTomcatV3.class.getResource("/").getPath();
        //System.out.println("path= " + path);
        //使用dom4j技术完成读取
        SAXReader saxReader = new SAXReader();
        //困难->真的掌握
        try {
            Document document = saxReader.read(new File(path + "web.xml"));
            System.out.println("document= " + document);
            //得到根元素
            Element rootElement = document.getRootElement();
            //得到根元素下面的所有元素
            List<Element> elements = rootElement.elements();
            //遍历并过滤到 servlet servlet-mapping
            for (Element element : elements) {
                if ("servlet".equalsIgnoreCase(element.getName())) {
                    //这是一个servlet配置
                    //System.out.println("发现 servlet");
                    //使用反射将该servlet实例放入到servletMapping
                    Element servletName = element.element("servlet-name");
                    Element servletClass = element.element("servlet-class");
                    servletMapping.put(servletName.getText(),
                            (HspHttpServlet) Class.forName(servletClass.getText().trim()).newInstance());
                } else if ("servlet-mapping".equalsIgnoreCase(element.getName())) {
                    //这是一个servlet-mapping
                    //System.out.println("发现 servlet-mapping");

                    Element servletName = element.element("servlet-name");
                    Element urlPatter = element.element("url-pattern");
                    servletUrlMapping.put(urlPatter.getText(), servletName.getText());

                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        //老韩验证,这两个容器是否初始化成功
        System.out.println("servletMapping= " + servletMapping);
        System.out.println("servletUrlMapping= " + servletUrlMapping);
    }
}

JavaWeb 三大组件之监听器 Listener

Listener 监听器它是 JavaWeb 的三大组件之一。JavaWeb 的三大组件分别是:Servlet 程序、Listener 监听器、Filter 过滤器

Listener 是 JavaEE 的规范,就是接口

监听器的作用是,监听某种变化(一般就是对象创建/销毁, 属性变化), 触发对应方法完成相应的任务

JavaWeb 中的监听器(共八个), 目前最常用的是 ServletContextListener

ServletContextListener 监听器

  1. 作用:监听 ServletContext 创建或销毁(当我们 Web 应用启动时,就会创建ServletContext),即生命周期监听,

    应用场景

    (1)加载初始化的配置文件;比如spring 的配置文件

    (2)任务调度(配合定时器 Timer/TimerTask)

  2. 相关方法

    void contextInitialized(ServletContextEvent sce) 创 建 Servletcontext 时触发

    voidcontextDestroyed(ServletContextEvent sce) 销毁 Servletcontext 时

java
package com.hspedu.listener;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpSessionListener;

/**
 * 老韩解读
 * 1. 当一个类实现了 ServletContextListener
 * 2. 该类就是一个监听器
 * 3. 该类可以监听的事件 由该类实现的监听接口决定 ,比如 实现ServletContextListener
 *    , 则该类就可以监听 ServletContext对象的创建和销毁, 以此类推
 * 4. HspServletContextListener 就是一个监听者
 * 5. 当web应用启动时,就会产生 ServletContextEvent 事件, 会调用监听器的对应事件处理方法
 *    contextInitialized, 同时会传递 事件对象
 * 6. 程序员可以通过 ServletContextEvent 事件对象,来获取需要的信息, 然后再进行业务处理
 * 7. tomcat怎么知道这个监听器存在 ? 因为我们需要在web.xml中配置
 */
public class HspServletContextListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        ServletContext servletContext = servletContextEvent.getServletContext();
        System.out.println("HspServletContextListener 监听到 " +
                servletContext + " 被创建..");
        //如果我们获取到ServletContext 对象..进行业务处理

    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        ServletContext servletContext = servletContextEvent.getServletContext();
        System.out.println("HspServletContextListener 监听到 " +
                servletContext + " 被销毁..");

        //比如可以对servletContext 数据进行处理, 或者日志的管理...
        System.out.println("进行处理工作.....");
    }
}
xml
<listener>
	<listener-class>com.hspedu.listerer.MyServletContextListener</listener-class>
</listener>

ServletContextAttributeListener 监听器

监听 ServletContext 属性变化

void attributeAdded(ServletContextAttributeEvent event) 添加属性时调用

void attributeReplaced(ServletContextAttributeEvent event) 替换属性时调用

void attributeRemoved(ServletContextAttributeEvent event) 移除属性时调用

java
package com.hspedu.listener;

import javax.servlet.ServletContextAttributeEvent;
import javax.servlet.ServletContextAttributeListener;

public class HspServletContextAttributeListener implements ServletContextAttributeListener {
    @Override
    public void attributeAdded(ServletContextAttributeEvent servletContextAttributeEvent) {

        System.out.println("HspServletContextAttributeListener 监听到添加属性.."
                + servletContextAttributeEvent.getName() + "=" + servletContextAttributeEvent.getValue() );
    }

    @Override
    public void attributeRemoved(ServletContextAttributeEvent servletContextAttributeEvent) {

        System.out.println("HspServletContextAttributeListener 监听到删除属性.."
                + servletContextAttributeEvent.getName() + "=" + servletContextAttributeEvent.getValue() );
    }

    @Override
    public void attributeReplaced(ServletContextAttributeEvent servletContextAttributeEvent) {

        System.out.println("HspServletContextAttributeListener 监听到修改属性.."
                + servletContextAttributeEvent.getName() + "=" + servletContextAttributeEvent.getValue() );

    }
}
xml
<listener>
	<listener-class>com.hspedu.listerer.HspServletContextAttributeListener</listener-class>
</listener>

HttpSessionListener 监听器

监听 Session 创建或销毁,即生命周期监听

void sessionCreated(HttpSessionEvent se) 创 建 session 时调用

void sessionDestroyed(HttpSessionEvent se)销毁 session 时调用

java
package com.hspedu.listener;

import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

public class HspHttpSessionListener implements HttpSessionListener {
    @Override
    public void sessionCreated(HttpSessionEvent httpSessionEvent) {
        HttpSession session = httpSessionEvent.getSession();
        //当session创建时,我们给它设置一个生命周期 30s
        session.setMaxInactiveInterval(30);
        System.out.println("HspHttpSessionListener 监听到 session创建= " +
                session.getId());
        System.out.println("用户id=" + session.getId() + " 上线");
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
        HttpSession session = httpSessionEvent.getSession();
        System.out.println("HspHttpSessionListener 监听到 session销毁= " +
                session.getId());
        System.out.println("用户id=" + session.getId() + " 离线");
    }
}
xml
<listener>
	<listener-class>com.hspedu.listerer.HspHttpSessionListener</listener-class>
</listener>

HttpSessionAttributeListener 监听器

监听 Session 属性的变化

void attributeAdded(ServletRequestAttributeEvent srae) 添加属性时

void attributeReplaced(ServletRequestAttributeEvent srae) 替换属性时

void attributeRemoved(ServletRequestAttributeEvent srae) 移除属性时

java
package com.hspedu.listener;

import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;

public class HspHttpSessionAttributeListener implements HttpSessionAttributeListener {
    @Override
    public void attributeAdded(HttpSessionBindingEvent httpSessionBindingEvent) {
        //HttpSession session = httpSessionBindingEvent.getSession();
        System.out.println("HspHttpSessionAttributeListener 监听到session添加属性" +
                httpSessionBindingEvent.getName() + "=" + httpSessionBindingEvent.getValue());
    }

    @Override
    public void attributeRemoved(HttpSessionBindingEvent httpSessionBindingEvent) {
        HttpSession session = httpSessionBindingEvent.getSession();

        System.out.println("HspHttpSessionAttributeListener 监听到session删除属性" +
                httpSessionBindingEvent.getName());
    }

    @Override
    public void attributeReplaced(HttpSessionBindingEvent httpSessionBindingEvent) {

        System.out.println("HspHttpSessionAttributeListener 监听到session修改属性" +
                httpSessionBindingEvent.getName() + "=" + httpSessionBindingEvent.getValue());
    }
}
xml
<listener>
	<listener-class>com.hspedu.listerer.HspHttpSessionAttributeListener</listener-class>
</listener>

ServletRequestAttributeListener 监听器

监听 Request 属性变化

void attributeAdded(ServletRequestAttributeEvent srae) 添加属性时

void attributeReplaced(ServletRequestAttributeEvent srae) 替换属性时

void attributeRemoved(ServletRequestAttributeEvent srae)移除属性时

java
package com.hspedu.listener;

import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.http.HttpServletRequest;

public class HspRequestListener implements ServletRequestListener {

    @Override
    public void requestInitialized(ServletRequestEvent servletRequestEvent) {
        System.out.println("HspRequestListener 监听到 request对象创建");
        ServletRequest servletRequest = servletRequestEvent.getServletRequest();
        System.out.println("记录访问日志....");
        System.out.println("访问IP= " + servletRequest.getRemoteAddr());
        System.out.println("访问的资源= " + ((HttpServletRequest)servletRequest).getRequestURL());
    }
    @Override
    public void requestDestroyed(ServletRequestEvent servletRequestEvent) {

        System.out.println("HspRequestListener 监听到 request对象被销毁");
    }
}
xml
<listener>
	<listener-class>com.hspedu.listerer.HspRequestListener</listener-class>
</listener>

其他监听器

HttpSessionBindingListener 感知监听器

HttpSessionActivationListener 感知监听器

JavaWeb 三大组件之 过滤器 Filter

image-20231127154643279

Filter 过滤器它是 JavaWeb 的三大组件之一(Servlet 程序、Listener 监听器、Filter 过滤器)

Filter 过滤器是 JavaEE 的规范,是接口

Filter 过滤器它的作用是:拦截请求,过滤响应。

应用场景

● 权限检查

● 日记操作

● 事务管理

Filter 过滤器基本原理

image-20231127154807471

java
package com.hspedu.servlet;

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

public class LoginCheckServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取到用户名和密码->DB
        //假设密码是123456, 就可以通过
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        System.out.println("request=" + request);
        if("123456".equals(password)) {

            //合法, 讲用户名,加入session
            request.getSession().setAttribute("username", username);
            //请求转发到admin.jsp
            request.getRequestDispatcher("/manage/admin.jsp")
                    .forward(request,response);
        } else {
            //不合法, 返回登录页面
            request.getRequestDispatcher("/login.jsp")
                    .forward(request,response);

        }
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}
java
package com.hspedu.filter;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
 * 老韩解读
 * 1. filter在web项目启动时, 由tomcat 来创建filter实例, 只会创建一个
 * 2. 会调用filter默认的无参构造器, 同时会调用 init方法, 只会调用一次
 * 3. 在创建filter实例时,同时会创建一个FilterConfig对象,并通过init方法传入
 * 4. 通过FilterConfig对象,程序员可以获取该filter的相关配置信息
 * 5. 当一个http请求和该filter的的url-patter匹配时,就会调用doFilter方法
 * 6. 在调用doFilter方法时,tomcat会同时创建ServletRequest 和 ServletResponse 和 FilterChain对象
 * , 并通过doFilter传入.
 * 7. 如果后面的请求目标资源(jsp,servlet..) 会使用到request,和 response,那么会继续传递
 * 8. 老师的提醒:到javaweb - ssm - springboot , 有 浏览器和 web服务器(tomcat)参与, 而这两个部分不是我们
 *    程序员自己写,所以理解起来比 java se要困难!!!
 */
public class ManageFilter implements Filter {

    private int count = 0;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        //当Tomcat 创建 Filter创建,就会调用该方法,进行初始化
        //老韩提醒:回忆我们自己实现tomcat底层机制+servlet程序, 就会了然
        //
        System.out.println("ManageFilter init被调用...");
    }

    @Override
    public void doFilter(ServletRequest servletRequest,
                         ServletResponse servletResponse,
                         FilterChain filterChain) throws IOException, ServletException {

        System.out.println("ManageFilter doFilter() 被调用=" + (++count));

        //到每次调用该filter时,doFilter就会被调用

        //如果这里,没有调用继续请求的方法,则就停止
        //如果继续访问目标资源-> 等价于放行

        //老师说明:在调用过滤器前,servletRequest对象=request已经被创建并封装
        //所以:我们这里就可以通过servletRequest获取很多信息, 比如访问url , session
        //比如访问的参数 ... 就可以做事务管理,数据获取,日志管理等
        //获取到session
        //可以继续使用 httpServletRequest 方法.
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        System.out.println("输入密码=" + httpServletRequest.getParameter("password"));
        HttpSession session = httpServletRequest.getSession();
        //获取username session对象, 还可以继续使用
        Object username = session.getAttribute("username");
        if (username != null) {
            //老韩解读filterChain.doFilter(servletRequest, servletResponse)
            //1. 继续访问目标资源url
            //2. servletRequest 和 servletResponse 对象会传递给目标资源/文件
            //3. 一定要理解filter传递的两个对象,再后面的servlet/jsp 是同一个对象(指的是在一次http请求)
            System.out.println("servletRequest=" + servletRequest);
            System.out.println("日志信息==");
            System.out.println("访问的用户名=" + username.toString());
            System.out.println("访问的url=" + httpServletRequest.getRequestURL());
            System.out.println("访问的IP=" + httpServletRequest.getRemoteAddr());
            filterChain.doFilter(servletRequest, servletResponse);
        } else {//说明没有登录过..回到登录页面
            servletRequest.getRequestDispatcher("/login.jsp").
                    forward(servletRequest, servletResponse);
        }
    }

    @Override
    public void destroy() {
        //当filter被销毁时,会调用该方法
        System.out.println("ManageFilter destroy()被调用..");
    }
}
xml
<!--filter 标签用于配置一个 Filter 过滤器-->
<filter>
  <!-- filter 别名-->
  <filter-name>ManageFilter</filter-name>
  <!-- filter 类全路径-->
  <filter-class>com.hspedu.servlet.ManageFilter</filter-class>
</filter>
<filter-mapping>
  <!-- filter 别名 -->
  <filter-name>ManageFilter</filter-name>
  <!-- 老韩解读(重要)
  1. url-pattern 配置拦截路径 /manage/*
  2. 第 1 个 / 被服务器解析为:http://ip:port/工程路径/
  3. /manage/* 表示:http://ip:port/工程路径/manage/* 所有资源请求都经过该过滤器
  -->
  <url-pattern>/manage/*</url-pattern>
</filter-mapping>

Filter 过滤器 url-pattern

1、url-pattern : Filter 的拦截路径, 即浏览器在请求什么位置的资源时,过滤器会进行拦截过滤

2、精确匹配 /a.jsp 对应的 请求地址http://ip[域名]:port/工程路径/a.jsp 会拦截

3、目录匹配 /manage/*对应的 请求地址http://ip[域名]:port/工程路径/manage/xx , 即 web 工程 manage 目录下所有资源 会拦截

4、后缀名匹配 .jsp 后缀名可变,比如.action *.do等等对应的 请求地址 http://ip[域名]:port/工程路径/xx.jsp , 后缀名为.jsp 请求会拦截

5、Filter 过滤器它只关心请求的地址是否匹配,不关心请求的资源是否存在

Filter 过滤器生命周期

image-20231127155143292

FilterConfig

image-20231127155333861

  1. FilterConfig 是 Filter 过滤器的配置类
  2. Tomcat 每次创建 Filter 的时候,也会创建一个 FilterConfig 对象,这里包含了Filter 配置文件的配置信息。
  3. FilterConfig 对象作用是获取 filter 过滤器的配置内容
java
package com.hspedu.filter;

import javax.servlet.*;
import java.io.IOException;
import java.util.Enumeration;

/**
 * 老师解读: 演示FilterConfig使用
 *
 */
public class HspFilterConfig implements Filter {

    private String ip; //从配置获取的ip

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("HspFilterConfig init() 被调用..");
        //通过filterConfig 获取相关的参数
        String filterName = filterConfig.getFilterName();
        ip = filterConfig.getInitParameter("ip");
        ServletContext servletContext = filterConfig.getServletContext();
        //可以获取到该filter所有的配置参数名
        Enumeration<String> initParameterNames =
                filterConfig.getInitParameterNames();

        //遍历枚举
        while (initParameterNames.hasMoreElements()) {
            System.out.println("名字=" + initParameterNames.nextElement());
        }

        System.out.println("filterName= " + filterName);
        System.out.println("ip= " + ip);
        System.out.println("servletContext= " + servletContext);


    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        //通过forbidden ip 来进行控制
        //先获取到访问ip
        String remoteAddr = servletRequest.getRemoteAddr();
        if(remoteAddr.contains(ip)) {
            System.out.println("封杀该网段..");
            servletRequest.getRequestDispatcher("/login.jsp").
                    forward(servletRequest,servletResponse);
            return; //直接返回
        }

        //继续访问目标资源
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {

    }
}
xml
<filter>
  <filter-name>FilterConfig_</filter-name>
  <filter-class>com.hspedu.servlet.FilterConfig_</filter-class>
  <init-param>
    <!-- 封杀 ip 段 -->
    <param-name>ip</param-name>
    <param-value>128.12</param-value>
  </init-param>
  <init-param>
    <param-name>port</param-name>
    <param-value>8888</param-value>
  </init-param>
</filter>
<filter-mapping>
  <filter-name>FilterConfig_</filter-name>
  <url-pattern>/abc/*</url-pattern>
</filter-mapping>

FilterChain 过滤器链

一句话: FilterChain: 在处理某些复杂业务时,一个过滤器不够,可以设计多个过滤器共同完成过滤任务,形成过滤器链。

image-20231127155507588

java
package com.hspedu.filter;

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

public class AFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        System.out.println("AFilter---> 线程id=" +
                Thread.currentThread().getId());

        System.out.println("AFilter doFilter 的前置代码...");
        System.out.println("执行 AFilter doFilter()");
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("AFilter doFilter 的后置代码...");
    }

    @Override
    public void destroy() {

    }
}
java
package com.hspedu.filter;

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

public class BFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        System.out.println("BFilter---> 线程id=" +
                Thread.currentThread().getId());

        System.out.println("BFilter doFilter 的前置代码...");

        System.out.println("执行 BFilter doFilter()");
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("BFilter doFilter 的后置代码...");
    }

    @Override
    public void destroy() {

    }
}
xml
<filter>
  <filter-name>AFilter</filter-name>
  <filter-class>com.hspedu.servlet.AFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>AFilter</filter-name>
  <url-pattern>/admin/*</url-pattern>
</filter-mapping>
<filter>
  <filter-name>BFilter</filter-name>
  <filter-class>com.hspedu.servlet.BFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>BFilter</filter-name>
  <url-pattern>/admin/*</url-pattern>
</filter-mapping>

注意事项

  1. 多个 filter 和目标资源在一次 http 请求,在同一个线程中
  2. 当一个请求 url 和 filter 的 url-pattern 匹配时, 才会被执行, 如果有多个匹配上,就会顺序执行,形成一个 filter 调用链(底层可以使用一个数据结构搞定)
  3. 多个 filter 共同执行时,因为是一次 http 请求, 使用同一个request 对象
  4. 多个 filter 执行顺序,和 web.xml 配置顺序保持一致.
  5. chain.doFilter(req, resp)方法 将执行下一个过滤器的 doFilter 方法, 如果后面没有过滤器,则执行目标资源。
  6. 小结:注意执行过滤器链时, 顺序是(用前面的案例分析) Http请求-> A 过滤器dofilter()-> A 过滤器前置代码 -> A 过滤器 chain.doFilter() -> B 过滤器dofilter() -> B 过滤器前置代码 -> B过滤器 chain.doFilter() -> 目标文件 -> B过滤器后置代码-> A过滤器后置代码->返回给浏览器页面/数据

线程数据共享和安全 -ThreadLocal

  1. ThreadLocal 的作用,可以实现在同一个线程数据共享, 从而解决多线程数据安全问题.
  2. ThreadLocal 可以给当前线程关联一个数据(普通变量、对象、数组)set 方法[源码!]
  3. ThreadLocal 可以像 Map 一样存取数据,key 为当前线程, get 方法
  4. 每一个 ThreadLocal 对象,只能为当前线程关联一个数据,如果要为当前线程关联多个数据,就需要使用多个 ThreadLocal 对象实例
  5. 每个 ThreadLocal 对象实例定义的时候,一般为 static 类型6. ThreadLocal 中保存数据,在线程销毁后,会自动释放

image-20231127155844278

快速入门 ThreadLocal

需求: 演示 ThreadLocal (作用:在一个线程中, 共享数据(线程安全))的使用-画图

java
package com.hspedu.threadlocal;

public class Dog {
}
java
package com.hspedu.threadlocal;

public class Pig {
}
java
package com.hspedu.threadlocal;

public class T2DAO {

    public void update() {
        //取出线程关联的threadLocal1对象的的数据
        Object o = T1.threadLocal1.get();
        //获取当前线程名
        String name = Thread.currentThread().getName();
        System.out.println("在T2DAO的update() 线程是= " + name + " 取出dog=" + o);
    }

}
java
package com.hspedu.threadlocal;

public class T1Service {

    public void update(){
        //取出threadLocal1对象关联的对象
        /**
         * 老韩解读 get
         * public T get() {
         *          //1. 先得到当前的线程对象
         *         Thread t = Thread.currentThread();
         *         //2.通过线程获取到对应的ThrealLocalMap
         *         ThreadLocalMap map = getMap(t);
         *         if (map != null) {
         *              //3. 如果map不为空, 根据当前的 threadlocal对象,得到对应的Entry
         *             ThreadLocalMap.Entry e = map.getEntry(this);
         *             //4. 如果e 不为null
         *             if (e != null) {
         *                 @SuppressWarnings("unchecked")
         *                 //返回当前threadlocal关联的数据value
         *                 T result = (T)e.value;
         *                 return result;
         *             }
         *         }
         *         return setInitialValue();
         *     }
         *
         */
        Object o = T1.threadLocal1.get();
        //获取当前线程名
        String name = Thread.currentThread().getName();
        System.out.println("在T1Service的update() 线程name= " +
                name + " dog= " + o);
        //调用dao-update
        new T2DAO().update();
    }
}
java
package com.hspedu.threadlocal;

public class T1 {

    //创建ThreadLocal对象, 做成public static.
    public static ThreadLocal<Object> threadLocal1 = new ThreadLocal<>();
    public static ThreadLocal<Object> threadLocal2 = new ThreadLocal<>();

    //Task 是线程类 -> 内部类 / 线程
    public static class Task implements Runnable {
        @Override
        public void run() {
            Dog dog = new Dog();
            Pig pig = new Pig();
            //给threadLocal1 对象放入set dog , 隔山打牛
            System.out.println("Task 放入了 dog= " + dog);
            /*
                老韩解读
                public void set(T value) {
                    //1. 获取当前线程, 关联到当前线程!
                    Thread t = Thread.currentThread();
                    //2. 通过线程对象, 获取到ThreadLocalMap
                    //   ThreadLocalMap 类型 ThreadLocal.ThreadLocalMap
                    ThreadLocalMap map = getMap(t);
                    //3. 如果map不为null, 将数据(dog,pig..) 放入map -key:threadLocal value:存放的数据
                    //   从这个源码我们已然看出一个threadlocal只能关联一个数据,如果你set, 就会替换
                    //4. 如果map为null, 就创建一个和当前线程关联的ThreadLocalMap, 并且该数据放入
                    if (map != null)
                        map.set(this, value);
                    else
                        createMap(t, value);
                }

             */
            threadLocal1.set(dog);
            //threadLocal1.set(pig);//替换
            threadLocal2.set(pig);//这个数据就会threadLocal2关联,并且都被当前Thread管理
            System.out.println("Task 在run 方法中 线程=" + Thread.currentThread().getName());
            new T1Service().update();
        }
    }

    public static void main(String[] args) {
        new Thread(new Task()).start();//主线程启动一个新的线程,注意不是主线程
    }
}

ThreadLocal 原理分析图

image-20231127160327896

web 应用常用功能 -文件上传下载

  1. 文件的上传和下载,是常见的功能。
  2. 后面项目就使用了文件上传下载。
  3. 如果是传输大文件,一般用专门工具或者插件
  4. 文件上传下载需要使用到两个包 , 需要导入

image-20231127160508414

文件上传原理

image-20231127160549981

java
package com.hspedu.servlet;

import com.hspedu.utils.WebUtils;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.UUID;

public class FileUploadServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //System.out.println("FileUploadServlet 被调用...");

        //1. 判断是不是文件表单(enctype="multipart/form-data")
        if (ServletFileUpload.isMultipartContent(request)) {
            //System.out.println("OK");
            //2. 创建 DiskFileItemFactory 对象, 用于构建一个解析上传数据的工具对象
            DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();
            //3. 创建一个解析上传数据的工具对象
            /**
             *     表单提交的数据就是 input 元素
             *     <input type="file" name="pic" id="" value="2xxx.jpg" onchange="prev(this)"/>
             *     家居名: <input type="text" name="name"><br/>
             *     <input type="submit" value="上传"/>
             */
            ServletFileUpload servletFileUpload =
                    new ServletFileUpload(diskFileItemFactory);
            //解决接收到文件名是中文乱码问题
            servletFileUpload.setHeaderEncoding("utf-8");

            //4. 关键的地方, servletFileUpload 对象可以把表单提交的数据text / 文件
            //   将其封装到 FileItem 文件项中
            //   老师的编程心得体会: 如果我们不知道一个对象是什么结构[1.输出 2.debug 3. 底层自动看到]
            try {
                List<FileItem> list = servletFileUpload.parseRequest(request);
                /*
                list==>

                [name=3.jpg, StoreLocation=D:\hspedu_javaweb\apache-tomcat-8.0.50-windows-x64\apache-tomcat-8.0.50\temp\xupload__7e34374f_17fce4168b1__7f4b_00000000.tmp, size=106398bytes, isFormField=false, FieldName=pic,
                name=null, StoreLocation=D:\hspedu_javaweb\apache-tomcat-8.0.50-windows-x64\apache-tomcat-8.0.50\temp\xupload__7e34374f_17fce4168b1__7f4b_00000001.tmp, size=6bytes, isFormField=true, FieldName=name]

                 */
                //System.out.println("list==>" + list);
                //遍历,并分别处理=> 自然思路
                for (FileItem fileItem : list) {
                    //System.out.println("fileItem=" + fileItem);
                    //判断是不是一个文件=> 你是OOP程序员
                    if (fileItem.isFormField()) {//如果是true就是文本 input text
                        String name = fileItem.getString("utf-8");
                        System.out.println("家具名=" + name);
                    } else {//是一个文件

                        //用一个方法
                        //获取上传的文件的名字
                        String name = fileItem.getName();
                        System.out.println("上传的文件名=" + name);

                        //把这个上传到 服务器的 temp下的文件保存到你指定的目录
                        //1.指定一个目录 , 就是我们网站工作目录下
                        String filePath = "/upload/";
                        //2. 获取到完整目录 [io/servlet基础]
                        //  这个目录是和你的web项目运行环境绑定的. 是动态.
                        //fileRealPath=D:\hspedu_javaweb\fileupdown\out\artifacts\fileupdown_war_exploded\xupload\
                        String fileRealPath =
                                request.getServletContext().getRealPath(filePath);
                        System.out.println("fileRealPath=" + fileRealPath);

                        //3. 创建这个上传的目录=> 创建目录?=> Java基础
                        //   老师思路; 我们也一个工具类,可以返回 /2024/11/11 字符串
                        File fileRealPathDirectory = new File(fileRealPath + WebUtils.getYearMonthDay());
                        if (!fileRealPathDirectory.exists()) {//不存在,就创建
                            fileRealPathDirectory.mkdirs();//创建
                        }

                        //4. 将文件拷贝到fileRealPathDirectory目录
                        //   构建一个上传文件的完整路径 :目录+文件名
                        //   对上传的文件名进行处理, 前面增加一个前缀,保证是唯一即可, 不错
                        name = UUID.randomUUID().toString() + "_" +System.currentTimeMillis() + "_" + name;
                        String fileFullPath = fileRealPathDirectory + "/" +name;
                        fileItem.write(new File(fileFullPath));

                        //5. 提示信息
                        response.setContentType("text/html;charset=utf-8");
                        response.getWriter().write("上传成功~");


                    }
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("不是文件表单...");
        }
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}

文件上传注意事项和细节

  1. 如果将文件都上传到一个目录下,当上传文件很多时,会造成访问文件速度变慢,因此可以将文件上传到不同目录 比如 一天上传的文件,统一放到一个文件夹年月日, 比如21001010 文件夹

  2. 一个完美的文件上传,要考虑的因素很多,比如断点续传、控制图片大小,尺寸,分片上传,防止恶意上传等,在项目中,可以考虑使用 WebUploader 组件(百度开发)http://fex.baidu.com/webuploader/doc/index.html

  3. 文件上传功能,在项目中建议有限制的使用,一般用在头像、证明、合同、产品展示等,如果不加限制,会造成服务器空间被大量占用 [比如 b 站评论,就不能传图片,微信发1次朋友圈最多 9 张图等..]

  4. 文件上传,创建 web/upload 的文件夹,在 tomcat 启动时,没有在out 目录下创建对应的 upload 文件夹, 原因是 tomcat 对应空目录是不会在 out 下创建相应目录的,所以,只需在 upload 目录下,放一个文件即可, 这个是 Idea + Tomcat 的问题, 实际开发不会存在.[演示]

文件下载

image-20231127160808471

java
package com.hspedu.servlet;

import org.apache.commons.io.IOUtils;
import sun.misc.BASE64Encoder;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;

public class FileDownloadServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("FileDownloadServlet 被调用...");
        //1. 先准备要下载的文件[假定这些文件是公共的资源]
        //   重要: 保证当我们的tomcat启动后,在工作目录out下有download文件夹, 并且有可供下载的文件!!
        //   老师再次说明,如果你没有看到你创建的download在工作目录out下 rebuild project -> restart, 就OK

        //2. 获取到要下载的文件的名字
        request.setCharacterEncoding("utf-8");
        String downLoadFileName = request.getParameter("name");
        //System.out.println("downLoadFileName= " + downLoadFileName);

        //3. 给http响应,设置响应头 Content-Type , 就是文件的MIME
        //   通过servletContext 来获取
        ServletContext servletContext = request.getServletContext();
        String downLoadPath = "/download/"; //下载目录从 web工程根目录计算 /download/1.jpg
        String downLoadFileFullPath = downLoadPath + downLoadFileName;
        String mimeType = servletContext.getMimeType(downLoadFileFullPath);
        System.out.println("mimeType= " + mimeType);
        response.setContentType(mimeType);

        //4. 给http响应,设置响应头 Content-Disposition
        //   这里考虑的细节比较多,比如不同的浏览器写法不一样,考虑编码
        //   ff 是 文件名中文需要 base64, 而 ie/chrome 是 URL编码
        //   这里我们不需要同学们记住,只需知道原理
        //   老韩解读
        //(1)如果是Firefox 则中文编码需要 base64
        //(2)Content-Disposition 是指定下载的数据的展示形式 , 如果attachment 则使用文件下载方式
        //(3)如果是其他(主流ie/chrome) 中文编码使用URL编码
        if (request.getHeader("User-Agent").contains("Firefox")) {
            // 火狐 Base64编码
            response.setHeader("Content-Disposition", "attachment; filename==?UTF-8?B?" +
                    new BASE64Encoder().encode(downLoadFileName.getBytes("UTF-8")) + "?=");
        } else {
            // 其他(主流ie/chrome)使用URL编码操作
            response.setHeader("Content-Disposition", "attachment; filename=" +
                    URLEncoder.encode(downLoadFileName, "UTF-8"));
        }

        //5. 读取下载的文件数据,返回给客户端/浏览器
        //(1) 创建一个和要下载的文件,关联的输入流
        InputStream resourceAsStream =
                servletContext.getResourceAsStream(downLoadFileFullPath);
        //(2) 得到返回数据的输出流 [因为返回文件大多数是二进制(字节), IO java基础]
        ServletOutputStream outputStream = response.getOutputStream();

        //(3) 使用工具类,将输入流关联的文件,对拷到输出流,并返回给客户端/浏览器
        IOUtils.copy(resourceAsStream, outputStream);

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}