spring boot


基础篇

快速上手 SpringBoot

SpringBoot 入门程序开发

  • SpringBoot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化 Spring 应用的初始搭建以及开发过程
    • Spring 程序缺点
      • 依赖设置繁琐
      • 配置繁琐
    • SpringBoot 程序优点
      • 起步依赖(简化依赖配置)
      • 自动配置(简化常用工程相关配置)
      • 辅助功能(内置服务器,…)

快速上手 SpringBoot

  • 1.创建新模块,选择 Spring Initializr,并配置模块相关基础信息

  • 2.选择当前模块需要使用的技术集(比如 web 里的 Spring Web)

  • 3.开发控制器类(controller 类)

    • ```java
      package com.itheima.controller;import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;// Rest 模式
      @RestController
      @RequestMapping(“/books”)
      public class BookController {
      @GetMapping
      public String getById() {
          System.out.println("springboot is running");
          return "springboot is running";
      }
      
      }
      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

      * 4.运行自动生成的 Application 类

      #### 入门案例

      * 基于 SpringBoot 官网创建项目,地址:https://start.spring.io/(官网版)

      * 基于阿里云创建项目,地址:https://start.aliyun.com

      * **注意:**阿里云提供的版本较低,如果需要使用高版本,进入工程后手工切换 SpringBoot 版本,阿里云提供的工程模板与 Spring 官网提供的工程模板略有不同

      * 手工创建项目(手工导入坐标)

      * 1.创建普通 Maven 工程

      * 2.继承 spring-boot-starter-parent

      * 3.添加依赖 spring-boot-start-web

      * 4.制作引导类 Application

      * ```xml
      <?xml version="1.0" encoding="UTF-8"?>
      <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>2.7.1</version>
      <relativePath/> <!-- lookup parent from repository -->
      </parent>
      <groupId>com.itheima</groupId>
      <artifactId>springboot_01_01_quickstart</artifactId>
      <version>0.0.1-SNAPSHOT</version>
      <dependencies>
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      </dependencies>
      </project>
  • 最简 SpringBoot 程序所包含的基础文件

    • pom.xml 文件
    • Application 类
  • Spring 程序与 SpringBoot 程序对比

image-20220719161719782

注意:基于idea 开发 SpringBoot 程序需要确保联网且能够加载到程序框架结构

入门案例解析
  • parent(解决配置问题)

    • 1.开发 SpringBoot 程序要继承 spring-boot-starter-parent
    • 2.spring-boot-starter-parent 中定义了若干个依赖管理
    • 3.继承 parent 模块可以避免多个依赖使用相同技术时出现依赖版本冲突
    • 4.继承 parent 的形式也可以采用引入依赖的形式实现效果
    • 5.所有 SpringBoot 项目要继承的项目,定义了若干个坐标版本号(依赖管理、而非依赖),以达到减少依赖冲突的目的
    • 6.spring-boot-starter-parent 各坂本间存在着诸多坐标版本不同
  • starter(解决配置问题)

    • spring-boot-starter-web
    • SpringBoot 中常见项目名称,定义了当前项目使用的所有依赖坐标,以达到减少配置的目的
  • 引导类

    • 启动方式

      • ```java
        @SpringBootApplication
        public class Springboot0101QuickstartApplication {
        public static void main(String[] args) {
            SpringApplication.run(Springboot0101QuickstartApplication.class, args);
        }
        
        }
        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

        * SpringBoot 的引导类是 SpringBoot 工程的执行入口,运行 main 方法就可以启动项目

        * SpringBoot 工程运行后初始化 Spring 容器,扫描引导类所在包加载 bean

        * 内嵌 tomcat

        * 使用 maven 依赖管理变更起步依赖项

        * ```xml
        <dependencies>
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <!-- web 起步依赖环境中,排除 Tomcat 起步依赖 -->
        <exclusions>
        <exclusion>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
        </exclusions>
        </dependency>
        <!-- 添加 Jetty 起步依赖,版本由 SpringBoot 的 starter 控制 -->
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>
        </dependencies>
    • Jetty 比 Tomcat 更轻量级,可拓展性更强(相较于 Tomcat),谷歌应用引擎(GAE)已经全面切换为 Jetty

    • 内置服务器

      • tomcat(默认):apache 出品,粉丝多,应用面广,负载了若干较重的组件

      • jetty:更轻量级,负载性能远不及 tomcat

      • undertow:undertow,负载性能勉强跑赢 tomcat

  • 实际开发

    • 使用任意坐标时,仅书写 GAV 中的 G 和 A,V 由 SpringBoot 提供,除非SpringBoot 未提供对应版本 V
    • 如发生错误,再指定 Version(小心版本冲突)

小结

  • 1.开发 SpringBoot 程序可以根据向导进行联网快速制作
  • 2.SpringBoot 程序需要基于 JDK8 进行制作
  • 3.SpringBoot 程序中需要使用何种功能通过勾选选择技术
  • 4.运行 SpringBoot 程序通过运行 Application 程序入口进行

  • 1.Idea 中隐藏指定文件或指定类型文件
    • Setting -> File Types -> Ignored Files and Folders
    • 输入要隐藏的文件名,支持 * 号通配符
    • 回车确认添加

REST 风格

REST 简介

RESTful 入门案例

@RequestMapping

  • 名称:@RequestMapping
  • 类型:方法注解
  • 位置:SpringMVC 控制器方法定义上方
  • 作用:设置当前控制器方法请求访问路径
  • 属性
    • value(默认):请求访问路径
    • method:http 请求动作,标准动作(GET/POST/PUT/DELETE)

@PathVariable

  • 名称:@PathVariable
  • 类型:形参注解
  • 位置:SpringMVC 控制器方法形参定义前面
  • 作用:绑定路径参数与处理器方法形参之间的关系,要求路径参数名与形参名一一对应

@RequestBody、@RequestParam、@PathVariable

  • 区别

    • @RequestParam 用于接收 url 地址传参或表单传参
    • @RequestBody 用于接收 json 数据
    • @PathVariable 用于接收路径参数,使用{参数名称}描述路径参数
  • 应用

    • 后期开发中,发送请求参数超过 1 个时,以 json 格式为主,@RequestBody 应用较广
    • 如果发送非 json 数据格式,选用@RequestParam 接收请求参数
    • 采用 RESTful 进行开发,当参数较少时,例如 1 个,可以采用 @PathVariable 接收请求路径变量,通常用于传 id 值

步骤

  • 1.设定 http 请求动作(动词)
  • 2.设定请求参数(路径变量)

Controller 类示例代码

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
@Controller
public class UserController {

// 保存
@RequestMapping(value = "/users", method = RequestMethod.POST)
@ResponseBody
public String save() {
System.out.println("user save...");
return "{'module': 'user save'}";
}

// 删除
@RequestMapping(value = "/users/{id}", method = RequestMethod.DELETE)
@ResponseBody
public String delete(@PathVariable Integer id) {
System.out.println("user delete..." + id);
return "{'module': 'user delete'}";
}

// 条件查询
@RequestMapping(value = "/users/{id}", method = RequestMethod.GET)
@ResponseBody
public String getById(@PathVariable Integer id) {
System.out.println("user getById..." + id);
return "{'module': 'user getById'}";
}
}

RESTful 快速开发

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
@RESTController
@RequestMapping("/users")
public class UserController {

// 保存
@PostMapping
public String save() {
System.out.println("user save...");
return "{'module': 'user save'}";
}

// 删除
@DeleteMapping("/{id}")
public String delete(@PathVariable Integer id) {
System.out.println("user delete..." + id);
return "{'module': 'user delete'}";
}

// 条件查询
@GetMapping("/{id}")
public String getById(@PathVariable Integer id) {
System.out.println("user getById..." + id);
return "{'module': 'user getById'}";
}
}

@RestController

  • 名称:@RestController
  • 类型:类注解
  • 位置:基于 SpringMVC 的 RESTful 开发控制器类定义上方
  • 作用:设置当前控制器类为 RESTful 风格,等同于 @Controller 与 @ResponseBody 两个注解组合功能

@GetMapping、@PostMapping、@PutMapping、@DeleteMapping

  • 名称:@GetMapping、@PostMapping、@PutMapping、@DeleteMapping
  • 类型:方法注解
  • 位置:基于 SpringMVC 的 RESTful 开发控制器方法定义上方
  • 作用:设置当前控制器方法请求访问路径与请求动作,每种对应一个请求动作,例如 @GetMapping 对应 GET 请求
  • 属性:value(默认):请求访问路径

基础配置

复制工程

  • 原则
    • 保留工程基础结构
    • 抹掉原始工程痕迹
  • 小结
    • 1.在工作空间中复制对应工程,并修改工程名称
    • 2.删除与 idea 相关配置文件,仅保留 src 目录与 pom.xml 文件
    • 3.修改 pom.xml 文件中的 artifactId 与新工程/模块名相同
    • 4.删除 name 标签(可选)
    • 5.保留工程供后期使用

属性配置

  • SpringBoot 默认配置文件 application.properties,通过键值对配置对应属性

  • 修改配置

    • 修改服务器端口

      • ```properties
        server.port=80
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22

        #### 多种配置

        ##### 修改 banner

        * spring.main.banner-mode=off(关闭 banner)
        * spring.banner.image.location=图片地址(更换 banner)

        ##### 日志

        * logging.level.root=debug(debug 级别的日志)

        更多查看官网:application-properties

        #### 配置方式

        * SpringBoot 提供了多种属性配置方式

        * application.properties(传统格式/默认格式)

        * ```properties
        server.port=80
    • application.yml(主流格式)

      • ```yml
        server:
        port: 81
        1
        2
        3
        4
        5
        6

        * application.yaml

        * ```yaml
        server:
        port:82

SpringBoot 配置文件加载顺序

  • application.properties > application.yml(常用) > application.yaml

  • 不同配置文件中相同配置按加载优先级相互覆盖,不同配置文件中不同配置全部保留

指定 SpringBoot 配置文件

  • 1.setting -> Project Structure -> facets
  • 2.选中对应项目
  • 3.Customize Spring Boot
  • 4.选择配置文件

配置文件分类

properties、yml、yaml

yaml 文件

  • YAML(YAML Ain’t Markup Language),一种数据序列化格式
  • 优点:
    • 容易阅读
    • 容易与脚本语言交互
    • 以数据为核心,重数据轻格式
  • YAML 文件拓展名
    • .yml(主流)
    • .yaml

yaml 语法规则

  • 大小写敏感

  • 属性层级关系使用多行描述,每行结尾都使用冒号结束

  • 使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(不允许使用 Tab 键)

  • 属性值前面添加空格(属性名与属性值之间使用冒号 + 空格作为分隔)

  • # 表示注释

  • 有数组以及对象

  • 核心规则:数据前面要加空格与冒号隔开

  • 字面值表示方式

    • ```yaml
      boolean: TRUE # TRUE,true,True,FALSE,false,False 均可
      float: 3.14 # 6.8523015e+5 支持科学计数法
      int: 123 # 0b1010_0111_0100_1010_1110 支持二进制、八进制、十六进制
      null: ~ # 使用 ~ 表示 null
      String: HelloWorld # 字符串可以直接书写
      String2: “Hello World” # 可以使用双冒号包裹特殊字符
      date: 2018-02-17 # 日期必须使用 yyyy-MM-dd 格式
      datetime: 2018-02-17T15:02:31+08:00 # 日期和事件之间使用 T 连接,最后用 + 代表时区
      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

      * 数组表示方式:在属性名书写位置的下方使用减号作为数据开始符号,每行书写一个数据,减号与数据间空格分隔

      * ```yaml
      subject:
      - Java
      - 前端
      - 大数据
      enterprise:
      name: itcast
      age: 16
      subject:
      - Java
      - 前端
      - 大数据
      likes: [game,music] #数组书写略缩格式

      users: # 对象数组格式
      - name: Tom
      age: 4
      - name: Jerry
      age: 5

      users: # 对象数组格式二
      -
      name: Tom
      age: 4
      -
      name: Jerry
      age: 5
      users2: [ { name:Tom, age:4 }, { name:Jerry, age:5 } ]

yaml 数据读取

  • 使用 @Value 读取单个数据,属性名引用方式:${一级属性名.二级属性名……}

    • ```java
      // 读取 yaml 数据中的单一数据
      @Value(“${country}”)
      private String country1;
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10

      * 在配置文件中可以使用属性名引用方式引用属性

      * ```yaml
      baseDir: /user/local/fire

      center:
      dataDir: ${baseDir}/data
      tempDir: ${baseDir}/tmp
      logDir: ${baseDir}/log
  • 属性值中如果出现转义字符,需要使用双引号包裹

    • ```yaml
      lesson: “Spring\tboot\nlesson”

      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

      * 封装全部数据到 Environment 对象

      * ```java
      package com.itheima.controller;

      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.beans.factory.annotation.Value;
      import org.springframework.core.env.Environment;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;

      // Rest 模式
      @RestController
      @RequestMapping("/books")
      public class BookController {

      // 读取 yaml 数据中的单一数据
      @Value("${country}")
      private String country1;

      @Value("${user.name}")
      private String name;

      @Value("${tempDir}")
      private String tempDir;
      // 使用自动装配将所有的数据封装到一个 Environment 对象中
      @Autowired
      private Environment env;

      @GetMapping
      public String getById() {
      System.out.println("springboot is running");
      System.out.println("country1 ====>" + country1);
      System.out.println("name1 ====>" + name);
      System.out.println(tempDir);
      System.out.println("---------------");
      System.out.println(env.getProperty("user.name"));
      return "springboot is running";
      }

      }
    • ```yaml

      application.yml

      country: china
      province: beijing
      user:
      name: zhangsan
      age: 20
      baseDir: /user/local/fire

      center:
      dataDir: ${baseDir}/data
      tempDir: ${baseDir}/tmp
      logDir: ${baseDir}/log

      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

      * 自定义对象封装指定数据

      * ```java
      // MyDataSource.java
      package com.itheima;


      import org.springframework.boot.context.properties.ConfigurationProperties;
      import org.springframework.stereotype.Component;

      // 1.定义数据模型封装 yaml 文件中对应的数据
      // 2.定义为 Spring 管控的 bean
      @Component
      // 3.指定加载的数据
      @ConfigurationProperties(prefix = "datasource")
      public class MyDataSource {
      private String driver;
      private String url;
      private String username;
      private String password;

      public MyDataSource() {
      }

      public MyDataSource(String driver, String url, String username, String password) {
      this.driver = driver;
      this.url = url;
      this.username = username;
      this.password = password;
      }

      @Override
      public String toString() {
      return "MyDataSource{" +
      "driver='" + driver + '\'' +
      ", url='" + url + '\'' +
      ", username='" + username + '\'' +
      ", password='" + password + '\'' +
      '}';
      }

      public String getDriver() {
      return driver;
      }

      public void setDriver(String driver) {
      this.driver = driver;
      }

      public String getUrl() {
      return url;
      }

      public void setUrl(String url) {
      this.url = url;
      }

      public String getUsername() {
      return username;
      }

      public void setUsername(String username) {
      this.username = username;
      }

      public String getPassword() {
      return password;
      }

      public void setPassword(String password) {
      this.password = password;
      }
      }
    • ```yaml

      application.yml

      创建类,用于封装下面的数据

      由 spring 帮我们去加载数据到对象中,一定要告诉 spring 加载这组信息

      使用的时候,从 spring 中直接获取信息使用

      datasource:
      driver: com.mysql.jdbs.Driver
      url: jdbc:mysql://localhost/springboot_db
      username: root
      password: root666

      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

      * ```java
      // controller 类
      package com.itheima.controller;

      import com.itheima.MyDataSource;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;

      // Rest 模式
      @RestController
      @RequestMapping("/books")
      public class BookController {

      @Autowired
      private MyDataSource myDataSource;

      @GetMapping
      public String getById() {
      System.out.println("springboot is running");
      System.out.println(myDataSource);
      return "springboot is running";
      }

      }

整合第三方技术

整合 JUnit

