本博客参照了韩顺平老师的 JavaWeb 课程讲义!

7 Tomcat

7.1 官方文档

7.1.1 地址: https://tomcat.apache.org/tomcat-8.0-doc/

7.2 WEB开发介绍

  1. WEB,在英语中 web 表示网/网络资源(页面, 图片, css, js)意思,它用于表示 WEB 服务器(主机)供浏览器访问的资源

  2. WEB 服务器(主机)上供外界访问的 web 资源分为:

  • 静态web资源(如 html 页面):指web页面中供人们浏览的数据始终是不变。
  • 动态web资源,比如 Servlet(java)、PHP 等
  1. 静态web 资源开发技术
  • Html、CSS、js 等
  1. 常用动态web 资源开发技术:
  • Servlet、SpringBoot、SpringvVC、 PHP、ASP.NET 等

7.3 JavaWeb开发技术栈图

-一图胜千言

image-20230704161447451

7.4 BS 与 CS 开发介绍

7.4.1 BS开发

B: browser(浏览器,种类太多 ff, chrome, ie, edge)

S: Server(服务端,考虑很多)
示意图:

image-20230704162955992

对BS的解读:

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

  2. 安全性,通常情况下,Bs 安全性不如 CS 好控制

  3. 易用性,BS 好于CS,浏览器可直接下载

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

image-20230704163213955

7.4.2 CS开发

C: Client(客户端)

S: Server(服务端)

示意图:

image-20230704163427794

7.5 JavaWeb服务软件

7.5.1 JavaWeb服务器软件介绍

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

2.老韩提问:学习Javaweb 开发,为什么必须要先装 WEB 服务软件?

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

7.5.2 手写简单 Web 服务程序

1. 需求**:** 手写 MyWebServer.java , 让大家体验一下 JavaWeb 服务本质

提醒: 这里,我们需要使用到 Java基础(IO/网络),

http://localhost:9999 表示浏览器向localhost(127.0.0.1)表示本机的9999端口发出请求

image-20230704164133005

2. 创建 hspedu_mytomcat Java 应用程序.

3. 创建 D:\idea_java_projects\hspedu_mytomcat\src\hello.html

​ Hello, I AM WEB Server!

4. 创建 D:\idea_java_projects\hspedu_mytomcat\src\MyWebServer.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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();
}
}

5. 完成测试: 浏览器 http://localhost:9999/

image-20230704165444597

7.5.3 常用 JavaWeb 服务软件

1. Tomcat:由 Apache 组织提供的一种 Web 服务器,提供对 jspServlet 的支持。它是一种轻量级的 javaWeb 容器(服务器),也是当前应用最广的 JavaWeb 服务器(免费)。

2. Jboss:是一个遵从 JavaEE 规范的、它支持所有的 JavaEE 规范(免费)。

3. GlassFish: 由 Oracle 公司开发的一款 JavaWeb 服务器,是一款商业服务器,达到产品级质量(应用很少)。

4. Resin:是CAUCHO 公司的产品,是一个非常流行的服务器,对 servlet JSP 提供了良好的支持, 性能也比较优良(收费)。

5. WebLogic【很猛】:是 Oracle 公司的产品,支持 JavaEE 规范, 而且不断的完善以适应新的开发要求,适合大型项目(收费,用的不多,适合大公司)。

7.6 Tomcat

7.6.1 Tomcat下载和安装

1. Tomcat 官方站点:**http://tomcat.apache.org/ **

2. 获取 Tomcat 安装程序包

tar.gz文件是Linux操作系统下的安装版本

zip文件是Windows系统下的压缩版本

3. 使用 zip 包安装 Tomcat

**4. which version https://tomcat.apache.org/whichversion.html ,**可以看到 Tomcat 仍然是支持 jsp el

5. Tomcat 最好的小伙伴是 JSP+EL

7.6.2 Tomcat启动

windows系统:双击 bin 目录下的 startup.bat 文件

macos:

1
2
3
4
5
6
7
8
9
🌟进入tomcat安装目录的bin目录下
cd /Applications/压缩包资源/apache-tomcat-9.0.73/bin

🌟启动tomcat
./startup.sh
🌟关闭tomcat
./shutdown.sh
🌟查看版本信息
./version.sh

输入 http://localhost:8080/,显示如下界面代表安装成功, 默认在 8080 端口

image-20230705084311991

在开发中,我们可以看一下哪些端口在监听: netstat -anb

7.6.3 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.6.4 Tomcat 目录结构

image-20230705085601438

解读
1. server.xml 用于配置 tomcat的基本设置(启动端口,关闭端口,主机名)

2. wex.xml 用于指定 tomcat 运行时配置(比如servlet等.)

3. webapps 目录是存放web应用,就是网站

7.6.5 catalina 启动 Tomcat(Windows)

1. 进入到 Tomcatbin 目录下

2. 执行命令: catalina run

7.6.6 停止 Tomcat

进入 Tomcat bin 目录下的 shutdown.bat 双击,就可以停止 Tomcat 服务器(windows)

7.6.7 修改 Tomcat 服务端口

1. Tomcat 目录下的 conf 目录,修改 server.xml 配置文件

image-20230705152146388

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

7.6.8 Tomcat 服务中部署 WEB 应用

  • 什么是Web应用
  1. WEB应用是多个web资源的集合。简单的说,可以把web应用理解为硬盘上的一个目录,这个目录用于管理多个web资源。

  2. web应用通常也称之为web应用程序,或web工程,通俗的说就是网站。

  • Web应用组成

一个 WEB 应用由多个WEB 资源或其它文件组成,包括html 文件、css 文件、js文件、动态web页面、java 程序、支持jar 包、配置文件等。开发人员在开发 web 应用时,按照规定目录结构存放这些文件。否则,在把 web 应用交给 web 服务器管理时,不仅可能会使web 应用无法访问,还会导致 web 服务器启动报错。

  • Javaweb程序/应用/工程目录结构
image-20230706111003709
  • 部署方式1:将web工程的目录拷贝到 Tomcat 的webapps 目录下
  1. news web工程(目前都是静态资源 html, 图片)

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

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

  • 部署方式2:

在tomcat下的 conf/Catalina/localhost/ 目录中,添加配置文件,可以映射到其他位置的web应用(在其他地方寻找web应用,解决磁盘空间分配问题)

ROOT 的工程的访问:

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

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

7.6.9 浏览器访问 Web 服务过程详解

7.6.9.1 回顾前面的 JavaWeb 开发技术栈图

image-20230704161447451

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

1. 说明

下面,我们对浏览器访问web服务器资源(html,css,图片,js)做详解,通过一个时序图加强对这个重要过程的理解,重要,核心

image-20230706111724367

7.7 IDEA 开发 JavaWeb 工程

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

1.需求/图解:使用 IDEA 开发 Javaweb 工程fishweb,并将网页部署到 fishweb 工程,看老师演示

image-20230706112454090

7.7.2 注意事项和细节

1. 热加载选项说明

image-20230706135810014

解读:

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

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

2. 端口修改

image-20230706140054643

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

3.out 目录是什么

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

4.当我们从外部拷贝资源到项目(图片,文件,js,css 等)

如果出现 404 不能访问错误,解决方式 rebulid project ->重启 Tomcat

7.7.3 JavaWeb 工程的目录介绍

image-20230706141714376

image-20230706141833960

8 动态 WEB 开发核心-Servlet

8.1 官方文档

8.1.1 地址: https://tomcat.apache.org/tomcat-8.0-doc/servletapi/index.html

8.1.3 Servlet和Tomcat的关系:一句话,Tomcat支持Servlet

8.2 为什么会出现 Servlet

提出需求**:** 请用你现有的html css javascript,开发网站,比如可以让用户留言**/购物/支付,** 你能搞定吗**?**

