1. 基本概念

1.1 前言

  • 静态Web:

    • 提供给所有人看数据不会发生变化!
    • HTML,CSS
  • 动态Web:

    • 有数据交互,登录账号密码,网站访问人数等
    • 技术栈:Servlet/JSP,ASP,PHP
  • 在Java中,动态web资源开发的技术统称为JavaWeb

1.2 Web 应用程序

  • Web 应用程序:可以提供浏览器访问的程序;
  • 这个统一的web资源会被放在同一个文件夹下,Web 应用程序 —> Tomcat:服务器

    • 一个 Web 应用由多部分组成(静态Web、动态Web)

      • HTML,CSS,JavaScript
      • JSP,Servlet
      • Java 程序
      • jar 包
      • 配置文件 (Properties)
  • Web 应用程序编写完毕后,若想提供给外界访问:需要一个服务器来统一管理;

1.3 动态 Web 的访问过程

image-20230621183154641

浏览器发送 HTTP 请求,服务器 Tomcat 接收请求,Servlet 容器从磁盘加载 Servlet 程序处理请求 request ,处理结束返回 response。

2. Web 服务器

2.1 技术讲解

  • ASP - Active Server Pages
    • 微软,国内最早流行
    • 在HTML中嵌入了VB的脚本,ASP+COM
    • 基本页面长且混乱(美化和功能相互嵌套) —> 维护成本高
    • 语言:C#
    • 外部服务器:IIS
  • PHP:
    • 作为开发速度很快,功能很强大,跨平台
    • 无法承载大访问量的情况
  • JSP/Servlet:
    • B/S;浏览和服务器
    • sun公司主推的B/S架构
    • 基于Java语言的(所有的大公司,或者一些开源的组件,都是用Java写的)
    • 可以承载三高问题带来的影响;
    • 语法像ASP,ASP->JSP,加强市场强度;

3. Tomcat

Tomcat Home

Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目。

Tomcat 技术先进、性能稳定,而且免费

Tomcat 服务器是一个免费的开放源代码的Web应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP程序的首选。对于一个Java初学web的人来说,它是最佳的选择

3.1 安装 Tomcat

  1. 安装网址:Apache Tomcat® - Apache Tomcat 10 Software Downloads
  2. 解压

3.2 Tomcat 启动和配置

image-20230621184616721

  • 打开:bin/startup.bat -> http://localhost:8080/
  • 关闭:bin/shutdown.bat
  • 闪退问题:注意版本匹配问题 Version? - apache-tomcat-9.0.76(可行)
  • 乱码问题:配置文件中设置 修改 conf/logging.properties 中的 java.util.logging.ConsoleHandler.encoding = GBK 解决乱码问题

3.3 配置

  • 配置启动的端口号

    • tomcat的默认端口号为:8080

      • mysql:3306
      • http:80
      • https:443
    • ```xml
      <Connector port=”8080” protocol=”HTTP/1.1”
        connectionTimeout="20000"
        redirectPort="8443" />
      
      1
      2
      3
      4
      5
      6
      7
      - 配置主机名称

      - 默认的主机名为:localhost->127.0.0.1
      - 默认网站应用存放的位置为:webapps
      - ```xml
      <Host name="www.aoyang.com" appBase="webapps"
      unpackWARs="true" autoDeploy="true">
  • 域名访问流程

    • 在这里插入图片描述

3.4 发布一个 Web 网站

  • image-20230624220731902
  • 网站的结构

    • ```sh
      —webapps :Tomcat服务器的web目录
      -ROOT
      -project_name :网站的目录名
          - WEB-INF
              -classes : java程序
              -lib:web应用所依赖的jar包
              -web.xml :网站配置文件
          - index.html 默认的首页
          - static 
              -css
                  -style.css
              -js
              -img
           -.....
      
      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

      # 4. HTTP

      ## 4.2 什么是 HTTP?

      > (超文本传输协议)是一个简单的请求-响应协议,它通常运行在TCP之上。
      >
      > - 文本:html,字符串,…
      > - 超文本:图片,音乐,视频,定位,地图.……
      > - 端口:80
      >
      > https: 安全的!- 443

      ## 4.2 两个时代

      - HTTP/1.0:客户端可以与web服务器连接后,只能获得一个web资源,断开连接
      - HTTP/1.1:客户端可以与web服务器连接后,可以获得多个web资源

      ## 4.3 HTTP 请求

      ![image-20230624221520642](https://s2.loli.net/2023/06/24/EpSR746ed9uhJfK.png)

      ```sh
      请求 URL: https://www.baidu.com/home 请求地址
      请求方法: GET 请求方法(GET/POST)
      状态代码: 200 OK 状态码
      远程地址: 127.0.0.1:7890
      引用者策略: origin-when-cross-origin

4.3.1 请求行

  • 请求行中的请求方式:GET
  • 请求方式:Get, Post, HEAD, DELETE, PUT, TRACT.…
    • get:请求能够携带的参数比较少,大小有限制,会在浏览器的URL地址栏显示数据内容,不安全,但高效
    • post:请求能够携带的参数没有限制,大小没有限制,不会在浏览器的URL地址栏显示数据内容,安全,但不高效

4.3.2 消息头

  • 消息头/请求标头

    • ```sh
      Accept: text/html,… 支持数据类型
      Accept-Encoding: gzip, deflate, br 支持编码格式
      Accept-Language: zh-CN,zh;q=0.9,en-GB;q=0.8,en;q=0.7,en-US;q=0.6 语言环境
      Cache-Control: max-age=0 缓存控制
      Connection: keep-alive 连接方式
      HOST: … 主机
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15

      ## 4.4 HTTP 响应

      ```sh
      HTTP/1.1 200 OK
      Cache-Control: private 缓存控制
      Connection: keep-alive 连接
      Content-Encoding: gzip 编码
      Content-Type: text/html;charset=utf-8 类型
      Date: Sat, 24 Jun 2023 14:14:36 GMT
      Expires: Sat, 24 Jun 2023 14:14:36 GMT
      Isprivate: 1
      Server: BWS/1.0
      Vary: Accept-Encoding
      Transfer-Encoding: chunked

4.4.1 响应体

1
2
3
4
5
6
7
8
Accept:告诉浏览器,它所支持的数据类型
Accept-Encoding:支持哪种编码格式 GBK UTF-8 GB2312 ISO8859-1
Accept-Language:告诉浏览器,它的语言环境
Cache-Control:缓存控制
Connection:告诉浏览器,请求完成是断开还是保持连接
HOST:主机..../.
Refresh:告诉客户端,多久刷新一次;
Location:让网页重新定位;

4.4.2 响应状态码

  • 200:请求响应成功200
  • 3xx:请求重定向
  • 4xx:找不到资源
    • 404:资源不存在
  • 5xx:服务器代码错误
    • 500
    • 502:网关错误

5. Maven

  • 在Javaweb开发中,需要使用大量的jar包,我们手动去导入
  • 如何能够让一个东西自动帮我导入和配置这个jar包。

5.1 Maven 项目架构管理工具

  • 核心思想:约定大于配置

  • 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
    44
    45
    46
    47
    48
    49
    <!-- servlet 依赖 -->
    <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    </dependency>
    <!-- JSP 依赖 -->
    <dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>jsp-api</artifactId>
    <version>2.2</version>
    </dependency>
    <!-- JSTL 依赖 -->
    <dependency>
    <groupId>javax.servlet.jsp.jstl</groupId>
    <artifactId>jstl-api</artifactId>
    <version>1.2</version>
    </dependency>
    <!-- JSTL 实现 -->
    <dependency>
    <groupId>taglibs</groupId>
    <artifactId>standard</artifactId>
    <version>1.1.2</version>
    </dependency>
    <!-- MySQL 依赖 -->
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.19</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.sun.mail/javax.mail -->
    <dependency>
    <groupId>com.sun.mail</groupId>
    <artifactId>javax.mail</artifactId>
    <version>1.6.2</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/javax.activation/activation -->
    <dependency>
    <groupId>javax.activation</groupId>
    <artifactId>activation</artifactId>
    <version>1.1.1</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.28</version>
    <scope>provided</scope>
    </dependency>
  • web.xml

    1
    2
    3
    4
    5
    6
    7
    8
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
    http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
    version="4.0"
    metadata-complete="true">
    </web-app>
  • CharacterEncodingFilter.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class CharacterEncodingFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    servletResponse.setCharacterEncoding("UTF-8");
    servletRequest.setCharacterEncoding("UTF-8");
    filterChain.doFilter(servletRequest, servletResponse); // 放行: 交给下一个过滤器
    }

    @Override
    public void destroy() {
    }
    }
  • SysFilter.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 SysFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    HttpServletRequest request = (HttpServletRequest) servletRequest;
    HttpServletResponse respone = (HttpServletResponse) servletResponse;

    // 过滤器, 从Session中获取用户
    User user = (User) request.getSession().getAttribute(Constants.USER_SESSION);

    if (user == null) { // Session失效, 用户不存在(注销或者未登录)
    // 已经被移除或者注销了, 或者未登录
    respone.sendRedirect("/error.jsp"); // 跳转到错误页面
    } else {
    filterChain.doFilter(servletRequest, servletResponse); // 放行
    }
    }

    @Override
    public void destroy() {
    }
    }
  • PageSupport.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
    57
    58
    59
    60
    61
    62
    63
    64
    public class PageSupport {
    // 当前页码
    private int currentPageNo = 1;

    // 总数量(表)
    private int totalCount = 0;

    // 页面容量
    private int pageSize = 0;

    // 总页数-totalCount/pageSize(向上取整)
    private int totalPageCount = 1;

    public int getCurrentPageNo() {
    return currentPageNo;
    }

    public void setCurrentPageNo(int currentPageNo) {
    if (currentPageNo > 0) {
    this.currentPageNo = currentPageNo;
    }
    }

    public int getTotalCount() {
    return totalCount;
    }

    public void setTotalCount(int totalCount) {
    if (totalCount > 0) {
    this.totalCount = totalCount;
    // 设置总页数
    this.setTotalPageCountByRs();
    }
    }

    public int getPageSize() {
    return pageSize;
    }

    public void setPageSize(int pageSize) {
    if (pageSize > 0) {
    this.pageSize = pageSize;
    }
    }

    public int getTotalPageCount() {
    return totalPageCount;
    }

    public void setTotalPageCount(int totalPageCount) {
    this.totalPageCount = totalPageCount;
    }

    // 设置总页数
    public void setTotalPageCountByRs() {
    if (this.totalCount % this.pageSize == 0) {
    this.totalPageCount = this.totalCount / this.pageSize;
    } else if (this.totalCount % this.pageSize > 0) {
    this.totalPageCount = this.totalCount / this.pageSize + 1;
    } else {
    this.totalPageCount = 0;
    }
    }
    }

5.2 下载安装 Maven

5.2.1 下载安装

5.2.2 配置环境变量

  • M2_HOME: E:\Coding\Java\apache-maven-3.9.2
  • MAVEN_HOME: E:\Coding\Java\apache-maven-3.9.2
  • path: %MAVEN_HOME%\bin

验证: mvn -version

image-20230624225103907

5.2.3 镜像设置

.\apache-maven-3.6.2\conf\ettings.xml

image-20230701174213342

1
2
3
4
5
6
<mirror>
<id>nexus-aliyun</id>
<mirrorOf>*,!jeecg,!jeecg-snapshots</mirrorOf>
<name>Nexus aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>

5.2.4 本地仓库

建立一个本地仓库:localRepository

  • 需要自己创建 maven-repo文件夹

.\apache-maven-3.6.2conf\ettings.xml

image-20230624225915279

1
<localRepository>E:\Coding\Java\apache-maven-3.9.2\maven-repo</localRepository>