SpringBoot 整合 JUnit

  • SpringBootTest 整合 JUnit

    • ```java
      package com.itheima;import com.itheima.dao.BookDao;
      import org.junit.jupiter.api.Test;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
      class Springboot04JunitApplicationTests {// 1.注入你要测试的对象
      @Autowired
      private BookDao bookDao;
      
      @Test
      void contextLoads() {
          //    2.执行要测试的对象对应的方法
          bookDao.save();
      }
      
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14

      * 名称:@SpringBootTest

      * 类型:**测试类注解**

      * 位置:测试类定义上方

      * 作用:设置 JUnit 加载的 SpringBoot 启动类

      * 范例:

      * ```java
      @SpringBootTest
      class Springboot04JunitApplicationTests {}
  • 相关属性:

    • classes:设置 SpringBoot 启动类
  • 注意事项:如果测试类在 SpringBoot 启动类的包或子包中,可以省略启动类的设置,也就是省略 classes 的设定

  • 详细步骤:

    • 1.导入测试对应的 starter
    • 2.测试类使用 @SpringBootTest
    • 3.使用自动装配的形式添加要测试的对象

整合 MyBatis

  • 核心配置:数据库连接相关信息(练什么?连谁?什么权限?)
  • 映射配置:SQL 映射(XML / 注解)

步骤:

  • 1.创建新模块,选择 Spring 初始化,并配置模块相关基础信息

  • 2.选择当前模块需要使用的技术集(MyBatis、MySQL)

  • 3.设置数据源参数

    • ```yaml

      应用名称

      spring:
      datasource:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/dorm_management?serverTimezone=GMT
      username: root
      password: root
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15

      * 4.定义数据层接口与映射配置

      * ```java
      package com.itheima.dao;

      import com.itheima.domain.Book;
      import org.apache.ibatis.annotations.Mapper;
      import org.apache.ibatis.annotations.Select;

      @Mapper
      public interface BookDao {
      @Select("select * from admin where aid = #{id}")
      public Book getById(Integer id);
      }
  • 5.测试类中注入 dao 接口,测试功能

    • ```java
      package com.itheima;

      import com.itheima.dao.BookDao;
      import org.junit.jupiter.api.Test;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.boot.test.context.SpringBootTest;

      @SpringBootTest
      class Springboot05MybatisApplicationTests {

      @Autowired
      private BookDao bookDao;
      
      @Test
      void contextLoads() {
          System.out.println(bookDao.getById(1));
      }
      

      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22

      #### 常见问题以及处理方式

      时区问题:在url后面加上 **?serverTimezone=GMT**

      ### 整合 MyBatis-Plus

      * MyBatis-Plus 与 MyBatis 区别
      * 导入坐标不同
      * 数据层实现简化

      #### 步骤:

      * 1.手动添加 SpringBoot 整合 MyBatis-Plus 的坐标,可以通过 mvnrepository 获取

      * ```xml
      <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
      <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus-boot-starter</artifactId>
      <version>3.4.3</version>
      </dependency>
    • 注意事项:由于 SpringBoot 中未收录 MyBatis-Plus 的坐标版本,需要指定对应的 version

整合 Druid

步骤:

  • 1.导入 Druid 对应的 starter

    • ```xml com.alibaba druid-spring-boot-starter 1.2.6
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11

      * 2.变更 Druid 的配置方式

      * ```yaml
      spring:
      datasource:
      druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/dorm_management?serverTimezone=GMT
      username: root
      password: zt20020806

整合任意第三方技术

  • 导入对应的 starter
  • 配置对应的设置或采用默认配置

基于 SpringBoot 的 SSMP 整合案例

  • 案例实现方案分析
    • 实体类开发——使用 LomBok 快速制作实体类
    • Dao 开发——整合 MyBatisPlus,制作数据层测试类
    • Service 开发——基于 MyBatisPlus 进行增量开发,制作业务层测试类
    • Controller 开发——基于 Restful 开发,使用 PostMan 测试接口功能
    • Controller 开发——前后端开发协议制作
    • 页面开发——基于 Vue + ElementUI 制作,前后端联调,页面数据处理,页面消息处理
      • 列表、新增、修改、删除、分页、查询
    • 项目异常处理
    • 按条件查询——页面功能调整、Controller 修正功能、Service 修正功能

1.创建模块

  • 1.勾选 SpringMVC 和 MySQL 坐标
  • 2.修改配置文件为 yml 格式
  • 3.设置端口为 80 方便访问

2.实体类开发

Lombok
  • Lombok,一个 Java 类库,提供了一组注解,简化 POJO 实体类开发

  • 常用注解:@Data

    • ```java
      @Data
      public class Book {
      private Integer id;
      private String type;
      private String name;
      private String descript;
      
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24

      * 为当前实体类在编译期间设置对应的 getter/setter 方法,toString 方法,hashCode 方法,equals 方法等

      #### 3.数据层开发

      * 技术实现方案

      * MyBatisPlus
      * Druid

      * 导入 MyBatisPlus 与 Druid 对应的 starter

      * ```xml
      <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus-boot-starter</artifactId>
      <version>3.4.3</version>
      </dependency>

      <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid-spring-boot-starter</artifactId>
      <version>1.2.6</version>
      </dependency>
  • 配置数据源与 MyBatisPlus 对应的基础配置(id 生成策略使用数据库自增策略)

    • ```yaml
      server:
      port: 80
      spring:
      datasource:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
      username: root
      password: zt20020806
      
      mybatis-plus:
      global-config:
      db-config:
        table-prefix: tb_
        id-type: auto
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14

      * 继承 BaseMapper 并指定泛型

      * ```java
      package com.itheima.dao;

      import com.baomidou.mybatisplus.core.mapper.BaseMapper;
      import com.itheima.domain.Book;
      import org.apache.ibatis.annotations.Mapper;

      @Mapper
      public interface BookDao extends BaseMapper<Book> {

      }
  • 制作测试类测试结果

    • ```java
      package com.itheima.dao;import com.itheima.domain.Book;
      import org.junit.jupiter.api.Test;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
      public class BookDaoTestCase {
      @Autowired
      private BookDao bookDao;
      
      @Test
      void TestGetById() {
          System.out.println(bookDao.selectById(1));
      }
      
      @Test
      void TestSave() {
          Book book = new Book();
          book.setType("测试数据123");
          book.setName("测试数据123");
          book.setDescription("测试数据123");
          bookDao.insert(book);
      }
      
      }
      1
      2
      3
      4
      5
      6
      7

      * 为方便调试可以开启 MyBatisPlus 的日志

      * ```yaml
      mybatis-plus:
      configuration:
      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
数据层开发——分页功能
  • 分页操作需要设定分页对象 IPage

    • ```java
      @Test
      void testGetPage() {
      IPage page = new Page(1,1);
      bookDao.selectPage(page, null);
      List records = page.getRecords();
      System.out.println(records);
      
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24

      * IPage 对象中封装了分页操作中的所有数据

      * 数据
      * 当前页码值
      * 每页数据总量
      * 最大页码值
      * 数据总量

      * 分页操作是在 MyBatisPlus 的常规操作基础上增强得到,内部是动态的拼写 SQL 语句,因此需要增强对应的功能,使用 MyBatisPlus 拦截器实现

      * ```java
      // 配置类
      @Configuration
      public class MPConfig {
      @Bean
      public MybatisPlusInterceptor mybatisPlusInterceptor() {
      // 1.定义 Mp 拦截器
      MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
      // 添加具体的拦截器
      interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
      return interceptor;
      }
      }
数据层开发——条件查询功能
  • 使用 QueryWrapper 对象封装查询条件,推荐使用 LambdaQueryWrapper 对象,所有查询操作封装成方法调用

    • ```java
      // QueryWrapper
      @Test
      void testGetBy() {
      QueryWrapper<Book> qw = new QueryWrapper<>();
      qw.like("name","测试");
      bookDao.selectList(qw);
      
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10

      * ```java
      // LambdaQueryWrapper
      @Test
      void testGetBy2() {
      String name = null;
      LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<>();
      lqw.like(name != null, Book::getName,name);
      bookDao.selectList(lqw);
      }
  • 查询条件支持动态条件拼装

4.业务层开发

  • Service 层接口定义与数据层接口定义具有较大区别,不要混用

    • selectByUserNameAndPassword(String username,String password);(数据层)
    • login(String username, String password);(业务层)
  • 实现类定义

    • ```java
      @Service
      public class BookServiceImpl implements BookService {

      @Autowired
      private BookDao bookDao;
      
      @Override
      public boolean save(Book book) {
          return bookDao.insert(book) > 0;
      }
      
      @Override
      public boolean update(Book book) {
          return bookDao.updateById(book) > 0;
      }
      
      @Override
      public boolean delete(Integer id) {
          return bookDao.deleteById(id) > 0;
      }
      
      @Override
      public Book getById(Integer id) {
          return bookDao.selectById(id);
      }
      
      @Override
      public List<Book> getAll() {
          return bookDao.selectList(null);
      }
      
      @Override
      public IPage<Book> getPage(int currentPage, int pageSize) {
          IPage page = new Page(currentPage, pageSize);
          return bookDao.selectPage(page, null);
      }
      

      }

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

      ##### 业务层开发——快速开发

      * 快速开发方案

      * 使用 MyBatisPlus 提供有业务层通用接口(IService<T>)与业务层通用实现类 (ServiceImpl<M, T>)
      * 在通用类基础上做功能重载或功能追加
      * 注意重载时不要覆盖原始操作,避免原始提供的功能丢失

      * 接口定义

      * ```java
      public interface IBookService extends IService<Book> {
      }
    • ```java
      public interface IBookService extends IService {

      // 追加的操作与原始操作通过名称区分,功能类似
      boolean delete(Integer id);
      boolean insert(Book book);
      

      }

      1
      2
      3
      4
      5
      6
      7

      * 实现类定义

      * ```java
      @Service
      public class BookServiceImpl extends ServiceImpl<BookDao, Book> implements IBookService {
      }
    • ```java
      @Service
      public class BookServiceImpl extends ServiceImpl<BookDao, Book> implements IBookService {

       // 实现类实现追加
       @Autowired
       private BookDao bookDao;
       
       public boolean delete(Integer id) {
           return bookDao.deleteById(id) > 0;
       }
       
       public boolean insert(Book book) {
           return bookDao.insert(book) > 0;
       }
      

      }

      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

      * 表现层开发

      * 基于 Restful 进行表现层接口开发
      * 使用 Postman 测试表现层接口功能

      * 功能测试

      * ```java
      @RestController
      @RequestMapping("/books")
      public class BookController {

      @Autowired
      private IBookService bookService;

      @GetMapping
      public List<Book> getAll() {
      return bookService.list();
      }

      @PostMapping
      public boolean save(@RequestBody Book book) {
      return bookService.save(book);
      }

      @PutMapping
      public boolean update(@RequestBody Book book) {
      return bookService.updateById(book);
      }

      @DeleteMapping("/{id}")
      public boolean delete(@PathVariable Integer id) {
      return bookService.removeById(id);
      }

      @GetMapping("/{id}")
      public Book getById(@PathVariable Integer id) {
      return bookService.getById(id);
      }
      }
表现层消息一致性处理
  • 增删改:boolean 值

  • 查单条:JSON

  • 查全部:数组

  • 不存在:null

  • 查询过程中抛出异常,catch 中返回 null

  • 设计表现层返回结果的模型类,用于后端与前端进行数据格式统一,也称为前后端数据协议

    • ```java
      @Data
      public class R {
      private boolean flag;
      private Object data;
      
      public R() {
      }
      
      public R(Boolean flag) {
          this.flag = flag;
      }
      
      public R(boolean flag, Object data) {
          this.flag = flag;
          this.data = data;
      }
      
      }
      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

      * 表现层接口统一返回值类型结果

      * ```java
      @RestController
      @RequestMapping("/books")
      public class BookController {

      @Autowired
      private IBookService bookService;

      @GetMapping
      public R getAll() {
      return new R(true,bookService.list());
      }

      @PostMapping
      public R save(@RequestBody Book book) {
      return new R(bookService.save(book));
      }

      @PutMapping
      public R update(@RequestBody Book book) {
      return new R(bookService.updateById(book));
      }

      @DeleteMapping("/{id}")
      public R delete(@PathVariable Integer id) {
      return new R(bookService.removeById(id));
      }

      @GetMapping("/{id}")
      public R getById(@PathVariable Integer id) {
      return new R(true, bookService.getById(id));
      }

      @GetMapping("{currentPage}/{pageSize}")
      public R getPage(@PathVariable int currentPage,@PathVariable int pageSize) {
      return new R(true, bookService.getPage(currentPage, pageSize));
      }
      }
小结:
  • 1.设计统一的返回值结果类型便于前端开发读取数据
  • 2.返回值结果类型可以根据需求自行设定,没有固定格式
  • 3.返回值结果模型用于后端与前端进行数据格式统一,也成为前后端数据协议

5.前后端协议联调

  • 前后端分离结构设计中页面归属前端服务器

  • 单体工程中页面放置在 resources 目录下的 static 目录中(建议执行 clean)

  • 前端发送异步请求,调用后端接口

    • ```js
      // 列表
      getAll() {
      axios.get("/books").then(res => {
          console.log(res.data);
      })
      
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18

      ###### 小结:

      * 1.单体项目中页面放置在 resources/static 目录下
      * 2.created 钩子函数用于初始化页面时发起调用
      * 3.页面使用 axios 发送异步请求获取数据后确认前后端是否联通

      #### 6.列表页

      将查询的数据返回到页面,利用前端数据双向绑定进行数据展示

      ```js
      // 列表
      getAll() {
      axios.get("/books").then(res => {
      console.log(res.data);
      })
      }

7.添加功能

  • 弹出添加窗口

    • ```js
      //弹出添加窗口
      handleCreate() {

      this.dialogFormVisible = true;
      this.resetForm();
      

      },

      1
      2
      3
      4
      5
      6
      7
      8

      * 清除数据

      * ```js
      //重置表单
      resetForm() {
      this.formData = {};
      },
    • ```js
      //弹出添加窗口
      handleCreate() {

      this.dialogFormVisible = true;
      this.resetForm();
      

      },

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

      * 添加

      * ```js
      //添加
      handleAdd () {
      axios.post("/books", this.formData).then(res => {
      if(res.data.flag) {
      // 1.关闭弹窗
      this.dialogFormVisible = false;
      this.$message.success("添加成功")
      } else {
      this.$message.error("添加失败")
      }
      }).finally(() => {
      // 2.重新加载数据
      this.getAll();
      })
      },

  • 取消添加

    • ```js
      //取消
      cancel(){
      this.dialogFormVisible = false;
      this.$message.info("当前操作取消");
      
      },
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21

      #### 8.删除

      ```js
      // 删除
      handleDelete(row) {
      this.$confirm("此操作永久删除当前信息,是否继续?", "提示", {type: "info"}).then(() => {
      axios.delete("/books/" + row.id).then(res => {
      if(res.data.flag) {
      this.$message.success("删除成功")
      } else {
      this.$message.error("删除失败")
      }
      }).finally(() => {
      // 重新加载数据
      this.getAll();
      })
      }).catch(() => {
      this.$message.info("取消操作");
      })
      },

9.修改功能

  • 弹出修改窗口

    • ```js
      //弹出编辑窗口
      handleUpdate(row) {
      axios.get("/books/" + row.id).then(res => {
          if(res.data.flag &&  res.data.data != null) {
              this.dialogFormVisible4Edit = true;
              this.formData = res.data.data;
          } else {
              this.$message.error("数据同步失败,自动刷新")
          }
      }).finally(() => {
          this.getAll();
      })
      
      },
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19

      * 修改

      * ```js
      //修改
      handleEdit() {
      axios.put("/books", this.formData).then(res => {
      if(res.data.flag) {
      // 1.关闭弹窗
      this.dialogFormVisible4Edit = false;
      this.$message.success("修改成功")
      } else {
      this.$message.error("修改失败")
      }
      }).finally(() => {
      // 2.重新加载数据
      this.getAll();
      })
      },