—不能,因为无法操作数据库

  • 引入我们动态网页(能和用户交互)技术 ===> Servlet
  • 对Java Web 技术体系的流程图改造说明(细化).[整体的概念]

image-20230706145610695

8.3 什么是 Servlet

  • 什么是Servlet

    • Servlet 在开发动态 WEB 工程中,得到广泛的应用,掌握好 Servlet 非常重要,Servlet(基石)是 SpringMVC 的基础
  • Servlet(java 服务器小程序),它的特点:

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

8.4 Servlet在JavaWeb项目位置

image-20230707084620291

8.5 Servlet基本使用

8.5.1 Servlet开发方式说明

  1. servlet3.0 前使用 web.xml , servlet3.0 版本以后**(**包括 **3.0)**支持注解, 同时支持 web.xml 配置

  2. 如何查看 servlet版本[如图]

image-20230707085030431

  1. 讲解SpringBoot 时,我们用注解方式,从 ssm,springboot 后面全部使用注解

  2. 这专门讲 servlet, 为让大家更清晰知道 servlet 使用原理,老师用配置方式(说明: 原生的 Servlet 在项目中使用很少

  3. 不管使用哪种方式,本质都一样

8.5.2 快速入门- 手动开发 Servlet

需求说明

1、开发一个 Helloservlet

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

  • 具体步骤
    • 编写类Helloservlet去实现 Servlet 接口
    • 实现 service 方法,处理请求,并响应数据
    • 在 web.xml 中去配置 servlet 程序的访问地址

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.study.servlet;

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

/**
* @author 浦原
* 2023/7/7
* 09:06
* @version 1.0
*/
public class HelloServlet implements Servlet {
//当创建HelloServlet时,会调用init方法
//该方法只会被调用一次
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("servlet被调用~~~~~");
}

//返回Servlet的配置信息
@Override
public ServletConfig getServletConfig() {
return null;
}

/**
* service 方法是专门用来处理请求和响应的
* @param servletRequest
* @param servletResponse
* @throws ServletException
* @throws IOException
*/
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("service~");
}

@Override
public String getServletInfo() {
return null;
}

@Override
public void destroy() {
System.out.println("destroy 被调用...");
}
}

4.在web.xml配置HelloServlet,即:给HelloServlet提供对外访问地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- 1.web.xml主要用来配置该web应用使用到的Servlet -->
<!-- 2.servlet-name:给Servlet取名(程序员决定),该名字唯一
3.servlet-class:Servlet的类的全路径:Tomcat在反射生成该Servlet需要使用
4.url-pattern:该servlet访问的url的配置(路径)
这时我们应访问:http://localhost:8080/servlet/helloServlet
5.url-pattern 取名是程序员决定的
6.load-on-startup:表示在tomcat启动时,会自动加载servlet实例
-->
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.study.servlet.HelloServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/helloServlet</url-pattern>
</servlet-mapping>
</web-app>

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

http://localhost:8080/servlet/helloServlet

8.5.3 浏览器调用 Servlet 流程分析

一图胜千言

image-20230707095156118

如果在web.xml中未查询到请求的资源,则会返回404错误!

image-20230707095634908

Servlet常驻内存,属于单例模式

8.5.4 Servlet生命周期

● 主要有三个方法:

1. init():初始化阶段

2. service():处理浏览器请求阶段

3. destroy():终止阶段

  • 示意图
image-20230707100629527
  • 初始化阶段

    • servlet 容器(比如:Tomcat)/加载 Servlet,加载完成后,Servlet 容器会创建一个 Servlet 实例

      并调用 init()方法,init()方法只会调用一次,Servlet 容器在下面的情况装载 Servlet:

      • Servlet 容器 (Tomcat) 启动时自动装载某些 servlet,实现这个需要在 web.xml 文件中添加

        1 1 表示装载的顺序

      • 在servlet 容器启动后,浏览器首次向 servlet 发送请求(这个前面说过)

      • Servlet 重新部署后(比如 tomcat 进行 redeploy 【*redeploy 会销毁所有的 servlet 实例!!!*】)。

        浏览器再向 Servlet 发送第1次请求(或配置了tomcat启动自动装载)

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

  • 每收到一个http 请求,服务器就会产生一个新的线程去处理[线程]

  • 创建一个用于封装 HTTP 请求消息的 ServletRequest 对象和一个代表 HTTP 响应消息的ServletResponse 对象

  • 然后调用 Servlet 的 service()方法并将请求和响应对象作为参数传递进去

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

    • 当web 应用被終止,或者 servlet 容器終止运行,或者Servlet 类重新装载时,会调用 destroy(方法

    • 比如重启 tomcat,或者redeploy web 应用

8.5.5 GET 和 POST 请求的分发处理

  • 开发 servlet,通常编写 doGet、doPost 方法。来对表单的get 和 post 请求进行分发处理

  • 代码演示 HelloServlet.java register.html

  1. 创建 servlet\web\register.html
1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"> <title>注册</title>
</head>
<body>
<h1>注册用户</h1>
<form action="http://localhost:8080/servlet/helloServlet"
method="get">
u: <input type="text" name="username"/><br><br> <input type="submit" value="注册用户"/>
</form>
</body>
</html>

2. 修改 servlet\src\com\hspedu\servlet\HelloServlet.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
//思考->从 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 请求
}
}

public void doGet(){
System.out.println("doGet被调用");
}

public void doPost(){
System.out.println("doPost被调用");
}

8.5.6 通过继承 HttpServlet 开发 Servlet

  • HttpServlet 介绍

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

image-20230707112036074
  • HttpServlet 介绍

1、通过继承 HttpServlet 开发一个 HiServlet

2、当浏览器 访问 http://localhost:8080/web 应用名/hiServlet 时,后台输出 “hi HiServelt”

  • 具体的开发步骤
  1. 编写一个类去继承 HttpServlet 类
  2. 根据业务需要重写 doGet 或 doPost 方法
  3. 到 web.xml 中的配置 Servlet 程序
  • 应用实例

1.创建HiServlet.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.study.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 浦原
* 2023/7/7
* 13:46
* @version 1.0
*/
public class HiServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("HiServlet doGet");
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("HiServlet doPost");
}
}

2.修改web.xml完成配置

1
2
3
4
5
6
7
8
<servlet>
<servlet-name>HiServlet</servlet-name>
<servlet-class>com.study.servlet.HiServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HiServlet</servlet-name>
<url-pattern>/hiServlet</url-pattern>
</servlet-mapping>

3.完成测试

http://localhost:8080/servlet/hiServlet

8.5.7 IDEA 开发 Servlet 程序

  • 说明

编手动开发 Servlet 需要程序员自己配置 Servlet ,比较麻烦,在工作中,直接使用 IDEA 开发 Servlet 会更加方便

  • 应用实例
image-20230707140732732 image-20230707140642567

8.5.8 Servlet 注意事项和细节

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

  2. 对于每次访问请求,Servlet 引擎都会创建一个新的 HttpServletRequest 请求对象和一个新的 HttpServletResponse 响应对象,然后将这两个对象作为参数传递给它调用的 Servlet的 service()方法,service 方法再根据请求方式分别调用 doXXX 方法

  3. 如果在元素中配置了一个元素,那么 WEB 应用程序在启动时,就会装载并创建 Servlet 的实例对象、以及调用 Servlet 实例对象的 init()方法 应用场景:(定时发送邮件的服务/自动启动->完成任务)

8.6 Servlet - 注解方式