5.3 在IDEA中使用Maven

  1. 启动 IDEA - 设置 Maven

    image-20230627224139128

  2. 添加

    • archetype-catalog
    • 创建 archetype-catalog.xml, 放到仓库根路径(E:\Coding\Java\apache-maven-3.9.2\maven-repo

      (待确定)修改 IDEA 设置

      image-20230627233602912

      -Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true

      image-20230627233649923

      ~DarchetypeCatalog=local

  3. 创建 Maven 项目

    image-20230627230710350

  4. 等待项目初始化完毕

    image-20230628211504502

    WARNING:

    image-20230628211543503

    • 5.3. 添加 archetype-catalog文件, 但实际并不起作用,可跳过(待解决)
    • .xml 文件远程下载失败

5.4 标记文件夹功能

image-20230628212820262

5.5 项目结构

image-20230628213059299

5.6 在 IDEA 中配置 Tomcat

  1. image-20230628213541866
  2. image-20230628213751310
  3. image-20230628214022786
  4. 即可运行

    image-20230628214517452

5.7 pom 文件

5.7.1 Maven内容

image-20230628214740961

5.7.2 pom.xml文件

image-20230628215223878

5.7.3 自动导入

  • Maven的高级之处:会自动导入JAR包所依赖的其他JAR

image-20230628215609967

5.7.4 无法导出/生效

maven由于 约定大于配置,可能会遇到配置文件无法导出或生效

解决方法:==在build中配置resources,来防止资源导出失败的问题==

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>

image-20230628220224751

5.8 IDEA 查看依赖树

image-20230628220412166

5.9 解决遇到的问题

5.9.1 报错

image-20230628220853356

5.9.2 默认web项目中web.xml版本问题

  • 替换为Tomcat默认的web.xml
    • image-20230628221611535
    • image-20230628221642487

5.9.3 Maven仓库

image-20230628221928470

  • 本地搜索,能搜索到 -> 导入
    • image-20230628221947389
  • 搜索不到 -> maven仓库Maven Repository
    • image-20230628222154322
    • image-20230628222221669
    • image-20230628222238671
    • image-20230628222354706

6. Servlet

6.1 Servlet简介

  • Servlet 就是sun公司开发==动态web==的一门技术
  • Sun在这些 API 中提供一个接口叫做:Servlet,如果你想开发一个Servlet程序,只需要完成两个小步骤:
    1. 编写一个,实现 Serlet接口
    2. 把开发好java类部署到web服务器中。
  • 把实现了 Servlet接口的Java程序叫做,==Servlet==

6.2 HelloServlet

  1. 创建一个普通的 Maven 项目,清空 src 目录,此空工程即为 Maven 主工程

    • 添加 Maven-Servlet 依赖

      • 直接搜索

        • ```xml
          <groupId>javax.servlet</groupId>
          <artifactId>javax.servlet-api</artifactId>
          <version>4.0.1</version>
          <scope>provided</scope>
          
          </dependency>
          <groupId>javax.servlet.jsp</groupId>
          <artifactId>javax.servlet.jsp-api</artifactId>
          <version>2.2.1</version>
          <scope>provided</scope>
          
          </dependency>
          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
          - Maven 仓库

          - 根据网上内容搜索最新的依赖包

          - ![image-20230629214042538](https://s2.loli.net/2023/06/29/pJ9FLSy1kahvWZx.png)
          - ```xml
          <dependencies>
          <!-- servlet 依赖 -->
          <dependency>
          <groupId>javax.servlet</groupId>
          <artifactId>javax.servlet-api</artifactId>
          <version>4.0.1</version>
          </dependency>
          <!-- JSP 依赖 -->
          <dependency>
          <groupId>javax.servlet.jsp</groupId>
          <artifactId>jsp-api</artifactId>
          <version>2.2</version>
          </dependency>
          <!-- JSTL 依赖 -->
          <dependency>
          <groupId>javax.servlet.jsp.jstl</groupId>
          <artifactId>jstl-api</artifactId>
          <version>1.2</version>
          </dependency>
          <!-- JSTL 实现 -->
          <dependency>
          <groupId>taglibs</groupId>
          <artifactId>standard</artifactId>
          <version>1.1.2</version>
          </dependency>
          <!-- MySQL 依赖 -->
          <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>8.0.19</version>
          </dependency>
          </dependencies>
  2. Maven 父子工程的理解

    • 父项目中

      • ```xml
        <module>servlet-01</module>
        
        </modules>
        1
        2
        3
        4
        5
        6
        7
        8
        - 子项目中

        - ```xml
        <parent>
        <groupId>com.bayyy</groupId>
        <artifactId>javaweb-02-servlet</artifactId>
        <version>1.0-SNAPSHOT</version>
        </parent>
    • 父项目中的 JAR 包 - 子项目可以直接使用
  3. Maven环境优化

    • 修改 web.xml 为最新版本

      • ```xml
        <?xml version=”1.0” encoding=”UTF-8”?>
        <web-app xmlns=”http://xmlns.jcp.org/xml/ns/javaee
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                                 http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0"
             metadata-complete="true">
        
        </web-app>
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
           - 将 Maven 结构搭建完整

        - ![image-20230629215342720](https://s2.loli.net/2023/06/29/vazcPdm8qiDFty7.png)
        4. 编写一个 Servlet 程序

        1. 编写一个普通类
        2. 实现 Servlet 接口(直接继承 `HttpServlet`)

        - ```java
        public class HelloServlet extends HttpServlet {
        // 由于get或者post只是请求实现的不同的方式,可以相互调用,业务逻辑都一样;
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // ServletOutputStream outputStream = resp.getOutputStream();
        PrintWriter writer = resp.getWriter(); //响应流
        writer.print("Hello, Servlet");
        }

        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req , resp);
        }
        }
  4. 编写 Servlet 的映射

    • 为什么需要映射?

      我们写的是JAVA程序,但是要通过浏览器访问,而浏览器需要连接web服务器,所以我们需要再web服务中注册我们写的Servlet,还需给他一个浏览器能够访问的路径;

    • 注册 web.xml

      • ```xml
        <servlet-name>hello</servlet-name>
        <servlet-class>com.bayyy.servlet.HelloServlet</servlet-class>
        
        </servlet>
        <servlet-name>hello</servlet-name>
        <url-pattern>/hello</url-pattern>
        
        </servlet-mapping>
        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
        6. 配置Tomcat

        - 如果没有 工件 项,请在 web.xml 中更新!
        - <img src="https://s2.loli.net/2023/06/28/epDxE7u5JOZal9R.png" alt="image-20230628213541866" style="zoom:33%;" />
        - <img src="https://s2.loli.net/2023/06/28/mDlHA1iUJfnWh6F.png" alt="image-20230628213751310" style="zoom:33%;" />
        - <img src="https://s2.loli.net/2023/06/28/isoVIG7WPtaZzld.png" alt="image-20230628214022786" style="zoom: 33%;" />
        -
        7. 启动测试

        - ![image-20230629222129640](https://s2.loli.net/2023/06/29/i27Kp3qFVZAl1N9.png)

        ## 6.3 Servlet 原理

        ![img](https://s2.loli.net/2023/06/29/mbe5xtsjnG2LAHX.png)

        - 首次访问 -> 生成 `target`

        ## 6.4 Mapping 问题

        ### 6.4.1 一个Servlet可以指定一个映射路径

        ```xml
        <servlet-mapping>
        <servlet-name>hello</servlet-name>
        <url-pattern>/hello</url-pattern>
        </servlet-mapping>

6.4.2 一个servlet可以指定多个映射路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello2</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello3</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello4</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello5</url-pattern>
</servlet-mapping>

6.4.3 一个servlet可以指定通用映射路径