10.异常消息处理

业务操作成功
  • 业务操作成功或失败返回数据格式

    • ```json
      {
      "flag": true,
      "data": null
      
      }
      {
      "flag": true,
      "data": null
      
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10

      * 后台代码 BUG 导致数据格式不统一

      * ```json
      {
      "timestamp": "2022-07-25T03:27:31.038+00:00",
      "status": 500,
      "error": "Internal Server Error",
      "path": "/books"
      }
  • 对异常进行统一处理,出现异常后,返回指定信息

    • ```java
      // 作为 springmvc 的异常处理器
      @RestControllerAdvice
      public class ProjectExceptionAdvice {
      // 拦截所有异常信息
      @ExceptionHandler(Exception.class)
      public R doException(Exception e) {
      
      // 记录日志
      // 通知运维
      // 通知开发
          e.printStackTrace();
          return new R("服务器故障,请稍后再试");
      }
      
      }
      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

      * 可以在表现层 Controller 中进行消息统一处理

      * 目的:国际化

      #### 11.分页功能

      * 页面使用 el 分页组件添加分页功能

      * ```vue
      <div class="pagination-container">

      <el-pagination
      class="pagiantion"

      @current-change="handleCurrentChange"

      :current-page="pagination.currentPage"

      :page-size="pagination.pageSize"

      layout="total, prev, pager, next, jumper"

      :total="pagination.total">

      </el-pagination>

      </div>
  • 定义分页组件需要使用的数据并将数据绑定到分页组件

    • ```js
      data: {
      pagination: {//分页相关模型数据
          currentPage: 1,//当前页码
          pageSize:10,//每页显示的记录数
          total:0//总记录数
      }
      
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13

      * 替换查询全部功能为分页功能

      * ```js
      getAll() {
      // 发送异步请求
      axios.get("/books/" + this.pagination.currentPage + "/" + this.pagination.pageSize).then(res => {
      this.pagination.pageSize = res.data.data.size;
      this.pagination.currentPage = res.data.data.current;
      this.pagination.total = res.data.data.total;
      this.dataList = res.data.data.records;
      })
      }
  • 页码值切换

    • ```js
      //切换页码
      handleCurrentChange(currentPage) {
      //    修改页码值为当前选中的页码值
      this.pagination.currentPage = currentPage;
      //    执行查询
      this.getAll();
      
      },
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16

      #### 12.删除功能维护

      * 对查询结果进行校验,如果当前页码值大于最大页码值,使用最大页码值作为当前页码值重新查询

      * ```java
      @GetMapping("{currentPage}/{pageSize}")
      public R getPage(@PathVariable int currentPage,@PathVariable int pageSize) {
      IPage<Book> page = bookService.getPage(currentPage, pageSize);
      // 如果当前页码值大于总页码值,那么重新执行查询操作,使用最大页码值作为当前页码值
      if(currentPage > page.getPages()) {
      page = bookService.getPage((int)page.getPages(), pageSize);
      }

      return new R(true, page);
      }

13.条件查询功能

  • 查询条件数据封装

    • 单独封装

    • 与分页操作混合封装

    • ```js
      pagination: {//分页相关模型数据

      currentPage: 1,//当前页码
      pageSize:10,//每页显示的记录数
      total:0,//总记录数
      type: "",
      name: "",
      description: ""
      

      }

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

      * 组织数据成为 get 请求发送的数据

      * ```js
      getAll() {
      // 组织参数,拼接 url 请求地址
      param = "?type=" + this.pagination.type;
      param += "&name=" + this.pagination.name;
      param += "&description=" + this.pagination.description;
      // 发送异步请求
      axios.get("/books/" + this.pagination.currentPage + "/" + this.pagination.pageSize + param).then(res => {
      this.pagination.pageSize = res.data.data.size;
      this.pagination.currentPage = res.data.data.current;
      this.pagination.total = res.data.data.total;
      this.dataList = res.data.data.records;
      })
      },
  • 条件参数组织可以通过判定书写的更简洁

  • controller 接收参数

    • ```java
      @GetMapping("{currentPage}/{pageSize}")
      public R getPage(@PathVariable int currentPage,@PathVariable int pageSize, Book book) {
          System.out.println(book);
          IPage<Book> page = bookService.getPage(currentPage, pageSize, book);
      
      // 如果当前页码值大于总页码值,那么重新执行查询操作,使用最大页码值作为当前页码值
          if(currentPage > page.getPages()) {
              page = bookService.getPage((int)page.getPages(), pageSize, book);
          }
      
          return new R(page != null, page);
      }
      
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11

      # 运维实用篇

      ## 工程打包与运行

      ### SpringBoot 项目快速启动(Windows版)

      * 1.对 SpringBoot 项目打包(执行 maven 构建指令 package)

      * ```cmd
      mvn package
  • 2.运行项目(执行启动指令)

    • ```cmd
      java -jar springboot.jar
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14

      * 注意事项

      * jar 支持命令行启动需要依赖 maven 插件支持,请确认打包时是否具有 SpringBoot 对应的 maven 插件

      * ```xml
      <build>
      <plugins>
      <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
      </plugins>
      </build>

jar 包描述文件(MANIFEST.MF)

  • 普通工程

    • ```mf
      Manifest-Version: 1.0
      Implementation-Title: springboot_08_ssmp
      Implementation-Version: 0.0.1-SNAPSHOT
      Build-Jdk-Spec: 1.8
      Created-By: Maven JAR Plugin 3.2.2
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16

      * 基于 spring-boot-maven-plugin 打包的工程

      * ```mf
      Manifest-Version: 1.0
      Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
      Implementation-Title: springboot_08_ssmp
      Implementation-Version: 0.0.1-SNAPSHOT
      Spring-Boot-Layers-Index: BOOT-INF/layers.idx
      Start-Class: com.itheima.SSMPApplication
      Spring-Boot-Classes: BOOT-INF/classes/
      Spring-Boot-Lib: BOOT-INF/lib/
      Build-Jdk-Spec: 1.8
      Spring-Boot-Version: 2.7.2
      Created-By: Maven JAR Plugin 3.2.2
      Main-Class: org.springframework.boot.loader.JarLauncher

命令行启动常见问题及解决方案

  • Windows 端口被占用

    • ```cmd

      查询端口

      netstat -ano

      查询指定端口

      nestat -ano |findstr “端口号”

      根据进程 PID 查询进程名称

      tasklist |findstr “进程PID号”

      根据PID杀死任务

      taskkill /F /PID “进程PID号”

      根据进程名称杀死任务

      taskkill -f -t -im “进程名称”
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16

      ### SpringBoot 项目快速启动(Linux版)

      * 基于 Linux(CenterOS7)
      * 安装 JDK,且版本不低于打包时使用的 JDK 版本
      * 安装包保存在 /usr/local/ 自定义的目录中或 $HOME 下
      * 其他操作按照 Windows 版执行

      ## 配置高级

      ### 临时属性设置

      * 带参数启动 SpringBoot

      * ```cmd
      java -jar springboot.jar --server.port=80
  • 携带多个属性启动 SpringBoot,属性间使用空格分隔

属性加载优先顺序

参看 https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config

临时属性设置(开发环境)

  • 通过编程形式带参数启动 SpringBoot 程序,为程序添加运行参数

    • ```java
      @SpringBootApplication
      public class SSMPApplication {
      public static void main(String[] args) {
          String[] arg = new String[1];
          arg[0] = "--server.port=8082";
          SpringApplication.run(SSMPApplication.class, arg);
      }
      
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11

      * 不带参数启动 SpringBoot 程序

      * ```java
      @SpringBootApplication
      public class SSMPApplication {

      public static void main(String[] args) {
      SpringApplication.run(SSMPApplication.class);
      }
      }

配置文件分类

  • 1.SpringBoot 中 4 级配置文件
    • 1 级:file:config/application.yml 最高
    • 2 级:file:application.yml
    • 3 级:classpath:config/application.yml
    • 4 级:classpath:application.yml 最低
  • 2.作用:
    • 1 级与 2 级留做系统打包后设置通用属性,1 级常用于运维经理进行线上整体项目部署方案调控
    • 3 级与 4 级用于系统开发阶段设置通用属性,3 级常用于项目经理进行整体项目属性调控

自定义配置文件

  • 通过启动参数加载指定文件路径下的配置文件时可以加载多个配置
    • image-20220727113253685

注意事项:多配置文件常用于将配置进行分类,进行独立管理,或将可选配置单独制作便于上线更新维护

自定义配置文件——重要说明

  • 单服务器项目:使用自定义配置文件需求较低
  • 多服务器项目:使用自定义配置文件需求较高,将所有配置放置在一个目录中,统一管理
  • 基于 SpringCloud 技术,所有服务器将不再设置配置文件,而是通过配置中心进行设定,动态加载配置信息

总结

  • 1.SpringBoot 在开发和运行环境均支持使用临时参数修改工程配置
  • 2.SpringBoot 支持 4 级配置文件,应用于开发与线上环境进行配置的灵活设置
  • 3.SpringBoot 支持使用自定义配置文件的形式修改配置文件存储位置
  • 4.基于微服务开发时配置文件将使用配置中心进行管理

多环境开发(YAML 版)

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
# 应用环境
# 启动指定环境
spring:
profiles:
active: pro
---

# 设置环境
# 生产环境
spring:
profiles: pro
server:
port: 80
---

# 开发环境
spring:
profiles: dev
server:
port: 81
---

# 测试环境
spring:
profiles: test
server:
port: 82

过时格式

1
2
3
4
5
6
7
8
9
10
11
12
13
# 应用环境
# 启动指定环境
spring:
profiles:
active: pro
---

# 设置环境
# 生产环境
spring:
profiles: pro
server:
port: 80

推荐格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 应用环境
# 启动指定环境
spring:
profiles:
active: pro
---

# 设置环境
# 生产环境
spring:
config:
activate:
on-profile: pro
server:
port: 80

多环境开发(YAML版)多配置文件格式

  • 1.主启动配置文件 application.yml

    • ```yaml

      应用环境

      spring:
      profiles:
      active: dev
      
      1
      2
      3
      4
      5
      6

      * 2.环境分类配置文件 application-pro.yml

      * ```yaml
      server:
      port: 8080
  • 3.环境分类配置文件 application-dev.yml

    • ```yaml
      server:
      port: 8081
      1
      2
      3
      4
      5
      6

      * 4.环境分类配置文件 application-test.yml

      * ```yaml
      server:
      port: 8082

多环境开发配置文件书写技巧(一)

  • 主配置文件中设置公共配置(全局)
  • 环境分类配置文件中常用于设置冲突属性(局部)

多环境开发(Properties版)多配置文件格式

  • 1.主启动配置文件 application.properties

    • ```properties
      spring.profiles.active=dev
      1
      2
      3
      4
      5

      * 2.环境分类配置文件 application-pro.properties

      * ```properties
      server.port=9081
  • 3.环境分类配置文件 application-dev.properties

    • ```properties
      server.port=9082
      1
      2
      3
      4
      5

      * 4.环境分类配置文件 application-test.properties

      * ```properties
      server.port=9080

多环境开发独立配置文件书写技巧(二)

  • 根据功能对配置文件中的信息进行拆分,并制作成独立的配置文件,命名规则如下:

    • application-devDB.yml
    • application-devRedis.yml
    • application-devMVC.yml
  • 使用 include 属性在激活指定环境的情况下,同时对多个环境进行加载使其生效,多个环境间使用逗号分隔

    • ```yml
      spring:
      profiles:
      active: dev
      include: devDB,devRedis,decMVC
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17

      **注意事项:**当主环境 dev 与其他环境有相同属性时,主环境属性生效,其他环境中有相同属性时,最后加载的环境属性生效

      ----

      * 从 Spring Boot 2.4 版本开始使用 group 属性替代 include 属性,降低了配置书写量

      * 使用 group 属性定义多种主环境与子环境的包含关系

      * ```yml
      spring:
      profiles:
      active: dev
      groupd:
      "dev": devDB,devRedis,decMVC
      "pro": proDB,proRedis,proMVC
      "test": testDB,testRedis,testMVC

多环境开发控制

Maven 与 SpringBoot 多环境兼容

  • 1.Maven 中设置多环境属性

    • ```xml dev_env dev true pro_env pro test_env test
      1
      2
      3
      4
      5
      6
      7

      * 2.SpringBoot 中引用 Maven 属性

      * ```yml
      spring:
      profiles:
      active: @profile.active@
  • 3.执行 Maven 打包指令,并在生成的 boot 打包文件 .jar 文件中查看对应信息

日志

日志基础

日志基础操作

  • 日志(log)作用
    • 编译期调试代码
    • 运营期记录信息
      • 记录日常运营重要信息(峰值流量、平均响应时长。。。)
      • 记录应用报错信息(错误堆栈)
      • 记录错误运维过程数据(扩容、宕机、报警。。。)

代码中使用日志工具记录日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Rest 模式
@RestController
@RequestMapping("/books")
public class BookController {

// 创建记录日志的对象
private static final Logger log = LoggerFactory.getLogger(BookController.class);

@GetMapping
public String getById() {
System.out.println("springboot is running");

log.debug("debug...");
log.info("info...");
log.warn("warn...");
log.error("error...");

return "springboot is running";
}

}
日志级别
  • TRACE:运行堆栈信息,使用率低
  • DEBUG:程序员调试代码使用
  • INFO:记录运维过程数据
  • WARN:记录运维过程报警数据
  • ERROR:记录错误堆栈信息
  • FATAL:灾难信息,合并计入 ERROR
设置日志输出级别
1
2
3
4
5
6
7
8
# application.yml
# 开启 debug 模式,输出调试信息,常用于检查系统运行状况
debug: true

# 设置日志级别,root 表示根节点,即整体应用日志级别
loggin:
level:
root: debug
设置日志组

设置日志组,控制包对应的日志输出级别也可以直接控制指定包对应的日志输出级别

1
2
3
4
5
6
7
8
9
10
11
12
# application.yml
logging:
# 设置日志组
group:
# 自定义组名,设置当前组中所包含的包
ebank: com.itheima.controller
level:
root: warn
# 为对应的组设置日志级别
ebank: debug
# 为对应的包设置日志级别
com.itheima.controller: debug
优化日志对象创建代码
  • 使用 lombok 提供的注解 @Slf4j 简化开发,减少日志对象的声明操作

    • ```java
      // Rest 模式
      @Slf4j
      @RestController
      @RequestMapping(“/books”)
      public class BookController {// 创建记录日志的对象
      private static final Logger log = LoggerFactory.getLogger(BookController.class);
      
      @GetMapping
      public String getById() {
          System.out.println("springboot is running");
      
          log.debug("debug...");
          log.info("info...");
          log.warn("warn...");
          log.error("error...");
      
          return "springboot is running";
      }
      
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17

      ### 日志输出格式控制

      ![image-20220729115500864](C:\Users\15135\AppData\Roaming\Typora\typora-user-images\image-20220729115500864.png)

      * 时间
      * 级别
      * PID:进程 ID,用于表明当前操作所处的进程,当多服务同时记录日志时,该值可用于协助程序员调试程序
      * 所属线程
      * 所属类/接口名:当前显示信息为 SpringBoot 重写后的信息,名称过长时,简化包名书写为首字母,甚至直接删除

      #### 设置日志输出格式

      ```yml
      logging:
      pattern:
      console: "%d - %m%n"
  • %d:日期

  • %m:消息

  • %n:换行

1
2
3
4
5
logging:
pattern:
# console: "%d - %m%n"
# console: "%d %clr(%5p) %n"
console: "%d %clr(%5p) --- [%16t] %clr(%-40.40c){cyan} : %m %n"

日志文件

  • 设置日志文件

    • ```yaml
      logging:
      file:
      name: server.log
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11

      * 日志文件详细配置

      * ```yaml
      logging:
      file:
      name: server.log
      logback:
      rollingpolicy:
      max-file-size: 3KB
      file-name-pattern: server.%d{yyyy-MM-dd}.%i.log