8.6.1 快速入门

  • 具体步骤
  1. 编写类OkServlet去继承HttpServlet

  2. 注解方式配置OkServlet, 一个Servlet支持配置多个urlPattern

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    package com.study.servlet.annotation;

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

    /**
    * @author 浦原
    * 2023/7/7
    * 14:25
    * @version 1.0
    * 1.注解方式配置
    * @WebServlet是一个注解
    * 2.源码:
    * @Target({ElementType.TYPE})
    * @Retention(RetentionPolicy.RUNTIME)
    * @Documented
    * public @interface WebServlet {
    * String name() default "";
    * String[] value() default {};
    * String[] urlPatterns() default {};
    * int loadOnStartup() default -1;
    * WebInitParam[] initParams() default {};
    * boolean asyncSupported() default false;
    * String smallIcon() default "";
    * String largeIcon() default "";
    * String description() default "";
    * String displayName() default "";
    * }
    * 3.urlPatterns 对应 <url-patterns><url-patterns/>
    * 4.{"/ok1","/ok2"} 可以给OkServlet配置多个url-pattern
    * 5.用户在浏览器可以这样访问:http://localhost:8080/servlet/ok2
    */
    @WebServlet(urlPatterns = {"/ok1","/ok2"})
    public class OkServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    System.out.println("注解方式 OkServlet doPost");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    System.out.println("注解方式 OkServlet doGet");
    }
    }

注解方式是如何实现的?

——Tomcat对包进行扫描,如果发现某个类被@WebServlet修饰,就说明该类是Servlet,就读取注解中的urlPatterns,然后进行下一步……(看请求的资源是否能从注解中找到……,找到则得到urlPatterns,去hashmap中看该servlet是否已经加载过……)

8.6.2 Servlet urlPattern 配置

8.6.2.1 精确匹配

——最简单、普遍的路径设置方式

配置路径 : @WebServlet(“/ok/zs”)

访问 servlet: localhost:8080/servlet/ok/zs

8.6.2.2 目录匹配

—— * 表示:零层、任意单层、任意多层