1
2
3
4
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello/*</url-pattern>
</servlet-mapping>

6.4.4 默认请求路径

1
2
3
4
5
<!--默认请求路径-->
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>

6.4.5 指定一些后缀或者前缀等等…

1
2
3
4
5
6
7
8
<!--  可以自定义后缀实现请求映射
注意点,*前面不能加项目映射的路径
hello/other.bayyy // 以其结尾均可成功
-->
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>*.bayyy</url-pattern>
</servlet-mapping>

6.4.6 优先级问题

  • 指定了固有的映射路径优先级最高,如果找不到就会走默认的处理请求;
1
2
3
4
5
6
7
8
9
<!--404-->
<servlet>
<servlet-name>error</servlet-name>
<servlet-class>com.bayyy.servlet.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>error</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>

6.5 ServletContext

web容器在启动的时候,它会为每个web程序都创建一个对应的 ServletContext 对象,它代表了当前的web应用;

  • 每个项目创建 服务启动时部署 时,尽量保证只有一个工件,避免打包时过多,影响响应速度

6.5.1 共享数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// HelloServlet.java

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doGet 方法被调用");

// this.getInitParameter() 初始化参数(现在基本不用了)
// this.getServletConfig() Servlet配置
// this.getServletContext() Servlet上下文
ServletContext context = this.getServletContext();

String username = "bayyy"; //数据
context.setAttribute("username",username); //将一个数据保存在了ServletContext中,名字为:username:
}

// GetServlet.java
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext context = this.getServletContext();
String username = (String) context.getAttribute("username");
resp.getWriter().print("name: " + username);
}
  • 不要忘记修改 web.xml, 配置映射Mapping

    • ```xml

      <servlet-name>hello</servlet-name>
      <servlet-class>com.bayyy.servlet.HelloServlet</servlet-class>
      

      </servlet>

      <servlet-name>hello</servlet-name>
      <url-pattern>/hello</url-pattern>
      

      </servlet-mapping>

      <servlet-name>getHello</servlet-name>
      <servlet-class>com.bayyy.servlet.GetServlet</servlet-class>
      

      </servlet>

      <servlet-name>getHello</servlet-name>
      <url-pattern>/getHello</url-pattern>
      

      </servlet-mapping>

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      - 此时先运行 `./hello` 在运行 `./getHello`, 即可获得共享数据

      - 测试结果:![image-20230701185338834](https://s2.loli.net/2023/07/01/Sb4KY7aXzkO9DoQ.png)

      ### 6.5.2 获取初始化参数

      ```java
      // ParamsServlet.java
      protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      ServletContext context = this.getServletContext();

      String pwd = context.getInitParameter("pwd");// 初始化参数(现在基本不用了)
      resp.getWriter().print("pwd: " + pwd);
      }
  • web.xml

    • ```xml
      <param-name>pwd</param-name>
      <param-value>123456</param-value>
      
      </context-param>
      <servlet-name>getParams</servlet-name>
      <servlet-class>com.bayyy.servlet.ParamsServlet</servlet-class>
      
      </servlet>
      <servlet-name>getParams</servlet-name>
      <url-pattern>/getParams</url-pattern>
      
      </servlet-mapping>
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      - 测试结果:![image-20230701190540843](https://s2.loli.net/2023/07/01/b78foShvIEVCudt.png)

      ### 6.5.3 请求转发

      > Dispatcher

      ```java
      // DispatcherServlet.java
      protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      ServletContext context = this.getServletContext();

      System.out.println("进入了dispatcherServlet");
      // RequestDispatcher requestDispatcher = context.getRequestDispatcher("/getHello"); // 请求转发的请求路径
      // requestDispatcher.forward(req, resp); // 调用forward实现请求转发
      context.getRequestDispatcher("/getHello").forward(req, resp); // 调用forward实现请求转发
      }
  • 测试结果:image-20230701192618089

    • 路径显示-http://localhost:8080/servlet_01_war/dispatcher 不会修改路径

6.5.4 读取资源文件

操作 .proerties -> 发现:都被打包到了同一个路径下:classes,我们俗称这个路径为classpath:

image-20230701193636889

1
2
3
4
5
6
7
8
9
10
11
// PropertiesServlet.java
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("进入了PropertiesServlet");
InputStream resourceAsStream = this.getServletContext().getResourceAsStream("/WEB-INF/classes/db.properties");// 读取资源文件
Properties prop = new Properties(); // 创建一个Properties对象
prop.load(resourceAsStream); // 加载资源文件
String username = prop.getProperty("username"); // 通过key获取value
String pwd = prop.getProperty("pwd");

resp.getWriter().print("username: " + username + ", pwd: " + pwd);
}
  • 测试结果:image-20230701194040841

  • 非存储在 resources 文件夹下的资源,打包时可能会丢失

    解决办法

6.6 HttpServletResponse

web服务器接收到客户端的http请求,针对这个请求,分别创建一个代表请求的HttpServletRequest对象,代表响应的一个HttpServletResponse

  • 获取客户端请求参数:HttpServletRequest
  • 获取发送客户端信息:HttpServletResponse

6.6.1 简单分类

1)向浏览器发送数据

1
2
servletOutputstream getOutputstream() throws IOException;
Printwriter getwriter() throws IOException;

2)向浏览器发送响应头

1
2
3
4
5
6
7
8
9
10
void setCharacterEncoding(String var1)
void setContentLength(int var1)
void setContentLengthLong(long var1);
void setContentType(String var1)
void setDateHeader(String varl,long var2)
void addDateHeader(String var1,long var2)
void setHeader(String var1,String var2);
void addHeader(String var1,String var2)
void setIntHeader(String var1,int var2);
void addIntHeader(String varl,int var2);

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
int SC_CONTINUE = 100;  // 100: 继续。客户端应继续其请求
int SC_SWITCHING_PROTOCOLS = 101; // 101: 转换协议。服务器根据客户端的请求切换协议
int SC_OK = 200; // 200: 请求成功。一般用于GET与POST请求
int SC_CREATED = 201; // 201: 已创建。成功请求并创建了新的资源
int SC_ACCEPTED = 202; // 202: 已接受。已经接受请求,但未处理完成
int SC_NON_AUTHORITATIVE_INFORMATION = 203; // 203: 非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本
int SC_NO_CONTENT = 204; // 204: 无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档
int SC_RESET_CONTENT = 205; // 205: 重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图
int SC_PARTIAL_CONTENT = 206; // 206: 部分内容。服务器成功处理了部分GET请求
int SC_MULTIPLE_CHOICES = 300; // 300: 多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择
int SC_MOVED_PERMANENTLY = 301; // 301: 永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI,今后任何新的请求都应使用新的URI代替
int SC_MOVED_TEMPORARILY = 302; // 302: 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI
int SC_FOUND = 302; // 302: 已找到。与302类似。使用GET定向
int SC_SEE_OTHER = 303; // 303: 查看其它地址。与301类似。使用GET和POST请求查看
int SC_NOT_MODIFIED = 304; // 304: 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源
int SC_USE_PROXY = 305; // 305: 使用代理。所请求的资源必须通过代理访问
int SC_TEMPORARY_REDIRECT = 307; // 307: 临时重定向。与302类似。使用GET请求重定向
int SC_BAD_REQUEST = 400; // 400: 客户端请求的语法错误,服务器无法理解
int SC_UNAUTHORIZED = 401; // 401: 请求要求用户的身份认证
int SC_PAYMENT_REQUIRED = 402; // 402: 保留,将来使用
int SC_FORBIDDEN = 403; // 403: 服务器理解请求客户端的请求,但是拒绝执行此请求
int SC_NOT_FOUND = 404; // 404: 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面
int SC_METHOD_NOT_ALLOWED = 405; // 405: 客户端请求中的方法被禁止
int SC_NOT_ACCEPTABLE = 406; // 406: 服务器无法根据客户端请求的内容特性完成请求
int SC_PROXY_AUTHENTICATION_REQUIRED = 407; // 407: 请求要求代理的身份认证,与401类似,但请求者应当使用代理进行授权
int SC_REQUEST_TIMEOUT = 408; // 408: 服务器等待客户端发送的请求时间过长,超时
int SC_CONFLICT = 409; // 409: 服务器完成客户端的PUT请求是可能返回此代码,服务器处理请求时发生了冲突
int SC_GONE = 410; // 410: 客户端请求的资源已经不存在,服务器上不再有此资源
int SC_LENGTH_REQUIRED = 411; // 411: 服务器无法处理客户端发送的不带Content-Length的请求信息
int SC_PRECONDITION_FAILED = 412; // 412: 客户端请求信息的先决条件错误
int SC_REQUEST_ENTITY_TOO_LARGE = 413; // 413: 由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个Retry-After的响应信息
int SC_REQUEST_URI_TOO_LONG = 414; // 414: 请求的URI过长(URI通常为网址),服务器无法处理
int SC_UNSUPPORTED_MEDIA_TYPE = 415; // 415: 服务器无法处理请求附带的媒体格式
int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416; // 416: 客户端请求的范围无效
int SC_EXPECTATION_FAILED = 417; // 417: 服务器无法满足Expect的请求头信息
int SC_INTERNAL_SERVER_ERROR = 500; // 500: 服务器内部错误,无法完成请求
int SC_NOT_IMPLEMENTED = 501; // 501: 服务器不支持请求的功能,无法完成请求
int SC_BAD_GATEWAY = 502; // 502: 充当网关或代理的服务器,从远端服务器接收到了一个无效的请求
int SC_SERVICE_UNAVAILABLE = 503; // 503: 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中
int SC_GATEWAY_TIMEOUT = 504; // 504: 充当网关或代理的服务器,未及时从远端服务器获取请求
int SC_HTTP_VERSION_NOT_SUPPORTED = 505; // 505: 服务器不支持请求的HTTP协议的版本,无法完成处理

6.6.2 下载文件

  1. 向浏览器输出消息(一直在讲,就不说了)
  2. 下载文件
    1. 要获取下载文件的路径
    2. 下载的文件名是啥?
    3. 设置想办法让浏览器能够支持下载我们需要的东西
    4. 获取下载文件的输入流
    5. 创建缓冲区
    6. 获取OutputStream对象
    7. 将FileOutputStream流写入到bufer缓冲区
    8. 使用OutputStream将缓冲区中的数据输出到客户端!
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
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("进入了FileServlet");
// 1.要获取下载文件的路径
String realPath = "D:\\Coding\\test\\temp\\JavaWeb\\javaweb-02-servlet\\response\\src\\main\\resources\\img.png";
System.out.println("下载文件的路径:" + realPath);
// 2.下载的文件名是啥?
String fileName = realPath.substring(realPath.lastIndexOf("\\") + 1);
// 3.设置想办法让浏览器能够支持(Content-Disposition)下载我们需要的东西
// resp.setHeader("Content-Disposition", "attachment;filename=" + fileName); // Content-Disposition是响应头,attachment是附件,filename是文件名
resp.setHeader("Content-Disposition", "inline;filename=" + URLEncoder.encode(fileName, "UTF-8")); // inline是在线打开,attachment是附件,filename是文件名,URLEncoder.encode是编码转换(实现中文路径)
// 4.获取下载文件的输入流
FileInputStream in = new FileInputStream(realPath);
// 5.创建缓冲区
int len = 0;
byte[] buffer = new byte[1024];
// 6.获取OutputStream对象
ServletOutputStream out = resp.getOutputStream();
// 7.将FileOutputStream流写入到buffer缓冲区,使用OutputStream将缓冲区中的数据输出到客户端
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
// 8.关闭流
out.close();
in.close();
}
  • 测试结果:image-20230701201400467

6.6.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
// ImageServlet.java
// 生成随机数
private String makeNum(){
Random random = new Random();
String num = random.nextInt(99999999) + "";
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 8 - num.length(); i++) {
sb.append("0");
}
String verify = sb.toString() + num;
return verify;
}

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("进入了ImageServlet");
// 如何让浏览器5秒刷新一次
resp.setHeader("refresh", "5"); // 设置refresh响应头,让浏览器每隔5秒刷新一次

// 在内存中创建一个图片
BufferedImage image = new BufferedImage(80, 20, BufferedImage.TYPE_INT_RGB); // 80*20的图片,TYPE_INT_RGB是颜色
// 得到图片
Graphics pen = image.getGraphics(); // 笔
// 设置图片的背景颜色
pen.setColor(Color.white);
pen.fillRect(0, 0, 80, 20); // 填充矩形,从(0,0)开始,宽80,高20
// 给图片写数据
pen.setColor(Color.blue);
pen.setFont(new Font(null, Font.BOLD, 20)); // 设置字体: 字体、字体样式、字体大小
pen.drawString(makeNum(), 0, 20); // 从(0,20)开始写
// 告诉浏览器,这个请求用图片的方式打开
resp.setContentType("image/jpeg"); // 响应头,告诉浏览器,这个请求用图片的方式打开
// 网站存在缓存,不让浏览器缓存
resp.setDateHeader("expires", -1);
resp.setHeader("Cache-Control", "no-cache"); // 不缓存
resp.setHeader("Pragma", "no-cache"); // 不缓存
// 把图片写给浏览器
ImageIO.write(image, "jpg", resp.getOutputStream());
}
  • 测试结果:image-20230701203409818

6.6.3 实现重定向

应用场景:用户登录

1
void sendRedirect(String var1) throws IOException;
  • 测试:

    • ```java
      protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      System.out.println("进入了RedirectServlet");
      //        resp.sendRedirect("/servlet/verify");   // 重定向: /verify (错误) 相对于根目录, 则需要加上 /servlet/verify
      resp.sendRedirect("verify");    // 重定向: verify 则是相对路径,相对于当前路径,即 /servlet/verify
      // resp.setHeader("Location", "verify"); // 重定向
      // resp.setStatus(HttpServletResponse.SC_FOUND);    // 302
      
      }
      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

      - `url` 地址栏会发生变化(区别于转发)

      ### 6.6.4 简单实现登录重定向

      ```jsp
      // index.jsp 会调用/request - RequestTest.java(需要引入jps包: javax.servlet.jsp-api)
      <%@ page contentType="text/html;charset=UTF-8" language="java" %>
      <%--contentType: 告诉浏览器以什么编码解析文本--%>
      <html>
      <body>
      <h2>Hello World!</h2>

      <%-- 这里超交的路径,需要寻找到项目的路径 --%>
      <%-- ${pageContext. request, contextPath}代表当前的项目 --%>
      <form action="${pageContext. request.contextPath}/request" method="get">
      <%-- get: 通过url传递参数 / post: 通过请求体传递参数 --%>
      用户名: <input type="text" name="username"> <br>
      密码: <input type="password" name="password"> <br>
      <input type="submit">
      </form>

      </body>
      </html>

      // success.jsp (成功跳转到本页面)
      <%@ page contentType="text/html; charset=UTF-8" language="java" %>
      <html>
      <head>
      <title>Title</title>
      </head>
      <body>
      <h1>success</h1>
      </body>
      </html>
1
2
3
4
5
6
7
8
9
10
11
// RequestTest.java
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("进入了RequestTest");
String username = req.getParameter( "username");
String password = req.getParameter( "password");

System.out.println(username+":"+password);

resp.sendRedirect("success.jsp"); // 可以直接写重定向的位置 !不可以加‘/’!
// resp.sendRedirect("/servlet/success.jsp");
}
  • 测试结果:
    • image-20230701210208178
    • image-20230701210222361

6.7 HttpServletRequest

  • HttpServletRequest代表客户端的请求:用户通过Http协议访问服务器, HTTP请求中的所有信息会被封装到HttpServletRequest,可获得客户端的所有信息;

6.7.1 获取参数/请求转发

1
2
3
4
req.getParameter(String s)	// String(*)
req.getParameterMap() // Map<String, String[]>
req.getParamterNames() // Enumeration<String>
req.getParamterValues(String s) // String[](*)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// jsp: javaweb-02-servlet/requese/index.jsp /+ success.jsp

// LoginServlet.java
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("进入了LoginServlet的doGet方法");
String username = req.getParameter("username");
String password = req.getParameter("password");
String[] hobbies = req.getParameterValues("hobbys");
System.out.println("==================");
System.out.println("username: " + username);
System.out.println("password: " + password);
System.out.println("hobbies: " + Arrays.toString(hobbies));
System.out.println("==================");

// 通过请求转发
req.getRequestDispatcher("success.jsp").forward(req, resp);
}
  • 测试结果:image-20230702144857981

7. Cookie/Session

7.1 会话

会话:用户打开一个浏览器,点击了很多超链接,访问多个web资源,关闭浏览器,这个过程可以称之为会话;

有状态会话:一个同学来过教室,下次再来教室,我们会知道这个同学,曾经来过,称之为有状态会话;

客户端 服务端

  1. 服务端给客户端一个 信件,客户端下次访问服务端带上信件就可以了; cookie
  2. 服务器登记你来过了,下次你来的时候我来匹配你; seesion

7.2 保存会话的两种技术

cookie

  • 客户端技术 (响应,请求)

session

  • 服务器技术,利用这个技术,可以保存用户的会话信息? 我们可以把信息或者数据放在Session中!

7.2.1 共同之处

  • cookie和session都是用来跟踪浏览器用户身份的会话方式。

7.2.2 工作原理

1.Cookie的工作原理

  1. 浏览器端第一次发送请求到服务器端
  2. 服务器端创建Cookie,该Cookie中包含用户的信息,然后将该Cookie发送到浏览器端
  3. 浏览器端再次访问服务器端时会携带服务器端创建的Cookie
  4. 服务器端通过Cookie中携带的数据区分不同的用户

在这里插入图片描述

2. Session的工作原理

  1. 浏览器端第一次发送请求到服务器端,服务器端创建一个Session,同时会创建一个特殊的Cookie(name为JSESSIONID的固定值,value为session对象的ID),然后将该Cookie发送至浏览器端
  2. 浏览器端发送第N(N>1)次请求到服务器端,浏览器端访问服务器端时就会携带该name为JSESSIONID的Cookie对象
  3. 服务器端根据name为JSESSIONID的Cookie的value(sessionId),去查询Session对象,从而区分不同用户。

在这里插入图片描述

  • 访问过程
    1. nameJSESSIONIDCookie不存在(关闭或更换浏览器),返回1中重新去创建Session与特殊的Cookie
    2. nameJSESSIONIDCookie存在,根据value中的SessionId去寻找session对象
    3. valueSessionId不存在(Session对象默认存活30分钟),返回1中重新去创建Session与特殊的Cookie
    4. valueSessionId存在,返回session对象

7.2.4 区别

  • cookie数据保存在客户端,session数据保存在服务端。
  • session

    • 简单的说,当你登陆一个网站的时候,如果web服务器端使用的是session,那么所有的数据都保存在服务器上,客户端每次请求服务器的时候会发送当前会话sessionid,服务器根据当前sessionid判断相应的用户数据标志,以确定用户是否登陆或具有某种权限。由于数据是存储在服务器上面,所以你不能伪造。
  • cookie

    • sessionid是服务器和客户端连接时候随机分配的,如果浏览器使用的是cookie,那么所有数据都保存在浏览器端,比如你登陆以后,服务器设置了cookie用户名,那么当你再次请求服务器的时候,浏览器会将用户名一块发送给服务器,这些变量有一定的特殊标记。服务器会解释为cookie变量,所以只要不关闭浏览器,那么cookie变量一直是有效的,所以能够保证长时间不掉线。
  • 如果你能够截获某个用户的cookie变量,然后伪造一个数据包发送过去,那么服务器还是 认为你是合法的。所以,使用cookie被攻击的可能性比较大。

  • 如果cookie设置了有效值,那么cookie会保存到客户端的硬盘上,下次在访问网站的时候,浏览器先检查有没有cookie,如果有的话,读取cookie,然后发送给服务器。

  • 所以你在机器上面保存了某个论坛cookie,有效期是一年,如果有人入侵你的机器,将你的cookie拷走,放在他机器下面,那么他登陆该网站的时候就是用你的身份登陆的。当然,伪造的时候需要注意,直接copy cookie文件到 cookie目录,浏览器是不认的,他有一个index.dat文件,存储了 cookie文件的建立时间,以及是否有修改,所以你必须先要有该网站的 cookie文件,并且要从保证时间上骗过浏览器

  • 两个都可以用来存私密的东西,session过期与否,取决于服务器的设定。cookie过期与否,可以在cookie生成的时候设置进去。

7.2.4 区别对比

  1. cookie数据存放在客户的浏览器上,session数据放在服务器上
  2. cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,如果主要考虑到安全应当使用session
  3. session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,如果主要考虑到减轻服务器性能方面,应当使用COOKIE
  4. 单个cookie在客户端的限制是3K,就是说一个站点在客户端存放的COOKIE不能3K。
  5. 所以:将登陆信息等重要信息存放为SESSION;其他信息如果需要保留,可以放在COOKIE中
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
// javaweb-02-servlet/session-cookie/CookieDemo01.java
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("进入了CookieDemo01方法");
// 解中文决乱码问题
req.setCharacterEncoding("utf-16");
resp.setCharacterEncoding("utf-16");

PrintWriter out = resp.getWriter();

// Cookie,服务器从客户端获取
Cookie[] cookies = req.getCookies(); // 这里返回数组,说明Cookie可能存在多个

// 判断Cookie是否存在
if (cookies != null) {
// 如果存在
out.write(String.valueOf("你上一次访问的时间是:"));
for (int i = 0; i < cookies.length; i++) {
Cookie cookie = cookies[i];
// 获取Cookie的名字
if (cookie.getName().equals("LastLoginTime")) {
// 获取Cookie的值
long lastLoginTime = Long.parseLong(cookie.getValue());
Date date = new Date(lastLoginTime);
out.write(date.toLocaleString());
}
}
} else {
// 如果不存在
out.write("这是您第一次访问本站!");
}
Cookie cookie = new Cookie("LastLoginTime", System.currentTimeMillis() + "");
resp.addCookie(cookie);
}
  • 测试结果:image-20230702153553768

  • 其他内容:

    • 从请求中拿到cookie信息

    • 服务器响应给客户端cookie

      • ```java
        Cookie[] cookies = req.getCookies(); //获得Cookie
        cookie.getName(); //获得cookie中的key
        cookie.getValue(); //获得cookie中的vlaue
        new Cookie(“lastLoginTime”, System.currentTimeMillis()+””); //新建一个cookie
        cookie.setMaxAge(246060); //设置cookie的有效期
        resp.addCookie(cookie); //响应给客户端一个cookie
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21

        - cookie:一般会保存在本地的 用户目录下 `appdata`

        - 一个网站cookie存在上限

        - 一个Cookie只能保存一个信息
        - 一个web站点可以给浏览器发送多个cookie,最多存放20个cookie
        - Cookie大小有限制4kb
        - 300个cookie浏览器上限

        - 删除Cookie;

        - 不设置有效期,关闭浏览器,自动失效

        - 设置有效期时间为 0

        - 编码解码:

        - ```java
        URLEncoder.encode("测试","utf-8")
        URLDecoder.decode(cookie.getValue(),"UTF-8")

7.4 Session(重点)

7.4.1 创建Session

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// SessionDemo01.java
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("进入了SessionDemo01方法");
// 解中文决乱码问题
req.setCharacterEncoding("utf-16");
resp.setCharacterEncoding("utf-16");

// Session,服务器从客户端获取
HttpSession session = req.getSession();

// 存储
session.setAttribute("name", "bayyy");
session.setAttribute("pwd", "123456");

// 获取ID
String sessionId = session.getId();

// 判断Session是否是新创建的
if (session.isNew()) {
resp.getWriter().write("Session创建成功,ID为:" + sessionId);
} else {
resp.getWriter().write("Session已经在服务器中存在了,ID为:" + sessionId);
}
}
  • 测试结果:image-20230702160857722

7.4.2 使用Session

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//得到Session
HttpSession session = req.getSession();

String usename = session.getAttribute("usename");
// 也可以放置复杂的内容:如对象等.

System.out.println(person.toString());

HttpSession session = req.getSession();

session.removeAttribute("name");

//手动注销Session
session.invalidate();

7.4.3 会话自动过期

1
2
3
4
5
<!--设置Session默认的失效时间-->
<session-config>
<!--15分钟后Session自动失效,以分钟为单位-->
<session-timeout>15</session-timeout>
</session-config>

8. JSP

8.1 什么是 JSP

Java Server Pages : Java服务器端页面,也和Servlet一样,用于动态Web技术!

  • 特点:
    • 写JSP就像在写HTML
    • 区别:
      • HTML只给用户提供静态的数据
      • JSP页面中可以嵌入JAVA代码,为用户提供动态数据;

8.2 JSP 原理

  • 代码层面没有任何问题

    • 代码层面未发生变化image-20230702172616272
  • 服务器内部工作

    • tomcat中有一个work目录image-20230702172700155

    • IDEA中使用Tomcat的会在IDEA的tomcat中生产一个work目录image-20230702173302105

    • 页面会转化为Java程序 -> JSP本质上就是一个Servlet

      • ```java
        //初始化
        public void _jspInit() {

        }
        //销毁
        public void _jspDestroy() {
        }
        //JSPService
        public void _jspService(.HttpServletRequest request,HttpServletResponse response)

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16

        - 过程:

        1. 判断请求

        2. 内置对象

        - ```java
        final javax.servlet.jsp.PageContext pageContext; //页面上下文
        javax.servlet.http.HttpSession session = null; //session
        final javax.servlet.ServletContext application; //applicationContext
        final javax.servlet.ServletConfig config; //config
        javax.servlet.jsp.JspWriter out = null; //out
        final java.lang.Object page = this; //page:当前
        HttpServletRequest request //请求
        HttpServletResponse response //响应
        1. 输出页面前增加代码

          • ```java
            response.setContentType(“text/html”); //设置响应的页面类型
            pageContext = _jspxFactory.getPageContext(this, request, response,
               null, true, 8192, true);
            
            _jspx_page_context = pageContext;
            application = pageContext.getServletContext();
            config = pageContext.getServletConfig();
            session = pageContext.getSession();
            out = pageContext.getOut();
            _jspx_out = out;
            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            19
            20

            4. 以上对象均可在JSP页面中直接使用

            - ![image-20230702173449597](https://s2.loli.net/2023/07/02/dt5FDqi2KUvoIys.png)

            - 访问时编译 (JSP->.java->.class)
            - HTML代码转化为 `os.write('<html>\r\n')`
            - JAVA代码直接输出

            ## 8.3 JSP 基础语法

            ### 8.3.1 表达式

            ```jsp
            <%--JSP表达式
            作用:用来将程序的输出,输出到客户端
            <%= 变量或者表达式%>
            --%>
            <%= new java.util.Date()%>
            <hr> <%--分割线-->

8.3.2 脚本

1
2
3
4
5
6
7
8
9
10
11
<%--JSP脚本
作用:用来定义java代码
<% java代码 %>
--%>
<%
int sum = 0;
for (int i = 0; i < 10; i++) {
sum += i;
}
out.println("<h1>Sum = " + sum + "</h1>");
%>

8.3.3 脚本可拆开/嵌入

1
2
3
4
5
6
7
8
9
10
<%--JSP脚本片段再实现
作用:可以拆开书写
<%! java代码 %>
--%>
<%! int sum = 0; %>
<p>定义了sum=0;</p>
<% for (int i = 0; i < 10; i++) {
sum += i;
} %>
<h1>Sum = <%= sum %></h1>

8.3.4 JSP声明

<&!...%>

1
2
3
4
5
6
7
8
9
10
11
12
<%!
static {
System.out.println("Loading Servlet!");
}

private int globalVar = 0;

public void Test(){
System.out.println("进入了方法Test()!");
}
%>

  • JSP声明:会被编译到JSP生成Java的类中!(其他的,就会被生成到_jspService方法中!)
  • 作用域会提升

8.3.5 JSP注释

1
2
3
4
5
<%%>	脚本片段
<%=%> 输出值
<%!%> JSP声明

<%--注释--%>
  • JSP的注释,不会在客户端(F12中)显示,HTML就会!

8.3.6 Error跳转

1
2
3
4
5
6
7
8
9
10
11
12
// .jsp - 错误跳转
<%@ page errorPage="error.jsp" %>

// .xml - 错误跳转
<error-page>
<error-code>404</error-code>
<location>/error/error404.jsp</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error/error500.jsp</location>
</error-page>

8.4 JSP 指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<%--@include会将两个页面合二为一: 合成一个文件(定义会重名)--%>

<%@include file="common/header.jsp"%>
<h1>网页主体</h1>
<%@include file="common/footer.jsp"%>

<hr>


<%--jSP标签
jsp:include:拼接页面,本质还是三个,引用
--%>
<jsp:include page="/common/header.jsp"/>
<h1>网页主体</h1>
<jsp:include page="/common/footer.jsp"/>

8.5 九大内置对象

  • PageContext 存东西
  • Request 存东西
  • Response
  • Session 存东西
  • Application [SerlvetContext]
  • config [SerlvetConfig]
  • out
  • page ,不用了解
  • exception
1
2
3
4
5
6
<%
pageContext.setAttribute("name1","1号"); //保存的数据只在一个页面中有效
request.setAttribute("name2","2号"); //保存的数据只在一次请求中有效,请求转发会携带这个数据
session.setAttribute("name3","3号"); //保存的数据只在一次会话中有效,从打开浏览器到关闭浏览器
application.setAttribute("name4","4号"); //保存的数据只在服务器中有效,从打开服务器到关闭服务器
%>

在这里插入图片描述

8.6 JSP标签、JSTL标签、EL表达式

==导包== ~2023.07.04~

1
2
3
4
5
6
7
8
9
10
11
12
<!-- https://mvnrepository.com/artifact/javax.servlet.jsp.jstl/jstl-api -->
<dependency>
<groupId>javax.servlet.jsp.jstl</groupId>
<artifactId>jstl-api</artifactId>
<version>1.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/taglibs/standard -->
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>

8.6.1 JSP标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<%-- jsptag.jsp --%>
<jsp:include page="./include.jsp"></jsp:include> <%-- 页面内嵌: jsp:include:拼接页面,本质还是三个,引用 --%>


<jsp:forward page="./forward.jsp"> <%-- 页面跳转: url不会变化 --%>
<jsp:param name="name" value="bayyy"></jsp:param> <%-- 传递参数 --%>
<jsp:param name="age" value="24"></jsp:param>
</jsp:forward>

<%-- include.jsp --%>
<body>
<h3>Include file(use by th jsptag.jsp)</h3>
</body>

<%-- forward.jsp --%>
<h2>This is the forwarded page.</h2>
<h3>(EL表达式) 测试取参数:name=${param.name}, age=${param.age}</h3>
<h3>
(JSP脚本) 测试取参数:
名字: <%=request.getParameter("name")%>
年龄: <%=request.getParameter("age")%>
</h3>
</body>
  • 结果测试:image-20230704223649822

8.6.2 JSTL标签

JSTL标签库的使用就是为了弥补HTML标签的不足;它自定义许多标签,可以供我们使用,标签的功能和Java代码一样!

(格式化标签、SQL标签、XML标签、核心标签~(掌握部分)~)

  • 使用步骤

    • 引入对应的 taglib
    • 使用其中的方法
    • ==在Tomcat 也需要引入 jstl的包,否则会报错:JSTL解析错误== ~报错后查看~
  • 部分核心标签

    • | 标签 | 描述 |
      | ——————- | —————————————————————————————— |
      | c:out | 用于在JSP中显示数据,类似<%=…> |
      | c:set | 用于保存数据 |
      | c:remove | 用于删除数据 |
      | c:if | if |
      | c:choose | 本身只当做 \ 和 \ 的父标签 |
      | c:when | \ 的子标签,用来判断条件是否成立 |
      | c:otherwise | \ 的子标签,接在 \ 后,当\ 标签判断为false 时被执行 |
      | c:forEach | 基础迭代标签,接收多种集合类型 |
      | c:url | 使用可选的查询参数来创造一个URL |

1)if

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<%-- coreif.jsp --%>

<%-- 引入JSTL标签库 --%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<body>
<h4>if 测试</h4>
<hr>

<%-- action: 表示提交的地址,method: 表示提交的方式 --%>
<form action="coreif.jsp" method="get">
<%--
EL 表达式 获取表单中的数据
${param.参数名}
--%>
<input type="text" name="usename" value="${param.usename}">
<input type="submit" value="登录">
</form>

<%-- 判断如果提交用户名是管理员, 则登录成功 --%>
<c:if test="${param.usename == 'root'}" var="isRoot" scope="page">
<c:out value="登陆成功"/>
</c:if>
</body>
  • 测试结果:image-20230704231342688

2)when/otherwise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<%-- --%>
<body>
<%--定义一个变量score,值为85--%>
<c:set var="score" value="55"/>

<c:choose>
<c:when test="${score>=90}">
你的成绩为优秀
</c:when>
<c:when test="${score>=80}">
你的成绩为一般
</c:when>
<c:when test="${score>=70}">
你的成绩为良好
</c:when>
<c:when test="${score>=30}">
你的成绩为不及格
</c:when>
<c:otherwise>
你的成绩不足30
</c:otherwise>
</c:choose>
</body>
  • 结果测试:image-20230705211528324

3)forEach

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
<%
ArrayList<String> people = new ArrayList<String>();
people.add("张三");
people.add("李四");
people.add("王五");
people.add("赵六");
people.add("田七");
request.setAttribute("list", people);
%>
<%--
var: 代表当前元素
items: 代表集合
--%>
<c:forEach var="people" items="${list}">
<c:out value="${people}"/>
<br/>
</c:forEach>

<c:out value="begin-end"/>
<br>

<%--
begin: 开始的索引
end: 结束的索引
step: 步长
--%>
<c:forEach begin="1" end="4" step="2" var="person" items="${list}">
<c:out value="${person}"/>
<br/>
</c:forEach>
  • 结果测试:image-20230705211833810

8.6.3 EL表达式

EL表达式: ${ }

  • 作用:

    • 获取数据

    • 执行运算

    • 获取web开发的常用对象

    • 调用 Java 程序 (麻烦,不用)

9. JavaBean

9.1 实体类

  • JavaBean有特定的写法:
    • 必须要有一个无参构造
    • 属性必须私有化
    • 必须有对应的get/set方法;

9.2 ORM

ORM: 对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。ORM框架是连接数据库的桥梁,只要提供了持久化类与表的映射关系,ORM框架在运行时就能参照映射文件的信息,把对象持久化到数据库中。

image-20230705212609750

  • 当前ORM框架主要有五种:

    1. Hibernate 全自动 需要写hql语句
    2. iBATIS 半自动 自己写sql语句,可操作性强,小巧
    3. mybatis
    4. eclipseLink
    5. JFinal
  • 对象关系映射

    1. 简单:ORM以最基本的形式建模数据。比如ORM会将MySQL的一张表映射成一个Java类(模型),表的字段就是这个类的成员变量
    2. 精确:ORM使所有的MySQL数据表都按照统一的标准精确地映射成java类,使系统在代码层面保持准确统一
    3. 易懂:ORM使数据库结构文档化。比如MySQL数据库就被ORM转换为了java程序员可以读懂的java类,java程序员可以只把注意力放在他擅长的java层面(当然能够熟练掌握MySQL更好)
    4. 易用:ORM包含对持久类对象进行CRUD操作的API,例如create(), update(), save(), load(), find(), find_all(), where()等,也就是将sql查询全部封装成了编程语言中的函数,通过函数的链式组合生成最终的SQL语句。通过这种封装避免了不规范、冗余、风格不统一的SQL语句,可以避免很多人为Bug,方便编码风格的统一和后期维护。
  • 对应

    • 表—>类

    • 字段—>属性

    • 行记录—>对象

  • 优缺点

    • 优点:
      1. 提高开发效率,降低开发成本
      2. 使开发更加对象化
      3. 可移植
      4. 可以很方便地引入数据缓存之类的附加功能
    • 缺点:
      1. 自动化进行关系数据库的映射需要消耗系统性能。其实这里的性能消耗还好啦,一般来说都可以忽略之。
      2. 在处理多表联查、where条件复杂之类的查询时,ORM的语法会变得复杂。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<%@page import="com.bayyy.entity.People" %>

<body>
<%
// People people = new People();
// people.setId(1);
// people.setName("Bay");
// people.setAge(18);
// people.setAddress("China");
%>

<jsp:useBean id="people" class="com.bayyy.entity.People" scope="page"/>

<jsp:setProperty name="people" property="id" value="1"/>
<jsp:setProperty name="people" property="name" value="Bay"/>
<jsp:setProperty name="people" property="age" value="18"/>
<jsp:setProperty name="people" property="address" value="China"/>

id:<jsp:getProperty name="people" property="id"/>
姓名:<jsp:getProperty name="people" property="name"/>
年龄:<jsp:getProperty name="people" property="age"/>
地址:<jsp:getProperty name="people" property="address"/>

</body>
  • 测试结果:image-20230705215628415

10. MVC 三层架构

MVC: Model view Controller 模型、视图、控制器

10.1 以前

image-20230705220727860

  • 为了易于维护和使用:
    • Servlet 专注于处理请求,以及控制视图跳转
    • JSP专注于显示数据
  • CRUD

    • 是指在做计算处理时的增加(Create)、读取查询(Retrieve)、更新(Update)和删除(Delete)几个单词的首字母简写。主要被用在描述软件系统中DataBase或者持久层的基本操作功能
  • 架构:没有什么是加一层解决不了的!

    • DB -> JDBC -> 调用

10.2 MVC 三层架构

image-20230705221035487

  • MVC框架

    • Model

      • 业务处理 :业务逻辑(Service)
      • 数据持久层:CRUD (Dao - 数据持久化对象)
    • VIew

      • 展示数据
      • 提供链接发起Servlet请求 (a,form,img…)
    • Controller

      • 展示数据

      • 提供链接发起Servlet请求 (a,form,img…)

      • 控制视图的跳转

        • ```mermaid
          graph LR
          登录 --> 接收用户的登录请求
          接收用户的登录请求 --> id2("处理用户的请求\n(获取用户登录的参数,username,password)")
          id2 --> id3("交给业务层处理登录业务\n(判断用户名密码是否正确:事务)")
          id3 --> Dao层查询用户名和密码是否正确
          Dao层查询用户名和密码是否正确 --> 数据库
          
          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

          - 框架解释

          - Dao
          - DAO(Data Access Object) 模型就是写一个类,把访问数据库的代码封装起来,DAO在数据库与业务逻辑(Service)之间。
          - Dao是数据访问层,Dao的作用是封装对数据库的访问:增删改查,不涉及业务逻辑,只是达到按某个条件获得指定数据的要求。
          - Entity
          - 实体层,放置一个个实体,及其相应的set、get方法。如果想要对数据库进行一些操作(比如说读取)的话,就要先写entity层
          - Service
          - Service被称作业务逻辑层。顾名思义,它处理逻辑上的业务,而不去考虑具体的实现。
          - Servlet
          - Servlet(Server Applet)是Java Servlet的简称,是为小服务程序或服务连接器,用Java编写的服务器端程序,主要功能在于交互式地浏览和修改数据,生成动态Web内容。
          - Utils
          - Util是utiliy的缩写,是一个多功能、基于工具的包。如字符串处理、日期处理等,(建立数据库之间的连接),是通用的、与业务无关的,可以独立出来,可供其他项目使用。

          # 11. Filter(重点)

          ![image-20230705222518482](https://s2.loli.net/2023/07/05/qK4WYyUpxlrjgfX.png)

          - 实现Filter接口,重写对应方法

          - ```java
          // CharacterEncodingFilter.java
          import javax.servlet.*;
          import java.io.IOException;

          // 注意: 这里的Filter是javax.servlet.Filter
          public class CharacterEncodingFilter implements Filter {
          // 初始化: web 服务器启动,就已经初始化了,随时等待过滤对象出现!
          @Override
          public void init(FilterConfig filterConfig) throws ServletException {
          // 初始化: 服务启动时初始化
          System.out.println("CharacterEncodingFilter init");
          }

          /*filterChain: 过滤器链, 用于执行下一个过滤器
          1. 过滤器中的所有代码, 在过滤特定请求的时候都会执行
          2. 必须要让过滤器继续通行
          */
          @Override
          public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
          // 过滤
          System.out.println("CharacterEncodingFilter doFilter");
          servletRequest.setCharacterEncoding("utf-8");
          servletResponse.setCharacterEncoding("utf-8");
          servletResponse.setContentType("text/html;charset=utf-8");

          System.out.println("CharacterEncodingFilter 执行前...");
          // filterChain: 过滤器链, 用于执行下一个过滤器
          filterChain.doFilter(servletRequest,servletResponse); // 让请求继续走, 如果不写, 请求将被拦截
          System.out.println("CharacterEncodingFilter 执行后...");

          }

          @Override
          public void destroy() {
          // 销毁: 服务断开时销毁
          System.out.println("CharacterEncodingFilter destroy");
          }
          }

          // ShowServlet.java
          protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
          System.out.println("ShowServlet doGet");
          resp.getWriter().write("预计中文乱码" +
          "\n");
          resp.getWriter().write("English is OK");
          }
  • 配置.xml

    • ```xml

      <servlet-name>ShowServlet</servlet-name>
      <servlet-class>com.bayyy.servlet.ShowServlet</servlet-class>
      

      </servlet>

      <servlet-name>ShowServlet</servlet-name>
      <url-pattern>/show</url-pattern>
      

      </servlet-mapping>

      <servlet-name>ShowServlet</servlet-name>
      <url-pattern>/servlet/show</url-pattern>
      

      </servlet-mapping>

      <filter-name>CharacterEncodingFilter</filter-name>
      <filter-class>com.bayyy.filter.CharacterEncodingFilter</filter-class>
      

      </filter>

      <filter-name>CharacterEncodingFilter</filter-name>
      <!-- 只要是/servlet的任何请求,会经过这个过滤器 -->
      <url-pattern>/servlet/*</url-pattern>
      <!-- 过滤所有请求 -->
      <!--        <url-pattern>/*</url-pattern>-->
      

      </filter-mapping>

      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

      - 导包不要导错

      - ![image-20230705230738882](https://s2.loli.net/2023/07/05/xahMoXZnFeB8sgl.png)

      - 测试结果:

      - 初始化:![image-20230705230420288](https://s2.loli.net/2023/07/05/fwGTEBN9MZ1Squo.png)

      - 销毁:![image-20230705230621506](https://s2.loli.net/2023/07/05/DnCgFNk4r2Kbd5V.png)

      - `/servlet/show`:![image-20230705230511011](https://s2.loli.net/2023/07/05/LFAp8cRkJv1Z4lX.png)

      ![image-20230705230547658](https://s2.loli.net/2023/07/05/LyPFAYeBMWS79it.png)

      - `/show`:![image-20230705230455912](https://s2.loli.net/2023/07/05/GRWdnM3Hx5qOkT4.png)

      ![image-20230705230559967](https://s2.loli.net/2023/07/05/Zif8AbkjlzaNvPq.png)

      # 12. 监听器

      - java

      ```java
      // HttpSessionListener.java

      // 统计网站在线人数: 统计session
      public class OnlineListenerListener implements HttpSessionListener {
      // 创建session监听
      // 一旦创建session就会触发一次这个事件
      @Override
      public void sessionCreated(HttpSessionEvent se) {
      ServletContext ctx = se.getSession().getServletContext();
      System.out.println(se.getSession().getId());
      Integer onlineCount = (Integer) ctx.getAttribute("OnlineCount");

      if (onlineCount == null) { // 判断session是否是第一次创建
      onlineCount = 1; // 首次访问
      } else {
      int count = onlineCount; // 获取在线人数
      onlineCount = count + 1; // 在线人数加一
      }
      ctx.setAttribute("OnlineCount", onlineCount);
      }

      // 销毁session监听
      // 一旦销毁session就会触发一次这个事件
      @Override
      public void sessionDestroyed(HttpSessionEvent se) {
      ServletContext ctx = se.getSession().getServletContext();
      Integer onlineCount = (Integer) ctx.getAttribute("OnlineCount");

      if (ctx.getAttribute("OnlineCount") == null) {
      onlineCount = 0;
      } else {
      int count = onlineCount;
      onlineCount = count - 1;
      }
      ctx.setAttribute("OnlineCount", onlineCount);
      }
      }
  • jsp

