基础篇
快速上手 SpringBoot
SpringBoot 入门程序开发
- SpringBoot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化 Spring 应用的初始搭建以及开发过程
- Spring 程序缺点
- 依赖设置繁琐
- 配置繁琐
- SpringBoot 程序优点
- 起步依赖(简化依赖配置)
- 自动配置(简化常用工程相关配置)
- 辅助功能(内置服务器,…)
- Spring 程序缺点
快速上手 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>
- ```java
最简 SpringBoot 程序所包含的基础文件
- pom.xml 文件
- Application 类
Spring 程序与 SpringBoot 程序对比
注意:基于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>
- ```java
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 简介
- REST(Representational State Transfer),表现形式状态转换
- 优点:
- 隐藏资源的访问行为,无法通过地址得知对资源是何种操作
- 书写简化
- 按照 REST 风格访问资源时使用行为动作区分对资源进行了何种操作
- http://localhost/users 查询全部用户信息 GET(查询)
- http://localhost/users/1 查询指定用户信息 GET(查询)
- http://localhost/users 添加用户信息 POST(新增/保存)
- http://localhost/users 修改用户信息 PUT(修改/更新)
- http://localhost/users/1 删除用户信息 DELETE(删除)
- 注意事项:上述行为是约定方式,约定不是规范,可以打破,所以称 REST 风格,而不是 REST 规范。描述模块的名称通常使用复数,也就是加 s 的格式描述,表示此类资源,而非单个资源,例如:users、books、accounts……
- 根据 REST 风格对资源进行访问称为 RESTful
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 |
|
RESTful 快速开发
1 |
|
@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=801
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
- ```properties
application.yml(主流格式)
- ```yml
server:
port: 811
2
3
4
5
6
* application.yaml
* ```yaml
server:
port:82
- ```yml
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
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
- ```java
属性值中如果出现转义字符,需要使用双引号包裹
```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/firecenter:
dataDir: ${baseDir}/data
tempDir: ${baseDir}/tmp
logDir: ${baseDir}/log1
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: root6661
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 {}
- ```java
相关属性:
- 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);
}
- ```yaml
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
- ```xml
整合任意第三方技术
- 导入对应的 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>
- ```java
配置数据源与 MyBatisPlus 对应的基础配置(id 生成策略使用数据库自增策略)
- ```yaml
server:
port: 80
spring:
datasource:
mybatis-plus:driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC username: root password: zt20020806
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> {
}
- ```yaml
制作测试类测试结果
- ```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
- ```java
数据层开发——分页功能
分页操作需要设定分页对象 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;
}
}
- ```java
数据层开发——条件查询功能
使用 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);
}
- ```java
查询条件支持动态条件拼装
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));
}
}
- ```java
小结:
- 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);
})
}
- ```js
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("取消操作");
})
},
- ```js
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();
})
},
- ```js
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"
}
- ```json
对异常进行统一处理,出现异常后,返回指定信息
- ```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>
- ```java
定义分页组件需要使用的数据并将数据绑定到分页组件
- ```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
页码值切换
- ```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);
}
- ```js
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
- ```java
2.运行项目(执行启动指令)
- ```cmd
java -jar springboot.jar1
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>
- ```cmd
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.21
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
- ```mf
命令行启动常见问题及解决方案
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
- ```cmd
携带多个属性启动 SpringBoot,属性间使用空格分隔
属性加载优先顺序
临时属性设置(开发环境)
通过编程形式带参数启动 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);
}
}
- ```java
配置文件分类
- 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 级常用于项目经理进行整体项目属性调控
自定义配置文件
- 通过启动参数加载指定文件路径下的配置文件时可以加载多个配置
注意事项:多配置文件常用于将配置进行分类,进行独立管理,或将可选配置单独制作便于上线更新维护
自定义配置文件——重要说明
- 单服务器项目:使用自定义配置文件需求较低
- 多服务器项目:使用自定义配置文件需求较高,将所有配置放置在一个目录中,统一管理
- 基于 SpringCloud 技术,所有服务器将不再设置配置文件,而是通过配置中心进行设定,动态加载配置信息
总结
- 1.SpringBoot 在开发和运行环境均支持使用临时参数修改工程配置
- 2.SpringBoot 支持 4 级配置文件,应用于开发与线上环境进行配置的灵活设置
- 3.SpringBoot 支持使用自定义配置文件的形式修改配置文件存储位置
- 4.基于微服务开发时配置文件将使用配置中心进行管理
多环境开发(YAML 版)
1 | # 应用环境 |
过时格式
1 | # 应用环境 |
推荐格式
1 | # 应用环境 |
多环境开发(YAML版)多配置文件格式
1.主启动配置文件 application.yml
3.环境分类配置文件 application-dev.yml
- ```yaml
server:
port: 80811
2
3
4
5
6
* 4.环境分类配置文件 application-test.yml
* ```yaml
server:
port: 8082
- ```yaml
多环境开发配置文件书写技巧(一)
- 主配置文件中设置公共配置(全局)
- 环境分类配置文件中常用于设置冲突属性(局部)
多环境开发(Properties版)多配置文件格式
1.主启动配置文件 application.properties
- ```properties
spring.profiles.active=dev1
2
3
4
5
* 2.环境分类配置文件 application-pro.properties
* ```properties
server.port=9081
- ```properties
3.环境分类配置文件 application-dev.properties
- ```properties
server.port=90821
2
3
4
5
* 4.环境分类配置文件 application-test.properties
* ```properties
server.port=9080
- ```properties
多环境开发独立配置文件书写技巧(二)
根据功能对配置文件中的信息进行拆分,并制作成独立的配置文件,命名规则如下:
- 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
- ```yml
多环境开发控制
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@
- ```xml
3.执行 Maven 打包指令,并在生成的 boot 打包文件 .jar 文件中查看对应信息
日志
日志基础
日志基础操作
- 日志(log)作用
- 编译期调试代码
- 运营期记录信息
- 记录日常运营重要信息(峰值流量、平均响应时长。。。)
- 记录应用报错信息(错误堆栈)
- 记录错误运维过程数据(扩容、宕机、报警。。。)
代码中使用日志工具记录日志
1 | // Rest 模式 |
日志级别
- TRACE:运行堆栈信息,使用率低
- DEBUG:程序员调试代码使用
- INFO:记录运维过程数据
- WARN:记录运维过程报警数据
- ERROR:记录错误堆栈信息
- FATAL:灾难信息,合并计入 ERROR
设置日志输出级别
1 | # application.yml |
设置日志组
设置日志组,控制包对应的日志输出级别也可以直接控制指定包对应的日志输出级别
1 | # application.yml |
优化日志对象创建代码
使用 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
### 日志输出格式控制

* 时间
* 级别
* PID:进程 ID,用于表明当前操作所处的进程,当多服务同时记录日志时,该值可用于协助程序员调试程序
* 所属线程
* 所属类/接口名:当前显示信息为 SpringBoot 重写后的信息,名称过长时,简化包名书写为首字母,甚至直接删除
#### 设置日志输出格式
```yml
logging:
pattern:
console: "%d - %m%n"
- ```java
%d:日期
%m:消息
%n:换行
1 | logging: |
日志文件
设置日志文件
- ```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
- ```yaml
开发实用篇
热部署
启动热部署
- 关于热部署
- 重启(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
#### 自动启动热部署
* 设置自动构建项目
* 
* 在 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/**
- ```xml
关闭热部署
设置高优先级属性禁用热部署
```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.Driver1
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 {
}
- ```java
注意事项:@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;
}
- ```java
数据校验
- 开启数据校验有助于系统安全性,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;
}
- ```xml
进制转换规则
数字记得加字符,否则可能会被误认为八进制或其他进制数
测试
加载测试专用属性
在启动测试环境时可以通过 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);
}
}
- ```java
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
虚拟请求状态匹配
- ```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);
// 定义本次调用的预期值
// 预计本次调用时成功的:状态 200StatusResultMatchers status = MockMvcResultMatchers.status();
// 添加预计值到本次调用过程中进行匹配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);
}
}
- ```java
虚拟请求体(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);
}
}
- ```java
注意:一般都把多个匹配测试写在一起,一个规则一个对象,精度更高
数据层测试事务回滚
为测试用例添加事务,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"));
}
}
- ```java
测试用例数据设定
测试用例数据通常采用随机值进行测试,使用 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
格式二
- ```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
通用配置无法设置具体的数据源配置信息,仅提供基本的连接相关配置,如需配置,在下一级配置中设置具体设定
```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:
datasource:console: path: /h2 enabled: true
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
- ```yaml
现有数据层解决方案技术选型
- 数据源: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.conf1
2
3
4
5
* 客户端启动命令
* ```cmd
redis-cli.exe
- ```cmd
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"));
}
}
- ```java
客户端: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
- ```java
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\db1
2
3
4
5
* 客户端启动
* ```cmd
mongo --host=127.0.0.1 --port=27017
- ```cmd
Windows 版 Mongo 安装问题及解决方案
”找不到 VCRUNTIME140_1.dll,无法继续执行代码,重新安装程序可能会解决此问题“
步骤一:下载对应的 dll 文件(通过浏览器搜索即可)
步骤二:拷贝到 windows 安装路径下的 system32 目录中
步骤三:执行命令注册对应 dll 文件
- ```cmd
regsvr32 vcruntime140_1.dll1
2
3
4
5
6
7
8
9
* 可视化客户端——Robo 3T、Studio 3T
##### 常用操作
* 新增
* ```mongo
db.集合名称.insert/save/insertOne(文档)
- ```cmd
修改
- ```mongo
db.集合名称.update(条件, {操作种类: {文档})1
2
3
4
5
* 删除
* ```mongo
db.集合名称.remove(条件)
- ```mongo
查询
- 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
- 1.基础查询
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
- ```xml
客户端读写 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"
}
}
}
}
- ```java
创建文档
- POST http://localhost:9200/books/_doc # 使用系统生成 id
- POST http://localhost:9200/books/_create/1 # 使用指定 id
- POST http://localhost:9200/books/_doc/1 # 使用指定 id,不存在创建,存在更新(版本递增)
查询文档
- GET http://localhost:9200/books/_doc/1 # 查询单个文档
- GET http://localhost:9200/books/_search # 查询全部文档
条件查询
删除文档
修改文档(全量修改)
修改文档(部分修改)
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
- ```xml
客户端
- ```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
配置(无)
客户端
- ```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
创建索引
- ```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
批量添加文档
- ```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
按条件查询文档
- ```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>
- ```java
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);
}
- ```java
- 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
- ```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>
- ```xml
<ehcache xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance"
设置 Redis 相关配置
- ```yaml
spring:
cache:
redis:type: redis redis: use-key-prefix: true # 是否使用前缀名(系统定义前缀名) cache-null-values: false # 追加自定义前缀名 key-prefix: aa # 有效时长 time-to-live: 10 # 是否允许存储空值
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>
- ```yaml
配置 memcached 服务器必要属性
- ```yaml
memcached:memcached 服务器地址
servers: localhost:11211连接池的数量
poolSize: 10设置默认操作超时时间
opTimeout: 30001
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;
}
- ```yaml
创建客户端配置类
- ```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);
}
}
- ```java
缓存供应商变更:jetcache
- jetcache 对 SpringCache 进行了封装,在原有功能基础上实现了多级缓存、缓存统计、自动刷新、异步调用、数据报表等功能
- jetcache 设定了本地缓存与远程缓存的多级缓存解决方案
- 本地缓存(local)
- LinkedHashMap
- Caffine
- 远程缓存(remote)
- Redis
- Tair
- 本地缓存(local)
具体步骤
加入 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
- ```xml
配置属性说明
开启 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
操作缓存
- ```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
使用方法注解操作缓存
```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
- ```java
配置一级缓存与二级缓存以及一级缓存数据到二级缓存的发送方式(j2cache.properties)
- ```properties
1 级缓存
j2cache.L1.provider_class = ehcache
ehcache.configXml = ehcache.xml设置是否启用二级缓存
j2cache.l2-cache-open = false2 级缓存
j2cache.L2.provider_class = net.oschina.j2cache.cache.support.redis.SpringRedisProvider
j2cache.L2.config_section = redis
redis.host = localhost:63791 级缓存中的数据如何到达二级缓存
可以使用 redis 提供的消息订阅模式,也可以使用 jgroups 多播实现
j2cache.broadcast = net.oschina.j2cache.cache.support.redis.SpringRedisPubSubPolicyredis.mode = single1
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);
}
}
- ```properties
数据淘汰策略
影响数据淘汰的相关配置
- 检测易失数据(可能会过期的数据集 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 | <dependency> |
- 定义具体要执行的任务,继承 QuartzJobBean
1 | public class MyQuartz extends QuartzJobBean { |
- 定义工作明细与触发器,并绑定对应关系
1 |
|
Spring Task
- 开启定时器
1 |
|
- 设置定时执行的任务,并设定执行周期
1 |
|
- 定时任务相关配置
1 | spring: |
邮件
- SMTP(Simple Mail Transfer Protocol):简单邮件传输协议,用于发送电子邮件的传输协议
- POP3(Post Office Protocol - Version 3):用于接收电子邮件的标准协议
- IMAP(Internet Mail Access Protocol):互联网消息协议,是 POP3 的替代协议
Spring Boot 整合 JavaMail
- 配置 JavaMail
1 | spring: |
- 对应业务层
1 |
|
- 发送附件
1 | package com.itheima.service.impl; |
消息
- 消息发送方
- 生产者
- 消息接收方
- 消费者
- 同步消息
- 异步消息
- 企业级应用中广泛使用的三种异步消息传递技术
- 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
- 下载地址:https://activemq.apache.org/components/classic/download/
- 启动服务:activemq.bat
- 访问服务器:http://127.0.0.1:8161
- 服务端口:61616,管理后台端口:8161
- 用户名&密码:admin
Spring Boot 整合 ActiveMQ
- 导入 SpringBoot 整合 ActiveMQ 坐标
1 | <dependency> |
- 配置 ActiveMQ(采用默认配置)
1 | server: |
- 生产与消费消息(使用默认消息存储队列)
1 |
|
- 使用消息监听器对消息队列监听
1 |
|
- 流程性业务消息消费完转入下一个消息队列
1 |
|
RabbitMQ
- RabbitMQ 基于 Erlang 语言编写,需要安装 Erlang
- Erlang
- 下载地址:https://www.erlang.org/downloads
- 安装:一键傻瓜式安装,安装完毕需重启,需要依赖 Windows 组件
- 环境变量配置
- ERLANG_HOME
- PATH
RabbitMQ 下载
SpringBoot 整合 RabbitMQ
- 导坐标
1 | <!-- rabbitmq--> |
- 配置 application.yml
1 | server: |
- 定义消息队列(direct)
1 |
|
- 生产消息与消费消息(direct)
1 |
|
- 使用消息监听器对消息队列监听(direct)
1 |
|
- 使用多消息监听器对消息队列监听进行消息轮循处理(direct)
1 |
|
- 定义消息队列(topic)
1 | package com.itheima.service.impl.rabbitmq.topic.config; |
- 绑定键匹配规则
- *(星号):用来表示一个单词,且该单词是必须出现的
- #(井号):用来表示任意数量,例如(topic.order.id 可以通过 topic.#)
- 生产与消费消息(topic)
1 |
|
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 | <dependency> |
- 配置
1 | server: |
- 启动类
1 |
|
- Admin 客户端
1 | <dependency> |
- 配置
1 | server: |
监控原理
- actuator 提供了 SpringBoot 生产就绪功能,通过端点的配置与访问,获取端点信息
- 端点描述了一组监控信息,SpringBoot 提供了多个内置端点,也可以根据需要自定义端点信息
- 访问当前应用所有端点信息:/actuator
- 访问端点详情信息:/actuator/端点名称
- 启用指定端点
1 | management: |
- 启用所有端点
1 | management: |
- 暴露端点功能
- 端点中包含的信息存在敏感信息,需要对外暴露端点功能时手动设定指定端点信息
属性 | 默认 |
---|---|
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 | info: |
- 通过编程的形式添加自定义指标
1 |
|
info不显示的加上这一段,原因可能是 springboot 版本过高
1 | management: |
health 端点指标控制
- 为 Health 端点添加自定义指标
1 |
|
Metrics 端点添加自定义指标
1 |
|
自定义端点
1 |
|
原理篇
自动装配
bean 的加载方式
- 1.XML 方式声明 bean
- 2.注解 + XML 方式声明 bean
- <context:component-scan base-package=”com.itheima.bean” /> 扫描指定加载 bean 的包,类上记得加注解(@Component、@Service、@Controller、@Repository之类)
- 加载第三方 bean
1 | //@Component |
- 3.注解方式声明配置类
1 |
|
- 4.使用 @Import 注解导入要注入的 bean 对应的字节码
1 |
|
- 被导入的 bean 无需声明为 bean
- 此形式可以有效的降低源代码与 Spring 技术的耦合度,在 spring 技术底层及诸多框架的整合中大量使用
1 | public class Dog{} |
- 5.使用上下文对象在容器初始化完毕后注入 bean
1 | public class AppImport { |
- 6.导入实现了 ImportSelector 接口的类,实现对导入源的编程式处理,再通过 @Import 导入该类
1 | public class MyImportSelector implements ImportSelector { |
- 7.导入实现了 ImportBeanDefinitionRegistrar 接口的类,通过 BeanDefinition 的注册器注册实名 bean,实现对容器中 bean 的裁定,例如对现有 bean 的覆盖,进而达成不修改源码的情况下实现更换实现的效果,同样通过 @Import 导入该类
1 | public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { |
- 8.导入实现了 BeanDefinitionRegistryPostProcessor 接口的类,通过 BeanDefinition 的注册器注册实名 bean,实现对容器中 bean 的最终裁定,可以覆盖重复的 bean
1 | public class MyPostProcessor implements BeanDefinitionRegistryPostProcessor { |
- 初始化实现 FactoryBean 接口的类,实现对 bean 加载到容器之前的批处理操作
1 | public class BookFactoryBean implements FactoryBean<Book> { |
1 | public class SpringConfig { |
- 加载配置类并加载配置文件(系统迁移)
1 |
|
- 使用 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 | public class MyImportSelector implements ImportSelector { |
bean 的加载控制(注解式)
- 匹配指定类(@ConditionalOnClass)
1 | public class SpringConfig { |
- 未匹配指定类(@ConditionalOnMissingClass)
1 | public class SpringConfig { |
- 匹配指定名称的 bean (@ConditionalOnBean)
1 | public class SpringConfig { |
- 匹配指定类型的 bean
1 | public class SpringConfig { |
- 匹配指定环境
1 | public class SpringConfig { |
bean 依赖的属性配置
- 将业务功能 bean 运行需要的资源抽取成独立的属性类(***Properties,设置读取配置文件信息)
1 |
|
- 配置文件中使用固定格式为属性类注入数据
1 | cartoon: |
- 定义业务功能 bean,通常使用 @Import 导入,解耦强制加载 bean
1 |
|
- 使用 @EnableConfigurationProperties 注解设定使用属性类时加载 bean
1 |
|
自动装配原理
自动装配思想
1.收集 Spring 开发者的编程习惯,整理开发过程使用的常用技术列表(技术集 A)
2.收集常用技术(技术集 A)的使用参数,整理开发过程中每个技术的常用设置列表(设置集 B)
3.初始化 SpringBoot 基础环境,加载用户自定义的 bean 和导入其他的坐标,形成初始化环境
4.将技术集 A包含的技术都定义出来,在 Spring/SpringBoot 启动时默认全部加载
5.将技术集 A中具有使用条件的技术约束出来,设置成按条件加载,由开发者决定是否使用该技术(与初始化环境对比)
6.将设置集 B作为默认配置加载(约定大于配置),减少开发者配置工作量
7.开放设置集 B的配置覆盖接口,由开发者根据自身需要决定是否覆盖默认配置
变更自动配置
- 自定义自动配置(META-INF/spring.factories)
1 | # Auto Configure |
- 控制 SpringBoot 内置自动配置类加载
1 | spring: |
自定义 starter
案例:记录系统访客独立 IP 访问次数
1.每次访问网站行为均进行统计
2.后台每 10 秒输出依次监控信息(格式:IP + 访问次数)
需求分析
1.数据记录位置:Map/Redis
2.功能触发位置:每次 web 请求(拦截器)
- 步骤一:降低难度,主动调用,仅统计单一操作访问次数(例如查询)
- 步骤二:开发拦截器
3.业务参数(配置项)
- 1.输出频率:默认 10 秒
- 2.数据特征:累计数据/阶段数据,默认累计数据
- 3.输出格式:详细模式/极简模式
具体实现
- 导入坐标
1 | <dependency> |
- 业务功能开发
1 | public class IpCountService { |
- 自动配置类
1 | package cn.itcast.autoconfig; |
- 配置,在 resource 文件夹下创建一个 META-INF 文件夹,在里面创建 spring.factories 文件,其他模块调用 stater 之后会自动加载这个文件
1 | # Auto configure |
- 模拟调用(非最终版本)
1 | package com.itheima.controller; |
开启定时任务
- 开启定时任务功能
1 |
|
- 设置定时任务
1 | package cn.itcast.service; |
设置自定义属性
- 自定义属性类,加载对应属性
1 |
|
- 设置加载 Properties 类为 bean
1 |
|
- 根据配置切换设置
1 | package cn.itcast.service; |
- 配置信息
1 | tools: |
设置自定义属性(补充)
- 自定义 bean 名称
1 |
|
- 放弃配置属性创建 bean 方式,改为手工控制
1 |
|
- 使用 #{beanName.attrName} 读取 bean 的属性
1 |
|
自定义拦截器
- 自定义拦截器
1 | public class IpCountInterceptor implements HandlerInterceptor { |
- 设置核心配置类,加载拦截器
1 |
|
辅助功能开发
- 导入配置处理器坐标
1 | <dependency> |
- 进行自定义提示功能开发(在 META-INF 中添加一个 spring-configuration-metadata.json 文件)
1 | { |
小结
- 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