配置路径 : @WebServlet(“/ok/*”)

访问文件: localhost:8080/servlet/ok/aaa localhost:8080/servlet/ok/bbb

8.6.2.3 扩展名匹配

——必须保证后缀为 .action

配置路径 : @WebServlet(“**.action”)*

访问文件: localhost:8080/hsp/zs.action localhost:8080/hsp/ls.action

提示: @WebServlet(“/*.action”) , 不能带 / , 否则 tomcat 报错

8.6.2.4 任意匹配

配置路径 : @WebServlet(“/“) @WebServlet(“/**”)*

访问文件: localhost:8080/hsp/aaa localhost:8080/hsp/bbb localhost:8080/hsp/ccc

提醒: / 和 /*的配置, 会匹配所有的请求, 这个比较不实用

8.6.2.5 注意事项和使用细节

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 是处理静态资源的,一旦拦截,静态资源不能处理」

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

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

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

8.8 🌟关联篇: HTTP 协议

8.8.1请求头、响应头介绍

https://github.com/Tangjiayang/picodemo/blob/main/img/HTTP%E5%B8%B8%E8%A7%81%E8%AF%B7%E6%B1%82%E5%92%8C%E5%93%8D%E5%BA%94%E5%A4%B4-%E8%AF%B4%E6%98%8E.pdf

8.8.2HTTP状态码

当浏览者访问一个网页时,浏览者的浏览器会向网页所在服务器发出请求。当浏览器接收并显示网页前,此网页所在的服务器会返回一个包含HTTP状态码的信息头(server header)用以响应浏览器的请求。

HTTP状态码的英文为HTTP Status Code。

下面是常见的HTTP状态码:

200 - 请求成功

301 - 资源(网页等)被永久转移到其它URL

404 - 请求的资源(网页等)不存在

500 - 内部服务器错误

HTTP状态码分类

HTTP状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型,后两个数字没有分类的作用。HTTP状态码共分为5种类型:

HTTP状态码分类
分类 分类描述
1** 信息,服务器收到请求,需要请求者继续执行操作
2** 成功,操作被成功接收并处理
3** 重定向,需要进一步的操作以完成请求
4** 客户端错误,请求包含语法错误或无法完成请求
5** 服务器错误,服务器在处理请求的过程中发生了错误

HTTP状态码列表:

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

GET请求行和请求头解析:

image-20230710100130745

POST请求行和请求头解析:

image-20230710101351184

何时使用 get / post ?

get和post方法功能类似的,使用建议:
1、get方式的安全性较Post方式要差些,包含机密信息的话,建议用Post数据提交方式;
2、在做数据查询时,建议用Get方式;而在做数据添加、修改或删除时,建议用Post方式;

区别表现如下:

  1. get是从服务器上获取数据,post是向服务器传送数据。
  2. get是把参数数据队列加到提交表单的ACTION属性所指的URL中,值和表单内各个字段一一对应,在URL中可以看到。post是通过HTTP post机制,将表单内各个字段与其内容放置在HTML HEADER内一起传送到ACTION属性所指的URL地址。用户看不到这个过程。
  3. 对于get方式,服务器端用Request.QueryString获取变量的值,对于post方式,服务器端用Request.Form获取提交的数据。
  4. get传送的数据量较小,不能大于2KB。post传送的数据量较大,一般被默认为不受限制。但理论上,IIS4中最大量为80KB,IIS5中为100KB。
  5. get安全性非常低,post安全性较高。但是执行效率却比Post方法好。

若符合下列任一情况,则用POST方法:

  • 请求的结果有持续性的副作用,例如,数据库内添加新的数据行。
  • 若使用GET方法,则表单上收集的数据可能让URL过长。
  • 要传送的数据不是采用7位的ASCII编码。

若符合下列任一情况,则用GET方法:

  • 请求是为了查找资源,HTML表单数据仅用来帮助搜索。
  • 请求结果无持续性的副作用。
  • 收集的数据及HTML表单内的输入字段名称的总长不超过1024个字符。

原文链接:https://blog.csdn.net/qq_47443027/article/details/114696716

响应体:

image-20230710211832628

常见状态码说明:

302

​ 资源被转移了,返回给你302 还告诉你该去哪里找不见的资源,因此浏览器需要发送两次请求才能访问到原来的资源(只发生一次302的情况)

304

(在浏览器允许缓存的情况下)浏览器在请求某资源时,如果有该资源的缓存,就会把该资源的最后修改日期也发给服务器。

如果最后修改日期 = 服务器端该资源的最后修改日期,则服务器告诉浏览器请求的资源未修改过,返回304,不返回资源给浏览器,让浏览器去自己的缓存去找。

MIME类型

MIME 介绍:
  1. MIME 是 HTTP 协议中数据类型。 MIME 的英文全称是”Multipurpose Internet Mail Extensions” 多功能 Internet 邮件扩充服务。 MIME 类型的格式是**”大类型/小类型”**,并与某一种文件的扩展名相对应

  2. 在响应包的Content-Type 就有指定

常见的 MIME 类型:
image-20230711083211146

8.9 ServletConfig

8.9.1 ServletConfig 基本介绍

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

8.9.2 ServletConfig 类能干什么

  • 获取 Servlet 程序的 servlet-name 的值

  • 获取初始化参数 init-param

  • 获取 ServletContext 对象

8.9.3 ServletConfig

应用实例

● 需求: 编写 DBServlet.java 完成如下功能

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

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

  3. 示意图(思路分析)

image-20230711084923867
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<servlet>
<servlet-name>DBServlet</servlet-name>
<servlet-class>com.study.servlet.DBServlet</servlet-class>
<init-param>
<param-name>username</param-name>
<param-value>donn</param-value>
</init-param>
<init-param>
<param-name>password</param-name>
<param-value>123456</param-value>
</init-param>
</servlet>

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletConfig servletConfig = getServletConfig();
System.out.println(servletConfig.getInitParameter("username"));
System.out.println(servletConfig.getInitParameter("password"));
}

8.10 ServletContext

——多个Servlet共享的数据空间

8.10.1 为什么需要 ServletContext

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

​ 方案1-DB:

image-20230711091848410

​ 方案2-ServletContext:

image-20230711092040710

8.10.2 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-20230711092825430

8.10.3 ServletContext 可以做什么

  1. 获取 web.xml 中配置的上下文参数 context-param [信息和整个 web 应用相关, 而不是属于某个 Servlet]

  2. 获取当前的工程路径,格式: /工程路径 ==> 比如 /servlet. 其实就是/工程名

  3. 获取工程部署后在服务器硬盘上的绝对路径( 比 如 :D:\hspedu_javaweb\servlet\out\artifacts\servlet_war_exploded)

  4. 像 Map 一样存取数据, 多个 Servlet 共享数据

image-20230711093633773

8.10.4 应用实例

1-获取工程相关信息

● 需求如下:

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

● 代码实现 - 具体看 项目中的代码.

1
2
3
4
5
6
7
8
9
10
11
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletConfig servletConfig = getServletConfig();
System.out.println(servletConfig.getInitParameter("username"));
System.out.println(servletConfig.getInitParameter("password"));
System.out.println("公共数据:" + servletConfig.getServletContext().getInitParameter("website"));
System.out.println("工程路径" + servletConfig.getServletContext().getContextPath());
// “/”表示项目发布后的根目录(out目录中)
System.out.println("绝对路径" + servletConfig.getServletContext().getRealPath("/"));

}

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

……

8.11 HttpServletRequest

8.11.1 HttpServletRequest 介绍

——http请求中的信息都被封装在HttpServletRequest中

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

8.11.2 HttpServletRequest 类图

image-20230711102743263

image-20230711102808631

8.11.3 HttpServletRequest 常用方法

  1. getRequestURI() 获取请求的资源路径

    http://localhost:8080/servlet/loginServlet中的「servlet/loginServlet」

  2. getRequestURL() 获取请求的统一资源定位符 ( 绝 对 路 径 )http://localhost:8080/servlet/loginServlet

  3. getRemoteHost() 获取客户端的主机, getRemoteAddr()

  4. getHeader() 获取请求头 request.getHeader(“Cookie(例)”) 可以获取http请求头中的任意信息

  5. getParameter() 获取请求的参数

  6. getParameterValues() 获取请求的参数(多个值的时候使用) , 比如 checkbox, 返回的数组

    可以获取前端表单传来的数据,根据键(前端标签中的name属性)获取值

  7. getMethod() 获取请求的方式 GET 或 POST

  8. setAttribute(key, value); 设置域数据

  9. getAttribute(key); 获取域数据

  10. 🌟getRequestDispatcher() 获取请求转发对象, 请求转发的核心对象

8.11.4 HttpServletRequest 应用实例

……

8.11.5 HttpServletRequest 注意事项和细节

  1. 获 取 doPost 参数中文乱码解决方案 , 注 意 setCharacterEncoding(“utf-8”) 要 写 在request.getParameter()前(在接受参数前设置字体 )。
image-20230711110015613
  1. 注意:如果通过 PrintWriter writer, 有返回数据给浏览器,建议将获取参数代码写在writer.print() 之前,否则可能获取不到参数值(doPost)
  2. 处理 http 响应数据中文乱码问题 (改变编码为utf-8)
image-20230711110448507
  1. 再次理解 Http 协议响应 Content-Type 的含义, 比如 text/plain(以文本方式解析) application/x-tar(表示返回的是文件,浏览器会以下载文件的方式处理)

8.11.7 请求转发

8.11.7.1 为什么需要请求转发

  1. 目前我们学习的都是一次请求, 对应一个 Servlet, 如图
image-20230711111006356
  1. 但是在实际开发中,往往业务比较复杂,需要在一次请求中,使用到多个 Servlet 完成一个任务(Servlet 链, 流水作业) 如图:

image-20230711111046853

8.11.7.2 请求转发说明

  1. 实现请求转发: 请求转发指一个 web 资源收到客户端请求后, 通知服务器去调用另外一个 web 资源进行处理

  2. HttpServletRequest 对象(也叫 Request 对象)提供了一个 getRequestDispatcher 方法,该方法返回一个 RequestDispatcher 对象,调用这个对象的 forward 方法可以实现请求转发

  3. request 对象同时也是一个域对象,开发人员通过 request 对象在实现转发时,把数据通过 request 对象带给其它 web 资源处理

    • setAttribute方法

    • getAttribute方法

    • removeAttribute方法

    • getAttributeNames方法

8.11.7.3 实现请求转发

请求转发原理示意图

image-20230711134908152

8.11.7.4 请求转发应用实例

……

请求转发中,各个servlet可以共享request域对象的原因:

​ 因为请求转发的方式为:

1
2
3
4
5
6
7
//  “/manageServlet”为发送的对象(转发给谁)
// 其中:“/” 会被解析成“/项目名” (前面一定是表示该服务器的http://工程名:端口)
// manageServlet为资源名
// 🌟因此,请求转发是不能转发到外网的(只能在服务器主机内部进行转发)
RequestDispatcher requestDispatcher = request.getRequestDispatcher("/manageServlet");
//而此处的request将被传给下一个servlet,所以可以共享request对象中的数据!!!!
requestDispatcher.forward(request, response);

8.11.7.5 请求转发注意事项和细节

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

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

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

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

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

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

8.11.8 课后作业

……

8.12 HttpServletResponse

8.12.1 HttpServletResponse 介绍

  1. 每次 HTTP 请求, Tomcat 会创建一个 HttpServletResponse 对象传递给 Servlet 程序去使用。

  2. HttpServletRequest 表示请求过来的信息HttpServletResponse 表示所有响应的信息,如果需要设置返回给客户端的信息,通过 HttpServletResponse 对象来进行设置即可

8.12.2 HttpServletResponse 类图

image-20230711152642270

8.12.3 向客户端返回数据方法

image-20230711152728428

  1. 字节流 getOutputStream(); 常用于下载(处理二进制数据)
  2. 字符流 getWriter(); 常用于回传字符串
  3. (细节:)两个流同时只能使用一个。 使用了字节流,就不能再使用字符流,反之亦然,否则就会报错

8.12.4 向客户端返回数据应用实例

  1. 需求: 浏览器请求 , 返回 hello, world

……

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

  1. 处理中文乱码问题-方案 1
image-20230711153139236
  1. 处理中文乱码问题-方案 2(常用,方便)
image-20230711153202800

8.12.6 请求重定向

8.12.6.1 请求重定向介绍

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

  2. 请求重定向原理示意图

image-20230711153900776

8.12.6.2 请求重定向应用实例

  1. 需 求 : 演示请求重定向的使用当访问 DownServlet 下 载 文 件 , 重定向到DownServletNew 下载文件
1
2
3
4
5
6
7
8
9
//1. sendRedirect 本质就会 返回 302 状态码 Location: /servlet/downservletNew
//2.因此 302和/servlet/downservletNew 是浏览器解析,而不是服务器
//3.浏览器在解析 /servlet/downservletnew => http://localhost:8080/downServletNew

//方法1:
response.sendRedirect("/servlet/downServletNew");
//方法2:
response.setStatus(302);
response.setHeader("Location","/servlet/downServletNew");

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

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

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

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

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

  5. 可以重定向到 Web 工程以外的资源, 比如 到 www.baidu.com

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

  7. 动态获取到 application context(这个是指该项目在tomcat上运行时的项目名称,重定向时每次都需要填上,因此在修改时会比较麻烦,我们可以采取动态获取的方式简化它

    使用方法:getServletContext().getContextPath()获取即可)

8.12.7 课后作业

  1. 编写一个 MyPayServlet , 能够接收到提交的数据

  2. 编写一个简单的支付页面 pay.html(如图)

  3. 如果支付金额大于 100, 则重定向到 payok.html, 否则重定向到原来的 pay.html

​ ……

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

10.1 先看一个小案例, 引出对 Tomcat 底层实现思考

10.1.1 完成小案例

image-20230712184414696
  1. 我们准备使用 Maven 来创建一个 WEB 项目, 老师先简单给小伙伴介绍一下 Maven 是什么, 更加详细的使用,我们还会细讲, 现在先使用一把
image-20230712184444044 image-20230712184525975
  1. 先创建一个 Maven 的 Web 项目 hsp-tomcat
image-20230712185354190
  • 配置阿里 maven 镜像

​ 在maven的settings.xml文件中修改mirror标签中的内容即可

  1. 修改 D:\java_projects2\hsp\pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.donn</groupId>
<artifactId>mytomcat</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>mytomcat Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<!--1.引入javax.servlet-api.jar,为了开发servlet
2.<dependency>标签表示引入一个包
3.groudId:包的公司信息:javax.servlet
4.artifactId:项目名:javax.servlet-api
groupId+artifactId 将以目录形式体现(在仓库中组成jar包路径
5.version:版本信息
6.scope:表示引入包的作用范围
在什么地方、在什么阶段生效
<scope>provided</scope> 表示tomcat本身有jar包,
在编译、测试时有效,但是打包发布后不要带上这个包
(意思就是打包时,项目会有自带的该jar包,
但是编译、测试时没有,provided指只在编译测试时添加该jar包)
7.下载的包在指定的本地仓库
-->

<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>

</dependencies>
<build>
<finalName>mytomcat</finalName>
</build>
</project>
  1. 创建 D:\java_projects2\hsp\src\main\webapp\cal.html
1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>计算器</title></head>
<body>
<h1>计算器</h1>
<form action="/calServlet" method="get">
num1:<input type="text" name="num1"><br/>
num2:<input type="text" name="num2"><br/>
<input type="submit" value="提交"></form>
</body>
</html>
  1. 创建 java 目录,存放 java 源文件.

  2. 创建 CalServlet.java

  3. 修改 web.xml , 配置 Servlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>CalServlet</servlet-name>
<servlet-class>com.donn.servlet.CalServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>CalServlet</servlet-name>
<url-pattern>/calServlet</url-pattern>
</servlet-mapping>
</web-app>
  1. 修改CalServlet.java, 完成计算任务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.donn.servlet; /**
* @author 浦原
* 2023/7/12
* 22:27
* @version 1.0
*/

import com.donn.utils.WebUtils;

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

public class CalServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request,response);
}

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//接受提交的数据进行计算
String num1 = request.getParameter("num1");
String num2 = request.getParameter("num2");
int i1 = WebUtils.parseInt(num1, 0);
int i2 = WebUtils.parseInt(num2, 0);
int res = i1 + i2;
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.print("<h1>" + i1 + "+" + i2 + "=" + res + "</h1>");
writer.flush();
writer.close();
}
}
  1. 创 建 工 具 类\utils\WebUtils.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.donn.utils;