开发实用篇

热部署

启动热部署

  • 关于热部署
    • 重启(Restart):自定义开发代码,包含类、页面、配置文件等,加载位置 restart 类加载器
    • 重载(ReLoad):jar 包,加载位置 base 类加载器

手动启动热部署

  • 1.在 pom.xml 文件中引入坐标

    • ```xml org.springframework.boot spring-boot-devtools
      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

      * 2.点击 构建 -> 构建项目或者 ctrl +f9

      #### 自动启动热部署

      * 设置自动构建项目
      * ![image-20220729163858212](C:\Users\15135\AppData\Roaming\Typora\typora-user-images\image-20220729163858212.png)
      * 在 controller 类中按 ctrl + shift + alt + / 选择 registry
      * 勾选 compiler.automake.allow.when.app.running
      * 激活方式:idea 失去焦点 5 秒后启动热部署

      ### 热部署范围配置

      * 默认不触发重启的目录列表

      * /META-INF/maven
      * /META-INF/resources
      * /resources
      * /static
      * /public
      * /templates

      * 自定义不参与中期排除项

      * ```yaml
      devtools:
      restart:
      exclude: public/**,static/**

关闭热部署

  • 设置高优先级属性禁用热部署

    • ```java
      @SpringBootApplication
      public class SSMPApplication {

      public static void main(String[] args) {
           System.setProperty("spring.devtools.restart.enabled", "false");
          SpringApplication.run(SSMPApplication.class, args);
      }
      

      }

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

      ## 配置高级

      ### @ConfigurationProperties

      * 使用 @ConfigurationProperties 为第三方 bean 绑定属性

      * ```java
      // 启动类
      @Bean
      @ConfigurationProperties(prefix = "datasource")
      public DruidDataSource dataSource() {
      DruidDataSource ds = new DruidDataSource();
      return ds;
      }
    • ```yaml

      application.yml

      datasource:
      driverClassName: com.mysql.cj.jdbc.Driver

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10

      * 解除使用 @ConfigurationProperties 注释警告

      * 在 pom.xml 中加入以下坐标

      * ```xml
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-configuration-processor</artifactId>
      </dependency>

@EnableConfigurationProperties

  • @EnableConfigurationProperties 注解可以将使用 @ConfigurationProperties 注解对应的类加入 Spring 容器

    • ```java
      // demo.java
      @SpringBootApplication
      @EnableConfigurationProperties(ServerConfig.class)
      public class DemoApplication {}
      1
      2
      3
      4
      5
      6
      7
      8

      * ```java
      // ServerConfig.java
      @Data
      @ConfigurationProperties(prefix = "servers")
      public class ServerConfig {

      }

注意事项:@EnableConfigurationProperties 与 @Component 不能同时使用

宽松绑定/松散绑定

宽松绑定

  • @ConfigurationProperties 绑定属性支持属性名宽松绑定

    • ```java
      public class ServerConfig {
      private String ipAddress;
      private int port;
      private long timeout;
      
      }
      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

      * 驼峰模式:ipAddress

      * 下划线模式:ip_address

      * 中划线模式(烤串模式):ip-address

      * 常量模式:IP_ADDRESS

      * **注意事项:**宽松绑定不支持注解 @Value 引用单个属性的方式,绑定前缀名命名规范:仅能使用纯小写字母、数字、中划线作为合法的字符

      ### 常用计量单位绑定

      * SpringBoot 支持 JDK8 提供的时间与空间计量单位

      * ```java
      @Component
      @Data
      @ConfigurationProperties(prefix = "servers")
      public class ServerConfig {
      private String ipAddress;
      private int port;
      private long timeout;
      @DurationUnit(ChronoUnit.MINUTES)
      private Duration serverTimeOut;
      @DataSizeUnit(DataUnit.MEGABYTES)
      private DataSize dataSize;
      }

数据校验

  • 开启数据校验有助于系统安全性,J2EE 规范中 JSR303 规范定义了一组有关数据校验相关的 API

开启 Bean 数据校验

  • 1.添加 JSR303 规范坐标与 Hibernate 校验框架对应坐标

    • ```xml javax.validation validation-api org.hibernate.validator hibernate-validator
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20

      * 2.对 Bean 开启校验功能

      * ```java
      // ServerConfig.java
      @Component
      @Data
      @ConfigurationProperties(prefix = "servers")
      // 2.开启对当前 bean 的属性注入校验
      @Validated
      public class ServerConfig {
      private String ipAddress;
      @Max(value = 8888, message = "最大值不能超过8888")
      private int port;
      private long timeout;
      @DurationUnit(ChronoUnit.MINUTES)
      private Duration serverTimeOut;
      @DataSizeUnit(DataUnit.MEGABYTES)
      private DataSize dataSize;
      }

进制转换规则

数字记得加字符,否则可能会被误认为八进制或其他进制数

测试

加载测试专用属性

  • 在启动测试环境时可以通过 properties 参数设置测试环境专用的属性

    • ```java
      // properties 属性可以为当前测试用例添加临时的属性配置
      //@SpringBootTest(properties = {“test.prop=testValue1”})
      // args 属性可以为当前测试用例添加临时的命令行参数
      //@SpringBootTest(args = {“–test.prop=testValue2”})
      @SpringBootTest(properties = {“test.prop=testValue1”}, args = {“–test.prop=testValue2”})
      public class PropertiesAndArgsTest {
      @Value("${test.prop}")
      private String msg;
      
      @Test
      void testPorperties() {
          System.out.println(msg);
      }
      
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20

      * 优势:比多环境开发中的测试环境影响范围更小,仅对当前测试类有效

      ### 加载测试专用配置

      * 使用 @Import 注解加载当前测试类专用的配置

      * ```java
      @SpringBootTest
      @Import({MsgConfig.class})
      public class ConfigurationTest {

      @Autowired
      private String msg;

      @Test
      void testConfiguration() {
      System.out.println(msg);
      }
      }

web 环境模拟测试

  • 模拟端口

    • ```java
      @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
      public class WebTest {
      @Test
      void test() {
      
      }
      
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16

      * 虚拟请求测试

      * ```java
      @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
      // 开启虚拟 MVC 的调用
      @AutoConfigureMockMvc
      public class WebTest {
      @Test
      void testWeb(@Autowired MockMvc mvc) throws Exception {
      // 创建虚拟请求
      MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
      // 执行对应请求
      mvc.perform(builder);
      }
      }
  • 虚拟请求状态匹配

    • ```java
      @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
      // 开启虚拟 MVC 的调用
      @AutoConfigureMockMvc
      public class WebTest {
      @Test
      void testStatus(@Autowired MockMvc mvc) throws Exception {
          MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
          ResultActions actions = mvc.perform(builder);
      
      // 设定预期值,与真实值进行比较,成功测试通过,失败测试失败
      // 定义本次调用的预期值
          StatusResultMatchers status = MockMvcResultMatchers.status();
      
      // 预计本次调用时成功的:状态 200
          ResultMatcher ok = status.isOk();
      
      // 添加预计值到本次调用过程中进行匹配
          actions.andExpect(ok);
      
      }
      
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22

      * 虚拟请求体匹配

      * ```java
      @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
      // 开启虚拟 MVC 的调用
      @AutoConfigureMockMvc
      public class WebTest {
      @Test
      void testBody(@Autowired MockMvc mvc) throws Exception {
      MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
      ResultActions actions = mvc.perform(builder);
      // 设定预期值,与真实值进行比较,成功测试通过,失败测试失败
      // 定义本次调用的预期值
      ContentResultMatchers content = MockMvcResultMatchers.content();
      ResultMatcher result = content.string("springboot");

      // 添加预计值到本次调用过程中进行匹配
      actions.andExpect(result);

      }
      }
  • 虚拟请求体(json)匹配

    • ```java
      @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
      // 开启虚拟 MVC 的调用
      @AutoConfigureMockMvc
      public class WebTest {
      @Test
      void testJson(@Autowired MockMvc mvc) throws Exception {
          MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
          ResultActions actions = mvc.perform(builder);
      
      // 设定预期值,与真实值进行比较,成功测试通过,失败测试失败
      // 定义本次调用的预期值
          ContentResultMatchers content = MockMvcResultMatchers.content();
          ResultMatcher result = content.json("{\"id\":1,\"name\":\"springboot\",\"type\":\"springboot\",\"description\":\"springboot\"}");
      
      // 添加预计值到本次调用过程中进行匹配
          actions.andExpect(result);
      
      }
      
      }
      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

      * 虚拟请求头匹配

      * ```java
      package com.itheima;

      import org.junit.jupiter.api.Test;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
      import org.springframework.boot.test.context.SpringBootTest;
      import org.springframework.test.web.servlet.MockMvc;
      import org.springframework.test.web.servlet.RequestBuilder;
      import org.springframework.test.web.servlet.ResultActions;
      import org.springframework.test.web.servlet.ResultMatcher;
      import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
      import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
      import org.springframework.test.web.servlet.result.ContentResultMatchers;
      import org.springframework.test.web.servlet.result.HeaderResultMatchers;
      import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
      import org.springframework.test.web.servlet.result.StatusResultMatchers;

      @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
      // 开启虚拟 MVC 的调用
      @AutoConfigureMockMvc
      public class WebTest {

      @Test
      void testContentType(@Autowired MockMvc mvc) throws Exception {
      MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
      ResultActions actions = mvc.perform(builder);
      // 设定预期值,与真实值进行比较,成功测试通过,失败测试失败
      // 定义本次调用的预期值
      HeaderResultMatchers header = MockMvcResultMatchers.header();
      ResultMatcher result = header.string("Content-Type", "application/json");
      // 添加预计值到本次调用过程中进行匹配
      actions.andExpect(result);

      }
      }

注意:一般都把多个匹配测试写在一起,一个规则一个对象,精度更高

数据层测试事务回滚

  • 为测试用例添加事务,SpringBoot 会对测试用例对应的事务提交操作进行回滚

    • ```java
      @SpringBootTest
      @Transactional
      public class DaoTest {
      @Autowired
      private BookService bookService;
      
      @Test
      void testSave() {
          bookService.save(new Book(2,"springboot2","springboot","springboot"));
      }
      
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18

      * 如果想在测试用例中提交事务,可以通过 @Rollback 注解设置

      * ```java
      @SpringBootTest
      @Transactional
      @Rollback(false)
      public class DaoTest {

      @Autowired
      private BookService bookService;

      @Test
      void testSave() {
      bookService.save(new Book(2,"springboot2","springboot","springboot"));
      }

      }

测试用例数据设定

  • 测试用例数据通常采用随机值进行测试,使用 SpringBoot 提供的随机数为其赋值

    • ```yaml
      testcase:
      book:
      id: ${random.int}
      name: ${random.value}
      uuid: ${random.uuid}
      publishTime: ${random.long}
      
      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

      * ${random.int} 表示随机数

      * ${random.int(10)} 表示 10 以内的随机数

      * ${randmom.int(10,20)} 表示 10 到 20 的随机数

      * 其中 () 可以是任意字符,例如 [], !!, @@ 均可

      ## 数据层解决方案

      ### SQL

      * 现有数据层解决方案技术选型:Druid + MyBtais-Plus + MySQL

      * 数据源:DruidDataSource
      * 持久化技术:Mybatis-Plus/Mybatis
      * 数据库:MySQL

      * 格式一

      * ```yaml
      spring:
      datasource:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
      username: root
      password: root
      type: com.alibaba.druid.pool.DruidDataSource
  • 格式二

    • ```yaml
      spring:
      datasource:
      druid: 
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
        username: root
        password: root
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20



      #### 数据源配置

      * SpringBoot 提供了 3 种内嵌的数据源对象供开发者选择

      * HikariCP:默认内置数据源对象

      * Tomcat 提供 DataSource:HikariCP 不可用的情况下,且在 web 环境中,将使用 tomcat 服务器配置的数据源对象

      * Commons DBCP:Hikari 不可用,tomcat 数据源也不可用,将使用 dbcp 数据源

      * ```yaml
      spring:
      datasource:
      url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
      driver-class-name: com.mysql.cj.jdbc.Driver
      username: root
      password: zt20020806
  • 通用配置无法设置具体的数据源配置信息,仅提供基本的连接相关配置,如需配置,在下一级配置中设置具体设定

    • ```yaml
      spring:
      datasource:

      url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
      driver-class-name: com.mysql.cj.jdbc.Driver
      username: root
      password: zt20020806
      hikari:
        maximum-pool-size: 50
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10

      * 内置持久化解决方案——JdbcTemplate

      * pom.xml 导入坐标

      * ```xml
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-jdbc</artifactId>
      </dependency>
    • ```java
      package com.itheima;

      import com.itheima.dao.BookDao;
      import com.itheima.domain.Book;
      import org.junit.jupiter.api.Test;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.boot.test.context.SpringBootTest;
      import org.springframework.jdbc.core.JdbcTemplate;
      import org.springframework.jdbc.core.RowMapper;

      import java.sql.ResultSet;
      import java.sql.SQLException;
      import java.util.List;
      import java.util.Map;

      @SpringBootTest
      class Springboot15SqlApplicationTests {

      @Test
      void testJdbcTemplate(@Autowired JdbcTemplate jdbcTemplate) {
          String sql = "select * from tb_book";
      

      // List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
      // System.out.println(maps);

          RowMapper<Book> rm = new RowMapper<Book>() {
              @Override
              public Book mapRow(ResultSet rs, int rowNum) throws SQLException {
      
                  Book temp = new Book();
                  temp.setId(rs.getInt("id"));
                  temp.setName(rs.getString("name"));
                  temp.setType(rs.getString("type"));
                  temp.setDescription(rs.getString("description"));
                  return temp;
              }
          };
          List<Book> bookList = jdbcTemplate.query(sql, rm);
          for (Book book : bookList) {
              System.out.println(book);
          }
      }
      

      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10

      * JdbTemplate 配置

      * ```yaml
      spring:
      jdbc:
      template:
      query-timeout: -1 # 查询超时时间
      max-rows: 500 # 最大行数
      fetch-size: -1 # 缓存行数

内嵌数据库

  • SpringBoot 提供了 3 种内嵌数据库供开发者选择,提高开发测试效率
    • H2
    • HSQL
    • Derby
内嵌数据库(H2)
  • 导入 H2 相关坐标

    • ```xml

      com.h2database h2 org.springframework.boot spring-boot-starter-data-jpa
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11

      * 设置当前项目为 web 工程,并配置 H2 管理控制台参数

      * ```yaml
      servrer:
      port: 80
      spring:
      h2:
      console:
      path: /h2
      enabled: true
    • 访问用户名 sa,默认密码 123456

  • 设置访问数据源

    • ```yaml
      server:
      port: 88
      spring:
      h2:
      console:
        path: /h2
        enabled: true
      
      datasource:
      url: jdbc:h2:~/test
      driver-class-name: org.h2.Driver
      username: sa
      password: 123456
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21

      * H2 数据库控制台仅用于开发阶段,线上项目请务必关闭控制台功能

      ###### 数据库设定

      * SpringBoot 可以根据 url 地址自动识别数据库种类,在保障驱动类存在的情况下,可以省略配置

      * ```yaml
      server:
      port: 88
      spring:
      h2:
      console:
      path: /h2
      enabled: true

      datasource:
      url: jdbc:h2:~/test
      # driver-class-name: org.h2.Driver
      username: sa
      password: 123456
  • 现有数据层解决方案技术选型

    • 数据源:Druid、Hikari
    • 持久化:MyBatis-Plus、MyBatis、JdbcTemplate
    • 数据库:MySQL、H2

NoSQL

  • 市面上常见的 NoSQL 解决方案
    • Redis
    • Mongo
    • ES
  • 说明:上述技术通常在 Linux 系统中安装部署

Redis

  • Redis 是一款 key-value 存储结构的内存级 NoSQL 数据库

    • 支持多种数据存储格式
    • 支持持久化
    • 支持集群
  • Redis 安装与启动(Windows版)

    • Windows 解压安装或一键式安装

    • 服务器端启动命令

      • ```cmd
        redis-server.exe redis.windows.conf
        1
        2
        3
        4
        5

        * 客户端启动命令

        * ```cmd
        redis-cli.exe
SpringBoot 整合 Redis
  • 导入 SpringBoot 整合 Redis 坐标

    • ```xml

      org.springframework.boot spring-boot-starter-data-redis
      1
      2
      3
      4
      5
      6
      7
      8

      * 配置 Redis(采用默认配置)

      * ```yaml
      spring:
      redis:
      host: localhost
      port: 6379
    • 主机:localhost(默认)

    • 端口:6379(默认)

  • RedisTemplate 提供操作各种数据存储类型的接口 API

  • 客户端:

    • ```java
      @SpringBootTest
      class Springboot16RedisApplicationTests {
      @Autowired
      private RedisTemplate redisTemplate;
      
      @Test
      void set() {
          ValueOperations ops = redisTemplate.opsForValue();
          ops.set("age", 41);
      }
      
      @Test
      void get() {
          ValueOperations ops = redisTemplate.opsForValue();
          System.out.println(ops.get("age"));
      }
      
      @Test
      void hset() {
          HashOperations ops = redisTemplate.opsForHash();
          ops.put("info","a","aa");
      }
      
      @Test
      void hget() {
          HashOperations ops = redisTemplate.opsForHash();
          System.out.println(ops.get("info", "a"));
      }
      
      }
      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

      **小结:**

      * SpringBoot 整合 Redis
      * 导入 redis 对应的 starter
      * 配置
      * 提供操作 Redis 接口对象 RedisTemplate
      * ops*:获取各种数据类型操作接口

      ##### SpringBoot 读写 Redis 客户端

      * 客户端:RedisTemplate 以对象作为 key 和 value,内部对数据进行序列化

      * ```java
      @SpringBootTest
      class Springboot16RedisApplicationTests {

      @Autowired
      private RedisTemplate redisTemplate;

      @Test
      void set() {
      ValueOperations ops = redisTemplate.opsForValue();
      ops.set("age", 41);
      }

      @Test
      void get() {
      ValueOperations ops = redisTemplate.opsForValue();
      System.out.println(ops.get("age"));
      }

      @Test
      void hset() {
      HashOperations ops = redisTemplate.opsForHash();
      ops.put("info","a","aa");
      }

      @Test
      void hget() {
      HashOperations ops = redisTemplate.opsForHash();
      System.out.println(ops.get("info", "a"));
      }
      }
  • 客户端:StringRedisTemplate(常用) 以字符串作为 key 和 value,与 Redis 客户端操作等效

    • ```java
      package com.itheima;import org.junit.jupiter.api.Test;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.boot.test.context.SpringBootTest;
      import org.springframework.data.redis.core.RedisTemplate;
      import org.springframework.data.redis.core.StringRedisTemplate;
      import org.springframework.data.redis.core.ValueOperations;@SpringBootTest
      public class StringRedisTemplateTest {
      private RedisTemplate redisTemplate;
      @Autowired
      private StringRedisTemplate stringRedisTemplate;
      
      @Test
      void get() {
          ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
          System.out.println(ops.get("name"));
      }
      
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11

      ##### SpringBoot 操作 Redis 客户端实现技术切换

      * 配置客户端

      * ```yaml
      spring:
      redis:
      host: localhost
      port: 6379
      client-type: jedis
  • lettcus 与 jedis 区别

    • jedis 连接 Redis 服务器是直连模式,当多线程模式下使用 jedis 会存在线程安全问题,解决方案可以通过配置连接池使每个连接专用,但这样整体性能就大受影响
    • lettcus 基于 Netty 框架进行与 Redis 服务器连接,底层设计中采用 StatefulRedisConnection。StatefulRedisConnection 自身是线程安全的,可以保障并发访问安全问题,所以一个连接可以被多线程复用。当然 lettcus 也支持多连接实例一起工作

Mongodb

  • MongoDB 是一个开源、高性能、无模式的文档型数据库。NoSQL 数据库产品中的一种,是最像关系型数据库的非关系型数据库
用例
  • 淘宝用户数据
    • 存储位置:数据库
    • 特征:永久性存储,修改频率极低
  • 游戏装备数据、游戏道具数据
    • 存储位置:数据库、Mongodb
    • 特征:永久性存储与临时存储相结合、修改频率较高
  • 直播数据、打赏数据、粉丝数据
    • 存储位置:数据库、Mongodb
    • 特征:永久性存储与临时存储相结合、修改频率极高
  • 物联网数据
    • 存储位置:Mongodb
    • 特征:临时存储、修改频率飞速
Mongodb 下载
  • Windows 版 Mongodb 下载

  • Windows 版 Mongodb 安装

    • 解压缩后设置数据目录
  • Windows 版 Mongodb 启动

    • 服务器启动

      • ```cmd
        mongod –dbpath=..\data\db
        1
        2
        3
        4
        5

        * 客户端启动

        * ```cmd
        mongo --host=127.0.0.1 --port=27017
  • Windows 版 Mongo 安装问题及解决方案

    • ”找不到 VCRUNTIME140_1.dll,无法继续执行代码,重新安装程序可能会解决此问题“

      • 步骤一:下载对应的 dll 文件(通过浏览器搜索即可)

      • 步骤二:拷贝到 windows 安装路径下的 system32 目录中

      • 步骤三:执行命令注册对应 dll 文件

        • ```cmd
          regsvr32 vcruntime140_1.dll
          1
          2
          3
          4
          5
          6
          7
          8
          9

          * 可视化客户端——Robo 3T、Studio 3T

          ##### 常用操作

          * 新增

          * ```mongo
          db.集合名称.insert/save/insertOne(文档)
  • 修改

    • ```mongo
      db.集合名称.update(条件, {操作种类: {文档})
      1
      2
      3
      4
      5

      * 删除

      * ```mongo
      db.集合名称.remove(条件)
  • 查询

    • 1.基础查询
      • 查询全部:db.集合.find()
      • 查第一条:db.集合.findOne()
      • 查询指定数量文档:db.集合.find().limit(10) // 查 10 条文档
      • 跳过指定数量文档:db.集合.find().skip(20) // 跳过 20 条文档
      • 统计:db.集合.count()
      • 排序:db.集合.sort({age:1}) // 按 age 升序排序
      • 投影:db.集合.sort.find(条件,{name:1,age:1}) // 仅保留 name 和 age 域
    • 2.条件查询
      • 基本格式:db.集合.find({条件})
      • 模糊查询:db.集合.find({域名:/正则表达式/}) // 等同 SQL 中的 like,比 like 强大,可以执行正则所有规则
      • 条件比较运算:db.集合.find({域名:{$gt:值}}) // 等同 SQL 中的数值比较操作,例如:name > 18
      • 包含查询:db.集合.find({域名:{$in:[值1, 值2]}}) // 等同于 SQL 中的 in
      • 条件连接查询: db.集合.find({$and:[{条件1},{条件2}]}) // 等同于 SQL 中的 and、or
SpringBoot 整合 Mongodb
  • 导入 对应坐标

    • ```xml org.springframework.boot spring-boot-starter-data-mongodb
      1
      2
      3
      4
      5
      6
      7
      8

      * 配置 mongodb 访问 uri

      * ```yaml
      spring:
      data:
      mongodb:
      uri: mongodb://localhost/itheima
  • 客户端读写 Mongodb

    • ```java
      @SpringBootTest
      class Springboot17MongodbApplicationTests {
      @Autowired
      private MongoTemplate mongoTemplate;
      
      @Test
      void save() {
          Book book = new Book();
          book.setId(2);
          book.setName("springboot");
          book.setType("springboot");
          book.setDescription("springboot");
          mongoTemplate.save(book);
      }
      
      @Test
      void find() {
          List<Book> all = mongoTemplate.findAll(Book.class);
          System.out.println(all);
      }
      
      }
      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

      #### Elasticsearch(ES)

      * ES 是一个分布式全文搜索引擎

      ##### ES 下载安装

      * Windows 版 ES 下载
      * https://www.elastic.co/cn/downloads/elasticsearch
      * 运行 elasticsearch.bat

      ##### ES 使用

      * PUT http://lcoalhost:9200/books
      * GET http://lcoalhost:9200/books
      * DELETE http://lcoalhost:9200/books

      ##### IK 分词器

      下载地址:https://github.com/medcl/elasticearch-analusis-ik/releases

      版本跟 ES 版本同步

      ---

      创建索引并指定规则

      ```json
      {
      "mapping": {
      "properties": {
      "id": {
      "type": "keyword"
      },
      "name": {
      "type": "text",
      "analyzer": "ik_max_word",
      "copy_to": "all"
      },
      "type": {
      "type": "keyword"
      },
      "description": {
      "type": "text",
      "analyzer": "ik_max_word",
      "copy_to": "all"
      },
      "all": {
      "type": "text",
      "analyzer": "ik_max_word"
      }
      }
      }
      }
  • 创建文档

  • 查询文档

  • 条件查询

  • 删除文档

  • 修改文档(全量修改)

  • 修改文档(部分修改)

springboot 整合 es
  • 导入坐标

    • ```xml org.springframework.boot spring-boot-starter-data-elasticsearch
      1
      2
      3
      4
      5
      6
      7
      8

      * 配置

      * ```yaml
      spring:
      elasticsearch:
      rest:
      uris: http://localhost:9200
  • 客户端

    • ```java
      @Autowired
      private ElasticsearchRestTemplate template;
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12

      ##### springboot 整合 high level es

      * springboot 平台并没有跟随 ES 的更新进行同步更新,ES 提供了 High Level Client 操作 ES

      * 导入坐标

      * ```xml
      <dependency>
      <groupId>org.elasticsearch.client</groupId>
      <artifactId>elasticsearch-rest-high-level-client</artifactId>
      </dependency>
  • 配置(无)

  • 客户端

    • ```java
      package com.itheima;import com.itheima.dao.BookDao;
      import org.apache.http.HttpHost;
      import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
      import org.elasticsearch.client.RequestOptions;
      import org.elasticsearch.client.RestClient;
      import org.elasticsearch.client.RestClientBuilder;
      import org.elasticsearch.client.RestHighLevelClient;
      import org.junit.jupiter.api.AfterEach;
      import org.junit.jupiter.api.BeforeEach;
      import org.junit.jupiter.api.Test;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.boot.test.context.SpringBootTest;import java.io.IOException;@SpringBootTest
      class Springboot18EsApplicationTests {
      @Autowired
      private RestHighLevelClient client;
      
      
      @Test
      void testCreateIndex() throws IOException {
          HttpHost host = HttpHost.create("http://localhost:9200");
          RestClientBuilder builder = RestClient.builder(host);
          client = new RestHighLevelClient(builder);
      
          CreateIndexRequest request = new CreateIndexRequest("books");
          client.indices().create(request, RequestOptions.DEFAULT);
      
          client.close();
      }
      
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21

      * 客户端简化

      * ```java
      @BeforeEach
      void setUp() {
      HttpHost host = HttpHost.create("http://localhost:9200");
      RestClientBuilder builder = RestClient.builder(host);
      client = new RestHighLevelClient(builder);
      }

      @AfterEach
      void tearDown() throws IOException {
      client.close();
      }

      @Test
      void testCreateIndex() throws IOException {
      CreateIndexRequest request = new CreateIndexRequest("books");
      client.indices().create(request, RequestOptions.DEFAULT);
      }
  • 创建索引

    • ```java
      @Test
      void testCreateIndexByIK() throws IOException {
      // 客户端操作
      CreateIndexRequest request = new CreateIndexRequest("books");
      // 设置要执行的操作
      String json = "{\n" +
          "    \"mapping\": {\n" +
          "        \"properties\": {\n" +
          "            \"id\": {\n" +
          "                \"type\": \"keyword\"\n" +
          "            },\n" +
          "            \"name\": {\n" +
          "                \"type\": \"text\",\n" +
          "                \"analyzer\": \"ik_max_word\",\n" +
          "                \"copy_to\": \"all\"\n" +
          "            },\n" +
          "            \"type\": {\n" +
          "                \"type\": \"keyword\"\n" +
          "            },\n" +
          "            \"description\": {\n" +
          "                \"type\": \"text\",\n" +
          "                \"analyzer\": \"ik_max_word\",\n" +
          "                \"copy_to\": \"all\"\n" +
          "            },\n" +
          "            \"all\": {\n" +
          "                \"type\": \"text\",\n" +
          "                \"analyzer\": \"ik_max_word\"\n" +
          "            }\n" +
          "        }\n" +
          "    }\n" +
          "}";
      //        设置请求中的参数,参数类型为 JSON
      request.source(json, XContentType.JSON);
      client.indices().create(request, RequestOptions.DEFAULT);
      
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14

      * 添加文档

      * ```java
      // 添加文档
      @Test
      void testCreateDoc() throws IOException {
      Book book = bookDao.selectById(1);

      IndexRequest request = new IndexRequest("books").id(book.getId().toString());
      String json = JSON.toJSONString(book);

      request.source(json, XContentType.JSON);
      client.index(request, RequestOptions.DEFAULT);
  • 批量添加文档

    • ```java
      // 批量添加文档
      @Test
      void testCreateDoc() throws IOException {
      List<Book> bookList = bookDao.selectList(null);
      
      BulkRequest bulk = new BulkRequest();
      
      for (Book book : bookList) {
          IndexRequest request = new IndexRequest("books").id(book.getId().toString());
          String json = JSON.toJSONString(book);
      
          request.source(json, XContentType.JSON);
      
          bulk.add(request);
      }
      
      client.bulk(bulk, RequestOptions.DEFAULT);
      
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12

      * 按 id 查询

      * ```java
      // 按 id 查询
      @Test
      void testGet() throws IOException {
      GetRequest request = new GetRequest("books","1");
      GetResponse response = client.get(request, RequestOptions.DEFAULT);
      String json = response.getSourceAsString();
      System.out.println(json);
      }
  • 按条件查询文档

    • ```java
      // 按条件查询
      @Test
      void testSearch() throws IOException {
      SearchRequest request = new SearchRequest("books");
      
      SearchSourceBuilder builder = new SearchSourceBuilder();
      builder.query(QueryBuilders.termQuery("name","springboot"));
      request.source(builder);
      
      SearchResponse response = client.search(request, RequestOptions.DEFAULT);
      SearchHits hits = response.getHits();
      for (SearchHit hit : hits) {
          System.out.println(hit.getSourceAsString());
      }
      
      }
      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


      ## 整合第三方技术

      ### 缓存

      * 缓存是一种介于数据永久存储介质与数据应用之间的数据临时存储介质
      * 使用缓存可以有效的减少低速数据读取过程的次数(例如磁盘 IO),提高系统性能
      * 缓存不仅可以用于提高永久性存储介质的数据读取效率,还可以提供临时的数据存储空间

      ---

      * SpringBoot 提供了缓存技术,方便缓存使用

      #### 缓存的使用

      * 启用缓存
      * 设置进入缓存的数据
      * 设置读取缓存的数据

      ##### 缓存使用的具体步骤

      * 1.导入缓存技术对应的 starter

      * ```xml
      <!-- cache -->
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-cache</artifactId>
      </dependency>
  • 2.启用缓存

    • ```java
      // main.java
      @SpringBootApplication
      // 开启缓存功能
      @EnableCaching
      public class Springboot19CacheApplication {
      public static void main(String[] args) {
          SpringApplication.run(Springboot19CacheApplication.class, args);
      }
      
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10

      * 3.设置当前操作的结果数据进入缓存

      * ```java
      @Override
      @Cacheable(value = "cacheSpace", key = "#id")
      public Book getById(Integer id) {

      return bookDao.selectById(id);
      }

  • SpringBoot 提供的缓存技术除了提供默认的缓存方案,还可以对其他缓存技术进行整合,统一接口,方便缓存技术的开发与管理
    • Generic
    • JCache
    • Ehcache
    • Hazelcast
    • Infinispan
    • Couchbase
    • Redis
    • Caffenine
    • Simple(默认)
    • memcached
缓存使用案例——手机验证码
  • 需求
    • 输入手机号获取验证码,组织文档以短信形式发送给用户(页面模拟)
    • 输入手机号和验证码验证结果
  • 需求分析
    • 提供 controller,传入手机号,业务层通过手机号计算出独有的 6 位验证码数据,存储缓存后返回此数据
    • 提供 controller,传入手机号和验证码,业务层通过手机号从缓存中读取验证码与输入验证码进行比对,返回比对结果
缓存供应商变更:Ehcache
  • 加入 Ehcache 坐标(缓存供应商实现)

    • ```xml net.sf.ehcache ehcache
      1
      2
      3
      4
      5
      6
      7
      8
      9

      * 缓存设定为使用 Ehcache

      * ```yaml
      spring:
      cache:
      type: ehcache
      ehcache:
      config: classpath:ehcache.xml
  • 提供 ehcache 配置文件 ehcache.xml

    • ```xml <ehcache xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
          updateCheck="false">
      <diskStore path="D:\ehcache" />
      
      <!-- eternal:是否永久存在,设置为 true 则不会被清除,此时与 timeout 冲突,通常设置为 false -->
      <!-- diskPersistent:是否启用磁盘持久化   -->
      <!-- maxElementsInMemory:最大缓存数量   -->
      <!-- overflowToDisk:超过最大缓存数量是否继续持久化到磁盘   -->
      <!-- timeToIdleSeconds:最大活动间隔,设置过长缓存容易溢出,设置过短无效果,可用于记录时效性数据,例如验证码   -->
      <!-- timeToLiveSeconds:最大存活时间   -->
      <!-- memoryStoreEvictionPolicy:缓存清理策略   -->
      
      <defaultCache
              eternal="false"
              diskPersistent="false"
              maxElementsInMemory="1000"
              overflowToDisk="false"
              timeToIdleSeconds="60"
              timeToLiveSeconds="60"
              memoryStoreEvictionPolicy="LRU"
      ></defaultCache>
      
      <cache
              name="smsCode"
              eternal="false"
              diskPersistent="false"
              maxElementsInMemory="1000"
              overflowToDisk="false"
              timeToIdleSeconds="60"
              timeToLiveSeconds="60"
              memoryStoreEvictionPolicy="LRU"
      ></cache>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10

      ##### 缓存供应商变更:Redis

      * 导坐标

      * ```xml
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
      </dependency>
  • 设置 Redis 相关配置

    • ```yaml
      spring:
      cache:
      type: redis
      redis:
        use-key-prefix: true      # 是否使用前缀名(系统定义前缀名)
        cache-null-values: false  # 追加自定义前缀名
        key-prefix: aa            # 有效时长     
        time-to-live: 10          # 是否允许存储空值
      
      redis:
      host: localhost
      port: 6379
      
      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

      ##### 缓存供应商变更:memcached

      * 下载 memcached
      * 地址:https:///www.runoob.com/memcached/window-install-memcached.html
      * 安装 memcached
      * 使用管理员身份运行 cmd 指令
      * 安装:memcached.exe -d install
      * 运行 memcached
      * 启动服务:memcached.exe -d start
      * 停止服务:memcached.exe -d stop

      ---

      * memcached 客户端选择
      * Memcached Client for Java:最早期客户端,稳定可靠,用户群广
      * SpyMemcached:效率更高
      * Xmemcached:并发处理更好
      * SpringBoot 未提供对 memcached 的整合,需要使用硬编码方式实现客户端初始化管理

      ###### 具体步骤

      * 加入 Xmemcache 坐标(缓存供应商实现)

      * ```xml
      <dependency>
      <groupId>com.googlecode.xmemcached</groupId>
      <artifactId>xmemcached</artifactId>
      <version>2.4.7</version>
      </dependency>
  • 配置 memcached 服务器必要属性

    • ```yaml
      memcached:

      memcached 服务器地址

      servers: localhost:11211

      连接池的数量

      poolSize: 10

      设置默认操作超时时间

      opTimeout: 3000
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12

      * 创建读取属性配置信息类,加载配置

      * ```java
      @Component
      @ConfigurationProperties(prefix = "memcached")
      @Data
      public class XMemcachedProperties {
      private String servers;
      private int poolSize;
      private long opTimeout;
      }
  • 创建客户端配置类

    • ```java
      @Configuration
      public class XMemcachedConfig {
      @Autowired
      private XMemcachedProperties xMemcachedProperties;
      
      @Bean
      public MemcachedClient getMemcachedClient() throws IOException {
          MemcachedClientBuilder memcachedClientBuilder = new XMemcachedClientBuilder(xMemcachedProperties.getServers());
          memcachedClientBuilder.setConnectionPoolSize(xMemcachedProperties.getPoolSize());
          memcachedClientBuilder.setOpTimeout(xMemcachedProperties.getOpTimeout());
          MemcachedClient memcachedClient = memcachedClientBuilder.build();
          return memcachedClient;
      }
      
      }
      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

      * 配置 memcached 属性

      * ```java
      package com.itheima.service.impl;

      import com.itheima.domain.SMSCode;
      import com.itheima.service.SMSCodeService;
      import com.itheima.utils.CodeUtils;
      import net.rubyeye.xmemcached.MemcachedClient;
      import net.rubyeye.xmemcached.exception.MemcachedException;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.cache.annotation.CachePut;
      import org.springframework.cache.annotation.Cacheable;
      import org.springframework.stereotype.Service;

      import java.util.concurrent.TimeoutException;

      @Service
      public class SMSCodeServiceImpl implements SMSCodeService {

      @Autowired
      private CodeUtils codeUtils;

      // 以下是 springboot 中使用 xmemcached

      @Autowired
      private MemcachedClient memcachedClient;

      @Override
      public String sendCodeToSMS(String tele) {
      String code = codeUtils.generator(tele);
      try {
      memcachedClient.set(tele, 0, code);
      } catch (Exception e) {
      e.printStackTrace();
      }
      return code;
      }

      @Override
      public boolean checkCode(SMSCode smsCode) {
      String code = null;
      try {
      code = memcachedClient.get(smsCode.getTele()).toString();
      } catch (Exception e) {
      e.printStackTrace();
      }
      return smsCode.getCode().equals(code);
      }

      }

缓存供应商变更:jetcache

  • jetcache 对 SpringCache 进行了封装,在原有功能基础上实现了多级缓存、缓存统计、自动刷新、异步调用、数据报表等功能
  • jetcache 设定了本地缓存与远程缓存的多级缓存解决方案
    • 本地缓存(local)
      • LinkedHashMap
      • Caffine
    • 远程缓存(remote)
      • Redis
      • Tair

具体步骤
  • 加入 jetcache 坐标

    • ```xml com.alicp.jetcache jetcache-starter-redis 2.6.7
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24

      * 配置远程缓存必要属性

      * ```yaml
      jetcache:
      # 本地方案
      local:
      default:
      type: linkedhashmap
      keyConvertor: fastjson
      # 远程方案
      remote:
      default:
      type: redis
      host: localhost
      port: 6379
      poolConfig:
      maxTotal: 50
      sms:
      type: redis
      host: localhost
      port: 6379
      poolConfig:
      maxTotal: 50
  • 配置属性说明

    • image-20220804171947266
  • 开启 jetcache 注解支持

    • ```java
      @SpringBootApplication
      // jetcache 启用缓存的主开关
      @EnableCreateCacheAnnotation
      public class Springboot20JetcacheApplication {
      public static void main(String[] args) {
          SpringApplication.run(Springboot20JetcacheApplication.class, args);
      }
      
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18

      * 声明缓存对象

      * ```java
      @Service
      public class SMSCodeServiceImpl implements SMSCodeService {

      @Autowired
      private CodeUtils codeUtils;

      @CreateCache(name = "jetCache", expire = 3600, timeUnit = TimeUnit.SECONDS, cacheType = CacheType.LOCAL)
      private Cache<String, String> jetCache;

      @CreateCache(area = "sms", name = "jetCache", expire = 3600, timeUnit = TimeUnit.SECONDS)
      private Cache<String, String> jetCache2;

      }

  • 操作缓存

    • ```java
      package com.itheima.service.impl;import com.alicp.jetcache.Cache;
      import com.alicp.jetcache.anno.CacheType;
      import com.alicp.jetcache.anno.CreateCache;
      import com.itheima.domain.SMSCode;
      import com.itheima.service.SMSCodeService;
      import com.itheima.utils.CodeUtils;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;@Service
      public class SMSCodeServiceImpl implements SMSCodeService {
      @Autowired
      private CodeUtils codeUtils;
      
      @CreateCache(name = "jetCache", expire = 3600, timeUnit = TimeUnit.SECONDS, cacheType = CacheType.LOCAL)
      private Cache<String, String> jetCache;
      
      @CreateCache(area = "sms", name = "jetCache", expire = 3600, timeUnit = TimeUnit.SECONDS)
      private Cache<String, String> jetCache2;
      
      @Override
      public String sendCodeToSMS(String tele) {
          String code = codeUtils.generator(tele);
          jetCache.put(tele, code);
          return code;
      }
      
      @Override
      public boolean checkCode(SMSCode smsCode) {
          String code = jetCache.get(smsCode.getTele());
          return smsCode.getCode().equals(code);
      }
      
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18

      ##### 启用方法注解

      * 启用方法注解

      * ```java
      @SpringBootApplication
      // jetcache 启用缓存的主开关
      @EnableCreateCacheAnnotation
      // 开启方法注解缓存
      @EnableMethodCache(basePackages = "com.itheima")
      public class Springboot20JetcacheApplication {

      public static void main(String[] args) {
      SpringApplication.run(Springboot20JetcacheApplication.class, args);
      }

      }
  • 使用方法注解操作缓存

    • ```java
      @Service
      public class BookServiceImpl implements BookService {

      @Autowired
      private BookDao bookDao;
      
      @Override
      @Cached(name = "book_", key = "#id", expire = 3600, cacheType = CacheType.LOCAL)
      

      // @CacheRefresh(refresh = 10)

      public Book getById(Integer id) {
      
          return bookDao.selectById(id);
      }
      
          @Override
      @CacheUpdate(name = "book_", key = "#book.id", value = "#book")
      public boolean update(Book book) {
          return bookDao.updateById(book) > 0;
      }
      
      @Override
      @CacheInvalidate(name = "book_", key = "#id")
      public boolean delete(Integer id) {
          return bookDao.deleteById(id) > 0;
      }
      

      }

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

      * 缓存对象必须保障可序列化

      * ```java
      // lombok
      @Data
      @NoArgsConstructor
      @AllArgsConstructor
      public class Book implements Serializable {
      private Integer id;
      private String type;
      private String name;
      private String description;
      }
    • ```yaml

      application.yml

      mybatis-plus:
      global-config:

      db-config:
        table-prefix: tb_
        id-type: auto
      

      configuration:

      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
      

      spring:
      datasource:

      druid:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=GMT
        username: root
        password: zt20020806
      

      jetcache:
      remote:

      default:
        type: redis
        keyConvertor: fastjson
        valueEcode: java
        valueDcode: java
      
      1
      2
      3
      4
      5
      6

      * 查看缓存统计报告

      * ```java
      jetcache:
      statIntervalMinutes: 1

缓存供应商变更:j2cache

  • j2cache 是一个缓存整合框架,可以提供缓存的整合方案,使各种缓存搭配使用,自身不提供缓存功能
  • 基于 ehcache + redis 进行整合

具体步骤
  • 加入 j2cache 坐标,加入整合缓存的坐标

    • ```java net.sf.ehcache ehcache
      <!-- https://mvnrepository.com/artifact/net.oschina.j2cache/j2cache-spring-boot2-starter -->
      
      net.oschina.j2cache j2cache-spring-boot2-starter 2.8.0-release
      <!-- https://mvnrepository.com/artifact/net.oschina.j2cache/j2cache-core -->
      
      net.oschina.j2cache j2cache-core 2.8.4-release org.slf4j slf4j-simple
      1
      2
      3
      4
      5
      6
      7
      8

      * 配置使用 j2cache(application.yml)

      * ```yaml
      server:
      port: 8080
      j2cache:
      config-location: j2cache.properties
  • 配置一级缓存与二级缓存以及一级缓存数据到二级缓存的发送方式(j2cache.properties)

    • ```properties

      1 级缓存

      j2cache.L1.provider_class = ehcache
      ehcache.configXml = ehcache.xml

      设置是否启用二级缓存

      j2cache.l2-cache-open = false

      2 级缓存

      j2cache.L2.provider_class = net.oschina.j2cache.cache.support.redis.SpringRedisProvider
      j2cache.L2.config_section = redis
      redis.host = localhost:6379

      1 级缓存中的数据如何到达二级缓存

      可以使用 redis 提供的消息订阅模式,也可以使用 jgroups 多播实现

      j2cache.broadcast = net.oschina.j2cache.cache.support.redis.SpringRedisPubSubPolicyredis.mode = single
      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

      * 设置使用缓存

      * ```java
      @Service
      public class SMSCodeServiceImpl implements SMSCodeService {

      @Autowired
      private CodeUtils codeUtils;

      @Autowired
      private CacheChannel cacheChannel;

      @Override
      public String sendCodeToSMS(String tele) {
      String code = codeUtils.generator(tele);
      cacheChannel.set("sms", tele, code);
      return code;
      }

      @Override
      public boolean checkCode(SMSCode smsCode) {
      String code = cacheChannel.get("sms",smsCode.getTele()).asString();
      return smsCode.getCode().equals(code);
      }
      }

数据淘汰策略

影响数据淘汰的相关配置
  • 检测易失数据(可能会过期的数据集 server.db[i].expires)
    • 1.volatile-lru:挑选最近使用时间最少的数据淘汰
    • 2.volatile-lfu:挑选最近使用次数最少的数据淘汰
    • 3.volatile-ttl:挑选将要过期的数据淘汰
    • 4.volatile-random:任意选择数据淘汰

任务

  • 定时任务是企业级应用中的常见操作
    • 年度报表
    • 缓存统计报告
    • ……
  • 市面上流行的定时任务技术
    • Quartz
    • Spring Task
  • 相关概念
    • 工作(Job):用于定义具体执行的工作
    • 工作明细(JobDetail):用于描述定时工作相关的信息
    • 触发器(Trigger):用于描述触发工作的规则,通常使用 cron 表达式定义调度规则
    • 调度器(Scheduler):描述了工作明细与触发器的对应关系

SpringBoot 整合 Quartz

  • 导入 SpringBoot 整合 quartz 的坐标
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
  • 定义具体要执行的任务,继承 QuartzJobBean
1
2
3
4
5
6
public class MyQuartz extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
System.out.println("quartz task run ...");
}
}
  • 定义工作明细与触发器,并绑定对应关系
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Configuration
public class QuartzConfig {

@Bean
public JobDetail printJobDetail() {
// 绑定具体的工作

return JobBuilder.newJob(MyQuartz.class).storeDurably().build();
}

@Bean
public Trigger printTrigger() {
// 绑定具体的工作明细
ScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/5 * * * * ?");

return TriggerBuilder.newTrigger().forJob(printJobDetail()).withSchedule(scheduleBuilder).build();
}
}

Spring Task

  • 开启定时器
1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
// 开启定时任务功能
@EnableScheduling
public class Springboot22TaskApplication {

public static void main(String[] args) {
SpringApplication.run(Springboot22TaskApplication.class, args);
}

}
  • 设置定时执行的任务,并设定执行周期
1
2
3
4
5
6
7
8
@Component
public class MyBean {

@Scheduled(cron = "0/1 * * * * ?")
public void print() {
System.out.println("task running ...");
}
}
  • 定时任务相关配置
1
2
3
4
5
6
7
8
9
10
11
12
13
spring: 
task:
scheduling:
# 任务调度线程池大小 默认 1
pool:
size: 1
# 调度线程名称前缀 默认 scheduling-
thread-name-prefix: ssm_
shutdown:
# 线程池关闭时等待所有任务完成
await-termination: false
# 调度线程关闭最大等待时间,确保最后一定关闭
await-termination-period: 10s

邮件

  • SMTP(Simple Mail Transfer Protocol):简单邮件传输协议,用于发送电子邮件的传输协议
  • POP3(Post Office Protocol - Version 3):用于接收电子邮件的标准协议
  • IMAP(Internet Mail Access Protocol):互联网消息协议,是 POP3 的替代协议

Spring Boot 整合 JavaMail

  • 配置 JavaMail
1
2
3
4
5
spring:
mail:
host: smtp.qq.com # smtp + xx.com
username: xxx@qq.com
password: ysbjqrserkiwfhfg # password 在对应邮箱设置 -> 账户 获取
  • 对应业务层
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

package com.itheima.service.impl;

import com.itheima.service.SendMailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;

@Service
public class SendMailServiceImpl implements SendMailService {

@Autowired
private JavaMailSender javaMailSender;

// 发送人
private String from = "xxx@qq.com";
// 接收人
private String to = "xxx@qq.com";
// 标题
private String subject = "测试邮件";
// 正文
private String context = "测试邮件正文内容";
@Override
public void sendMail() {
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(from + "(大甜甜)");
message.setTo(to);
message.setSubject(subject);
message.setText(context);
javaMailSender.send(message);

}
}

  • 发送附件
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
package com.itheima.service.impl;

import com.itheima.service.SendMailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.io.File;

@Service
public class SendMailServiceImpl2 implements SendMailService {

@Autowired
private JavaMailSender javaMailSender;

// 发送人
private String from = "1513591616@qq.com";
// 接收人
private String to = "2906050148@qq.com";
// 标题
private String subject = "测试邮件";
// 正文
private String context = "<a href='https://www.bilibili.com/video/BV11E411C7N9?spm_id_from=333.337.search-card.all.click&vd_source=19d2b6b549b6d961b6aabb1b1e1ed551&t=0.9'>点开有惊喜</a>";
@Override
public void sendMail() {
MimeMessage message = javaMailSender.createMimeMessage();

try {
MimeMessageHelper helper = new MimeMessageHelper(message, true);

helper.setFrom(from + "(大甜甜)");
helper.setTo(to);
helper.setSubject(subject);
helper.setText(context, true);

// 添加附件
File f1 = new File("D:\\springboot\\demo\\springboot_23_mail\\target\\springboot_23_mail-0.0.1-SNAPSHOT.jar");
File f2 = new File("D:\\springboot\\demo\\springboot_23_mail\\src\\main\\resources\\66807h669p0.png");

helper.addAttachment(f1.getName(), f1);
helper.addAttachment("好康的.png", f2);
} catch (Exception e) {
e.printStackTrace();
}
javaMailSender.send(message);

}
}

消息

  • 消息发送方
    • 生产者
  • 消息接收方
    • 消费者

  • 同步消息
  • 异步消息

  • 企业级应用中广泛使用的三种异步消息传递技术
    • JMS
    • AMQP
    • MQTT

JMS

  • JMS(Java Message Service):一个规范,等同于 JDBC 规范,提供了与消息服务相关的 API 接口
  • JMS 消息模型
    • peer-2-peer:点对点模型,消息发送到一个队列中,队列保存消息。队列的消息只能被一个消费者消费,或超时
    • publish-subscribe:发布订阅模型,消息可以被多个消费者消费,生产者和消费者完全独立,不需要感知对方的存在
  • JMS 消息种类
    • TextMessage
    • MapMessage
    • BytesMessage
    • StreamMessage
    • ObjectMessage
    • Message(只有消息头和属性)
  • JMS 实现:ActiveMQ、Reids、HornetMQ、RabbitMQ、RocketMQ(没有完全遵守 JMS 规范)

AMQP

  • AMQP(advanced message queuing protocol):一种协议(高级消息队列协议,也就是消息代理规范),规范了网络交换的数据格式,兼容 JMS
  • 优点:具有跨平台性,服务器供应商,生产者,消费者可以使用不同的语言来实现
  • AMQP 消息模型
    • direct exchange
    • fanout exchange
    • topic exchange
    • headers exchange
    • system exchange
  • AMQP 消息种类:byte[]
  • AMQP 实现:RabbitMQ、StormMQ、RocketMQ

MQTT

  • MQTT(Message Queueing Telemetry Transoprt)消息队列遥测传输,专为小设备设计,是物联网(IOT)生态系统中主要成分之一

Kafka

  • Kafka,一种高吞吐量的分布式发布订阅消息系统,提供实时消息功能

ActiveMQ

Spring Boot 整合 ActiveMQ
  • 导入 SpringBoot 整合 ActiveMQ 坐标
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
  • 配置 ActiveMQ(采用默认配置)
1
2
3
4
5
6
7
8
9
server:
port: 8080
spring:
activemq:
broker-url: tcp://localhost:61616
jms:
template:
default-destination: itheima
pub-sub-domain: true
  • 生产与消费消息(使用默认消息存储队列)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Service
public class MessageServiceActivemqImpl implements MessageService {
@Autowired
private JmsMessagingTemplate messagingTemplate;

@Override
public void sendMessage(String id) {
System.out.println("待发送短信的订单已纳入处理队列,id:" + id);
messagingTemplate.convertAndSend("order.queue.id",id);
}

@Override
public String doMessage() {
String id = messagingTemplate.receiveAndConvert("order.queue.id", String.class);
System.out.println("已完成短信发送业务,id:" + id);
return id;
}
}
  • 使用消息监听器对消息队列监听
1
2
3
4
5
6
7
8
9
10
@Component
public class MessageListener {

@JmsListener(destination = "order.queue.id")
public String receive(String id) {
System.out.println("已完成短信发送业务,id:" + id);
return id;
}
}

  • 流程性业务消息消费完转入下一个消息队列
1
2
3
4
5
6
7
8
9
10
11
@Component
public class MessageListener {

@JmsListener(destination = "order.queue.id")
@SendTo("order.other.queue.id")
public String receive(String id) {
System.out.println("已完成短信发送业务,id:" + id);
return id;
}
}

RabbitMQ

  • RabbitMQ 基于 Erlang 语言编写,需要安装 Erlang
  • Erlang
    • 下载地址:https://www.erlang.org/downloads
    • 安装:一键傻瓜式安装,安装完毕需重启,需要依赖 Windows 组件
    • 环境变量配置
      • ERLANG_HOME
      • PATH
RabbitMQ 下载
SpringBoot 整合 RabbitMQ
  • 导坐标
1
2
3
4
5
<!--        rabbitmq-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
  • 配置 application.yml
1
2
3
4
5
6
server:
port: 8080
spring:
rabbitmq:
host: localhost
port: 5672
  • 定义消息队列(direct)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Configuration
public class RabbitConfigDirect {

@Bean
public Queue directQueue() {
// durable:是否持久化,默认 false
// exclusive:是否当前连接专用,默认 false,连接关闭后队列即被删除
// autoDelete:是否自动删除,当生产者或消费者不再使用此队列,自动删除
return new Queue("direct_queue");
}

@Bean
public DirectExchange directExchange() {
return new DirectExchange("directExchange");
}

@Bean
public Binding bindingDirect() {
return BindingBuilder.bind(directQueue()).to(directExchange()).with("direct");
}

}
  • 生产消息与消费消息(direct)
1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class MessageServiceRabbitmqDirectImpl implements MessageService {

@Autowired
private AmqpTemplate amqpTemplate;

@Override
public void sendMessage(String id) {
System.out.println("待发送短信的订单已纳入处理队列(rabbitmq direct),id:" + id);
amqpTemplate.convertAndSend("directExchange","direct",id);
}

}
  • 使用消息监听器对消息队列监听(direct)
1
2
3
4
5
6
7
8
@Component
public class MessageListener {

@RabbitListener(queues = "direct_queue")
public void receive(String id) {
System.out.println("已完成短信发送业务(rabbitmq direct),id:" + id);
}
}
  • 使用多消息监听器对消息队列监听进行消息轮循处理(direct)
1
2
3
4
5
6
7
8
9
@Component
public class MessageListener2 {

@RabbitListener(queues = "direct_queue")
public void receive(String id) {
System.out.println("已完成短信发送业务(rabbitmq direct two),id:" + id);

}
}
  • 定义消息队列(topic)
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
package com.itheima.service.impl.rabbitmq.topic.config;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class RabbitConfigTopic {

@Bean
public Queue topicQueue() {
return new Queue("topic_queue");
}

@Bean
public TopicExchange topicExchange() {
return new TopicExchange("topicExchange");
}

@Bean
public Binding bindingTopic() {
return BindingBuilder.bind(topicQueue()).to(topicExchange()).with("topic.order.id");
}
@Bean
public Queue topicQueue2() {
return new Queue("topic_queue2");
}

@Bean
public TopicExchange topicExchange2() {
return new TopicExchange("topicExchange");
}

@Bean
public Binding bindingTopic2() {
return BindingBuilder.bind(topicQueue()).to(topicExchange()).with("topic.*.#");
}

}
  • 绑定键匹配规则
    • *(星号):用来表示一个单词,且该单词是必须出现的
    • #(井号):用来表示任意数量,例如(topic.order.id 可以通过 topic.#)
  • 生产与消费消息(topic)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Service
public class MessageServiceRabbitmqDirectImpl implements MessageService {

@Autowired
private AmqpTemplate amqpTemplate;

@Override
public void sendMessage(String id) {
System.out.println("待发送短信的订单已纳入处理队列(rabbitmq topic),id:" + id);
amqpTemplate.convertAndSend("topicExchange","topic.order.id",id);
}

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

RocketMQ

  • 下载地址:https://rocketmq.apache.org/
  • 安装:解压缩
    • 默认服务端口:9876
  • 环境变量配置
    • ROCKETMQ_HOME
    • PATH
    • NAME_ADDR(建议):127.0.0.1:9876
  • 命名服务器与 broker
    • 启动命名服务器:mqnamesrv
    • 启动 broker:mqbroker
  • 服务器功能测试:生产者
1
tools org.apache.rocketmq.example.quickstart.Producer
  • 服务器功能测试:消费者
1
tools org.apache.rocketmq.example.quickstart.Consumer

监控

监控的意义

  • 监控服务状态是否宕机
  • 监控服务运行指标(内存、虚拟机、线程、请求等)
  • 监控日志
  • 管理服务(服务下线)

监控的实施方式

  • 显示监控信息的服务器:用于获取服务信息,并显示对应的信息
  • 运行时的服务:启动时主动上报,告知监控服务器子级需要
  • 受到监控

可视化监控平台

  • Spring Boot Admin,开源社区项目,用于管理和监控 SpringBoot 应用程序。客户端注册到服务端后,通过 HTTP 请求方式,服务端定期从客户端获取对应的信息,并通过 UI 界面展示对应信息。

具体步骤

  • Admin 服务端
    • 坐标
1
2
3
4
5
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
<version>2.7.2</version>
</dependency>
    • 配置
1
2
server:
port: 8080
    • 启动类
1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
@EnableAdminServer
public class Springboot25AdminServerApplication {

public static void main(String[] args) {
SpringApplication.run(Springboot25AdminServerApplication.class, args);
}

}

  • Admin 客户端
1
2
3
4
5
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>2.7.2</version>
</dependency>
    • 配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
server:
port: 8081

spring:
boot:
admin:
client:
url: http://localhost:8080
management:
endpoint:
health:
show-details: always
endpoints:
web:
exposure:
include: "*"

监控原理

  • actuator 提供了 SpringBoot 生产就绪功能,通过端点的配置与访问,获取端点信息
  • 端点描述了一组监控信息,SpringBoot 提供了多个内置端点,也可以根据需要自定义端点信息
  • 访问当前应用所有端点信息:/actuator
  • 访问端点详情信息:/actuator/端点名称

  • 启用指定端点
1
2
3
4
5
6
7
management: 
endpoint:
health: # 端点名称
enabled: true
show-details: always
beans: # 端点名称
enabled: true
  • 启用所有端点
1
2
3
management: 
endpoints:
enabled-by-default: true
  • 暴露端点功能
    • 端点中包含的信息存在敏感信息,需要对外暴露端点功能时手动设定指定端点信息
属性 默认
management.endpoints.jmx.exposure.exclude
management.endpoints.jmx.exposure.include *
management.endpoints.web.exposure.exclude
management.endpoints.web.exposure.include info,health

info 端点指标控制

  • 为 info 端点添加自定义指标
1
2
3
info:
appName: @project.artifactId@
author: water_monster
  • 通过编程的形式添加自定义指标
1
2
3
4
5
6
7
8
9
10
@Component
public class InfoConfig implements InfoContributor {
@Override
public void contribute(Info.Builder builder) {
builder.withDetail("runTime",System.currentTimeMillis());
Map mapInfo = new HashMap();
mapInfo.put("buildTime","2022");
builder.withDetails(mapInfo);
}
}

info不显示的加上这一段,原因可能是 springboot 版本过高

1
2
3
4
management: 
info:
env:
enabled: true

health 端点指标控制

  • 为 Health 端点添加自定义指标
1
2
3
4
5
6
7
8
9
10
11
@Component
public class HealthConfig extends AbstractHealthIndicator {
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
builder.withDetail("runTime",System.currentTimeMillis());
Map mapInfo = new HashMap();
mapInfo.put("buildTime","2022");
builder.withDetails(mapInfo);
builder.up(); // builder.status(Status.UP)
}
}

Metrics 端点添加自定义指标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Service
public class BookServiceImpl extends ServiceImpl<BookDao, Book> implements IBookService {

@AutoWired
private BookDao bookDao;

private Counter counter;

public BookServiceImpl(MeterRegistry meterRegistry) {
counter = meterRegistry.counter("用户付费操作次数:");
}
@Override
public boolean delete(Integer id) {
counter.increment();
return bookDao.deleteById(id) > 0;
}
}

自定义端点

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
@Endpoint(id = "pay", enableByDefault = true)
public class PayEndpoint {

@ReadOperation
public Object getPay() {
Map payMap = new HashMap();
payMap.put("level1","300");
payMap.put("level2","400");
payMap.put("level3","500");
return payMap;
}
}

原理篇

自动装配

bean 的加载方式

  • 1.XML 方式声明 bean
  • 2.注解 + XML 方式声明 bean
    • <context:component-scan base-package=”com.itheima.bean” /> 扫描指定加载 bean 的包,类上记得加注解(@Component、@Service、@Controller、@Repository之类)
    • 加载第三方 bean
1
2
3
4
5
6
7
8
9
//@Component
@Configuration
public class DbConfig {
@Bean
public DruidDataSource dataSource() {
DruidDataSource ds = new DruidDataSource();
return ds;
}
}
  • 3.注解方式声明配置类
1
2
3
4
@ComponentScan("com.itheima")
public class SpringConfig {

}
  • 4.使用 @Import 注解导入要注入的 bean 对应的字节码
1
2
3
4
@Import(Dog.class)
public class SpringConfig{

}
  • 被导入的 bean 无需声明为 bean
    • 此形式可以有效的降低源代码与 Spring 技术的耦合度,在 spring 技术底层及诸多框架的整合中大量使用
1
public class Dog{}
  • 5.使用上下文对象在容器初始化完毕后注入 bean
1
2
3
4
5
6
7
8
9
10
public class AppImport {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
ctx.register(Cat.class);
String[] names = ctx.getBeanDefinitionNames();
for(String name : names) {
System.out.println(name);
}
}
}
  • 6.导入实现了 ImportSelector 接口的类,实现对导入源的编程式处理,再通过 @Import 导入该类
1
2
3
4
5
6
7
8
9
public class MyImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata metadata) {
boolean flag = metadata.hasAnnotation("org.springframework.context.annotation.Import");
if(flag) {
return new String[]{"com.itheima.domain.Dog"};
}
return new String[]{"com.itheima.domain.Cat"};
}
}
  • 7.导入实现了 ImportBeanDefinitionRegistrar 接口的类,通过 BeanDefinition 的注册器注册实名 bean,实现对容器中 bean 的裁定,例如对现有 bean 的覆盖,进而达成不修改源码的情况下实现更换实现的效果,同样通过 @Import 导入该类
1
2
3
4
5
6
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl.class).getBeanDefinition();
registry.registerBeanDefinition("bookService", beanDefinition);
}
}
  • 8.导入实现了 BeanDefinitionRegistryPostProcessor 接口的类,通过 BeanDefinition 的注册器注册实名 bean,实现对容器中 bean 的最终裁定,可以覆盖重复的 bean
1
2
3
4
5
6
public class MyPostProcessor implements BeanDefinitionRegistryPostProcessor {
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl.class).getBeanDefinition();
registry.registerBeanDefinition("bookService", beanDefinition);
}
}

  • 初始化实现 FactoryBean 接口的类,实现对 bean 加载到容器之前的批处理操作
1
2
3
4
5
6
7
8
9
10
11
public class BookFactoryBean implements FactoryBean<Book> {
public Book getObject() throws Exception {
Book book = new Book();
// 进行 book 对象相关的初始化工作
return book;
}

public Class<?> getObjectType() {
return Book.class;
}
}
1
2
3
4
5
6
public class SpringConfig {
@Bean
public BookFactoryBean book() {
return new BookFactoryBean();
}
}
  • 加载配置类并加载配置文件(系统迁移)
1
2
3
4
5
@Configuration
@ComponentScan("com.itheima")
@ImportResource("applicationContext-config.xml")
public class SpringConfig {
}
  • 使用 proxyBeanMethods=true 可以保障调用此方法得到的对象是从容器中获取的而不是重新创建的
小结
  • 1.xml + <bean />
  • 2.xml:context + 注解(@Component + 4 个 @Bean)
  • 3.配置类 + 扫描 + 注解(@Component + 4 个 @Bean)
    • @Bean 定义 FactoryBean 接口
    • @ImportResource
    • @Configuration 注解的 proxyBeanMethods 属性
  • 4.@Import 导入 bean 的类
    • @Import 导入配置类
  • 5.AnnotationConfigApplicationContext 调用 register 方法
  • 6.@Import 导入 ImportSelector 接口
  • 7.@Import 导入 ImportBeanDefinitionRegistara 接口
  • 8.@Import 导入 BeanDefinitionRegistryPostProcessor 接口

bean 的加载控制(编程式)

  • bean 的加载控制指根据特定情况对 bean 进行选择性加载以达到适用于项目的目标

  • 根据任意条件确认是否加载 bean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
try {
Class<?> clazz = Class.forName("com.itheima.bean.Mouse");
if(clazz != null) {
return new String[]{"com.itheima.bean.Cat"};
}
} catch (ClassNotFoundException e) {
return new String[0];
}
return null;
}
}

bean 的加载控制(注解式)

  • 匹配指定类(@ConditionalOnClass)
1
2
3
4
5
6
7
public class SpringConfig {
@Bean
@ConditionalOnClass(Mouse.class)
public Cat tom() {
return new Cat();
}
}
  • 未匹配指定类(@ConditionalOnMissingClass)
1
2
3
4
5
6
7
public class SpringConfig {
@Bean
@ConditionalOnMissingClass(Mouse.class)
public Cat tom() {
return new Cat();
}
}
  • 匹配指定名称的 bean (@ConditionalOnBean)
1
2
3
4
5
6
7
public class SpringConfig {
@Bean
@ConditionalOnBean(name = "com.itheima.bean.Mouse")
public Cat tom() {
return new Cat();
}
}
  • 匹配指定类型的 bean
1
2
3
4
5
6
7
public class SpringConfig {
@Bean
@ConditionalOnBean(Mouse.class)
public Cat tom() {
return new Cat();
}
}
  • 匹配指定环境
1
2
3
4
5
6
7
8
9
public class SpringConfig {
@Bean
@ConditionalOnClass(Mouse.class)
@ConditionalOnMissingClass("com.itheima.bean.wolf")
@ConditionalOnNotWebApplication // 是否为 web 程序
public Cat tom() {
return new Cat();
}
}

bean 依赖的属性配置

  • 将业务功能 bean 运行需要的资源抽取成独立的属性类(***Properties,设置读取配置文件信息)
1
2
3
4
5
6
@ConfigurationProperties(prefix = "cartoon")
@Data
public class CartoonProperties {
private Cat cat;
private Mouse mouse;
}
  • 配置文件中使用固定格式为属性类注入数据
1
2
3
4
5
6
7
cartoon: 
cat:
name: "图多盖洛"
age: 5
mouse:
name: "泰菲"
age: 1
  • 定义业务功能 bean,通常使用 @Import 导入,解耦强制加载 bean
1
2
3
4
5
6
7
8
@Component
public class CartoonCatAndMouse {
private Cat cat;
private Mouse mouse;
public void play() {
System.out.println(cat.getAge() + "岁的" + cat.getName() + "与" + mouse.getAge() + "岁的" + mouse.getName() + "打起来了");
}
}
  • 使用 @EnableConfigurationProperties 注解设定使用属性类时加载 bean
1
2
3
4
5
6
7
8
9
@Component
@EnableConfigurationProperties(CartoonProperties.class)
public class CartoonCatAndMouse {
private CartoonProperties cartoonProperties;
public CartoonCatAndMouse(CartoonProperties cartoonProperties) {
this.cartoonProperties = cartoonProperties;
cat = new cat();
}
}

自动装配原理

自动装配思想

1.收集 Spring 开发者的编程习惯,整理开发过程使用的常用技术列表(技术集 A

2.收集常用技术(技术集 A)的使用参数,整理开发过程中每个技术的常用设置列表(设置集 B

3.初始化 SpringBoot 基础环境,加载用户自定义的 bean 和导入其他的坐标,形成初始化环境

4.将技术集 A包含的技术都定义出来,在 Spring/SpringBoot 启动时默认全部加载

5.将技术集 A中具有使用条件的技术约束出来,设置成按条件加载,由开发者决定是否使用该技术(与初始化环境对比)

6.将设置集 B作为默认配置加载(约定大于配置),减少开发者配置工作量

7.开放设置集 B的配置覆盖接口,由开发者根据自身需要决定是否覆盖默认配置

变更自动配置

  • 自定义自动配置(META-INF/spring.factories)
1
2
3
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.itheima.bean.CartoonCatAndMouse
  • 控制 SpringBoot 内置自动配置类加载
1
2
3
4
5
spring: 
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
- org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguraion

自定义 starter

案例:记录系统访客独立 IP 访问次数

1.每次访问网站行为均进行统计

2.后台每 10 秒输出依次监控信息(格式:IP + 访问次数)

需求分析

  • 1.数据记录位置:Map/Redis

  • 2.功能触发位置:每次 web 请求(拦截器)

    • 步骤一:降低难度,主动调用,仅统计单一操作访问次数(例如查询)
    • 步骤二:开发拦截器
  • 3.业务参数(配置项)

    • 1.输出频率:默认 10 秒
    • 2.数据特征:累计数据/阶段数据,默认累计数据
    • 3.输出格式:详细模式/极简模式

具体实现

  • 导入坐标
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
  • 业务功能开发
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class IpCountService {

private Map<String, Integer> ipCountMap = new HashMap<String, Integer>();

@Autowired
// 当前的 request 对象的注入工作由使用当前 starter 的工程提供自动装配
private HttpServletRequest httpServletRequest;

public void count() {
// 每次调用当前操作,就记录当前访问的 IP,然后累加访问次数
// 1.获取当前操作的 IP 地址
String ip = httpServletRequest.getRemoteAddr();
System.out.println("------------------" + ip);
// 2.根据 IP 地址从 Map 取值,并递增
Integer count = ipCountMap.get(ip);
if(count == null) {
ipCountMap.put(ip, 1);
} else {
ipCountMap.put(ip, ipCountMap.get(ip) + 1);
}
}
}

  • 自动配置类
1
2
3
4
5
6
7
8
9
10
11
12
package cn.itcast.autoconfig;

import cn.itcast.service.IpCountService;
import org.springframework.context.annotation.Bean;

public class IpAutoConfiguration {

@Bean
public IpCountService ipCountService() {
return new IpCountService();
}
}
  • 配置,在 resource 文件夹下创建一个 META-INF 文件夹,在里面创建 spring.factories 文件,其他模块调用 stater 之后会自动加载这个文件
1
2
3
# Auto configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.itcast.autoconfig.IpAutoConfiguration
  • 模拟调用(非最终版本)
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
package com.itheima.controller;

import cn.itcast.service.IpCountService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.itheima.controller.utils.R;
import com.itheima.domain.Book;
import com.itheima.service.IBookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/books")
public class BookController {

@Autowired
private IBookService bookService;

@Autowired
private IpCountService ipCountService;


@GetMapping("{currentPage}/{pageSize}")
public R getPage(@PathVariable int currentPage,@PathVariable int pageSize, Book book) {

ipCountService.count();

IPage<Book> page = bookService.getPage(currentPage, pageSize, book);
// 如果当前页码值大于总页码值,那么重新执行查询操作,使用最大页码值作为当前页码值
if(currentPage > page.getPages()) {
page = bookService.getPage((int)page.getPages(), pageSize, book);
}

return new R(true, page);
}
}

开启定时任务
  • 开启定时任务功能
1
2
3
4
5
6
7
8
@EnableScheduling
public class IpAutoConfiguration {

@Bean
public IpCountService ipCountService() {
return new IpCountService();
}
}
  • 设置定时任务
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
package cn.itcast.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

public class IpCountService {

private Map<String, Integer> ipCountMap = new HashMap<String, Integer>();

@Autowired
// 当前的 request 对象的注入工作由使用当前 starter 的工程提供自动装配
private HttpServletRequest httpServletRequest;

public void count() {
// 每次调用当前操作,就记录当前访问的 IP,然后累加访问次数
// 1.获取当前操作的 IP 地址
String ip = httpServletRequest.getRemoteAddr();
System.out.println("------------------" + ip);
// 2.根据 IP 地址从 Map 取值,并递增
Integer count = ipCountMap.get(ip);
if(count == null) {
ipCountMap.put(ip, 1);
} else {
ipCountMap.put(ip, ipCountMap.get(ip) + 1);
}
}

@Scheduled(cron = "0/5 * * * * ?")
public void print() {
System.out.println(" IP 访问监控");
System.out.println("+-----ip-address-----+--num--+");
for (Map.Entry<String, Integer> entry : ipCountMap.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(String.format("|%18s |%5d |", key, value));
}

System.out.println("+--------------------+-------+");

}
}

设置自定义属性
  • 自定义属性类,加载对应属性
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
@ConfigurationProperties(prefix = "tools.ip")
public class IpProperties {
/*
* 日志显示周期
* */