1
2
3
4
5
6
7
<%@page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<html>
<body>
<h2>Hello World!</h2>
</body>
<h1>当前有 <span style="color: blue"><%=this.getServletConfig().getServletContext().getAttribute("OnlineCount")%></span> 人在线</h1>
</html>
  • web.xml
1
2
3
4
5
6
7
8
9
<!-- 监听器 -->
<listener>
<listener-class>com.bayyy.listener.OnlineListenerListener</listener-class>
</listener>

<!-- Session 过期时间 -->
<session-config>
<session-timeout>15</session-timeout>
</session-config>
  • 测试结果:image-20230706192851268

13. 过滤器/监听器的常见应用

13.1 Listener

  • GUI 编程中经常使用
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
public static void main(String[] args) {
Frame frame = new Frame("TestPanel"); // 新建一个窗体
Panel panel = new Panel(null); // 新建一个面板
frame.setLayout(null); // 设置窗体的布局

frame.setBounds(300, 300, 500, 500); // 设置窗体的位置和大小
frame.setBackground(new Color(0, 0, 255)); // 设置窗体的背景颜色

panel.setBounds(50, 50, 300, 300); // 设置面板的位置和大小
panel.setBackground(new Color(0, 255, 0)); // 设置面板的背景颜色

frame.add(panel); // 将面板添加到窗体中

frame.setVisible(true); // 设置窗体可见


// 监听关闭事件
frame.addWindowListener(new WindowListener() {

@Override
public void windowOpened(WindowEvent e) {
System.out.println("窗口打开");
}

@Override
public void windowClosing(WindowEvent e) {
System.out.println("窗口关闭中ing");
System.exit(0); // 退出程序(0:正常退出, 1:异常退出)
}

@Override
public void windowClosed(WindowEvent e) {
System.out.println("窗口已关闭");
}

@Override
public void windowIconified(WindowEvent e) {
System.out.println("窗口最小化");

}

@Override
public void windowDeiconified(WindowEvent e) {
System.out.println("窗口非最小化");

}

@Override
public void windowActivated(WindowEvent e) {
System.out.println("窗口被激活");

}

@Override
public void windowDeactivated(WindowEvent e) {
System.out.println("窗口未被激活");
}
});


// 实现监听器的子类,可选择的实现监听事件
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.out.println("窗口关闭中ing");
System.exit(0); // 退出程序(0:正常退出, 1:异常退出)
}
});
}