/**
* @author 浦原
* 2023/7/12
* 22:38
* @version 1.0
*/
public class WebUtils {

/**
* 将一个字符串数字,转成 int, 如果转换失败,就返回传入 defaultVal
* @param strNum
* @param defaultVal
* @return
*/
public static int parseInt(String strNum, int defaultVal) {

try {
return Integer.parseInt(strNum);
} catch (NumberFormatException e) {
System.out.println(strNum + " 格式不对,转换失败");
}

return defaultVal;
}


}

10.1.3 完成测试

10.1.3.1 配置 tomcat

10.1.3.2 启动 tomcat

10.1.3.3 浏览器访问: http://localhost:8080/cal.html

10.1.4 思考问题: Tomcat 底层实现 和 调用到 Servlet 流程?

10.1.5 我们的目标:

不用 Tomcat, 不用系统提供的 Servlet, 模拟 Tomcat 底层实现并能调用我们自己设计的 Servle, 也能完成相同的功能

10.2 🌟Tomcat 整体架构分析

10.2.1 一图胜千言

● 说明: Tomcat 有三种运行模式(BIO, NIO, APR) , 因为老师核心讲解的是 Tomcat 如何接收客户端请求, 解析请求, 调用 Servlet , 并返回结果的机制流程, 采用 BIO 线程模型来模拟.[绘图]

image-20230713085315598

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

10.4.1 实现任务阶段 1- 编写自己 Tomcat, 能给浏览器返回 Hi, Hspedu

10.4.1.1 基于 socket 开发服务端-流程

image-20230713090001942

10.4.1.2 需求分析

  1. 需求分析如图, 浏览器请求 http://localhost:8080/??, 服务端返回 hi , donn

10.4.1.3 分析+代码实现

● 分析示意图

image-20230713093415805

● 代码实现

  1. 创 建DonnTomcatV1.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.donn.tomcat;

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

/**
* @author 浦原
* 2023/7/13
* 09:09
* @version 1.0
* 接受客户端请求,并返回相关信息
*/
public class DonnTomcatV1 {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8081);
System.out.println("mytomcat在8081端口监听");
while (!serverSocket.isClosed()){
//等待浏览器的连接
//如果有连接来,就创建一个socket
//这个socket就是服务器和浏览器连接的通道
Socket socket = serverSocket.accept();
//先接受浏览器发送的数据
//inputStream是字节流 =BufferedReader为字符流
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;
}
System.out.println(mes);
}
//tomcat回送httpResponse
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, donn";
System.out.println("========我们的 tomcat 给浏览器会送的数据======");
System.out.println(resp);
outputStream.write(resp.getBytes());
//将 resp 字符串以 byte[] 方式返回
outputStream.flush();
outputStream.close();
inputStream.close();
socket.close();
}

}
}

10.4.1.4 测试 浏览器 : http://localhost:8081/

10.4.1.5 问题分析: 没有使用 BIO 线程模型, 没有实现多线程, 性能差

10.4.2 实现任务阶段 2__ 使用 BIO 线程模型, 支持多线程

10.4.2.1 BIO 线程模型介绍

image-20230713093624025

10.4.2.2 需求分析/图解

  1. 需求分析如图, 浏览器请求 http://localhost:8081, 服务端返回 hi , donn, 后台donntomcat 使用 BIO 线程模型,支持多线程=> 对前面的开发模式进行改造

10.4.2.3 分析+代码实现

示意图:

image-20230713100725181

创建线程类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.donn.tomcat.handler;

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

/**
* @author 浦原
* 2023/7/13
* 09:41
* @version 1.0
* DonnRequestHandler对象是一个线程对象
* 处理一个http请求
*/
public class DonnRequestHandler implements Runnable{
private Socket socket = null;

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

@Override
public void run() {
//在这里,我们对客户端进行交互
try {
InputStream inputStream = socket.getInputStream();
//转换成字符流,方便按行读取
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
System.out.println("==donnTomcatV2接收到的数据如下==");
String mes = null;
//输出消息
while (((mes = bufferedReader.readLine()) != null)){
if(mes.length() == 0){
break;
}
System.out.println(mes);
}
//构建http响应头
String respHeader = "HTTP/1.1 200 OK\r\n" +
"Content-Type: text/html;charset=utf-8\r\n\r\n";
String resp = respHeader + "<h1>bye~ donn</h1>";
System.out.println("V2返回的数据");
//返回数据给我们的浏览器 -> 封装成http响应
OutputStream outputStream = socket.getOutputStream();
outputStream.write(resp.getBytes());
outputStream.flush();
outputStream.close();
inputStream.close();
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
if (socket != null){
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

}

}

调用线程类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.donn.tomcat;

import com.donn.tomcat.handler.DonnRequestHandler;

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

/**
* @author 浦原
* 2023/7/13
* 09:55
* @version 1.0
*/
public class DonnTomcatV2 {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8081);
System.out.println("myTomcatV2在8081端口监听");
while (!serverSocket.isClosed()){
//等待浏览器的连接
//如果有连接来,就创建一个socket
//这个socket就是服务器和浏览器连接的通道
Socket socket = serverSocket.accept();
new Thread(new DonnRequestHandler(socket)).start();

}
}
}