private Long cycle = 5L;

/*
* 是否周期内重置数据
* */

private boolean cycleReset = false;

/*
* 日志输出模式 detail:详细模式 simple:极简模式
* */

private String model = LogModel.DETAIL.value;

public enum LogModel {
DETAIL("detail"),
SIMPLE("simple");
private String value;
LogModel(String value) {
this.value = value;
}

public String getValue() {
return value;
}
}

public Long getCycle() {
return cycle;
}

public void setCycle(Long cycle) {
this.cycle = cycle;
}

public boolean getCycleReset() {
return cycleReset;
}

public void setCycleReset(boolean cycleReset) {
this.cycleReset = cycleReset;
}

public String getModel() {
return model;
}

public void setModel(String model) {
this.model = model;
}
}
  • 设置加载 Properties 类为 bean
1
2
3
4
5
6
7
8
9
@EnableScheduling
@EnableConfigurationProperties(IpProperties.class)
public class IpAutoConfiguration {

@Bean
public IpCountService ipCountService() {
return new IpCountService();
}
}
  • 根据配置切换设置
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
package cn.itcast.service;

import cn.itcast.properties.IpProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

public class IpCountService {

private Map<String, Integer> ipCountMap = new HashMap<String, Integer>();

@Autowired
// 当前的 request 对象的注入工作由使用当前 starter 的工程提供自动装配
private HttpServletRequest httpServletRequest;

@Autowired
private IpProperties ipProperties;

@Scheduled(cron = "0/5 * * * * ?")
public void print() {

if(ipProperties.getModel().equals(IpProperties.LogModel.DETAIL.getValue())) {
// 明细模式
System.out.println(" IP 访问监控");
System.out.println("+-----ip-address-----+--num--+");
for (Map.Entry<String, Integer> entry : ipCountMap.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(String.format("|%18s |%5d |", key, value));
}
System.out.println("+--------------------+-------+");

} else if(ipProperties.getModel().equals(IpProperties.LogModel.SIMPLE.getValue())) {
// 极简模式
System.out.println(" IP 访问监控");
System.out.println("+-----ip-address-----+");
for (String key : ipCountMap.keySet()) {
System.out.println(String.format("|%18s |", key));
}
System.out.println("+--------------------+");

}


if(ipProperties.getCycleReset()) {
ipCountMap.clear();
}
}
}

  • 配置信息