13.2 登录/注销 实现

  • 需求内容

    • 登陆成功 -> 可进入主页
    • 注销之后 -> 无法进入主页
  • 流程

    1. 用户登录后,向 Session 中放入用户的数据
    2. 进入页面时需要判断用户是否已经登录 -> 滤波器实现
  • ```java
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

    System.out.println("SysFilter doFilter");
    
    HttpServletRequest request = (HttpServletRequest) servletRequest;
    HttpServletResponse response = (HttpServletResponse) servletResponse;
    
    Object user_session = request.getSession().getAttribute("USER_SESSION");
    
    if (user_session == null) {
        response.sendRedirect(request.getContextPath()+"/error/notfind.jsp");
    }
    filterChain.doFilter(servletRequest,servletResponse);
    

    }

    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
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104

    # 14. JDBC

    ## 14.1 数据库创建

    ```sql
    create database if not exists smbms;

    USE `smbms`;

    DROP TABLE IF EXISTS `smbms_address`;

    CREATE TABLE `smbms_address` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
    `contact` varchar(15) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '联系人姓名',
    `addressDesc` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '收货地址明细',
    `postCode` varchar(15) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '邮编',
    `tel` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '联系人电话',
    `createdBy` bigint(20) DEFAULT NULL COMMENT '创建者',
    `creationDate` datetime DEFAULT NULL COMMENT '创建时间',
    `modifyBy` bigint(20) DEFAULT NULL COMMENT '修改者',
    `modifyDate` datetime DEFAULT NULL COMMENT '修改时间',
    `userId` bigint(20) DEFAULT NULL COMMENT '用户ID',
    PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

    insert into `smbms_address`(`id`,`contact`,`addressDesc`,`postCode`,`tel`,`createdBy`,`creationDate`,`modifyBy`,`modifyDate`,`userId`) values (1,'王丽','北京市东城区东交民巷44号','100010','13678789999',1,'2016-04-13 00:00:00',NULL,NULL,1),(2,'张红丽','北京市海淀区丹棱街3号','100000','18567672312',1,'2016-04-13 00:00:00',NULL,NULL,1),(3,'任志强','北京市东城区美术馆后街23号','100021','13387906742',1,'2016-04-13 00:00:00',NULL,NULL,1),(4,'曹颖','北京市朝阳区朝阳门南大街14号','100053','13568902323',1,'2016-04-13 00:00:00',NULL,NULL,2),(5,'李慧','北京市西城区三里河路南三巷3号','100032','18032356666',1,'2016-04-13 00:00:00',NULL,NULL,3),(6,'王国强','北京市顺义区高丽营镇金马工业区18号','100061','13787882222',1,'2016-04-13 00:00:00',NULL,NULL,3);

    DROP TABLE IF EXISTS `smbms_bill`;

    CREATE TABLE `smbms_bill` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
    `billCode` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '账单编码',
    `productName` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '商品名称',
    `productDesc` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '商品描述',
    `productUnit` varchar(10) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '商品单位',
    `productCount` decimal(20,2) DEFAULT NULL COMMENT '商品数量',
    `totalPrice` decimal(20,2) DEFAULT NULL COMMENT '商品总额',
    `isPayment` int(10) DEFAULT NULL COMMENT '是否支付(1:未支付 2:已支付)',
    `createdBy` bigint(20) DEFAULT NULL COMMENT '创建者(userId)',
    `creationDate` datetime DEFAULT NULL COMMENT '创建时间',
    `modifyBy` bigint(20) DEFAULT NULL COMMENT '更新者(userId)',
    `modifyDate` datetime DEFAULT NULL COMMENT '更新时间',
    `providerId` bigint(20) DEFAULT NULL COMMENT '供应商ID',
    PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

    insert into `smbms_bill`(`id`,`billCode`,`productName`,`productDesc`,`productUnit`,`productCount`,`totalPrice`,`isPayment`,`createdBy`,`creationDate`,`modifyBy`,`modifyDate`,`providerId`) values (2,'BILL2016_002','香皂、肥皂、药皂','日用品-皂类','块','1000.00','10000.00',2,1,'2016-03-23 04:20:40',NULL,NULL,13),(3,'BILL2016_003','大豆油','食品-食用油','斤','300.00','5890.00',2,1,'2014-12-14 13:02:03',NULL,NULL,6),(4,'BILL2016_004','橄榄油','食品-进口食用油','斤','200.00','9800.00',2,1,'2013-10-10 03:12:13',NULL,NULL,7),(5,'BILL2016_005','洗洁精','日用品-厨房清洁','瓶','500.00','7000.00',2,1,'2014-12-14 13:02:03',NULL,NULL,9),(6,'BILL2016_006','美国大杏仁','食品-坚果','袋','300.00','5000.00',2,1,'2016-04-14 06:08:09',NULL,NULL,4),(7,'BILL2016_007','沐浴液、精油','日用品-沐浴类','瓶','500.00','23000.00',1,1,'2016-07-22 10:10:22',NULL,NULL,14),(8,'BILL2016_008','不锈钢盘碗','日用品-厨房用具','个','600.00','6000.00',2,1,'2016-04-14 05:12:13',NULL,NULL,14),(9,'BILL2016_009','塑料杯','日用品-杯子','个','350.00','1750.00',2,1,'2016-02-04 11:40:20',NULL,NULL,14),(10,'BILL2016_010','豆瓣酱','食品-调料','瓶','200.00','2000.00',2,1,'2013-10-29 05:07:03',NULL,NULL,8),(11,'BILL2016_011','海之蓝','饮料-国酒','瓶','50.00','10000.00',1,1,'2016-04-14 16:16:00',NULL,NULL,1),(12,'BILL2016_012','芝华士','饮料-洋酒','瓶','20.00','6000.00',1,1,'2016-09-09 17:00:00',NULL,NULL,1),(13,'BILL2016_013','长城红葡萄酒','饮料-红酒','瓶','60.00','800.00',2,1,'2016-11-14 15:23:00',NULL,NULL,1),(14,'BILL2016_014','泰国香米','食品-大米','斤','400.00','5000.00',2,1,'2016-10-09 15:20:00',NULL,NULL,3),(15,'BILL2016_015','东北大米','食品-大米','斤','600.00','4000.00',2,1,'2016-11-14 14:00:00',NULL,NULL,3),(16,'BILL2016_016','可口可乐','饮料','瓶','2000.00','6000.00',2,1,'2012-03-27 13:03:01',NULL,NULL,2),(17,'BILL2016_017','脉动','饮料','瓶','1500.00','4500.00',2,1,'2016-05-10 12:00:00',NULL,NULL,2),(18,'BILL2016_018','哇哈哈','饮料','瓶','2000.00','4000.00',2,1,'2015-11-24 15:12:03',NULL,NULL,2);

    DROP TABLE IF EXISTS `smbms_provider`;

    CREATE TABLE `smbms_provider` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
    `proCode` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '供应商编码',
    `proName` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '供应商名称',
    `proDesc` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '供应商详细描述',
    `proContact` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '供应商联系人',
    `proPhone` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '联系电话',
    `proAddress` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '地址',
    `proFax` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '传真',
    `createdBy` bigint(20) DEFAULT NULL COMMENT '创建者(userId)',
    `creationDate` datetime DEFAULT NULL COMMENT '创建时间',
    `modifyDate` datetime DEFAULT NULL COMMENT '更新时间',
    `modifyBy` bigint(20) DEFAULT NULL COMMENT '更新者(userId)',
    PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

    insert into `smbms_provider`(`id`,`proCode`,`proName`,`proDesc`,`proContact`,`proPhone`,`proAddress`,`proFax`,`createdBy`,`creationDate`,`modifyDate`,`modifyBy`) values (1,'BJ_GYS001','北京三木堂商贸有限公司','长期合作伙伴,主营产品:茅台、五粮液、郎酒、酒鬼酒、泸州老窖、赖茅酒、法国红酒等','张国强','13566667777','北京市丰台区育芳园北路','010-58858787',1,'2013-03-21 16:52:07',NULL,NULL),(2,'HB_GYS001','石家庄帅益食品贸易有限公司','长期合作伙伴,主营产品:饮料、水饮料、植物蛋白饮料、休闲食品、果汁饮料、功能饮料等','王军','13309094212','河北省石家庄新华区','0311-67738876',1,'2016-04-13 04:20:40',NULL,NULL),(3,'GZ_GYS001','深圳市泰香米业有限公司','初次合作伙伴,主营产品:良记金轮米,龙轮香米等','郑程瀚','13402013312','广东省深圳市福田区深南大道6006华丰大厦','0755-67776212',1,'2014-03-21 16:56:07',NULL,NULL),(4,'GZ_GYS002','深圳市喜来客商贸有限公司','长期合作伙伴,主营产品:坚果炒货.果脯蜜饯.天然花茶.营养豆豆.特色美食.进口食品.海味零食.肉脯肉','林妮','18599897645','广东省深圳市福龙工业区B2栋3楼西','0755-67772341',1,'2013-03-22 16:52:07',NULL,NULL),(5,'JS_GYS001','兴化佳美调味品厂','长期合作伙伴,主营产品:天然香辛料、鸡精、复合调味料','徐国洋','13754444221','江苏省兴化市林湖工业区','0523-21299098',1,'2015-11-22 16:52:07',NULL,NULL),(6,'BJ_GYS002','北京纳福尔食用油有限公司','长期合作伙伴,主营产品:山茶油、大豆油、花生油、橄榄油等','马莺','13422235678','北京市朝阳区珠江帝景1号楼','010-588634233',1,'2012-03-21 17:52:07',NULL,NULL),(7,'BJ_GYS003','北京国粮食用油有限公司','初次合作伙伴,主营产品:花生油、大豆油、小磨油等','王驰','13344441135','北京大兴青云店开发区','010-588134111',1,'2016-04-13 00:00:00',NULL,NULL),(8,'ZJ_GYS001','慈溪市广和绿色食品厂','长期合作伙伴,主营产品:豆瓣酱、黄豆酱、甜面酱,辣椒,大蒜等农产品','薛圣丹','18099953223','浙江省宁波市慈溪周巷小安村','0574-34449090',1,'2013-11-21 06:02:07',NULL,NULL),(9,'GX_GYS001','优百商贸有限公司','长期合作伙伴,主营产品:日化产品','李立国','13323566543','广西南宁市秀厢大道42-1号','0771-98861134',1,'2013-03-21 19:52:07',NULL,NULL),(10,'JS_GYS002','南京火头军信息技术有限公司','长期合作伙伴,主营产品:不锈钢厨具等','陈女士','13098992113','江苏省南京市浦口区浦口大道1号新城总部大厦A座903室','025-86223345',1,'2013-03-25 16:52:07',NULL,NULL),(11,'GZ_GYS003','广州市白云区美星五金制品厂','长期合作伙伴,主营产品:海绵床垫、坐垫、靠垫、海绵枕头、头枕等','梁天','13562276775','广州市白云区钟落潭镇福龙路20号','020-85542231',1,'2016-12-21 06:12:17',NULL,NULL),(12,'BJ_GYS004','北京隆盛日化科技','长期合作伙伴,主营产品:日化环保清洗剂,家居洗涤专卖、洗涤用品网、墙体除霉剂、墙面霉菌清除剂等','孙欣','13689865678','北京市大兴区旧宫','010-35576786',1,'2014-11-21 12:51:11',NULL,NULL),(13,'SD_GYS001','山东豪克华光联合发展有限公司','长期合作伙伴,主营产品:洗衣皂、洗衣粉、洗衣液、洗洁精、消杀类、香皂等','吴洪转','13245468787','山东济阳济北工业区仁和街21号','0531-53362445',1,'2015-01-28 10:52:07',NULL,NULL),(14,'JS_GYS003','无锡喜源坤商行','长期合作伙伴,主营产品:日化品批销','周一清','18567674532','江苏无锡盛岸西路','0510-32274422',1,'2016-04-23 11:11:11',NULL,NULL),(15,'ZJ_GYS002','乐摆日用品厂','长期合作伙伴,主营产品:各种中、高档塑料杯,塑料乐扣水杯(密封杯)、保鲜杯(保鲜盒)、广告杯、礼品杯','王世杰','13212331567','浙江省金华市义乌市义东路','0579-34452321',1,'2016-08-22 10:01:30',NULL,NULL);

    DROP TABLE IF EXISTS `smbms_role`;

    CREATE TABLE `smbms_role` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
    `roleCode` varchar(15) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '角色编码',
    `roleName` varchar(15) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '角色名称',
    `createdBy` bigint(20) DEFAULT NULL COMMENT '创建者',
    `creationDate` datetime DEFAULT NULL COMMENT '创建时间',
    `modifyBy` bigint(20) DEFAULT NULL COMMENT '修改者',
    `modifyDate` datetime DEFAULT NULL COMMENT '修改时间',
    PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

    insert into `smbms_role`(`id`,`roleCode`,`roleName`,`createdBy`,`creationDate`,`modifyBy`,`modifyDate`) values (1,'SMBMS_ADMIN','系统管理员',1,'2016-04-13 00:00:00',NULL,NULL),(2,'SMBMS_MANAGER','经理',1,'2016-04-13 00:00:00',NULL,NULL),(3,'SMBMS_EMPLOYEE','普通员工',1,'2016-04-13 00:00:00',NULL,NULL);

    DROP TABLE IF EXISTS `smbms_user`;

    CREATE TABLE `smbms_user` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
    `userCode` varchar(15) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '用户编码',
    `userName` varchar(15) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '用户名称',
    `userPassword` varchar(15) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '用户密码',
    `gender` int(10) DEFAULT NULL COMMENT '性别(1:女、 2:男)',
    `birthday` date DEFAULT NULL COMMENT '出生日期',
    `phone` varchar(15) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '手机',
    `address` varchar(30) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '地址',
    `userRole` bigint(20) DEFAULT NULL COMMENT '用户角色(取自角色表-角色id)',
    `createdBy` bigint(20) DEFAULT NULL COMMENT '创建者(userId)',
    `creationDate` datetime DEFAULT NULL COMMENT '创建时间',
    `modifyBy` bigint(20) DEFAULT NULL COMMENT '更新者(userId)',
    `modifyDate` datetime DEFAULT NULL COMMENT '更新时间',
    PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

    insert into `smbms_user`(`id`,`userCode`,`userName`,`userPassword`,`gender`,`birthday`,`phone`,`address`,`userRole`,`createdBy`,`creationDate`,`modifyBy`,`modifyDate`) values (1,'admin','系统管理员','1234567',1,'1983-10-10','13688889999','北京市海淀区成府路207号',1,1,'2013-03-21 16:52:07',NULL,NULL),(2,'liming','李明','0000000',2,'1983-12-10','13688884457','北京市东城区前门东大街9号',2,1,'2014-12-31 19:52:09',NULL,NULL),(5,'hanlubiao','韩路彪','0000000',2,'1984-06-05','18567542321','北京市朝阳区北辰中心12号',2,1,'2014-12-31 19:52:09',NULL,NULL),(6,'zhanghua','张华','0000000',1,'1983-06-15','13544561111','北京市海淀区学院路61号',3,1,'2013-02-11 10:51:17',NULL,NULL),(7,'wangyang','王洋','0000000',2,'1982-12-31','13444561124','北京市海淀区西二旗辉煌国际16层',3,1,'2014-06-11 19:09:07',NULL,NULL),(8,'zhaoyan','赵燕','0000000',1,'1986-03-07','18098764545','北京市海淀区回龙观小区10号楼',3,1,'2016-04-21 13:54:07',NULL,NULL),(10,'sunlei','孙磊','0000000',2,'1981-01-04','13387676765','北京市朝阳区管庄新月小区12楼',3,1,'2015-05-06 10:52:07',NULL,NULL),(11,'sunxing','孙兴','0000000',2,'1978-03-12','13367890900','北京市朝阳区建国门南大街10号',3,1,'2016-11-09 16:51:17',NULL,NULL),(12,'zhangchen','张晨','0000000',1,'1986-03-28','18098765434','朝阳区管庄路口北柏林爱乐三期13号楼',3,1,'2016-08-09 05:52:37',1,'2016-04-14 14:15:36'),(13,'dengchao','邓超','0000000',2,'1981-11-04','13689674534','北京市海淀区北航家属院10号楼',3,1,'2016-07-11 08:02:47',NULL,NULL),(14,'yangguo','杨过','0000000',2,'1980-01-01','13388886623','北京市朝阳区北苑家园茉莉园20号楼',3,1,'2015-02-01 03:52:07',NULL,NULL),(15,'zhaomin','赵敏','0000000',1,'1987-12-04','18099897657','北京市昌平区天通苑3区12号楼',2,1,'2015-09-12 12:02:12',NULL,NULL);