10.4.2.5 问题分析: HspTomcat 只是简单返回结果, 没有和 Servlet、 web.xml 关联

10.4.3 实现任务阶段 3- 处理 Servlet

10.4.3.1 Servlet 生命周期-回顾

image-20230713101011930

10.4.3.2 需求分析/图解

● 需求分析如图, 浏览器请求 http://localhost:8080/hspCalServlet, 提交数据, 完成计算任务, 如果 servlet 不存在, 返回 404

10.4.3.3 分析+代码实现

● 分析示意图

image-20230713101359727

image-20230713101431086

实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package com.donn.tomcat.http;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;

/**
* @author 浦原
* 2023/7/13
* 10:19
* @version 1.0
* 1. DonnRequest的作用是封装http请求的数据
* 2. 比如method(GET)、uri(资源名)、参数列表(num1=10&num2=30)
* 3. DonnRequest的作用等价原生servlet中的HttpServletRequest
* 4. 这里先只考虑get请求
*/
public class DonnRequest {
/**
* 这三个参数都存在于http请求中的第一行中
*/
private String method;
private String uri;
/**
* 存放参数列表
*/
private HashMap<String,String> parametersMapping = new HashMap<>();


public String getParameter(String name) {
if(parametersMapping.containsKey(name)) {
return parametersMapping.get(name);
}else{
return "";
}
}

public String getMethod() {
return method;
}

/**
* 构造器
* inputStream与http请求的socket关联
*/
public DonnRequest(InputStream inputStream){
try {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
//读取第一行
String readLine = bufferedReader.readLine();
method = readLine.split(" ")[0];
int index = readLine.split(" ")[1].indexOf('?');
if (index == -1){
//说明没有参数列表
uri = readLine.split(" ")[1];
}else{
uri = readLine.split(" ")[1].substring(0,index);
//获取参数列表
String parameters = readLine.split(" ")[1].substring(index + 1);
String[] parametersPair = parameters.split("&");
if(parametersPair != null){
for (String parameter :parametersPair) {
String[] split = parameter.split("=");
if(split.length == 2){
parametersMapping.put(split[0],split[1]);
}
}

}
}
//这里不能关闭,inputStream和socket关联
// inputStream.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

回顾 Tomcat 工作架构图

image-20230713101359727

11 WEB 工程路径专题

11.3 WEB 工程路径注意事项和细节

11.3.1 注意事项和细节说明

  1. Web 工程的相对路径和绝对路径:

相对路径:

● .表示当前目录

● ..表示上一级目录

● 资源名 表示当前目录的资源名

绝对路径: http://ip:port/工程路径/资源路径

  1. 在实际开发中, 路径都使用绝对路径, 而不是相对路径

  2. 在 web 中 / 斜杠 如果被浏览器解析, 得到的地址是: http://ip[域名]:port/

    比如: <ahref=”/“>斜杠

  3. 在 web 中 / 斜杠如果被服务器解析,得到的地址是: **http://ip[域名]:port/工程路径/**,你也可以理解成 /工程路径/

    下面的几种情况就是如此:

​ ● /servelturl

​ ● servletContext.getRealPath(“/“); ==> 是得到执行路径/工作路径

​ ● request.getRequestDispatcher(“/“);

  1. 在 javaWeb 中 路径最后带 / 和不带 / 含义不同, 一定要小心,比如

    < a href=”/a/servlet03”>网址 中的servlet03 表示资源

    < a href=”/a/servlet03/“>网址 中的servlet03 表示路径

  2. 🌟特别说明: 重定向 response.sendRediect(“/“); 这条语句虽然是在服务器执行的, 但是,服务器是把斜杠 / 发送给浏览器解析。 因此得到地址 http://ip[域名]:port/

小结: 在编写资源路径时 , 考虑这么几点

(1) 这个路径前面有没有 /

(2) 这个路径在哪里被解析 [服务器还是浏览器] , 如果前面有 / , 并且是在 浏览器被解析的 被解析成 http://ip:port/ , 如果在服务器端被解析 , 被解析成 /工程路径/

(3) 如果这个路径, 前面没有 / , 并且在浏览器被解析, 则以浏览器当前的地址栏 去掉资源部分, 作为一个相对路径,再拼接这条路径.

(4) 这个路径, 最后有没有 / , 如果最后有/ 表示路径, 如果没有 / 表示资源

12 Web 开发会话技术 Cookie&Session

12.1 会话

12.1.1 基本介绍

  1. 什么是会话?

    ​ 会话可简单理解为:用户开一个浏览器,点击多个超链接,访问服务器多个 web 资源,然后关闭浏览器,整个过程称之为一个会话。

  2. 会话过程中要解决的一些问题?

    每个用户在使用浏览器与服务器进行会话的过程中,不可避免各自会产生一些数据,服务器要想办法为每个用户保存这些数据

    例如:多个用户点击超链接通过一个 servlet 各自购买了一个商品,服务器应该想办法把每一个用户购买的商品保存在各自的地方,以便于这些用户点结帐 servlet 时,结帐servlet 可以得到用户各自购买的商品为用户结帐

12.1.2 会话的两种技术

12.1.2.1 Session

思考问题 1-抛砖引玉☞

​ 大家在访问某个网站的时候,是否能看到提示你上次登录网站的时间,而且要注意的是不同用户上次登录的时间肯定是不一样的,这是怎么实现的?

思考问题 2-抛砖引玉☞

​ 大家在访问某个购物网站的时候,是否能看到提示你曾经浏览过的商品,不同用户浏览过的商品肯定不一样,这是怎么实现的?

解决之道—cookie 技术

​ Cookie(小甜饼)是客户端技术,服务器把每个用户的数据以 cookie 的形式写给用户各自的浏览器。当用户使用浏览器再去访问服务器中的 web 资源时,就会带着各自的数据去。这样,web 资源处理的就是用户各自的数据了。【简单示意图】

image-20230713193537687

  1. Cookie 是服务器在客户端保存用户的信息, 比如登录名, 浏览历史等, 就可以以 cookie方式保存.
  2. Cookie 信息就像是小甜饼(cookie 中文)一样,数据量并不大,服务器端在需要的时候可以从客户端/浏览器读取(http 协议),可以通过图来理解
image-20230713193754793 image-20230713194449881
  1. 再次说明: cookie 数据是保存在浏览器的
  1. 保存上次登录时间等信息

  2. 保存用户名,密码, 在一定时间不用重新登录

  3. 网站的个性化,比如定制网站的服务,内容

12.4.1 文档: java_ee_api_中英文对照版.chm

  1. Cookie 有点象一张表(K-V), 分两列, 一个是名字, 一个是值, 数据类型都是 String

  2. 如何创建一个 Cookie(在服务端创建的)

​ Cookie c=new Cookie(String name,String val); c.setMaxAge();//保存时间

  1. 如何将一个 Cookie 添加到客户端

    response.addCookie(c);

  2. 如何读取 cookie(在服务器端读取到 cookie 信息)

    request.getCookies();

12.7.1 介绍

  1. Cookie 的生命周期指的是如何管理 Cookie 什么时候被销毁(删除)
  2. setMaxAge()

​ ● 正数,表示在指定的秒数后过期 过期后,浏览器不会删除该cookie,只是不再携带该cookie

​ ● 负数,表示浏览器关闭, Cookie 就会被删除(默认值是-1)

​ ● 0, 表示马上删除 Cookie 设置为0后,浏览器会把该cookie直接删除

12.8.1 有效路径规则

  1. Cookie 有效路径 Path 的设置
  2. Cookie 的 path 属性可以有效的过滤哪些 Cookie 可以发送给服务器,哪些不发。 path属性是通过请求的地址来进行有效的过滤
  3. 规则如下:
image-20230714164006880

没设置默认为项目工程路径

  1. 一个 Cookie 只能标识一种信息, 它至少含有一个标识该信息的名称(NAME) 和设置值(VALUE) 。
  2. 一个 WEB 站点可以给一个浏览器发送多个 Cookie,一个浏览器也可以存储多个 WEB 站点提供的 Cookie。
  3. cookie 的总数量没有限制,但是每个域名的 COOKIE 数量和每个 COOKIE 的大小是有限制的 (不同的浏览器限制不同, 知道即可) , Cookie 不适合存放数据量大的信息
  4. 注意,删除 cookie 时,path 必须一致,否则不会删除
  5. Java servlet 中 cookie 中文乱码解决

​ 如果存放中文的 cookie, 默认报错, 可以通过 URL 编码和解码来解决, 不建议存放中文的 cookie 信息

1
2
3
4
5
6
7
8
9
10
11
//通过将中文转化成utf-8形式的String,便可以将转化的结果放入cookie中了
String urlName = URLEncoder.encode("韩顺平教育", "utf-8");

//但是这样的话,在获取cookie中信息时,我们对读出的cookie数据进行解码,从而转换成中文


Cookie[] cookies = request.getCookies();
//处理的中文乱码问题
for (Cookie cookie : cookies) {
writer.println( cookie.getName()+ URLDecoder.decode(cookie.getValue(),"utf-8"));
}

12.11 session 有什么用

思考两个问题—抛砖引玉

  1. 不同的用户登录网站后,不管该用户浏览该网站的哪个页面,都可显示登录人的名字,还可以随时去查看自己的购物车中的商品, 是如何实现的?
  2. 也就是说,一个用户在浏览网站不同页面时,服务器是如何知道是张三在浏览这个页面,还是李四在浏览这个页面?
  • 方案1 : 可以把每个用户的信息都存到服务器的一个数据库中,再用一个‘🔑’来确定哪个用户对应db中的哪条记录
  • 方案2 : 也可以将用户信息直接保存到cookie中,但是当切换用户后cookie内容不变,不安全;而且cookie作为传输的数据容易泄露,不便于传输敏感信息

解决之道—session 技术

  1. Session 是服务器端技术,服务器在运行时为每一个用户的浏览器创建一个其独享的session 对象/集合( 其实相当于前面说的方案1 )

  2. 由于 session 为各个用户浏览器独享,所以用户在访问服务器的不同页面时,可以从各自的 session 中读取/添加数据, 从而完成相应任务

12.12 session 基本原理

12.12.1 Sesson 原理示意图

image-20230714220951863
  1. 当用户打开浏览器,访问某个网站, 操作 session 时,服务器就会在内存(在服务端)为该浏览器分配一个 session 对象,该 session 对象被这个浏览器独占, 如图⬆️
  2. 这个 session 对象也可看做是一个容器/集合,session 对象默认存在时间为 30min(这是在tomcat/conf/web.xml),也可修改
image-20230714221054233

12.12.2 Session 可以做什么

  1. 网上商城中的购物车
  2. 保存登录用户的信息
  3. 将数据放入到 Session 中, 供用户在访问不同页面时, 实现跨页面访问数据
  4. 防止用户非法登录到某个页面
  5. …..

12.12.3 如何理解 Session

  1. session 存储结构示意图
image-20230714221456503
  1. 你可以把 session 看作是一容器类似 HashMap,有两列(K-V),每一行就是 session 的一个属性。
  2. 每个属性包含有两个部分,一个是该属性的名字(String),另外一个是它的值(Object)

12.13 session 常用方法

12.13.1 网上去找文档

12.13.2 Session 的基本使用

  1. 创建/获取 Session, API 一样

    • HttpSession session = request.getSession();
    • 第 1 次调用是创建 Session 会话, 之后调用是获取创建好的 Session 对象
  2. 向 session 添加属性session.setAttribute(String name,Object val);

  3. 从 session 得到某个属性Object obj=session.getAttribute(String name);

  4. 从 session 删除调某个属性:

    • hs.removeAttribute(String name);
  5. isNew(); 判断是不是刚创建出来的 Session

  6. 每个 Session 都有 1 个唯一标识 Id 值。 通过 getId() 得到 Session 的会话 id 值,
    即cookie中的JSESSIONID

12.14 session 底层实现机制

12.14.1 原理分析图(一图胜千言)

● session 底层实现机制图解(重要)

image-20230714222324161 image-20230714222400458

如果在本次会话中,服务器为浏览器创建了session空间(说明产生了一组新的「sessionId + session体」的结构出现),那么服务器会让浏览器将set sessionid信息放入cookie中

12.14.3 Session 实现原理动画

● 服务器是如何实现一个 session 为一个用户浏览器服务的

image-20230715092237931

12.15 session 生命周期

12.15.1 Session 生命周期-说明

  1. public void setMaxInactiveInterval(int interval) 设置 Session 的超时时间(以为单位) ,超过指定的时长, Session 就会被销毁。

  2. 值为正数的时候,设定 Session 的超时时长。

  3. 负数表示永不超时

  4. public int **getMaxInactiveInterval()**获取 Session 的超时时间

  5. public void invalidate() 让当前 Session 会话立即无效

  6. 如果没有调用 setMaxInactiveInterval() 来指定 Session 的生命时长,Tomcat 会以 Session默认时长为准,Session 默认的超时为 30 分钟, 可以在 tomcat 的 web.xml 设置

  7. Session 的生命周期指的是 :客户端/浏览器两次请求最大间隔时长,而不是累积时长。**即当客户端访问了自己的 session,session 的生命周期将从 0 开始重新计算。**(解读: 指的是同一个会话两次请求之间的间隔时间)

  8. 🌟底层: Tomcat 用一个线程来轮询会话状态,如果某个会话的空闲时间超过设定的最大值,则将该会话销毁

14 JavaWeb 三大组件之监听器 Listener

14.1 官方文档

14.1.1 文档……

14.2 Listener 监听器介绍

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

  2. Listener 是 JavaEE 的规范,就是接口

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

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

14.3 JavaWeb 的监听器

14.3.1 ServletContextListener 监听器

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

    1. 加载初始化的配置文件; 比如 spring 的配置文件
    2. 任务调度(配合定时器 Timer/TimerTask)
  2. 相关方法

1
2
void contextInitialized(ServletContextEvent sce) 	//创建 Servletcontext 时触发 
void contextDestroyed(ServletContextEvent sce) //销毁 Servletcontext 时触发
  1. 应 用 实 例 创 建 MyServletContextListener.java
1
2
3
4
5
6
7
8
9
10
public class MyServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent{
System.out.println("ServletContext 创建,完成 WEB 项目初始化的工作..");
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent){
System.out.println("ServletContext 销毁, 完成资源回收工作..");
}
}
  1. 在 web.xml 配置 MyServletContextListener

14.3.2 ServletContextAttributeListener 监听器

  1. 作用: 🌟监听 ServletContext 属性变化
  2. 相关方法
1
2
3
void attributeAdded(ServletContextAttributeEvent event) 		//添加属性时调用
void attributeReplaced(ServletContextAttributeEvent event) //替换属性时调用
void attributeRemoved(ServletContextAttributeEvent event) //移除属性时调用
  1. 使用少,再给大家举个例(后面的监听器类似)

创 建MyServletContextAttributeListener.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MyServletContextAttributeListener implements ServletContextAttributeListener {

@Override
public void attributeAdded(ServletContextAttributeEvent servletContextAttributeEvent) {
System.out.println("添加了 servletContext 属性名= "+servletContextAttributeEvent.getName() + " 属 性 值 =" +servletContextAttributeEvent.getValue());
}
@Override
public void attributeRemoved(ServletContextAttributeEvent servletContextAttributeEvent){
System.out.println("删除了 servletContext 属性");
}
@Override
public void attributeReplaced(ServletContextAttributeEvent servletContextAttributeEvent){
System.out.println("替换了 servletContext 属性 ");
}
}

14.3.3 其它监听器-使用较少

14.3.3.1 HttpSessionListener 监听器

  1. 作用: 🌟监听 Session 创建或销毁, 即生命周期监听
  2. 相关方法
1
2
void sessionCreated(HttpSessionEvent se)  	//创建 session 时调用 
void sessionDestroyed(HttpSessionEvent se) //销毁 session 时调用
  1. 使用方法和前面一样, 可以用于监控用户上线,离线

14.3.3.2 HttpSessionAttributeListener 监听器

  1. 作用: 🌟监听 Session 属性的变化
  2. 相关方法
1
2
3
void attributeAdded(ServletRequestAttributeEvent srae) 				//添加属性时
void attributeReplaced(ServletRequestAttributeEvent srae) //替换属性时
void attributeRemoved(ServletRequestAttributeEvent srae) //移除属性时
  1. 使用少 , 使用方法和前面一样。

14.3.3.3 ServletRequestListener 监听器

  1. ServletRequestListener 监听器

  2. 作用:监听 Request 创建或销毁,即 Request 生命周期监听

  3. 🌟可以用来监控, 某个 IP 访问我们网站的频率, 日志记录 ,访问资源的情况

14.3.3.4 ServletRequestAttributeListener 监听器

  1. 作用: 监听 Request 属性变化

……

14.3.3.5 HttpSessionBindingListener 感知监听器

……

14.3.3.6 HttpSessionActivationListener 感知监听器

……

15 JavaWeb 三大组件之 过滤器 Filter

15.1 官方文档

15.1.1 文档: ……

15.2 Filter 过滤器说明

15.2.1 为啥要过滤器-需求示意图

● 一图胜千言

image-20230715181935717

15.2.2 过滤器介绍

  1. Filter 过滤器它是 JavaWeb 的三大组件之一(Servlet 程序、 Listener 监听器、 Filter 过滤器)
  2. Filter 过滤器是 JavaEE 的规范,是接口image-20230715182105900
  3. Filter 过滤器它的作用是:拦截请求,过滤响应。
  4. 应用场景
  • 权限检查
  • 日志操作
  • 事务管理

15.3 Filter 过滤器基本原理

● 一图胜千言

image-20230715182618162

15.4 Filter 过滤器快速入门

● 需求: 在 web 工程下, 有后台管理目录 manage, 要求该目录下所有资源(html、 图片、jsp 、 Servlet 等) 用户登录后才能访问

听老师说明, 完成模块的套路/流程[多年的体会]

  1. 先完成一个正确的流程-看到一个效果-> 写后面代码就可以验证
  2. 加入其它的功能[加入 session,验证合法性]
  3. 完善功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
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()被调用..");
}
}