1
2
3
4
5
tools:
ip:
cycle: 10
cycle-reset: false
model: "detail"
设置自定义属性(补充)
  • 自定义 bean 名称
1
2
3
4
@Component("ipProperties")
@ConfigurationProperties(prefix = "tools.ip")
public class IpProperties {
}
  • 放弃配置属性创建 bean 方式,改为手工控制
1
2
3
4
5
6
7
8
9
10
@EnableScheduling
//@EnableConfigurationProperties(IpProperties.class)
@Import(IpProperties.class)
public class IpAutoConfiguration {

@Bean
public IpCountService ipCountService() {
return new IpCountService();
}
}
  • 使用 #{beanName.attrName} 读取 bean 的属性
1
2
3
@Scheduled(cron = "0/#{ipProperties.cycle} * * * * ?")
public void print() {
}
自定义拦截器
  • 自定义拦截器
1
2
3
4
5
6
7
8
9
10
11
12
public class IpCountInterceptor implements HandlerInterceptor {

@Autowired
private IpCountService ipCountService;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
ipCountService.count();
return true;
}
}

  • 设置核心配置类,加载拦截器
1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class SpringMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(ipCountInterceptor()).addPathPatterns("/**");
}

@Bean
public IpCountInterceptor ipCountInterceptor() {
return new IpCountInterceptor();
}
}