14.2 JDBC 固定步骤

  1. 加载驱动
  2. 建立连接, 返回连接对象 Connection
  3. 创建数据库操作对象 Statement
  4. 执行 sql 语句,返回结果集 ResultSet
  5. 关闭连接
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
// 加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");

// 配置信息
String url = "jdbc:mysql://localhost:3306/jdbc?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8";
String name = "root";
String password = "123456";

// 建立连接, 返回连接对象 Connection
Connection connection = DriverManager.getConnection(url, name, password);

// 创建数据库操作对象 Statement
Statement statement = connection.createStatement();

// 执行sql语句
String sql = "SELECT * FROM users";

// 返回结果集
ResultSet resultSet = statement.executeQuery(sql);

// 处理结果集
while (resultSet.next()) {
System.out.println("id=" + resultSet.getObject("id"));
System.out.println("name=" + resultSet.getObject("name"));
System.out.println("password=" + resultSet.getObject("password"));
System.out.println("email=" + resultSet.getObject("email"));
System.out.println("birthday=" + resultSet.getObject("birthday"));
}

// 关闭连接
resultSet.close();
statement.close();
connection.close();

14.3 预编译 SQL

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
// 加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");

// 配置信息
String url = "jdbc:mysql://localhost:3306/jdbc?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8";
String name = "root";
String pwd = "123456";

// 建立连接, 返回连接对象 Connection
Connection connection = DriverManager.getConnection(url, name, pwd);

// 预编译sql语句
String sql = "SELECT * FROM users WHERE id = ?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1, 1);

ResultSet resultSet = preparedStatement.executeQuery();

// 处理结果集
while (resultSet.next()) {
System.out.println("id=" + resultSet.getObject("id"));
System.out.println("name=" + resultSet.getObject("name"));
System.out.println("password=" + resultSet.getObject("password"));
System.out.println("email=" + resultSet.getObject("email"));
System.out.println("birthday=" + resultSet.getObject("birthday"));
}

// 关闭连接
resultSet.close();
preparedStatement.close();
connection.close();

14.4 事务

  • ==ACID原则==