●在 web.xml 配置过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--老师解读:filter一般写在其它servlet的前面
1. 观察我们发现filter 配置和 servlet 非常相似. filter也是被tomcat管理和维护
2. url-pattern 就是当请求的url 和 匹配的时候,就会调用该filter
3. /manage/* 第一个 / 解析成 http://ip:port/工程路径
4. 完整的路径就是 http://ip:port/工程路径/manage/* 当请求的资源url满足该条件时
都会调用filter , /manage/admin.jsp
-->
<filter>
<filter-name>ManageFilter</filter-name>
<filter-class>com.hspedu.filter.ManageFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ManageFilter</filter-name>
<url-pattern>/manage/*</url-pattern>
</filter-mapping>

15.5 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 过滤器它只关心请求的地址是否匹配, 不关心请求的资源是否存在

15.6 Filter 过滤器生命周期

● Filter 生命周期图解

image-20230716085623113

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
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()被调用..");
}
}

🌟 如果后面的请求目标资源(jsp,servlet..) 会使用到request 和 response,那么会继续传递——即使访问的url不同了,但是只要还是被这个filter拦截,那么就是传递相同的request 和 response

15.7 FilterConfig

● FilterConfig 接口图

image-20230716090922457

● FilterConfig 说明

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

● 应用实例 FilterConfig_.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
* 老师解读: 演示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() {

}
}
  1. 配置 web.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<filter>
<filter-name>HspFilterConfig</filter-name>
<filter-class>com.hspedu.filter.HspFilterConfig</filter-class>
<!--这里就是给该filter配置的参数-有程序员根据业务逻辑来设置-->
<init-param>
<param-name>ip</param-name>
<param-value>127.0</param-value>
</init-param>
<init-param>
<param-name>port</param-name>
<param-value>8888</param-value>
</init-param>
<init-param>
<param-name>email</param-name>
<param-value>hsp@sohu.com</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>HspFilterConfig</filter-name>
<url-pattern>/abc/*</url-pattern>
</filter-mapping>

15.8 FilterChain 过滤器链

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

15.8.2 基本原理示意图

image-20230716093408721

15.8.3 应用实例 AFilter.java BFilter.java hi.jsp web.xml

  1. 需求: 演示过滤器链的使用

image-20230716093621006

image-20230716094326928

15.8.4 FilterChain 注意事项和细节

  1. 多个 filter 和目标资源一次 http 请求在同一个线程中

  2. 当一个请求 url 和 filter 的 url-pattern 匹配时, 才会被执行, 如果有多个匹配上, 就会顺序执行, 形成一个 filter 调用链(底层可以使用一个数据结构搞定)

  3. 多个 filter 共同执行时,因为是一次 http 请求, 使用同一个 request 对象

  4. 多个 filter 执行顺序和 web.xml 配置顺序保持一致.

  5. chain.doFilter(req, resp)方法 将执行下一个过滤器的 doFilter 方法, 如果后面没有过滤器,则执行目标资源。

小结: 注意执行过滤器链时, 顺序是(用前面的案例分析) Http请求 -> A 过滤器 dofilter()-> A 过滤器前置代码 -> A 过滤器 chain.doFilter() -> B 过滤器 dofilter() -> B 过滤器前置代码 -> B过滤器 chain.doFilter() -> 目标文件 -> B过滤器后置代码 -> A过滤器后置代码 ->返回给浏览器页面/数据

🌟后置代码指:过滤器中 doFilter()方法中,chain.doFilter()之后的代码

18 线程数据共享和安全 -ThreadLocal

18.1 什么是 ThreadLocal

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

18.2 快速入门 ThreadLocal

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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();//主线程启动一个新的线程,注意不是主线程
}
}

18.3 ThreadLocal 源码解读+画图

image-20230716181639550

  1. 老韩说明: 这里涉及到的弱引用,涉及到知识点很多,暂不深入

一个Thread有一个 threadLocals(TheadLocalMap类型) ,threadLocals中有一个table,table中有许多桶,每个桶都是Entry类型,(Entry的key是ThreadLocal类型,value是你存进去的数据)

⚠️疑问:

!!!threadLocals中只有一个table,table中存着该线程存放的许多Entry,问:为什么不把Entry直接放在threadLocals下面,而是要多加一层table?

​ 分层、用于未来扩展吧?