辅助功能开发
  • 导入配置处理器坐标
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
  • 进行自定义提示功能开发(在 META-INF 中添加一个 spring-configuration-metadata.json 文件)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
// 提示功能
"hints": [
{
"name": "tools.ip.model",
"values": [
{
"value": "detail",
"description": "详细模式."
},
{
"value": "simple",
"description": "简洁模式."
}
]
}
]
}
小结
  • 1.使用自动配置加载业务功能
  • 2.切记使用之前先 clean 后 install 安装到 maven 仓库,确保资源更新

核心原理

SpringBoot 启动流程

  • 1.初始化各种属性,加载成对象
    • 读取环境属性(Environment)
    • 系统配置(spring.factories)
    • 参数(Arguments、application.properties)
  • 2.创建 Spring 容器对象 ApplicationContext,加载各种配置
  • 3.在容器创建前,通过监听器机制,应对不同阶段加载数据、更新数据的需求
  • 4.容器初始化过程中追加各种功能,例如统计时间、输出日志等

监听器类型

  • 1.在应用运行但未进行任何处理时,将发送 ApplicationStartingEvent
  • 2.当 Environment 被使用,且上下文创建之前,将发送 ApplicationEnvironmentPreparedEvent
  • 3.在开始刷新之前,bean 定义被加载之后发送 ApplicationPreparedEvent;
  • 4.在上下文刷新之后且所有的应用和命令运行器被调用之前发送 ApplicationStartedEvent
  • 5.在应用程序和命令行运行器被调用之后,将发出 ApplicationReadyEvent,用于通知应用已经准备处理请求
  • 6.启动时发生异常,将发送 ApplicationFailedEvent

文章作者: Water monster
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Water monster !
评论
  目录