14.5 Junit单元测试

  • 项目依赖

    1
    2
    3
    4
    5
    6
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>3.8.1</version>
    <scope>test</scope>
    </dependency>
  • 简单使用:@Test注解只有在方法上有效,只要加了这个注解的方法,就可以直接运行

    • 成功image-20230707230830996
    • 失败image-20230707230913274

15. 文件上传

15.1 文件上传准备

image-20230714225321270

  • 导入所需包

对于文件上传,浏览器在上传的过程中是将文件以流的形式提交到服务器端

  • 选择apache的开源工具common-fileupload作为文件上传组件(依赖于common-io包)

    • ```xml
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.13.0</version>
      
      </dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.5</version>
      
      </dependency>
      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

      ## 15.2 文件上传注意事项

      1. 为保证服务器安全,上传文件应该放在外界无法直接访问的目录下,比如放在`WEB-INF`目录中
      2. 为放置文件覆盖的现象发生,要为上传文件产生一个唯一的文件名(时间戳、uuid、MD5、位运算)
      3. 要限制文件上传的最大值
      4. 可以限制上传文件的类型,在收到上传文件名时,判断后缀名是否合法

      ## 15.3 工具类

      - `ServletFileUpload`负责处理上传的文件数据,并将表单中每个输入项封装成一个`Fileltem`对象
      - 在使用`ServletFileUpload`对象解析请求时需要`DiskFileltemFactory`对象
      - 所以,我们需要在进行解析工作前构造好`DiskFileltemFactory`对象,通过`ServletFileUpload`对象的构造方法或`setFileltemFactory()`方法设置`ServletFileUpload`对象的`fileltemFactory`属性

      ### 15.3.1 FileItem类

      > - 在 HTML 页面 `input` 必须有 `name<input type="file" name="filename">`
      >
      > - 表单如果包含一个文件上传输入项的话,这个表单的 `enctype` 属性就必须设置为 `multipart/form-data` -> 服务器端获取数据通过流

      ```jsp
      <html>
      <body>
      <h2>Hello World!</h2>
      </body>
      <%-- 通过表单上传
      get: 上传文件大小有限制,不安全
      post: 上传文件大小无限制,安全 (√)
      --%>
      <form action="${pageContext.request.contextPath}/upload.do" enctype="multipart/form-data" method="post">
      上传用户: <input type="text" name="username"><br>
      上传文件1: <input type="file" name="file1"><br>
      上传文件2: <input type="file" name="file2"><br>
      <p><input type="submit" value="上传"> <input type="reset" value="重置"></p>
      </form>
      </html>
  • 常用方法介绍:

    • ```java
      //isFormField方法用于判断FileItem类对象封装的数据是一个普通文本表单
      //还是一个文件表单,如果是普通表单字段则返回true,否则返回false
      boolean isFormField();//即:只要是上传的文件,返回的就是false

      //getFieldName方法用于返回表单标签name属性的值。
      String getFieldName();//获取这个input的name属性值

      //getString方法用于将FileItem对象中保存的数据流内容以一个字符串返回
      String getString();//用字符串存储文件的数据流

      //getName方法用于获得文件上传字段中的文件名
      String getName();

      //以流的形式返回上传文件的数据内容。
      InputStream getInputStream()

      //delete方法用来清空FileItem类对象中存放的主体内容
      //如果主体内容被保存在临时文件中,delete方法将删除该临时文件。
      void delete();

      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

      ### 15.3.2 ServletFileUpload 类

      > `ServletFileUpload`负责处理上传的文件数据,并将表单中每个输入项封装成一个`FileItem`对象中 . 使用其`parseRequest(HttpServletRequest) `方法可以将通过表单中每一个HTML标签提交的数据封装成一个`FileItem`对象,然后以List列表的形式返回,使用该方法处理上传文件简单易用

      - 负责处理上传的文件数据

      ## 15.4 代码实现

      ### 15.4.1 JSP

      - `index.jsp`

      ```jsp
      <%@ page contentType="text/html;charset=UTF-8" language="java" %>
      <html>
      <body>
      <h2>Hello World!</h2>
      </body>
      <%-- 通过表单上传
      get: 上传文件大小有限制,不安全
      post: 上传文件大小无限制,安全 (√)
      --%>
      <form action="${pageContext.request.contextPath}/upload.do" enctype="multipart/form-data" method="post">
      上传用户: <input type="text" name="username"><br>
      上传文件1: <input type="file" name="file1"><br>
      上传文件2: <input type="file" name="file2"><br>
      <p><input type="submit" value="上传"> <input type="reset" value="重置"></p>
      </form>
      </html>
  • info.jsp

1
2
3
4
5
6
7
8
9
10
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
${msg}
</body>
</html>

13.4.2 FileServlet

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
public class FileServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("========== 进入 FileServlet ==========");
// 1. 判断上传的文件是普通表单还是带文件的表单
if (!ServletFileUpload.isMultipartContent(req)) {
System.out.println("进入了 普通表单");
return; // 终止方法运行,说明这是一个普通的表单,直接返回
}

// 2. 文件存储空间创建
// 创建上传文件的保存路径,建议在 WEB-INF 路径下,安全,用户无法直接访问上传的文件
System.out.println("带文件的表单");
String uploadPath = this.getServletContext().getRealPath("/WEB-INF/upload"); // 获取上传文件存储目录文件名, 不存在就创建
File uploadDir = new File(uploadPath);
if (!uploadDir.exists()) {
uploadDir.mkdir(); // 没有这个目录就创建这个目录
}

// 3. 缓存存储空间创建(缓存,临时文件)
// 临时路径,假如文件超过了预期的大小,我们就把它放到一个临时文件中,过几天自动删除,或者提醒用户转存为永久
String tmpPath = this.getServletContext().getRealPath("/WEB-INF/tmp"); // 获取临时文件存储目录文件名, 不存在就创建
File tmpDir = new File(tmpPath);
if (!tmpDir.exists()) {
tmpDir.mkdir(); // 没有这个目录就创建这个目录
}

// 处理上传的文件,一般都需要通过流来获取,我们可以使用 request.getInputStream() 来获取请求的正文内容, 原生态的文件上传流获取请求的正文内容, 但是用起来比较麻烦
// * 建议使用 Apache 的文件上传组件来实现,common-fileupload, 它需要依赖于 commons-io 组件
String msg = "文件上传失败";
try {
// 1. 创建 DiskFileItemFactory 对象,处理文件上传路径或者大小限制的
DiskFileItemFactory factory = getDiskFileItemFactory(tmpDir);
// 2. 获取 ServletFileUpload
ServletFileUpload upload = getServletFileUpload(factory);
// 3. 处理上传的文件
msg = uploadParseRequest(upload, req, uploadPath);
} catch (FileUploadException e) {
throw new RuntimeException(e);
}

// 4. 返回信息
req.setAttribute("msg", msg);
req.getRequestDispatcher("info.jsp").forward(req, resp);
}

public DiskFileItemFactory getDiskFileItemFactory(File tempDir) {
DiskFileItemFactory factory = new DiskFileItemFactory();
// 通过这个工厂设置一个缓冲区,当上传的文件大小超过这个缓冲区的时候,就会生成一个临时文件存放到指定的临时目录中
factory.setSizeThreshold(1024 * 1024); // 缓冲区大小为 1M
factory.setRepository(tempDir); // 设置临时目录
return factory;
}

public static ServletFileUpload getServletFileUpload(DiskFileItemFactory factory) {
ServletFileUpload upload = new ServletFileUpload(factory);
// 监听文件上传进度
upload.setProgressListener(new ProgressListener() {
@Override
// pBytesRead: 已经读取到的文件大小
// pContentLength: 文件大小
public void update(long pBytesRead, long pContentLength, int PItems) {
System.out.println("总大小: " + pContentLength + " 已上传: " + pBytesRead);
}
});

// 处理乱码问题
upload.setHeaderEncoding("UTF-8");
// 设置单个文件的最大值
upload.setFileSizeMax(1024 * 1024 * 10);
// 设置总共能够上传文件的大小
// 1024 = 1kb * 1024 = 1M * 10 = 10M
upload.setSizeMax(1024 * 1024 * 10);
return upload;
}

public static String uploadParseRequest(ServletFileUpload upload, HttpServletRequest req, String uploadPath) throws FileUploadException, IOException {
String msg = "文件上传失败";

// 1. 解析前端请求,封装成一个 FileItem 对象
List<FileItem> fileItems = upload.parseRequest(req); // 使用文件解析对象的 parseRequest(req) 方法解析 request 对象,会将前端表单封装成一个个 FileItem 对象,然后放入到一个 List 中
// 循环判断,每一个表单项,是普通类型,还是上传的文件
for (FileItem fileItem : fileItems) {
// 判断上传的文件是普通的表单还是带文件的表单
if (fileItem.isFormField()) {
// 为 true,表示这是一个普通的表单项
// getFieldName 拿到的是前端表单控件的 name
String name = fileItem.getFieldName(); // 获取非文件<input>表单项的 name 属性值
String value = fileItem.getString("UTF-8"); // 获取非文件<input>标签的value内容
System.out.println(name + ": " + value);
} else {
// 判断其为上传的文件
/* ========== 处理文件 ========== */

// 拿到文件名
String uploadFileName = fileItem.getName();
System.out.println("上传的文件名: " + uploadFileName);

if (uploadFileName.trim().equals("") || uploadFileName == null) { // 如果上传的文件名为空,即没有上传文件
continue; // 跳过本次循环
}

// 获得上传的文件名 /img/date/this.png
String fileName = uploadFileName.substring(uploadFileName.lastIndexOf("/") + 1);
// 获得文件的后缀名
String fileExtName = uploadFileName.substring(uploadFileName.lastIndexOf(".") + 1);

System.out.println("文件信息 [文件名: " + fileName + " ---文件类型: " + fileExtName);

// 可以使用 UUID(唯一识别的通用码), 保证文件名唯一
// UUID.randomUUID().toString() 生成随机数
String uuidPath = UUID.randomUUID().toString();

/* ========== 文件处理完毕 ========== */

// 存到哪? uploadPath
// 文件真实存在的路径 realPath = uploadPath + "/" + uuidPath = /WEB-INF/upload/uuidPath
String realPath = uploadPath + "/" + uuidPath; // 文件存储文件夹的真实路径 String
// 给每个文件创建一个对应的文件夹
File realFilePath = new File(realPath); // 文件存储文件夹的真实路径 File
if (!realFilePath.exists()) {
realFilePath.mkdir(); // 创建文件夹(一般都是不存在的)
}

/* ========== 存放地址 ========== */
InputStream is = null;

// 获取文件上传的流
try {
is = fileItem.getInputStream();
} catch (IOException e) {
e.printStackTrace();
}

// 创建一个文件输出流
// realPath = 真实的文件夹
// 差了一个文件; 加上输出文件的名字 + "/" + uuidFileName
FileOutputStream fos = new FileOutputStream(realPath + "/" + fileName);

// 创建一个缓冲区
byte[] buffer = new byte[1024 * 1024];

// 判断是否读取完毕
int len = 0;
// 如果大于 0 说明还存在数据
while ((len = is.read(buffer)) > 0) {
fos.write(buffer, 0, len); // 输出文件(将文件流写入 realPath + "/" + uuidFileName)
}

// 关闭流
fos.close();
is.close();

msg = "文件上传成功";
fileItem.delete(); // 上传成功,清除临时文件
}
}
return msg;
}
}

16. 邮件发送

16.1 电子邮件

想要在网络中实现邮件功能,必须要有专门的邮件服务器

  • 接收和发送
    • 接收:POP3协议
    • 发送:SMTP协议
  • SMTP服务器地址 -> smtp.xxx.com 每个网站都有一个自己的服务器地址(固定的)
  • QQ服务授权:image-20230715144113115

文件发送原理图

  1. 张三通过smtp协议连接到Smtp服务器,然后发送一封邮件给网易的邮件服务器
  2. 网易分析发现需要去QQ的邮件服务器,通过smtp协议将邮件转投给QQ的Smtp服务器
  3. QQ将接收到的邮件存储在lisi@qq.com这个邮件账号的空间中
  4. 李四通过Pop3协议连接到Pop3服务器收取邮件
  5. 从lisi@qq.com这个邮件账号的空间中取出邮件
  6. Pop3服务器将取出来的邮件送到李四手中

16.2 传输协议

  • SMTP协议

发送邮件:我们通常把处理用户smtp请求(邮件发送请求)的服务器称之为SMTP服务器(邮件发送服务器)。

  • POP3协议

接收邮件:我们通常把处理用户pop3请求(邮件接收请求)的服务器称之为POP3服务器(邮件接收服务器)。

16.3 Java发送邮件

16.3.1 导包

mail.jar

activation.jar

JavaMail 是sun公司(现以被甲骨文收购)为方便Java开发人员在应用程序中实现邮件发送和接收功能而提供的一套==标准开发包==,

它支持一些常用的邮件协议,如前面所讲的SMTP,POP3,IMAP, 还有MIME(Multipurpose Internet Mail Extensions, 多用途互联网邮件扩展类型)(附件和图片)等。

我们在使用JavaMail API 编写邮件时,无须考虑邮件的底层实现细节,只要调用JavaMail 开发包中相应的API类就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- https://mvnrepository.com/artifact/javax.mail/mail -->
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4.7</version>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.activation/activation -->
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>

16.3.2 基本流程

  1. 创建包含邮件服务器的网络连接信息的Session对象。
  2. 创建代表邮件内容的Message对象
  3. 创建Transport对象,连接服务器,发送Message,关闭连接
  • 四大核心类:

    • image-20230715145029169

    • Session:定义整个程序,所有的信息。比如:smtp.qq.com、邮箱账号、密码

    • Transport:发送邮件
    • Message:信息的内容。对应:收件人、主题、正文

16.3.3 权限

  • QQ邮箱
    • 获取授权码
      • image-20230715145832261
      • jgzstcpgefazbjbg

16.4 纯文本邮件

  1. 准备消息
  2. 发送到哪里去
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.bayyy;

import com.sun.mail.util.MailSSLSocketFactory;

import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.util.Properties;


/*
1. 创建定义整个应用程序所需要的环境信息的session对象
2. 通过session得到transport对象
3. 使用邮箱的用户名和授权码连上邮件服务器
4. 创建邮件:写邮件
5. 发送邮件
6. 关闭连接,一切网络操作注意都需要进行连接等资源的关闭
*/
public class MailDemo1 {
public static void main(String[] args) throws Exception {

// Properties中 设置属性
Properties prop = new Properties();
prop.setProperty("mail.host", "smtp.qq.com");///设置QQ邮件服务器
prop.setProperty("mail.transport.protocol", "smtp");///邮件发送协议
prop.setProperty("mail.smtp.auth", "true");//需要验证用户密码

// QQ邮箱需要设置SSL加密,原因:大厂。其他邮箱不需要
MailSSLSocketFactory sf = new MailSSLSocketFactory();
sf.setTrustAllHosts(true);
prop.put("mail.smtp.ssl.enable", "true");//使用安全的连接为true
prop.put("mail.smtp.ssl.socketFactory", sf);//socket工厂,使用自己的socket工厂

// 使用javaMail发送邮件的5个步骤

// 1.创建定义整个应用程序所需要的环境信息的session对象
// QQ才有!其他邮箱就不用
Session session = Session.getDefaultInstance(prop, new Authenticator() {//获取默认的实例
@Override
protected PasswordAuthentication getPasswordAuthentication() {
//发件人邮箱、授权码
return new PasswordAuthentication("475417309@qq.com", "jgzstcpgefazbjbg");
}
});

// 开启session的debug模式,这样可以查看到程序发送Email的运行状态
session.setDebug(true);

// 2.通过session得到transport对象
Transport ts = session.getTransport();

// 3.使用邮箱的用户名和授权码连上邮件服务器
ts.connect("smtp.qq.com", "475417309@qq.com", "jgzstcpgefazbjbg");

// 4.创建邮件:写邮件
// 需要传递session
MimeMessage message = new MimeMessage(session);

// 指明邮件的发件人
message.setFrom(new InternetAddress("475417309@qq.com"));

// 指明邮件的收件人,现在发件人和收件人是一样的,那就是自己给自己发
message.setRecipient(Message.RecipientType.TO, new InternetAddress("475417309@qq.com"));

// 邮件的标题 只包含文本的简单邮件
message.setSubject("发送的标题");

// 邮件的文本内容
message.setContent("你好", "text/html;charset=UTF-8");

// 5.发送邮件
ts.sendMessage(message, message.getAllRecipients());

// 6.关闭连接,一切网络都需要关闭
ts.close();
}
}

  • 测试结果:image-20230715151848586

  • DEBUG 内容

    • ```java
      DEBUG: setDebug: JavaMail version 1.6.2
      DEBUG: getProvider() returning javax.mail.Provider[TRANSPORT,smtp,com.sun.mail.smtp.SMTPTransport,Oracle]
      DEBUG SMTP: useEhlo true, useAuth true
      DEBUG SMTP: trying to connect to host “smtp.qq.com”, port 465, isSSL true
      220 newxmesmtplogicsvrszb6-0.qq.com XMail Esmtp QQ Mail Server.
      DEBUG SMTP: connected to host “smtp.qq.com”, port: 465
      EHLO bayyy
      250-newxmesmtplogicsvrszb6-0.qq.com
      250-PIPELINING
      250-SIZE 73400320
      250-AUTH LOGIN PLAIN XOAUTH XOAUTH2
      250-AUTH=LOGIN
      250-MAILCOMPRESS
      250 8BITMIME
      DEBUG SMTP: Found extension “PIPELINING”, arg “”
      DEBUG SMTP: Found extension “SIZE”, arg “73400320”
      DEBUG SMTP: Found extension “AUTH”, arg “LOGIN PLAIN XOAUTH XOAUTH2”
      DEBUG SMTP: Found extension “AUTH=LOGIN”, arg “”
      DEBUG SMTP: Found extension “MAILCOMPRESS”, arg “”
      DEBUG SMTP: Found extension “8BITMIME”, arg “”
      DEBUG SMTP: protocolConnect login, host=smtp.qq.com, user=475417309@qq.com, password=
      DEBUG SMTP: Attempt to authenticate using mechanisms: LOGIN PLAIN DIGEST-MD5 NTLM XOAUTH2
      DEBUG SMTP: Using mechanism LOGIN
      DEBUG SMTP: AUTH LOGIN command trace suppressed
      DEBUG SMTP: AUTH LOGIN succeeded
      DEBUG SMTP: use8bit false
      MAIL FROM:475417309@qq.com
      250 OK
      RCPT TO:475417309@qq.com
      250 OK
      DEBUG SMTP: Verified Addresses
      DEBUG SMTP: 475417309@qq.com
      DATA
      354 End data with ..
      Date: Sat, 15 Jul 2023 15:18:14 +0800 (CST)
      From: 475417309@qq.com
      To: 475417309@qq.com
      Message-ID: 1952779858.0.1689405494818@smtp.qq.com
      Subject: =?UTF-8?B?5Y+R6YCB55qE5qCH6aKY?=
      MIME-Version: 1.0
      Content-Type: text/html;charset=UTF-8
      Content-Transfer-Encoding: base64

      5L2g5aW9
      .
      250 OK: queued as.
      DEBUG SMTP: message successfully delivered to mail server
      QUIT
      221 Bye.

      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
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131

      ## 16.5 富文本邮件

      > MIME(多用途互联网邮件扩展类型)

      - MimeBodyPart类 (内容的主体)

      - javax.mail.internet.MimeBodyPart类


      - 表示的是一个MIME消息,它和MimeMessage类一样都是从Part接口继承过来。


      - MimeMultipart类 (内容的封装)

      - javax.mail.internet.MimeMultipart是抽象类


      - Multipart的实现子类,它用来组合多个MIME消息。


      - 一个MimeMultipart对象可以包含多个代表MIME消息的MimeBodyPart对象。

      ![image-20230715152615407](C:/Users/bayyy/AppData/Roaming/Typora/typora-user-images/image-20230715152615407.png)

      ```java
      package com.bayyy;

      import com.sun.mail.util.MailSSLSocketFactory;

      import javax.activation.DataHandler;
      import javax.activation.FileDataSource;
      import javax.mail.*;
      import javax.mail.internet.InternetAddress;
      import javax.mail.internet.MimeBodyPart;
      import javax.mail.internet.MimeMessage;
      import javax.mail.internet.MimeMultipart;
      import java.util.Properties;

      public class MailDemo2 {
      public static void main(String[] args) throws Exception {
      // 打印当前路径
      System.out.println(System.getProperty("user.dir"));
      //Properties中 设置属性
      Properties prop = new Properties();
      prop.setProperty("mail.host", "smtp.qq.com");///设置QQ邮件服务器
      prop.setProperty("mail.transport.protocol", "smtp");///邮件发送协议
      prop.setProperty("mail.smtp.auth", "true");//需要验证用户密码

      //QQ邮箱需要设置SSL加密,原因:大厂。其他邮箱不需要
      MailSSLSocketFactory sf = new MailSSLSocketFactory();
      sf.setTrustAllHosts(true);
      prop.put("mail.smtp.ssl.enable", "true");//使用安全的连接为true
      prop.put("mail.smtp.ssl.socketFactory", sf);//socket工厂,使用自己的socket工厂

      //使用javaMail发送邮件的5个步骤
      //1.创建定义整个应用程序所需要的环境信息的session对象

      //QQ才有!其他邮箱就不用
      Session session = Session.getDefaultInstance(prop, new Authenticator() {//获取默认的实例
      @Override
      protected PasswordAuthentication getPasswordAuthentication() {
      //发件人邮箱、授权码
      return new PasswordAuthentication("475417309@qq.com", "jgzstcpgefazbjbg");
      }
      });

      //开启session的debug模式,这样可以查看到程序发送Email的运行状态
      session.setDebug(true);

      //2.通过session得到transport对象
      Transport ts = session.getTransport();

      //3.使用邮箱的用户名和授权码连上邮件服务器
      ts.connect("smtp.qq.com", "475417309@qq.com", "jgzstcpgefazbjbg");

      //4.创建邮件:写邮件
      //需要传递session
      MimeMessage message = new MimeMessage(session);

      //指明邮件的发件人
      message.setFrom(new InternetAddress("475417309@qq.com"));

      //指明邮件的收件人,现在发件人和收件人是一样的,那就是自己给自己发
      message.setRecipient(Message.RecipientType.TO, new InternetAddress("475417309@qq.com"));

      //邮件的标题 只包含文本的简单邮件
      message.setSubject("发送的标题");

      //邮件的文本内容
      message.setContent("你好", "text/html;charset=UTF-8");

      /* ================== 图片和附件的邮件 ================== */
      /* ================== 准备图片数据 ================== */
      MimeBodyPart image = new MimeBodyPart();
      //图片需要经过数据化的处理 DataHandler:数据处理 FileDataSource:加载文件的资源
      DataHandler dh = new DataHandler(new FileDataSource("D:\\Coding\\test\\temp\\JavaWeb\\javaweb-06-extension\\mail\\src\\main\\resources\\girl.jpg"));
      //在body中放入这个处理的图片数据
      image.setDataHandler(dh);
      //给这个body设置一个ID名字
      image.setContentID("girl.jpg");

      /* ================== 准备正文数据 ================== */
      MimeBodyPart text = new MimeBodyPart();
      text.setContent("这是一张正文<img src='cid:girl.jpg'>", "text/html;charset=UTF-8");

      /* ================== 准备附件数据 ================== */
      MimeBodyPart body1 = new MimeBodyPart();
      body1.setDataHandler(new DataHandler(new FileDataSource("D:\\Coding\\test\\temp\\JavaWeb\\javaweb-06-extension\\mail\\src\\main\\resources\\watermelon.jpg")));
      body1.setFileName("1.txt");

      //描述数据关系
      MimeMultipart mm = new MimeMultipart();
      mm.addBodyPart(body1);
      mm.addBodyPart(text);
      mm.addBodyPart(image);
      mm.setSubType("mixed");

      //设置到消息中,保存修改
      message.setContent(mm);
      message.saveChanges();

      /* ================== 图片和附件的邮件准备结束 ================== */

      //5.发送邮件
      ts.sendMessage(message, message.getAllRecipients());

      //6.关闭连接,一切网络都需要关闭
      ts.close();
      }
      }

16.6 JavaWeb 发送邮件

16.6.1 前端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>注册</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/register.do" method="post">
用户名:<input type="text" name="username"><br/>
密码:<input type="password" name="password"><br/>
邮箱:<input type="text" name="email"><br/>
<input type="submit" value="注册">
</form>
</body>
</html>

16.6.2 实体类

  • Lombok

    • ```xml
      <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
      <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <version>1.18.28</version>
          <scope>provided</scope>
      </dependency>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14

      ```java
      import lombok.AllArgsConstructor;
      import lombok.Data;
      import lombok.NoArgsConstructor;

      @Data // 生成get set方法
      @AllArgsConstructor // 生成全参构造器
      @NoArgsConstructor // 生成无参构造器
      public class User {
      private String username;
      private String password;
      private String email;
      }
  • 测试结果:image-20230715155602070

16.6.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
79
80
81
82
83
84
85
86
87
88
/*
网站3秒原则:用户体验

让用户不等待,事情也还做?
用户执行完这个方法,它还走它的东西,我走我的页面
通过多线程,实现用户体验!(异步处理)

*/
public class SendMail extends Thread {


// 用于给用户发送邮件的邮箱
private String from = "475417309@qq.com";
// 邮箱的用户名
private String username = "bayyy";
// 邮箱的密码
private String password = "jgzstcpgefazbjbg";
// 发送邮件的服务器地址
private String host = "smtp.qq.com";

// 导入一个类
private User user;

public SendMail(User user) {
this.user = user;
}


@Override
public void run() {

try {
Properties prop = new Properties();

prop.setProperty("mail.host", host);///设置QQ邮件服务器
prop.setProperty("mail.transport.protocol", "smtp");///邮件发送协议
prop.setProperty("mail.smtp.auth", "true");//需要验证用户密码
// QQ邮箱需要设置SSL加密
MailSSLSocketFactory sf = new MailSSLSocketFactory();
sf.setTrustAllHosts(true);
prop.put("mail.smtp.ssl.enable", "true");
prop.put("mail.smtp.ssl.socketFactory", sf);

// 使用javaMail发送邮件的5个步骤

// 1.创建定义整个应用程序所需要的环境信息的session对象
Session session = Session.getDefaultInstance(prop, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(from, password);
}
});
// 开启session的debug模式,这样可以查看到程序发送Email的运行状态
session.setDebug(true);

// 2.通过session得到transport对象
Transport ts = session.getTransport();

// 3.使用邮箱的用户名和授权码连上邮件服务器
ts.connect(host, username, password);

// 4.创建邮件:写文件

// 注意需要传递session
MimeMessage message = new MimeMessage(session);
// 指明邮件的发件人
message.setFrom(new InternetAddress(from));
// 指明邮件的收件人
message.setRecipient(Message.RecipientType.TO, new InternetAddress(user.getEmail())); // 从前端接收的
// 邮件标题
message.setSubject("注册通知");
// 邮件的文本内容

String info = "恭喜你(" + user.getUsername() + ")成功注册!" + "密码:" + user.getPassword();
message.setContent(info, "text/html;charset=UTF-8");
message.saveChanges();//保存更改

// 5.发送邮件
ts.sendMessage(message, message.getAllRecipients());

// 6.关闭连接
ts.close();
} catch (Exception e) {
System.out.println(e);
}
}
}

16.6.4 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
public class RegisterServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 接收用户请求
String username = req.getParameter("username");
String password = req.getParameter("password");
String email = req.getParameter("email");

User user = new User(username, password, email);

// 用户注册成功后,会给用户发送一封邮件
// 使用多线程给用户发送邮件, 防止出现耗时操作, 用户体验不好
SendMail sendMail = new SendMail(user);
// 启动线程, 启动后会自动执行run方法
sendMail.start();

// 注册用户
req.setAttribute("msg", "注册成功, 已通过电子邮箱向您发送注册信息, 请注意查收! ");
req.getRequestDispatcher("info.jsp").forward(req, resp);
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp);
}
}
  • 测试结果:image-20230715160655279