SpringBoot

SpringBoot项目快速创建

IDEA联网版

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

image-20220815204303365

2.选择当前模块需要使用的技术集

image-20220815204334478

3.开发控制器类

@RestController
@RequestMapping("/books")
public class BookController {
@GetMapping("/{id}")
public String getById(@PathVariable Integer id){
System.out.println("id ==> "+id);
return "hello , spring boot!";
}
}

4.运行自动生成的Application类

image-20220815204420157

官网创建版

基于idea开发SpringBoot程序需要确保联网

也可官网创建:

https://spring.io/projects/spring-boot

进入到 SpringBoot 官网后拖到最下方就可以看到如下内容

image-20220815205521318

然后点击 Spring Initializr 超链接就会跳转到如下页面

image-20210911174110687

选择 Spring Web 可以点击上图右上角的 ADD DEPENDENCIES... CTRL + B 按钮,就会出现如下界面

image-20210911174650679

阿里云版

image-20211122163605950

image-20211122163937408

手工版

前提是jar之前已下过

image-20211122165341684

参照标准SpringBoot工程的pom文件,书写自己的pom文件即可

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.4</version>
</parent>

<groupId>com.itheima</groupId>
<artifactId>springboot_01_04_quickstart</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>

创建启动类

@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(<Application.class);
}
}

总结

  1. 创建普通Maven工程
  2. 继承spring-boot-starter-parent
  3. 添加依赖spring-boot-starter-web
  4. 制作引导类Application

最简单SpringBoot程序所包含的基础文件

pom.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.5.0</version>
</parent>
<groupId>com.itheima</groupId>
<artifactId>springboot_01_quickstart</artifactId>
<version>0.0.1-SNAPSHOT</version>

<!--JDK 的版本-->
<properties>
<java.version>8</java.version>
</properties>

<dependencies>
<!--该依赖就是我们在创建 SpringBoot 工程勾选的那个 Spring Web 产生的-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--这个是单元测试的依赖,我们现在没有进行单元测试,所以这个依赖现在可以没有-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<!--这个插件是在打包时需要的,而这里暂时还没有用到-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

Application类

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

Spring程序与SpringBoot程序对比

image-20220815204945231

入门案例解析

image-20220823230203695

  • starter

    • SpringBoot中常见项目名称,定义了当前项目使用的所有依赖坐标,以达到减少依赖配置的目的
  • parent

    • 所有SpringBoot项目要继承的项目,定义了若干个坐标版本号(依赖管理,而非依赖),以达到减少依赖冲突的目的

    • spring-boot-starter-parent各版本间存在着诸多坐标版本不同

  • 实际开发

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

复制工程(不建议使用此方式)

原则

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

properties开发

# 关闭运行日志图标(banner)
spring.main.banner-mode=off
# 设置日志相关
logging.level.root=debug

可选内置属性查询

Properties文件多环境启动

主启动配置文件application.properties:

spring.profiles.active=pro

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

server.port=80

环境分类配置文件application-dev.properties:

server.port=81

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

server.port=82

yaml开发

自动提示功能消失解决方案

image-20220815214245487

加载优先级

  • application.properties > application.yml > application.yaml
  • 不同配置文件中相同配置按照加载优先级相互覆盖,不同配置文件中不同配置全部保留

yaml的格式

  • YAML(YAML Ain’t Markup Language),一种数据序列化格式。

  • 重数据,轻格式。

  • 扩展名.yml(主流) .yaml

语法规则

  • 大小写敏感

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

  • 使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(不允许使用Tab键)
    空格的个数并不重要,只要保证同层级的左侧对齐即可。

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

  • # 表示注释

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

字面值表示方式

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连接,最后使用+代表时区

数组表示方式

subject:
- Java
- 前端
- 大数据
enterprise:
name: itcast
age: 16
subject:
- Java
- 前端
- 大数据
likes: [王者荣耀,刺激战场] #数组书写缩略格式
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数据读取方式(3种)

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

baseDir: /usr/local/fire
center:
dataDir: ${baseDir}/data
tmpDir: ${baseDir}/tmp
logDir: ${baseDir}/log
msgDir: ${baseDir}/msgDir

属性值中如果出现转义字符,需要使用双引号包裹

lesson: "Spring\tboot\nlesson"

1.使用 @Value("表达式") 注解可以从配合文件中读取数据,注解中用于读取属性名引用方式是:${一级属性名.二级属性名……}

image-20220815220234055

2.封装全部数据到Environment对象

image-20220816085514755

3.自定义对象封装指定数据

image-20220816085638687

在实体类上有如下警告提示

image-20220816085907896

警告解决方案:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

yaml文件实现多环境启动

单文件

#设置启用的环境
spring:
profiles:
active: dev

# 上面的配置项已经过时。最新的配置项是:
spring:
config:
activate:
on-profile: pro

---
#开发
spring:
profiles: dev
server:
port: 80
---
#生产
spring:
profiles: pro
server:
port: 81
---
#测试
spring:
profiles: test
server:
port: 82
---

多文件

主启动配置文件application.yml

spring:
profiles:
active: pro

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

server:
port: 80

环境分类配置文件application-dev.yml

server:
port: 81

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

server:
port: 82

多文件方式2

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

  • application-devDB.yml
  • application-devRedis.yml
  • application-devMVC.yml

使用incloud属性在激活指定环境的情况下,同时对多个环境进行加载使其生效,多个环境间使用逗号分隔

spring:
profiles:
active: dev
include: devDB,devRedis,devMVC

当主环境dev与其他环境有相同属性时,主环境属性生效;其他环境中有相同属性时,最后加载的环境属性生效。

从Spring2.4开始使用group属性替代include属性

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

spring:
profiles:
active: dev
group:
"dev": devDB,devRedis,devMVC
"pro": proDB,proRedis,proMVC
"test": testDB,testRedis,testMVC

多环境开发使用group属性设置配置文件分组,便于线上维护管理

属性加载优先顺序

从上到下,由低到高

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

  1. Default properties (specified by setting SpringApplication.setDefaultProperties).
  2. @PropertySource annotations on your @Configuration classes. Please note that such property sources are not added to the Environment until the application context is being refreshed. This is too late to configure certain properties such as logging.* and spring.main.* which are read before refresh begins.
  3. Config data (such as application.properties files).
  4. A RandomValuePropertySource that has properties only in random.*.
  5. OS environment variables.
  6. Java System properties (System.getProperties()).
  7. JNDI attributes from java:comp/env.
  8. ServletContext init parameters.
  9. ServletConfig init parameters.
  10. Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable or system property).
  11. Command line arguments.
  12. properties attribute on your tests. Available on @SpringBootTest and the test annotations for testing a particular slice of your application.
  13. @TestPropertySource annotations on your tests.
  14. Devtools global settings properties in the $HOME/.config/spring-boot directory when devtools is active.

Maven与SpringBoot多环境兼容

  • 当Maven与SpringBoot同时对多环境进行控制时,以Mavn为主, SpringBoot使用@..@占位符读取Maven对应的配置属性值

  • 基于SpringBoot读取Maven配置属性的前提下,如果在Idea下测试 工程时pom.xml每次更新需要手动compile方可生效(这里的不生效是由缓存引起)

①:Maven中设置多环境属性

<profiles>
<profile>
<id>dev_env</id>
<properties>
<profile.active>dev</profile.active>
</properties>
// 激活
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>pro_env</id>
<properties>
<profile.active>pro</profile.active>
</properties>
</profile>
<profile>
<id>test_env</id>
<properties>
<profile.active>test</profile.active>
</properties>
</profile>
</profiles>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!--对资源文件开启对默认占位符的解析-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<encoding>UTF-8</encoding>
<useDefaultDelimiters>true</useDefaultDelimiters>
</configuration>
</plugin>
</plugins>
</build>

②:SpringBoot中引用Maven属性

spring:
profiles:
active: ${profile.active}
---
spring:
profiles: dev
server:
port: 80
---
spring:
profiles: pro
server:
port: 81
---
spring:
profiles: test
server:
port: 82

或者用@:

spring:
profiles:
active: @profile.active@

③:执行Maven打包指令,并在生成的boot打包文件.jar文件中查看对应信息

SpringBoot中4级配置文件

1级: file :config/application.yml 【最高】—– config目录中配置文件:服务于运维经理整体调控
2级: file :application.yml ——————- 工程路径配置文件:服务于运维人员配置涉密线上环境
3级:classpath:config/application.yml ———– 项目类路径config目录中配置文件:服务于项目经理整体调控
4级:classpath:application.yml 【最低】———— 项目类路径配置文件:服务于开发人员本机开发与测试

作用

1级与2级留做系统打包后设置通用属性,1级常用于运维经理进行线上整体项目部署方案调控

3级与4级用于系统开发阶段设置通用属性,3级常用于项目经理进行整体项目属性调控

多层级配置文件间的属性采用叠加并覆盖的形式作用于程序

示例:

image-20220816205137808

image-20220816205157749

日志

日志级别

  • TRACE:运行堆栈信息,使用率低

  • DEBUG:程序员调试代码使用

  • INFO:记录运维过程数据

  • WARN:记录运维过程报警数据

  • ERROR:记录错误堆栈信息

  • FATAL:灾难信息,合并计入ERROR

设置日志输出级别

# 开启debug模式,输出调试信息,常用于检查系统运行状况 
debug: true
# 设置日志级别,root表示根节点,即整体应用日志级别
logging:
level:
root: debug

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

logging:
# 设置日志组
group:
# 自定义组名,设置当前组中所包含的包
ebank: com.itheima.controller
level:
root: warn
# 为对应组设置日志级别
ebank: debug
# 为对包设置日志级别
com.itheima.controller: debug

基于lombok提供的@Slf4j注解为类快速添加日志对象

日志输出格式控制

image-20220825223808285

设置日志输出格式

logging:
pattern:
# %d: 日期 %m: 消息 %n: 换行
# console: "%d - %m%n"
console: "%d %clr(%p) --- [%16t] %clr(%-40.40c){cyan} : %m %n"

设置日志文件

logging:
file:
name: server.log
# 日志文件详细配置
logback:
rollingpolicy:
max-file-size: 3KB
file-name-pattern: server.%d{yyyy-MM-dd}.%i.log

SpringBoot整合第三方技术

整合Junit

可对比:Spring整合Junit

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
class Springboot07TestApplicationTests {

@Autowired
private BookService bookService;

@Test
public void save() {
bookService.save();
}
}

这里的引导类所在包必须是测试类所在包及其子包。

例如:

  • 引导类所在包是 com.itheima
  • 测试类所在包是 com.itheima

如果不满足这个要求的话,就需要在使用 @SpringBootTest 注解时,使用 classes 属性指定引导类的字节码对象。如 @SpringBootTest(classes = Springboot07TestApplication.class)

@RunWith就是一个运行器

@RunWith(JUnit4.class)就是指用JUnit4来运行

–@RunWith(SpringJUnit4ClassRunner.class),让测试运行于Spring测试环 境,以便在测试开始的时候自动创建Spring的应用上下文

@RunWith(Suite.class)的话就是一套测试集合

标准测试类里是要有@RunWith的,作用是告诉java你这个类通过用什么运行环境运行,例如启动和创建spring的应用上下文。否则你需要为此在启动时写一堆的环境配置代码

你在IDEA里去掉@RunWith仍然能跑是因为在IDEA里识别为一个JUNIT的运行环境,相当于就是一个自识别的RUNWITH环境配置。但在其他IDE里并没有。

所以,为了你的代码能在其他IDE里边正常跑,建议还是加@RunWith

整合MyBatis

可对比:Spring整合MyBatis

基于SpringBoot实现SSM

  • SpringBoot整合Spring(不存在)
  • SpringBoot整合SpringMVC(不存在)
  • SpringBoot整合MyBatis(主要)
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--1.导入mybatis与spring整合的jar包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<!--导入spring操作数据库必选的包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
</dependencies>

设置数据源参数

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

SpringBoot 版本低于2.4.3(不含),Mysql驱动版本大于8.0时,需要在url连接串中配置时区 jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC,或在MySQL数据库端配置时区解决此问题

定义数据层接口与映射配置 @Mapper

@Mapper
public interface BookDao {
@Select("select * from tbl_book where id = #{id}")
public Book getById(Integer id);
}

整合MyBatis-plus

具体参考:MyBatisPlus

<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
starter所属 命名规则 示例
官方提供 spring-boot-starter-技术名称 spring-boot-starter-web
spring-boot-starter-test
第三方提供 第三方技术名称-spring-boot-starter druid-spring-boot-starter
第三方提供 第三方技术名称-boot-starter(第三方技术名称过长,简化命名) mybatis-plus-boot-starter
mybatis-plus:
global-config:
db-config:
table-prefix: tbl_ #设置所有表的通用前缀名称为tbl_
#2.配置相关信息
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=Asia/Shanghai
username: root
password: root
@Mapper
public interface BookDao extends BaseMapper<Book> {
}

整合Duird

<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
</dependencies>

修改配置,在数据源配置中有一个type属性,专用于指定数据源类型

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

第二种整合方式(推荐):

<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>
</dependencies>
spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
username: root
password: root

整合jetty

Spring容器移除tomcat,并更换为jetty

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

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

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

使用Maven依赖管理变更起步依赖项

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!--排除tomcat依赖-->
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>

<!--添加jetty依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

Jetty比Tomcat更轻量级、扩展性更强,谷歌搜索引擎(GAE)已全面切换为Jetty

整合Eureka

服务端配置

<!--eureka服务端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
server:
port: 10086 # 服务端口
spring:
application:
name: eurekaserver # eureka的服务名称
eureka:
client:
service-url: # eureka的地址信息
defaultZone: http://127.0.0.1:10086/eureka

客户端配置

<!--eureka客户端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
spring:
application:
name: userservice
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka

基于SpringBoot的SSM整合案例

具体参考:

  1. 实体类开发————使用Lombok快速制作实体类

  2. Dao开发————整合MyBatisPlus,制作数据层测试

  3. Service开发————基于MyBatisPlus进行增量开发,制作业务层测试类

  4. Controller开发————基于Restful开发,使用PostMan测试接口功能

  5. Controller开发————前后端开发协议制作

  6. 页面开发————基于VUE+ElementUI制作,前后端联调,页面数据处理,页面消息处理

    列表/分页/CRU

  7. 项目异常处理

  8. 按条件查询————页面功能调整、Controller修正功能、Service修正功能

表结构

-- ----------------------------
-- Table structure for tbl_book
-- ----------------------------
DROP TABLE IF EXISTS `tbl_book`;
CREATE TABLE `tbl_book` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`type` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 51 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of tbl_book
-- ----------------------------
INSERT INTO `tbl_book` VALUES (1, '计算机理论', 'Spring实战 第5版', 'Spring入门经典教程,深入理解Spring原理技术内幕');
INSERT INTO `tbl_book` VALUES (2, '计算机理论', 'Spring 5核心原理与30个类手写实战', '十年沉淀之作,手写Spring精华思想');
INSERT INTO `tbl_book` VALUES (3, '计算机理论', 'Spring 5 设计模式', '深入Spring源码剖析Spring源码中蕴含的10大设计模式');
INSERT INTO `tbl_book` VALUES (4, '计算机理论', 'Spring MVC+MyBatis开发从入门到项目实战', '全方位解析面向Web应用的轻量级框架,带你成为Spring MVC开发高手');
INSERT INTO `tbl_book` VALUES (5, '计算机理论', '轻量级Java Web企业应用实战', '源码级剖析Spring框架,适合已掌握Java基础的读者');
INSERT INTO `tbl_book` VALUES (6, '计算机理论', 'Java核心技术 卷I 基础知识(原书第11版)', 'Core Java 第11版,Jolt大奖获奖作品,针对Java SE9、10、11全面更新');
INSERT INTO `tbl_book` VALUES (7, '计算机理论', '深入理解Java虚拟机', '5个维度全面剖析JVM,大厂面试知识点全覆盖');
INSERT INTO `tbl_book` VALUES (8, '计算机理论', 'Java编程思想(第4版)', 'Java学习必读经典,殿堂级著作!赢得了全球程序员的广泛赞誉');
INSERT INTO `tbl_book` VALUES (9, '计算机理论', '零基础学Java(全彩版)', '零基础自学编程的入门图书,由浅入深,详解Java语言的编程思想和核心技术');
INSERT INTO `tbl_book` VALUES (10, '市场营销', '直播就该这么做:主播高效沟通实战指南', '李子柒、李佳琦、薇娅成长为网红的秘密都在书中');
INSERT INTO `tbl_book` VALUES (11, '市场营销', '直播销讲实战一本通', '和秋叶一起学系列网络营销书籍');
INSERT INTO `tbl_book` VALUES (12, '市场营销', '直播带货:淘宝、天猫直播从新手到高手', '一本教你如何玩转直播的书,10堂课轻松实现带货月入3W+');
  1. pom.xml

    配置起步依赖,必要的资源坐标(druid)

  2. application.yml

    设置数据源、端口等

  3. 配置类

    全部删除

  4. dao

    设置@Mapper

  5. 测试类

  6. 页面

    放置在resources目录下的static目录中

image-20220816220052095

SpringBoot工程启动

普通启动

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

需要Maven插件

<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

2.执行启动指令

java -jar springboot.jar

可执行jar包目录结构

image-20220825211118371

jar包描述文件(MANIFEST.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.0
  • 基于spring-boot-maven-plugin打包的工程

    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.5.4
    Created-By: Maven Jar Plugin 3.2.0
    Main-Class: org.springframework.boot.loader.JarLauncher

    jar启动器:Main-Class: org.springframework.boot.loader.JarLauncher

多环境命令行启动参数设置

带参数启动SpringBoot

java -jar springboot.jar --server.port=88 --spring.profiles.active=test

防坑:执行package之前先clean

打包可能乱码,解决方案:

image-20220816191519547

idea工具带参运行

image-20220825213702811

通过编程形式添加参数

public static void main(String[] args) {
String[] arg = new String[1];
arg[0] = "--server.port=8080";
SpringApplication.run(Application.class, arg);
}

不携带参考启动

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

指定配置文件

image-20220825221321777

properties与yaml均支持

类路径方式:

--spring.config.location=classpath:/ebank.properties

多参数:

--spring.config.location=classpath:/ebank.properties,classpath:/ebank-server.properties

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

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

idea热部署

开启开发者工具

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>

激活热部署Build:Ctrl/command + F9

关于热部署

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

热部署仅仅加载当前开发者自定义开发的资源,不加载jar资源

idea热部署自动构建

步骤①:设置自动构建项目

打开【File】,选择【settings…】,在面板左侧的菜单中找到【Compile】选项,然后勾选【Build project automatically】,意思是自动构建项目

image-20220309184531333

自动构建项目选项勾选后

步骤②:允许在程序运行时进行自动构建

使用快捷键【Ctrl】+【Alt】+【Shit】+【/】打开维护面板,选择第1项【Registry…】

image-20220222124006910

在选项中搜索compile,然后勾选对应项即可
在这里插入图片描述

这样程序在运行的时候就可以进行自动构建了,实现了热部署的效果。

  • 激活方式:Idea失去焦点5秒后启动热部署

热部署范围配置

配置中默认不参与热部署的目录信息如下

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

自定义不参与重启排除项

spring:
devtools:
restart:
# 设置不参与热部署的文件或文件夹
exclude: static/**,public/**,config/application.yml

关闭热部署功能

禁用热部署

  • 配制文件中

    spring:
    devtools:
    restart:
    enabled: false

如果当心配置文件层级过多导致相符覆盖最终引起配置失效,可以提高配置的层级,在更高层级中配置关闭热部署。例如在启动容器前通过系统属性设置关闭热部署功能。

参见: 属性加载优先顺序

@SpringBootApplication
public class HotDeployApplication {

public static void main(String[] args) {
//禁用热部署
System.setProperty("spring.devtools.restart.enabled", "false");
SpringApplication.run(HotDeployApplication.class);
}

}

第三方bean属性绑定

@ConfigurationProperties注解,此注解的作用是用来为bean绑定属性的。开发者可以在yml配置文件中以对象的格式添加若干属性

servers:
ip-address: 192.168.0.1
port: 2345
timeout: -1

然后再开发一个用来封装数据的实体类,注意要提供属性对应的setter方法

使用@ConfigurationProperties注解就可以将配置中的属性值关联到开发的模型类上

@Component
@Data
@ConfigurationProperties(prefix = "servers")
public class ServerConfig {
private String ipAddress;
private int port;
private long timeout;
}

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

步骤①:使用@Bean注解定义第三方bean

@Bean
public DruidDataSource datasource(){
DruidDataSource ds = new DruidDataSource();
return ds;
}

步骤②:在yml中定义要绑定的属性,注意datasource此时全小写

datasource:
driverClassName: com.mysql.jdbc.Driver

步骤③:使用@ConfigurationProperties注解为第三方bean进行属性绑定,注意前缀是全小写的datasource

@Bean
@ConfigurationProperties(prefix = "datasource")
public DruidDataSource datasource(){
DruidDataSource ds = new DruidDataSource();
return ds;
}

测试:

@SpringBootApplication
public class Springboot13ConfigurationApplication {

@Bean
@ConfigurationProperties(prefix = "datasource")
public DruidDataSource datasource() {
DruidDataSource ds = new DruidDataSource();
return ds;
}

public static void main(String[] args) {
//获取容器对象
ConfigurableApplicationContext run = SpringApplication.run(Springboot13ConfigurationApplication.class, args);
ServerConfig bean = run.getBean(ServerConfig.class);
System.out.println(bean);

DruidDataSource druidDataSource = run.getBean(DruidDataSource.class);
System.out.println(druidDataSource.getDriverClassName());

}
}

@EnableConfigurationProperties

作用: 标注使用@ConfigurationProperties注解绑定属性的bean是哪些

步骤①:在配置类上开启@EnableConfigurationProperties注解,并标注要使用@ConfigurationProperties注解绑定属性的类

@SpringBootApplication
@EnableConfigurationProperties(ServerConfig.class)
public class Springboot13ConfigurationApplication {
}

步骤②:在对应的类上直接使用@ConfigurationProperties进行属性绑定

@Data
@ConfigurationProperties(prefix = "servers")
public class ServerConfig {
private String ipAddress;
private int port;
private long timeout;
}

当使用@EnableConfigurationProperties注解时,spring会默认将其标注的类定义为bean,因此无需再次声明@Component注解了

注意:

@EnableConfigurationProperties与@Component不能同时使用

解除使用@ConfigurationProperties注释警告

image-20220222145535749

出现这个提示后只需要添加一个坐标此提醒就消失了

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

小结:

  1. 使用@ConfigurationProperties可以为使用@Bean声明的第三方bean绑定属性
  2. 当使用@EnableConfigurationProperties声明进行属性绑定的bean后,无需使用@Component注解再次进行bean声明

松散绑定

即配置文件中的命名格式与变量名的命名格式可以进行格式上的最大化兼容

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

image-20220309232731490

servers:
# ipAddress: 192.168.0.1 # 驼峰模式
# ip_address: 192.168.0.2 # 下划线模式
ip-address: 192.168.0.3 # 烤肉串模式
# IP_ADDRESS: 192.168.0.4 # 常量模式

image-20220828162712342

@Bean
@ConfigurationProperties(prefix = "data-source")
public DruidDataSource datasource() {
DruidDataSource ds = new DruidDataSource();
return ds;
}

注意:松散绑定不支持注解@Value引用单个属性的方式

@Value@ConfigurationProperties比较

项目 @ConfigurationProperties @Value
功能 批量注入配置文件中的属性 分别指定
松散绑定(松散语法) 支持 不支持
SpEL 不支持 支持
JSR-303数据校验 支持 不支持
复杂类型封装 支持 不支持

无论配置文件是 yml 还是 properties 他们都能获取到值。

如果说,我们只是在某个业务逻辑中需要获取一下配置文件中的某项值,使用 @Value

如果说,我们专门编写了一个 JavaBean 来和配置文件进行映射,我们就直接使用@ConfigurationProperties

常用计量单位应用

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

@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;
}

Duration:表示时间间隔,可以通过@DurationUnit注解描述时间单位,例如上例中描述的单位分钟(ChronoUnit.MINUTES)

DataSize:表示存储空间,可以通过@DataSizeUnit注解描述存储空间单位,例如上例中描述的单位为MB(DataUnit.MEGABYTES)

Druation常用单位如下:

在这里插入图片描述

DataSize常用单位如下:

在这里插入图片描述

bean属性校验

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

开启Bean数据校验

步骤①:开启校验框架

<!--1.导入JSR303规范-->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
<!--使用hibernate框架提供的校验器做实现-->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>

步骤②:在需要开启校验功能的类上使用注解@Validated开启校验功能

@Component
@Data
@ConfigurationProperties(prefix = "servers")
//开启对当前bean的属性注入校验
@Validated
public class ServerConfig {
}

步骤③:对具体的字段设置校验规则

@Component
@Data
@ConfigurationProperties(prefix = "servers")
//开启对当前bean的属性注入校验
@Validated
public class ServerConfig {
//设置具体的规则
@Max(value = 8888,message = "最大值不能超过8888")
@Min(value = 202,message = "最小值不能低于202")
private int port;
}

非法时报错信息演示:

Binding to target org. springframework. boot. context. proper
Property: servers. port
Value: 20
Origin: class path resource application. yml - 6:9
Reason:最小值不能低于202

进制数据转换规则

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 连接,最后使用 + 代表时区

二进制前缀 0b
八进制前缀 0
十六进制前缀 0X

进制基数(radix) 前缀 示例
二进制 binary 0b 0B 0b11 = 2+1=3
八进制 octal 0o 0O 0 0o11 = 8+1=9
十进制 decimal 无前缀 11 = 11
十六进制 hex 0x 0X 0x11

注意在配制文件中 字符串的书写 加上引号包裹,养成习惯,遇到0开头的数据多注意。

项目测试

加载测试专用属性

加载测试专用属性

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

//properties属性可以为当前测试用例添加临时的属性配置
@SpringBootTest(properties = {"test.prop=testValue1"})
class PropertiesAndArgsTest {

@Value("${test.prop}")
private String msg;

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

}

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

在启动测试环境时可以通过args参数设置测试环境专用的传入参数

//args属性可以为当前测试用例添加临时的命令行参数
@SpringBootTest(args={"--test.prop=testValue2"})
public class PropertiesAndArgsTest {

@Value("${test.prop}")
private String msg;

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

当 args参数 与 properties参数 设置共存时, args属性配置优先于properties属性配置加载。

image-20220310205307680

加载测试专用配置

步骤①:在测试包test中创建专用的测试环境配置类

@Configuration
public class MsgConfig {
@Bean
public String msg(){
return "bean msg";
}
}

上述配置仅用于演示当前实验效果,实际开发可不能这么注入String类型的数据

步骤②:在启动测试环境时,导入测试环境专用的配置类,使用@Import注解即可实现

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

@Autowired
private String msg;

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

测试类中启动web环境

模拟端口

每一个springboot的测试类上方都会标准@SpringBootTest注解,而注解带有一个属性,叫做webEnvironment。通过该属性就可以设置在测试用例中启动web环境,具体如下:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class WebTest {
@Test
void testRandomPort() {

}
}

测试类中启动web环境时,可以指定启动的Web环境对应的端口,springboot提供了4种设置值,分别如下:

在这里插入图片描述

  • MOCK:根据当前设置确认是否启动web环境,例如使用了Servlet的API就启动web环境,属于适配性的配置
  • DEFINED_PORT:使用自定义的端口作为web服务器端口
  • RANDOM_PORT:使用随机端口作为web服务器端口
  • NONE:不启动web环境

发送虚拟请求

新建 BookController

@RestController
@RequestMapping("/books")
public class BookController {
@GetMapping
public String getById() {
System.out.println("getById is running .....");
return "springboot";
}
}

虚拟请求测试

//1.使用 webEnvironment 属性开启web环境
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
//2.开启虚拟mvc调用
@AutoConfigureMockMvc
public class WebTest {
@Test
void testRandomPort() {

}

@Test
//注入虚拟mvc调用对象
public void testWeb(@Autowired MockMvc mvc) throws Exception {
//3.创建虚拟请求,当前访问/books
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
//执行请求
ResultActions actions = mvc.perform(builder);

}
}

匹配响应执行状态

虚拟请求状态匹配

@Test
//注入虚拟mvc调用对象
public void testStatus(@Autowired MockMvc mvc) throws Exception {
//创建虚拟请求,当前访问/books
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
//执行请求
ResultActions actions = mvc.perform(builder);
//设定预期值 与真实值进行比较,成功测试通过,失败测试失败
//定义本次调用的预期值
StatusResultMatchers status = MockMvcResultMatchers.status();
//预计本次调用时成功的:状态200
ResultMatcher ok = status.isOk();
//添加预计值到本次调用过程中进行匹配
actions.andExpect(ok);

}

匹配失败的信息提示如:

java.lang.AssertionError: Status expected:<200> but was:<404>
Expected :200
Actual :404
<Click to see difference>

匹配响应体(非json数据格式)

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

匹配失败的信息提示如:

MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Content-Type:"text/plain;charset=UTF-8", Content-Length:"10"]
Content type = text/plain;charset=UTF-8
Body = springboot
Forwarded URL = null
Redirected URL = null
Cookies = []

java.lang.AssertionError: Response content expected:<springboot2> but was:<springboot>
Expected :springboot2
Actual :springboot
<Click to see difference>

匹配响应体(json)

创建 book 实体类

@Data
public class Book {
private Integer id;
private String type;
private String name;
private String description;
}

修改 BookController

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

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

@GetMapping("{id}")
public Book getById1(@PathVariable int id) {
System.out.println("getById is running .....");
Book book = new Book();
book.setId(1);
book.setName("springboot");
book.setType("springboot");
book.setDescription("springboot");
return book;
}

}

测试方法

@Test
void testJson(@Autowired MockMvc mvc) throws Exception {
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books/1");
ResultActions action = mvc.perform(builder);
//设定预期值 与真实值进行比较,成功测试通过,失败测试失败
//定义本次调用的预期值
ContentResultMatchers content = MockMvcResultMatchers.content();
ResultMatcher result = content.json("{\"id\":1,\"name\":\"springboot2\",\"type\":\"springboot\"}");
//添加预计值到本次调用过程中进行匹配
action.andExpect(result);
}

匹配失败的信息提示如:

image-20220311095132674

匹配响应头

虚拟请求响应头匹配

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

匹配失败的信息提示如:

image-20220311095856222

完整匹配校验

@Test
void testGetById(@Autowired MockMvc mvc) throws Exception {
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books/1");
ResultActions action = mvc.perform(builder);

StatusResultMatchers status = MockMvcResultMatchers.status();
ResultMatcher ok = status.isOk();
action.andExpect(ok);

HeaderResultMatchers header = MockMvcResultMatchers.header();
ResultMatcher contentType = header.string("Content-Type", "application/json");
action.andExpect(contentType);

ContentResultMatchers content = MockMvcResultMatchers.content();
ResultMatcher result = content.json("{\"id\":1,\"name\":\"springboot\",\"type\":\"springboot\"}");
action.andExpect(result);
}

业务层测试事务回滚

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

@SpringBootTest
@Transactional
public class DaoTest {
@Autowired
private BookService bookService;

@Test
void testSave() {
Book book = new Book();
book.setName("springboot3");
book.setType("springboot3");
book.setDescription("springboot3");

bookService.save(book);
}
}

如果想在测试用例中提交事务,可以通过@Rollback注解设置回滚状态为false即可正常提交事务

@SpringBootTest
@Transactional
@Rollback(false)
public class DaoTest {
}

测试用例设置随机数据

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

①yml中设置随机值

testcase:
book:
id: ${random.int}
id2: ${random.int(10)}
type: ${random.int!5,10!}
name: ${random.value}
uuid: ${random.uuid}
publishTime: ${random.long}

②创建 BookCase 类封装数据

//1.定义数据模型封装yaml文件中对应的数据
//2.定义为spring管控的bean
@Component
//3.指定加载的数据
@ConfigurationProperties(prefix = "testcase.book")
@Data
public class BookCase {
private int id;
private int id2;
private int type;
private String name;
private String uuid;
private long publishTime;
}

③测试

@SpringBootTest
public class BookCaseRandom {

@Autowired
private BookCase bookCase;

@Test
void testProperties() {
System.out.println(bookCase);
}
}

对于随机值的产生,还有一些小的限定规则,比如产生的数值性数据可以设置范围等,具体如下:

在这里插入图片描述

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

数据源、数据库

内置数据源Hikari

1.现有数据层解决方案技术选型 Mysql+Druid+MyBatisPlus

  • 数据源:DruidDataSource
  • 持久化技术:MyBatis-Plus / MyBatis
  • 数据库:MySQL

目前我们使用的数据源技术是Druid,运行时可以在日志中看到对应的数据源初始化信息,具体如下:

INFO 28600 --- [           main] c.a.d.s.b.a.DruidDataSourceAutoConfigure : Init DruidDataSource
INFO 28600 --- [ main] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited

如果不配制数据源默认是 HikariDataSource

2.数据源配置格式

格式一 (通用配制未声明数据源技术)默认是 hikari 数据源

spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springboot_db?serverTimezone=UTC
username: root
password: 123456

格式二 (配制指定的数据源需要导入对应的 starter 整合Duird)

# druid 数据源配制
spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springboot_db?serverTimezone=UTC
username: root
password: 123456
# hikari 数据源配制 url地址要单独配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
hikari:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root

3.数据源配置

springboot提供了3款内嵌数据源技术,分别如下:

  • HikariCP:默认内置数据源对象
  • Tomcat提供DataSource:HikariCP不可用的情况下,且在web环境中,将使用tomcat服务器配置的数据源对象
  • Commons DBCP:Hikari不可用,tomcat数据源也不可用,将使用dbcp数据源

4.通用配置无法设置具体的数据源配置信息,仅提供基本的连接相关配置,如需配置,在下一级配置中设置具体设定

spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springboot_db?serverTimezone=UTC
username: root
password: 123456
hikari:
maximum-pool-size: 50

JdbcTemplate

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

步骤①:导入jdbc对应的坐标,记得是starter

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

步骤②:自动装配JdbcTemplate对象

@SpringBootTest
class Springboot15SqlApplicationTests {
@Test
void testJdbcTemplate(@Autowired JdbcTemplate jdbcTemplate){
}
}

步骤③:使用JdbcTemplate实现查询操作(非实体类封装数据的查询操作)

@Test
void testJdbcTemplate(@Autowired JdbcTemplate jdbcTemplate){
String sql = "select * from tbl_book";
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
System.out.println(maps);
}

步骤④:使用JdbcTemplate实现查询操作(实体类封装数据的查询操作)

@Test
void testJdbcTemplate(@Autowired JdbcTemplate jdbcTemplate){

String sql = "select * from tbl_book";
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> list = jdbcTemplate.query(sql, rm);
System.out.println(list);
}

步骤⑤:使用JdbcTemplate实现增删改操作

@Test
void testJdbcTemplateSave(@Autowired JdbcTemplate jdbcTemplate){
String sql = "insert into tbl_book values(3,'springboot1','springboot2','springboot3')";
jdbcTemplate.update(sql);
}

如果想对JdbcTemplate对象进行相关配置,可以在yml文件中进行设定,具体如下:

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

小结

  1. SpringBoot内置JdbcTemplate持久化解决方案
  2. 使用JdbcTemplate需要导入spring-boot-starter-jdbc的坐标

H2数据库

SpringBoot提供了3种内嵌数据库供开发者选择,提高开发测试效率

  • H2
  • HSQL
  • Derby

内嵌数据库(H2)

步骤①:导入H2数据库对应的坐标,一共2个

<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

步骤②:将工程设置为web工程,启动工程时启动H2数据库

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

步骤③:通过配置开启H2数据库控制台访问程序,也可以使用其他的数据库连接软件操作

spring:
h2:
console:
enabled: true
path: /h2

web端访问路径/h2,访问密码123456,如果访问失败,先配置下列数据源,启动程序运行后再次访问/h2路径就可以正常访问了

datasource:
url: jdbc:h2:~/test
hikari:
driver-class-name: org.h2.Driver
username: sa
password: 123456

运行报错

org.h2.jdbc.JdbcSQLInvalidAuthorizationSpecException: Wrong user name or password [28000-200]

原因: 我们之前创建过该数据库,H2数据库会初始化用户名和密码,配置文件又中重新定义了数据库的用户名和密码,造成了冲突。

解决:

处理方案一:删除原来的数据库 (C:\Users\你的用户名)

image-20220311225449816

处理方案二:修改一个不存在的数据库名称

image-20220311225713059

操作数据库(创建表)

create table tbl_book (id int,name varchar,type varchar,description varchar)

image-20220311230244261

步骤④:使用JdbcTemplate或MyBatisPlus技术操作数据库

记得测试的时候 要web服务器给关了,不然会报如下错误

Caused by: org.h2.jdbc.JdbcSQLNonTransientConnectionException: Database may be already in use: null. Possible solutions: close all other connection(s); use the server mode [90020-200]

Caused by: java.lang.IllegalStateException: The file is locked: nio:C:/Users/Chen/test.mv.db [1.4.200/7]

@SpringBootTest
class Springboot15SqlApplicationTests {

@Autowired
private BookDao bookDao;

@Test
void contextLoads() {
Book book = bookDao.selectById(1);
System.out.println(book);
}

/**
* 非实体类封装数据的查询操作
*
* @param jdbcTemplate
*/
@Test
void testJdbcTemplate(@Autowired JdbcTemplate jdbcTemplate) {
String sql = "select * from tbl_book";
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
System.out.println(maps);
}

/**
* 实体类封装数据的查询操作
*
* @param jdbcTemplate
*/
@Test
void testJdbcTemplate1(@Autowired JdbcTemplate jdbcTemplate) {
String sql = "select * from tbl_book";
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> list = jdbcTemplate.query(sql, rm);
System.out.println(list);
}

@Test
void testJdbcTemplateSave(@Autowired JdbcTemplate jdbcTemplate) {
String sql = "insert into tbl_book values(3,'springboot1','springboot2','springboot3')";
jdbcTemplate.update(sql);
}
}

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

#h2 数据库配制
spring:
h2:
console:
enabled: true
path: /h2
datasource:
url: jdbc:h2:~/test
hikari:
# driver-class-name: org.h2.Driver
username: sa
password: sa

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

server:
port: 80
spring:
h2:
console:
path: /h2
# 关闭
enabled: false

数据源、持久化、数据库

  • 数据源:DruidDataSource、HikariCP、Tomcat提供DataSource、Commons DBCP
  • 持久化:MyBatis-Plus、MyBatis、JdbcTemplate
  • 数据库:H2、HSQL、Derby

image-20220312091151024

缓存

缓存的作用

企业级应用主要作用是信息处理,当需要读取数据时,由于受限于数据库的访问效率,导致整体系统性能偏低。

在这里插入图片描述

应用程序直接与数据库打交道,访问效率低

为了改善上述现象,开发者通常会在应用程序与数据库之间建立一种临时的数据存储机制,该区域中的数据在内存中保存,读写速度较快,可以有效解决数据库访问效率低下的问题。这一块临时存储数据的区域就是缓存。

在这里插入图片描述

使用缓存后,应用程序与缓存打交道,缓存与数据库打交道,数据访问效率提高

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

自定义缓存

@Autowired
private BookDao bookDao;

private HashMap<Integer,Book> cache = new HashMap<Integer,Book>();

@Override
public Book getById(Integer id) {
//如果当前缓存中没有本次要查询的数据,则进行查询,否则直接从缓存中获取数据返回
Book book = cache.get(id);
if(book == null){
book = bookDao.selectById(id);
cache.put(id,book);
}
return book;
}

提供临时的数据存储空间

private HashMap<String ,String> cache = new HashMap<String,String>();

@Override
public String get(String tele) {
String code = tele.substring(tele.length() - 6);
cache.put(tele,code);
return code;
}

@Override
public boolean check(String tele, String code) {
String queryCode = cache.get(tele);
return code.equals(queryCode);
}

Spring缓存使用方式

springboot技术提供有内置的缓存解决方案,可以帮助开发者快速开启缓存技术,并使用缓存技术进行数据的快速操作,例如读取缓存数据和写入数据到缓存。

步骤①:导入springboot提供的缓存技术对应的starter

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>

步骤②:启用缓存,在引导类上方标注注解@EnableCaching配置springboot程序中可以使用缓存

@SpringBootApplication
//开启缓存功能
@EnableCaching
public class Springboot19CacheApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot19CacheApplication.class, args);
}
}

步骤③:设置操作的数据是否使用缓存

@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;

@Cacheable(value="cacheSpace",key="#id")
public Book getById(Integer id) {
return bookDao.selectById(id);
}
}

在业务方法上面使用注解@Cacheable声明当前方法的返回值放入缓存中,其中要指定缓存的存储位置,以及缓存中保存当前方法返回值对应的名称。上例中value属性描述缓存的存储位置,可以理解为是一个存储空间名,key属性描述了缓存中保存数据的名称,使用#id读取形参中的id值作为缓存名称。

使用@Cacheable注解后,执行当前操作,如果发现对应名称在缓存中没有数据,就正常读取数据,然后放入缓存;如果对应名称在缓存中有数据,就终止当前业务方法执行,直接返回缓存中的数据。

SpringBoot提供的缓存技术除了提供默认的缓存方案,还可以对其他缓存技术进行整合,统一接口,方便缓存技术的开发与管理

  • Generic
  • JCache
  • Ehcache
  • Hazelcast
  • Infinispan
  • Couchbase
  • Redis
  • Caffenine
  • Simple(默认)
  • Memcached

手机验证码案例-生成验证码

  • 需求
    • 输入手机号获取验证码,组织文档以短信形式发送给用户(页面模拟)
    • 输入手机号和验证码验证结果
  • 需求分析
    • 提供controller,传入手机号,业务层通过手机号计算出独有的6位验证码数据,存入缓存后返回此数据
    • 提供controller,传入手机号与验证码,业务层通过手机号从缓存中读取验证码与输入验证码进行比对,返回比对结果

注意: 启用缓存,在引导类上方标注注解@EnableCaching配置springboot程序中可以使用缓存

步骤①:定义验证码对应的实体类,封装手机号与验证码两个属性

@Data
public class SMSCode {
private String tele;
private String code;
}

步骤②:定义验证码功能的业务层接口与实现类

public interface SMSCodeService {
public String sendCodeToSMS(String tele);
public boolean checkCode(SMSCode smsCode);
}

@Service
public class SMSCodeServiceImpl implements SMSCodeService {
@Autowired
private CodeUtils codeUtils;

@CachePut(value = "smsCode", key = "#tele")
public String sendCodeToSMS(String tele) {
String code = codeUtils.generator(tele);
return code;
}

public boolean checkCode(SMSCode smsCode) {
return false;
}
}

获取验证码后,当验证码失效时必须重新获取验证码,因此在获取验证码的功能上不能使用@Cacheable注解,@Cacheable注解是缓存中没有值则放入值,缓存中有值则取值。此处的功能仅仅是生成验证码并放入缓存,并不具有从缓存中取值的功能,因此不能使用@Cacheable注解,应该使用仅具有向缓存中保存数据的功能,使用@CachePut注解即可。

步骤③:定义验证码的生成策略与根据手机号读取验证码的功能

@Component
public class CodeUtils {
private String [] patch = {"000000","00000","0000","000","00","0",""};

public String generator(String tele){
int hash = tele.hashCode();
int encryption = 20206666;
long result = hash ^ encryption;
long nowTime = System.currentTimeMillis();
result = result ^ nowTime;
long code = result % 1000000;
code = code < 0 ? -code : code;
String codeStr = code + "";
int len = codeStr.length();
return patch[len] + codeStr;
}
}

步骤④:定义验证码功能的web层接口,一个方法用于提供手机号获取验证码,一个方法用于提供手机号和验证码进行校验

@RestController
@RequestMapping("/sms")
public class SMSCodeController {

@Autowired
private SMSCodeService smsCodeService;

@GetMapping
public String getCode(String tele){
String code = smsCodeService.sendCodeToSMS(tele);
return code;
}

@PostMapping
public boolean checkCode(SMSCode smsCode){
return smsCodeService.checkCode(smsCode);
}

}

手机验证码案例-验证码校验

获取缓存中的验证码

//定义为spring管控的bean
@Component
public class CodeUtils {

private String[] patch = {"000000", "00000", "0000", "000", "00", "0", ""};

public String generator(String tele) {
int hash = tele.hashCode();
int encryption = 20206666;
long result = hash ^ encryption;
long nowTime = System.currentTimeMillis();
result = result ^ nowTime;
long code = result % 1000000;
code = code < 0 ? -code : code;
String codeStr = code + "";
int len = codeStr.length();
return patch[len] + codeStr;
}

/**
* 获取缓存中的验证码,要以bean 方式注入到spring的容器中
*
* @param tele
* @return
*/
@Cacheable(value = "smsCode", key = "#tele")
public String get(String tele) {
return null;
}

}

校验

public boolean checkCode(SMSCode smsCode) {
String code = smsCode.getCode();
//取出内存中的验证码与传递过来的验证码比对,如果相同,返回true
String cacheCode = codeUtils.get(smsCode.getTele());
return code.equals(cacheCode);
}

变更缓存供应商Ehcache

步骤①:导入Ehcache的坐标

<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>

此处为什么不是导入Ehcache的starter,而是导入技术坐标呢?其实springboot整合缓存技术做的是通用格式,不管你整合哪种缓存技术,只是实现变化了,操作方式一样。这也体现出springboot技术的优点,统一同类技术的整合方式。

步骤②:配置缓存技术实现使用Ehcache

spring:
cache:
type: ehcache
#ehcache配置文件路径
ehcache:
config: classpath:/ehcache.xml

配置缓存的类型type为ehcache,此处需要说明一下,当前springboot可以整合的缓存技术中包含有ehcach,所以可以这样书写。其实这个type不可以随便写的,不是随便写一个名称就可以整合的。

由于ehcache的配置有独立的配置文件格式,因此还需要指定ehcache的配置文件,以便于读取相应配置

注意: 配制文件的路径要从 classpath: 开始

新建 resources/ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="D:\ehcache" />

<!--默认缓存策略 -->
<!-- external:是否永久存在,设置为true则不会被清除,此时与timeout冲突,通常设置为false-->
<!-- diskPersistent:是否启用磁盘持久化-->
<!-- maxElementsInMemory:最大缓存数量-->
<!-- overflowToDisk:超过最大缓存数量是否持久化到磁盘-->
<!-- timeToIdleSeconds:最大不活动间隔,设置过长缓存容易溢出,设置过短无效果,可用于记录时效性数据,例如验证码-->
<!-- timeToLiveSeconds:最大存活时间-->
<!-- memoryStoreEvictionPolicy:缓存清除策略-->
<defaultCache
eternal="false"
diskPersistent="false"
maxElementsInMemory="1000"
overflowToDisk="false"
timeToIdleSeconds="60"
timeToLiveSeconds="60"
memoryStoreEvictionPolicy="LRU" />

<cache
name="smsCode"
eternal="false"
diskPersistent="false"
maxElementsInMemory="1000"
overflowToDisk="false"
timeToIdleSeconds="10"
timeToLiveSeconds="10"
memoryStoreEvictionPolicy="LRU" />
</ehcache>

注意前面的案例中,设置了数据保存的位置是smsCode

@CachePut(value = "smsCode", key = "#tele")
public String sendCodeToSMS(String tele) {
String code = codeUtils.generator(tele);
return code;
}

这个设定需要保障ehcache中有一个缓存空间名称叫做smsCode的配置,前后要统一。在企业开发过程中,通过设置不同名称的cache来设定不同的缓存策略,应用于不同的缓存数据。

到这里springboot整合Ehcache就做完了,可以发现一点,原始代码没有任何修改,仅仅是加了一组配置就可以变更缓存供应商了,这也是springboot提供了统一的缓存操作接口的优势,变更实现并不影响原始代码的书写。

数据淘汰策略

影响数据淘汰的相关配置

检测易失数据(可能会过期的数据集server.db[i].expires)

  1. volatile-lru: 挑选最近最少使用的数据淘汰
  2. volatile-lfu: 挑选最近使用次数最少的数据淘汰
  3. volatile-ttl: 挑选将要过期的数据淘汰
  4. volatile-random: 任意选择数据淘汰

image-20220831145842254

  • LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的
  • LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存

变更缓存供应商Redis

步骤①:导入redis的坐标

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

步骤②:配置缓存技术实现使用redis

spring:
redis:
host: localhost
port: 6379
cache:
type: redis

如果需要对redis作为缓存进行配置,注意不是对原始的redis进行配置,而是配置redis作为缓存使用相关的配置,隶属于spring.cache.redis节点下,注意不要写错位置了。

spring:
redis:
host: localhost
port: 6379
cache:
type: redis
redis:
use-key-prefix: false # 是否使用前缀名(系统定义前缀名)值为 false, key-prefix设置无效
key-prefix: sms_ # 追加自定义前缀名
cache-null-values: false # 是否允许存储空值
time-to-live: 10s # 有效时长

Redis 客户端相关操作

127.0.0.1:6379> keys *
1) "smsCode::188666688881"
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> keys *
1) "188666688881"
127.0.0.1:6379> keys *
1) "sms_smsCode::188666688881"

memcached下载与安装

安装

windows版安装包下载

image-20220226174957040

memcached.exe,执行此文件时会报错:

在这里插入图片描述

此处出现问题的原因是注册系统服务时需要使用管理员权限,当前账号权限不足导致安装服务失败,切换管理员账号权限启动命令行

在这里插入图片描述

然后再次执行安装服务的命令即可,如下:

memcached.exe -d install

服务安装完毕后可以使用命令启动和停止服务,如下:

memcached.exe -d start		# 启动服务
memcached.exe -d stop # 停止服务

也可以在任务管理器中进行服务状态的切换

在这里插入图片描述

变更缓存供应商memcached

  • memcached客户端选择
    • Memcached Client for Java:最早期客户端,稳定可靠,用户群广
    • SpyMemcached:效率更高
    • Xmemcached:并发处理更好
  • SpringBoot未提供对memcached的整合,需要使用硬编码方式实现客户端初始化管理
<dependency>
<groupId>com.googlecode.xmemcached</groupId>
<artifactId>xmemcached</artifactId>
<version>2.4.7</version>
</dependency>
memcached:
# memcached默认对外服务端口11211。
servers: localhost:11211
# 连接池的数量
poolSize: 10
# 设置默认操作超时
opTimeout: 3000
@Data
@Component
@ConfigurationProperties(prefix = "memcached")
public class XMemcachedProperties {
private String servers;
private int poolSize;
private long opTimeout;
}
@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;
}
}
@Service
public class SMSCodeServiceImpl implements SMSCodeService {
@Autowired
private CodeUtils codeUtils;
@Autowired
private MemcachedClient memcachedClient;

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

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
    • Caffeine
  • 远程缓存(Remote)
    • Redis
    • Tair

步骤①:导入springboot整合jetcache对应的坐标starter,当前坐标默认使用的远程方案是redis

<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-starter-redis</artifactId>
<version>2.6.2</version>
</dependency>

步骤②:远程方案基本配置

jetcache:
remote:
default:
type: redis
host: localhost
port: 6379
poolConfig:
maxTotal: 50

其中poolConfig是必配项,否则会报错

步骤③:启用缓存,在引导类上方标注注解@EnableCreateCacheAnnotation配置springboot程序中可以使用注解的形式创建缓存

@SpringBootApplication
//jetcache启用缓存的主开关
@EnableCreateCacheAnnotation
public class Springboot20JetCacheApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot20JetCacheApplication.class, args);
}
}

步骤④:创建缓存对象Cache,并使用注解@CreateCache标记当前缓存的信息,然后使用Cache对象的API操作缓存,put写缓存,get读缓存。

@Service
public class SMSCodeServiceImpl implements SMSCodeService {
@Autowired
private CodeUtils codeUtils;

@CreateCache(name="jetCache_",expire = 10,timeUnit = TimeUnit.SECONDS)
private Cache<String ,String> jetCache;

public String sendCodeToSMS(String tele) {
String code = codeUtils.generator(tele);
jetCache.put(tele,code);
return code;
}

public boolean checkCode(SMSCode smsCode) {
String code = jetCache.get(smsCode.getTele());
return smsCode.getCode().equals(code);
}
}

通过上述jetcache使用远程方案连接redis可以看出,jetcache操作缓存时的接口操作更符合开发者习惯,使用缓存就先获取缓存对象Cache,放数据进去就是put,取数据出来就是get,更加简单易懂。并且jetcache操作缓存时,可以为某个缓存对象设置过期时间,将同类型的数据放入缓存中,方便有效周期的管理。

上述方案中使用的是配置中定义的default缓存,其实这个default是个名字,可以随便写,也可以随便加。例如再添加一种缓存解决方案,参照如下配置进行:

jetcache:
remote:
default:
type: redis
host: localhost
port: 6379
poolConfig:
maxTotal: 50
sms:
type: redis
host: localhost
port: 6379
poolConfig:
maxTotal: 50

如果想使用名称是sms的缓存,需要再创建缓存时指定参数area,声明使用对应缓存即可

@Service
public class SMSCodeServiceImpl implements SMSCodeService {
@Autowired
private CodeUtils codeUtils;

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

public String sendCodeToSMS(String tele) {
String code = codeUtils.generator(tele);
jetCache.put(tele,code);
return code;
}

public boolean checkCode(SMSCode smsCode) {
String code = jetCache.get(smsCode.getTele());
return smsCode.getCode().equals(code);
}
}

循环依赖

Description:

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
| redisAutoInit (field protected com.alicp.jetcache.anno.support.ConfigProvider com.alicp.jetcache.autoconfigure.AbstractCacheAutoInit.configProvider)
↑ ↓
| springConfigProvider
└─────┘

Action:

Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.

设置 spring.main.allow-circular-references 为 true 打破循环

spring:
main:
allow-circular-references: true

jetcache本地缓存方案

纯本地方案

远程方案中,配置中使用remote表示远程,换成local就是本地,只不过类型不一样而已。

步骤①:导入springboot整合jetcache对应的坐标starter

<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-starter-redis</artifactId>
<version>2.6.2</version>
</dependency>

步骤②:本地缓存基本配置

jetcache:
local:
default:
type: linkedhashmap
keyConvertor: fastjson

为了加速数据获取时key的匹配速度,jetcache要求指定key的类型转换器。简单说就是,如果你给了一个Object作为key的话,我先用key的类型转换器给转换成字符串,然后再保存。等到获取数据时,仍然是先使用给定的Object转换成字符串,然后根据字符串匹配。由于jetcache是阿里的技术,这里推荐key的类型转换器使用阿里的fastjson。

步骤③:启用缓存

@SpringBootApplication
//jetcache启用缓存的主开关
@EnableCreateCacheAnnotation
public class Springboot20JetCacheApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot20JetCacheApplication.class, args);
}
}

步骤④:创建缓存对象Cache时,标注当前使用本地缓存

@Service
public class SMSCodeServiceImpl implements SMSCodeService {
@CreateCache(name="jetCache_",expire = 1000,timeUnit = TimeUnit.SECONDS,cacheType = CacheType.LOCAL)
private Cache<String ,String> jetCache;

public String sendCodeToSMS(String tele) {
String code = codeUtils.generator(tele);
jetCache.put(tele,code);
return code;
}

public boolean checkCode(SMSCode smsCode) {
String code = jetCache.get(smsCode.getTele());
return smsCode.getCode().equals(code);
}
}

cacheType控制当前缓存使用本地缓存还是远程缓存,配置cacheType=CacheType.LOCAL即使用本地缓存。

本地+远程方案

本地和远程方法都有了,两种方案一起使用如何配置呢?其实就是将两种配置合并到一起就可以了。

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

在创建缓存的时候,配置cacheType为BOTH即则本地缓存与远程缓存同时使用。

@Service
public class SMSCodeServiceImpl implements SMSCodeService {
@CreateCache(name="jetCache_",expire = 1000,timeUnit = TimeUnit.SECONDS,cacheType = CacheType.BOTH)
private Cache<String ,String> jetCache;
}

cacheType如果不进行配置,默认值是REMOTE,即仅使用远程缓存方案。关于jetcache的配置,参考以下信息

属性 默认值 说明
jetcache.statIntervalMinutes 0 统计间隔,0表示不统计
jetcache.hiddenPackages 自动生成name时,隐藏指定的包名前缀
jetcache.[local|remote].${area}.type 缓存类型,本地支持linkedhashmap、caffeine,远程支持redis、tair
jetcache.[local|remote].${area}.keyConvertor key转换器,当前仅支持fastjson
jetcache.[local|remote].${area}.valueEncoder java 仅remote类型的缓存需要指定,可选java和kryo
jetcache.[local|remote].${area}.valueDecoder java 仅remote类型的缓存需要指定,可选java和kryo
jetcache.[local|remote].${area}.limit 100 仅local类型的缓存需要指定,缓存实例最大元素数
jetcache.[local|remote].${area}.expireAfterWriteInMillis 无穷大 默认过期时间,毫秒单位
jetcache.local.${area}.expireAfterAccessInMillis 0 仅local类型的缓存有效,毫秒单位,最大不活动间隔

jetcache方法缓存方案

jetcache提供了方法缓存方案,只不过名称变更了而已。在对应的操作接口上方使用注解@Cached即可

步骤①:导入springboot整合jetcache对应的坐标starter

<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-starter-redis</artifactId>
<version>2.6.2</version>
</dependency>

步骤②:配置缓存

jetcache:
local:
default:
type: linkedhashmap
keyConvertor: fastjson
remote:
default:
type: redis
host: localhost
port: 6379
keyConvertor: fastjson
valueEncode: java
valueDecode: java
poolConfig:
maxTotal: 50
sms:
type: redis
host: localhost
port: 6379
poolConfig:
maxTotal: 50

由于redis缓存中不支持保存对象,因此需要对redis设置当Object类型数据进入到redis中时如何进行类型转换。需要配置keyConvertor表示key的类型转换方式,同时标注value的转换类型方式,值进入redis时是java类型,标注valueEncode为java,值从redis中读取时转换成java,标注valueDecode为java。

注意,为了实现Object类型的值进出redis,需要保障进出redis的Object类型的数据必须实现序列化接口

@Data
public class Book implements Serializable {
private Integer id;
private String type;
private String name;
private String description;
}

步骤③:启用缓存时开启方法缓存功能,并配置basePackages,说明在哪些包中开启方法缓存

@SpringBootApplication
//jetcache启用缓存的主开关
@EnableCreateCacheAnnotation
//开启方法注解缓存
@EnableMethodCache(basePackages="com.example")
public class Springboot20JetCacheApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot20JetCacheApplication.class, args);
}
}

步骤④:使用注解@Cached标注当前方法使用缓存

@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;

@Override
@Cached(name="book_",key="#id",expire = 3600,cacheType = CacheType.REMOTE)
public Book getById(Integer id) {
return bookDao.selectById(id);
}
}

远程方案的数据同步

由于远程方案中redis保存的数据可以被多个客户端共享,这就存在了数据同步问题。jetcache提供了3个注解解决此问题,分别在更新、删除操作时同步缓存数据,和读取缓存时定时刷新数据

更新缓存

@CacheUpdate(name="book_",key="#book.id",value="#book")
public boolean update(Book book) {
return bookDao.updateById(book) > 0;
}

删除缓存

@CacheInvalidate(name="book_",key = "#id")
public boolean delete(Integer id) {
return bookDao.deleteById(id) > 0;
}

定时刷新缓存

@Cached(name="book_",key="#id",expire = 3600,cacheType = CacheType.REMOTE)
@CacheRefresh(refresh = 5)
public Book getById(Integer id) {
return bookDao.selectById(id);
}

数据报表

jetcache还提供有简单的数据报表功能,帮助开发者快速查看缓存命中信息,只需要添加一个配置即可

jetcache:
statIntervalMinutes: 1

设置后,每1分钟在控制台输出缓存数据命中信息

[DefaultExecutor] c.alicp.jetcache.support.StatInfoLogger  : jetcache stat from 2022-02-28 09:32:15,892 to 2022-02-28 09:33:00,003
cache | qps| rate| get| hit| fail| expire| avgLoadTime| maxLoadTime
---------+-------+-------+------+-------+-------+---------+--------------+--------------
book_ | 0.66| 75.86%| 29| 22| 0| 0| 28.0| 188
---------+-------+-------+------+-------+-------+---------+--------------+--------------

总结

  1. jetcache是一个类似于springcache的缓存解决方案,自身不具有缓存功能,它提供有本地缓存与远程缓存多级共同使用的缓存解决方案
  2. jetcache提供的缓存解决方案受限于目前支持的方案,本地缓存支持两种,远程缓存支持两种
  3. 注意数据进入远程缓存时的类型转换问题
  4. jetcache提供方法缓存,并提供了对应的缓存更新与刷新功能
  5. jetcache提供有简单的缓存信息命中报表方便开发者即时监控缓存数据命中情况

j2cache基本操作

jetcache可以在限定范围内构建多级缓存,但是灵活性不足,不能随意搭配缓存,本节介绍一种可以随意搭配缓存解决方案的缓存整合框架,j2cache。下面就来讲解如何使用这种缓存框架,以Ehcache与redis整合为例:

步骤①:导入j2cache、redis、ehcache坐标

<dependency>
<groupId>net.oschina.j2cache</groupId>
<artifactId>j2cache-core</artifactId>
<version>2.8.4-release</version>
</dependency>
<dependency>
<groupId>net.oschina.j2cache</groupId>
<artifactId>j2cache-spring-boot2-starter</artifactId>
<version>2.8.0-release</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>

j2cache的starter中默认包含了redis坐标,官方推荐使用redis作为二级缓存,因此此处无需导入redis坐标

步骤②:配置一级与二级缓存,并配置一二级缓存间数据传递方式,配置书写在名称为j2cache.properties的文件中。如果使用ehcache还需要单独添加ehcache的配置文件

application.yml:

j2cache:
config-location: j2cache.properties

j2cache.properties

# 1级缓存
j2cache.L1.provider_class = ehcache
ehcache.configXml = ehcache.xml

# 2级缓存
j2cache.L2.provider_class = net.oschina.j2cache.cache.support.redis.SpringRedisProvider
j2cache.L2.config_section = redis
redis.hosts = localhost:6379

# 1级缓存中的数据如何到达二级缓存
j2cache.broadcast = net.oschina.j2cache.cache.support.redis.SpringRedisPubSubPolicy

此处配置不能乱配置,需要参照官方给出的配置说明进行。例如1级供应商选择ehcache,供应商名称仅仅是一个ehcache,但是2级供应商选择redis时要写专用的Spring整合Redis的供应商类名SpringRedisProvider,而且这个名称并不是所有的redis包中能提供的,也不是spring包中提供的。因此配置j2cache必须参照官方文档配置,而且还要去找专用的整合包,导入对应坐标才可以使用。

一级与二级缓存最重要的一个配置就是两者之间的数据沟通方式,此类配置也不是随意配置的,并且不同的缓存解决方案提供的数据沟通方式差异化很大,需要查询官方文档进行设置。

步骤③:使用缓存

@Service
public class SMSCodeServiceImpl implements SMSCodeService {
@Autowired
private CodeUtils codeUtils;

@Autowired
private CacheChannel cacheChannel;

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

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

j2cache的使用和jetcache比较类似,但是无需开启使用的开关,直接定义缓存对象即可使用,缓存对象名CacheChannel。

总结

  1. j2cache是一个缓存框架,自身不具有缓存功能,它提供多种缓存整合在一起使用的方案
  2. j2cache需要通过复杂的配置设置各级缓存,以及缓存之间数据交换的方式
  3. j2cache操作接口通过CacheChannel实现

j2cache相关配置

j2cache的使用不复杂,配置是j2cache的核心,毕竟是一个整合型的缓存框架。缓存相关的配置过多,可以查阅j2cache-core核心包中的j2cache.properties文件中的说明。如下:

#J2Cache configuration
#########################################
# Cache Broadcast Method
# values:
# jgroups -> use jgroups's multicast
# redis -> use redis publish/subscribe mechanism (using jedis)
# lettuce -> use redis publish/subscribe mechanism (using lettuce, Recommend)
# rabbitmq -> use RabbitMQ publisher/consumer mechanism
# rocketmq -> use RocketMQ publisher/consumer mechanism
# none -> don't notify the other nodes in cluster
# xx.xxxx.xxxx.Xxxxx your own cache broadcast policy classname that implement net.oschina.j2cache.cluster.ClusterPolicy
#########################################
j2cache.broadcast = redis

# jgroups properties
jgroups.channel.name = j2cache
jgroups.configXml = /network.xml

# RabbitMQ properties
rabbitmq.exchange = j2cache
rabbitmq.host = localhost
rabbitmq.port = 5672
rabbitmq.username = guest
rabbitmq.password = guest

# RocketMQ properties
rocketmq.name = j2cache
rocketmq.topic = j2cache
# use ; to split multi hosts
rocketmq.hosts = 127.0.0.1:9876

#########################################
# Level 1&2 provider
# values:
# none -> disable this level cache
# ehcache -> use ehcache2 as level 1 cache
# ehcache3 -> use ehcache3 as level 1 cache
# caffeine -> use caffeine as level 1 cache(only in memory)
# redis -> use redis as level 2 cache (using jedis)
# lettuce -> use redis as level 2 cache (using lettuce)
# readonly-redis -> use redis as level 2 cache ,but never write data to it. if use this provider, you must uncomment `j2cache.L2.config_section` to make the redis configurations available.
# memcached -> use memcached as level 2 cache (xmemcached),
# [classname] -> use custom provider
#########################################

j2cache.L1.provider_class = caffeine
j2cache.L2.provider_class = redis

# When L2 provider isn't `redis`, using `L2.config_section = redis` to read redis configurations
# j2cache.L2.config_section = redis

# Enable/Disable ttl in redis cache data (if disabled, the object in redis will never expire, default:true)
# NOTICE: redis hash mode (redis.storage = hash) do not support this feature)
j2cache.sync_ttl_to_redis = true

# Whether to cache null objects by default (default false)
j2cache.default_cache_null_object = true

#########################################
# Cache Serialization Provider
# values:
# fst -> using fast-serialization (recommend)
# kryo -> using kryo serialization
# json -> using fst's json serialization (testing)
# fastjson -> using fastjson serialization (embed non-static class not support)
# java -> java standard
# fse -> using fse serialization
# [classname implements Serializer]
#########################################

j2cache.serialization = json
#json.map.person = net.oschina.j2cache.demo.Person

#########################################
# Ehcache configuration
#########################################

# ehcache.configXml = /ehcache.xml

# ehcache3.configXml = /ehcache3.xml
# ehcache3.defaultHeapSize = 1000

#########################################
# Caffeine configuration
# caffeine.region.[name] = size, xxxx[s|m|h|d]
#
#########################################
caffeine.properties = /caffeine.properties

#########################################
# Redis connection configuration
#########################################

#########################################
# Redis Cluster Mode
#
# single -> single redis server
# sentinel -> master-slaves servers
# cluster -> cluster servers (数据库配置无效,使用 database = 0)
# sharded -> sharded servers (密码、数据库必须在 hosts 中指定,且连接池配置无效 ; redis://user:password@127.0.0.1:6379/0)
#
#########################################

redis.mode = single

#redis storage mode (generic|hash)
redis.storage = generic

## redis pub/sub channel name
redis.channel = j2cache
## redis pub/sub server (using redis.hosts when empty)
redis.channel.host =

#cluster name just for sharded
redis.cluster_name = j2cache

## redis cache namespace optional, default[empty]
redis.namespace =

## redis command scan parameter count, default[1000]
#redis.scanCount = 1000

## connection
# Separate multiple redis nodes with commas, such as 192.168.0.10:6379,192.168.0.11:6379,192.168.0.12:6379

redis.hosts = 127.0.0.1:6379
redis.timeout = 2000
redis.password =
redis.database = 0
redis.ssl = false

## redis pool properties
redis.maxTotal = 100
redis.maxIdle = 10
redis.maxWaitMillis = 5000
redis.minEvictableIdleTimeMillis = 60000
redis.minIdle = 1
redis.numTestsPerEvictionRun = 10
redis.lifo = false
redis.softMinEvictableIdleTimeMillis = 10
redis.testOnBorrow = true
redis.testOnReturn = false
redis.testWhileIdle = true
redis.timeBetweenEvictionRunsMillis = 300000
redis.blockWhenExhausted = false
redis.jmxEnabled = false

#########################################
# Lettuce scheme
#
# redis -> single redis server
# rediss -> single redis server with ssl
# redis-sentinel -> redis sentinel
# redis-cluster -> cluster servers
#
#########################################

#########################################
# Lettuce Mode
#
# single -> single redis server
# sentinel -> master-slaves servers
# cluster -> cluster servers (数据库配置无效,使用 database = 0)
# sharded -> sharded servers (密码、数据库必须在 hosts 中指定,且连接池配置无效 ; redis://user:password@127.0.0.1:6379/0)
#
#########################################

## redis command scan parameter count, default[1000]
#lettuce.scanCount = 1000
lettuce.mode = single
lettuce.namespace =
lettuce.storage = hash
lettuce.channel = j2cache
lettuce.scheme = redis
lettuce.hosts = 127.0.0.1:6379
lettuce.password =
lettuce.database = 0
lettuce.sentinelMasterId =
lettuce.maxTotal = 100
lettuce.maxIdle = 10
lettuce.minIdle = 10
# timeout in milliseconds
lettuce.timeout = 10000
# redis cluster topology refresh interval in milliseconds
lettuce.clusterTopologyRefresh = 3000

#########################################
# memcached server configurations
# refer to https://gitee.com/mirrors/XMemcached
#########################################

memcached.servers = 127.0.0.1:11211
memcached.username =
memcached.password =
memcached.connectionPoolSize = 10
memcached.connectTimeout = 1000
memcached.failureMode = false
memcached.healSessionInterval = 1000
memcached.maxQueuedNoReplyOperations = 100
memcached.opTimeout = 100
memcached.sanitizeKeys = false

例如: j2cache.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.hosts = localhost:6379

# 1级缓存中的数据如何到达二级缓存
j2cache.broadcast = net.oschina.j2cache.cache.support.redis.SpringRedisPubSubPolicy

redis.mode = single

redis.namespace = j2cache

总结:

  1. spring-cache
  • simple
  • ehcache
  • redis
  • memcached
  1. jetcache
  2. j2cache

springboot整合quartz

  • 定时任务是企业级应用中的常见操作
    • 年度报表
    • 缓存统计报告
    • … …
  • 市面上流行的定时任务技术
    • Quartz
    • Spring Task

学习springboot整合Quartz前先普及几个Quartz的概念。

  • 工作(Job):用于定义具体执行的工作
  • 工作明细(JobDetail):用于描述定时工作相关的信息
  • 触发器(Trigger):描述了工作明细与调度器的对应关系
  • 调度器(Scheduler):用于描述触发工作的执行规则,通常使用cron表达式定义规则

简单说就是你定时干什么事情,这就是工作,工作不可能就是一个简单的方法,还要设置一些明细信息。工作啥时候执行,设置一个调度器,可以简单理解成设置一个工作执行的时间。工作和调度都是独立定义的,它们两个怎么配合到一起呢?用触发器。完了,就这么多。下面开始springboot整合Quartz。

步骤①:导入springboot整合Quartz的starter

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

步骤②:定义任务Bean,按照Quartz的开发规范制作,继承QuartzJobBean

public class MyQuartz extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
System.out.println("quartz task run...");
}
}

步骤③:创建Quartz配置类,定义工作明细(JobDetail)与触发器的(Trigger)bean

@Configuration
public class QuartzConfig {
@Bean
public JobDetail printJobDetail(){
//绑定具体的工作
return JobBuilder.newJob(MyQuartz.class).storeDurably().build();
}
@Bean
public Trigger printJobTrigger(){
ScheduleBuilder schedBuilder = CronScheduleBuilder.cronSchedule("0/5 * * * * ?");
//绑定对应的工作明细
return TriggerBuilder.newTrigger().forJob(printJobDetail()).withSchedule(schedBuilder).build();
}
}

工作明细中要设置对应的具体工作,使用newJob()操作传入对应的工作任务类型即可。

触发器需要绑定任务,使用forJob()操作传入绑定的工作明细对象。此处可以为工作明细设置名称然后使用名称绑定,也可以直接调用对应方法绑定。触发器中最核心的规则是执行时间,此处使用调度器定义执行时间,执行时间描述方式使用的是cron表达式。有关cron表达式的规则,各位小伙伴可以去参看相关课程学习,略微复杂,而且格式不能乱设置,不是写个格式就能用的,写不好就会出现冲突问题。

总结

  1. springboot整合Quartz就是将Quartz对应的核心对象交给spring容器管理,包含两个对象,JobDetail和Trigger对象
  2. JobDetail对象描述的是工作的执行信息,需要绑定一个QuartzJobBean类型的对象
  3. Trigger对象定义了一个触发器,需要为其指定绑定的JobDetail是哪个,同时要设置执行周期调度器

springboot整合task

步骤①:开启定时任务功能,在引导类上开启定时任务功能的开关,使用注解@EnableScheduling

@SpringBootApplication
//开启定时任务功能
@EnableScheduling
public class Springboot22TaskApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot22TaskApplication.class, args);
}
}

步骤②:定义Bean,在对应要定时执行的操作上方,使用注解@Scheduled定义执行的时间,执行时间的描述方式还是cron表达式

@Component
public class MyBean {
@Scheduled(cron = "0/1 * * * * ?")
public void print(){
System.out.println(Thread.currentThread().getName()+" :spring task run...");
}
}

完事,这就完成了定时任务的配置。总体感觉其实什么东西都没少,只不过没有将所有的信息都抽取成bean,而是直接使用注解绑定定时执行任务的事情而已。

如何想对定时任务进行相关配置,可以通过配置文件进行

spring:
task:
scheduling:
pool:
# 任务调度线程池大小 默认 1
size: 1
# 调度线程名称前缀 默认 scheduling-
thread-name-prefix: ssm_
shutdown:
# 线程池关闭时等待所有任务完成
await-termination: false
# 调度线程关闭前最大等待时间,确保最后一定关闭
await-termination-period: 10s

发送简单邮件

springboot整合第三方技术第三部分我们来说说邮件系统,发邮件是java程序的基本操作,springboot整合javamail其实就是简化开发。不熟悉邮件的小伙伴可以先学习完javamail的基础操作,再来看这一部分内容才能感触到springboot整合javamail究竟简化了哪些操作。简化的多码?其实不多,差别不大,只是还个格式而已。

学习邮件发送之前先了解3个概念,这些概念规范了邮件操作过程中的标准。

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

简单说就是SMPT是发邮件的标准,POP3是收邮件的标准,IMAP是对POP3的升级。我们制作程序中操作邮件,通常是发邮件,所以SMTP是使用的重点,收邮件大部分都是通过邮件客户端完成,所以开发收邮件的代码极少。除非你要读取邮件内容,然后解析,做邮件功能的统一处理。例如HR的邮箱收到求职者的简历,可以读取后统一处理。但是为什么不制作独立的投递简历的系统呢?所以说,好奇怪的需求,因为要想收邮件就要规范发邮件的人的书写格式,这个未免有点强人所难,并且极易收到外部攻击,你不可能使用白名单来收邮件。如果能使用白名单来收邮件然后解析邮件,还不如开发个系统给白名单中的人专用呢,更安全,总之就是鸡肋了。下面就开始学习springboot如何整合javamail发送邮件。

步骤①:导入springboot整合javamail的starter

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>

步骤②:配置邮箱的登录信息

spring:
mail:
host: smtp.126.com
username: test@126.com
password: test

java程序仅用于发送邮件,邮件的功能还是邮件供应商提供的,所以这里是用别人的邮件服务,要配置对应信息。

host配置的是提供邮件服务的主机协议,当前程序仅用于发送邮件,因此配置的是smtp的协议。

password并不是邮箱账号的登录密码,是邮件供应商提供的一个加密后的密码,也是为了保障系统安全性。不然外部人员通过地址访问下载了配置文件,直接获取到了邮件密码就会有极大的安全隐患。有关该密码的获取每个邮件供应商提供的方式都不一样,此处略过。可以到邮件供应商的设置页面找POP3或IMAP这些关键词找到对应的获取位置。下例仅供参考:

image-20220228111251036

步骤③:使用JavaMailSender接口发送邮件

@Service
public class SendMailServiceImpl implements SendMailService {
@Autowired
private JavaMailSender javaMailSender;

//发送人
private String from = "test@qq.com";
//接收人
private String to = "test@126.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);
}
}

将发送邮件的必要信息(发件人、收件人、标题、正文)封装到SimpleMailMessage对象中,可以根据规则设置发送人昵称等。

发送多部件邮件

发送简单邮件仅需要提供对应的4个基本信息就可以了,如果想发送复杂的邮件,需要更换邮件对象。使用MimeMessage可以发送特殊的邮件。

发送网页正文邮件

@Service
public class SendMailServiceImpl2 implements SendMailService {
@Autowired
private JavaMailSender javaMailSender;

//发送人
private String from = "test@qq.com";
//接收人
private String to = "test@126.com";
//标题
private String subject = "测试邮件";
//正文
private String context = "<img src='ABC.JPG'/><a href='https://www.itcast.cn'>点开有惊喜</a>";

public void sendMail() {
try {
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message);
helper.setFrom(to+"(小甜甜)");
helper.setTo(from);
helper.setSubject(subject);
helper.setText(context,true); //此处设置正文支持html解析

javaMailSender.send(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}

发送带有附件的邮件

@Service
public class SendMailServiceImpl2 implements SendMailService {
@Autowired
private JavaMailSender javaMailSender;

//发送人
@Value("${spring.mail.username}")
private String from;
//接收人
private String to = "2564661125@qq.com";

private String[] tos = {"2564661125@qq.com", "bingomail@qq.com"};

//标题
private String subject = "测试邮件";
//正文
private String context = "<a href='https://blog.csdn.net/qq_42324086'>点开有惊喜</a>";

@Override
public void sendMail() {
try {
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
//MimeMessageHelper helper = new MimeMessageHelper(mimeMessage);
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
helper.setFrom(from + "(小甜甜)");
//helper.setTo(to);
//设置多个接收人
helper.setTo(tos);
helper.setSubject(subject);
helper.setText(context, true); //此处设置正文支持html解析

//添加附件
File f1 = new File("D:\\code\\Java\\IdeaProjects\\springboot-study\\springboot_23_mail\\target\\springboot_23_mail-0.0.1-SNAPSHOT.jar");
File f2 = new File("D:\\code\\Java\\IdeaProjects\\springboot-study\\springboot_23_mail\\src\\main\\resources\\logo.png");

helper.addAttachment(f1.getName(), f1);
helper.addAttachment("样图.png", f2);

javaMailSender.send(mimeMessage);
} catch (Exception e) {
e.printStackTrace();
}

}
}

总结: springboot整合javamail其实就是简化了发送邮件的客户端对象JavaMailSender的初始化过程,通过配置的形式加载信息简化开发过程

监控的意义

监控的意义

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

监控的实施方式

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

image-20220319153708074

小结:

  1. 监控是一个非常重要的工作,是保障程序正常运行的基础手段
  2. 监控的过程通过一个监控程序进行,它汇总所有被监控的程序的信息集中统一展示
  3. 被监控程序需要主动上报自己被监控,同时要设置哪些指标被监控

SpringBootAdmin

Spring Boot Admin,这是一个开源社区项目,用于管理和监控SpringBoot应用程序。这个项目中包含有客户端和服务端两部分,而监控平台指的就是服务端。我们做的程序如果需要被监控,将我们做的程序制作成客户端,然后配置服务端地址后,服务端就可以通过HTTP请求的方式从客户端获取对应的信息,并通过UI界面展示对应信息。

下面就来开发这套监控程序,先制作服务端,其实服务端可以理解为是一个web程序,收到一些信息后展示这些信息。

服务端开发

步骤①:导入springboot admin对应的starter,版本与当前使用的springboot版本保持一致,并将其配置成web工程

<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
<version>2.5.4</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

上述过程可以通过创建项目时使用勾选的形式完成。

在这里插入图片描述

步骤②:在引导类上添加注解@EnableAdminServer,声明当前应用启动后作为SpringBootAdmin的服务器使用

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

做到这里,这个服务器就开发好了,启动后就可以访问当前程序了,界面如下。

在这里插入图片描述

由于目前没有启动任何被监控的程序,所以里面什么信息都没有。下面制作一个被监控的客户端程序。

客户端开发

客户端程序开发其实和服务端开发思路基本相似,多了一些配置而已。

步骤①:导入springboot admin对应的starter,版本与当前使用的springboot版本保持一致,并将其配置成web工程

<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>2.5.4</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

上述过程也可以通过创建项目时使用勾选的形式完成,不过一定要小心,端口配置成不一样的,否则会冲突。

步骤②:设置当前客户端将信息上传到哪个服务器上,通过yml文件配置

spring:
boot:
admin:
client:
url: http://localhost:8080

做到这里,这个客户端就可以启动了。启动后再次访问服务端程序,界面如下。

在这里插入图片描述

可以看到,当前监控了1个程序,点击进去查看详细信息。

在这里插入图片描述

由于当前没有设置开放哪些信息给监控服务器,所以目前看不到什么有效的信息。下面需要做两组配置就可以看到信息了。

  1. 开放指定信息给服务器看

  2. 允许服务器以HTTP请求的方式获取对应的信息

    配置如下:

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

上述配置对于初学者来说比较容易混淆。简单解释一下,到下一节再做具体的讲解。springbootadmin的客户端默认开放了13组信息给服务器,但是这些信息除了一个之外,其他的信息都不让通过HTTP请求查看。所以你看到的信息基本上就没什么内容了,只能看到一个内容,就是下面的健康信息。

在这里插入图片描述

但是即便如此我们看到健康信息中也没什么内容,原因在于健康信息中有一些信息描述了你当前应用使用了什么技术等信息,如果无脑的对外暴露功能会有安全隐患。通过配置就可以开放所有的健康信息明细查看了。

management:
endpoint:
health:
show-details: always

健康明细信息如下:

在这里插入图片描述

目前除了健康信息,其他信息都查阅不了。原因在于其他12种信息是默认不提供给服务器通过HTTP请求查阅的,所以需要开启查阅的内容项,使用*表示查阅全部。记得带引号。

endpoints:
web:
exposure:
include: "*"

配置后再刷新服务器页面,就可以看到所有的信息了。

在这里插入图片描述

以上界面中展示的信息量就非常大了,包含了13组信息,有性能指标监控,加载的bean列表,加载的系统属性,日志的显示控制等等。

配置多个客户端

可以通过配置客户端的方式在其他的springboot程序中添加客户端坐标,这样当前服务器就可以监控多个客户端程序了。每个客户端展示不同的监控信息。

在这里插入图片描述

进入监控面板,如果你加载的应用具有功能,在监控面板中可以看到3组信息展示的与之前加载的空工程不一样。

  • 类加载面板中可以查阅到开发者自定义的类,如左图

在这里插入图片描述
在这里插入图片描述

  • 映射中可以查阅到当前应用配置的所有请求

在这里插入图片描述
在这里插入图片描述

  • 性能指标中可以查阅当前应用独有的请求路径统计数据

在这里插入图片描述
在这里插入图片描述

总结

  1. 开发监控服务端需要导入坐标,然后在引导类上添加注解@EnableAdminServer,并将其配置成web程序即可
  2. 开发被监控的客户端需要导入坐标,然后配置服务端服务器地址,并做开放指标的设定即可
  3. 在监控平台中可以查阅到各种各样被监控的指标,前提是客户端开放了被监控的指标

actuator

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

通过查阅监控中的映射指标,可以看到当前系统中可以运行的所有请求路径,其中大部分路径以/actuator开头

在这里插入图片描述

首先这些请求路径不是开发者自己编写的,其次这个路径代表什么含义呢?既然这个路径可以访问,就可以通过浏览器发送该请求看看究竟可以得到什么信息。

image-20220301170723057

通过发送请求,可以得到一组json信息,如下

{
"_links": {
"self": {
"href": "http://localhost:81/actuator",
"templated": false
},
"beans": {
"href": "http://localhost:81/actuator/beans",
"templated": false
},
"caches-cache": {
"href": "http://localhost:81/actuator/caches/{cache}",
"templated": true
},
"caches": {
"href": "http://localhost:81/actuator/caches",
"templated": false
},
"health": {
"href": "http://localhost:81/actuator/health",
"templated": false
},
"health-path": {
"href": "http://localhost:81/actuator/health/{*path}",
"templated": true
},
"info": {
"href": "http://localhost:81/actuator/info",
"templated": false
},
"conditions": {
"href": "http://localhost:81/actuator/conditions",
"templated": false
},
"shutdown": {
"href": "http://localhost:81/actuator/shutdown",
"templated": false
},
"configprops": {
"href": "http://localhost:81/actuator/configprops",
"templated": false
},
"configprops-prefix": {
"href": "http://localhost:81/actuator/configprops/{prefix}",
"templated": true
},
"env": {
"href": "http://localhost:81/actuator/env",
"templated": false
},
"env-toMatch": {
"href": "http://localhost:81/actuator/env/{toMatch}",
"templated": true
},
"loggers": {
"href": "http://localhost:81/actuator/loggers",
"templated": false
},
"loggers-name": {
"href": "http://localhost:81/actuator/loggers/{name}",
"templated": true
},
"heapdump": {
"href": "http://localhost:81/actuator/heapdump",
"templated": false
},
"threaddump": {
"href": "http://localhost:81/actuator/threaddump",
"templated": false
},
"metrics-requiredMetricName": {
"href": "http://localhost:81/actuator/metrics/{requiredMetricName}",
"templated": true
},
"metrics": {
"href": "http://localhost:81/actuator/metrics",
"templated": false
},
"scheduledtasks": {
"href": "http://localhost:81/actuator/scheduledtasks",
"templated": false
},
"mappings": {
"href": "http://localhost:81/actuator/mappings",
"templated": false
}
}
}

其中每一组数据都有一个请求路径,而在这里请求路径中有之前看到过的health,发送此请求又得到了一组信息

{
"status": "UP",
"components": {
"diskSpace": {
"status": "UP",
"details": {
"total": 297042808832,
"free": 72284409856,
"threshold": 10485760,
"exists": true
}
},
"ping": {
"status": "UP"
}
}
}

当前信息与监控面板中的数据存在着对应关系

在这里插入图片描述

原来监控中显示的信息实际上是通过发送请求后得到json数据,然后展示出来。按照上述操作,可以发送更多的以/actuator开头的链接地址,获取更多的数据,这些数据汇总到一起组成了监控平台显示的所有数据。

到这里我们得到了一个核心信息,监控平台中显示的信息实际上是通过对被监控的应用发送请求得到的。那这些请求谁开发的呢?打开被监控应用的pom文件,其中导入了springboot admin的对应的client,在这个资源中导入了一个名称叫做actuator的包。被监控的应用之所以可以对外提供上述请求路径,就是因为添加了这个包。

image-20220301171437817

这个actuator是什么呢?这就是本节要讲的核心内容,监控的端点。

Actuator,可以称为端点,描述了一组监控信息,SpringBootAdmin提供了多个内置端点,通过访问端点就可以获取对应的监控信息,也可以根据需要自定义端点信息。通过发送请求路劲/actuator可以访问应用所有端点信息,如果端点中还有明细信息可以发送请求/actuator/端点名称来获取详细信息。以下列出了所有端点信息说明:

ID 描述 默认启用
auditevents 暴露当前应用程序的审计事件信息。
beans 显示应用程序中所有 Spring bean 的完整列表。
caches 暴露可用的缓存。
conditions 显示在配置和自动配置类上评估的条件以及它们匹配或不匹配的原因。
configprops 显示所有 @ConfigurationProperties 的校对清单。
env 暴露 Spring ConfigurableEnvironment 中的属性。
flyway 显示已应用的 Flyway 数据库迁移。
health 显示应用程序健康信息
httptrace 显示 HTTP 追踪信息(默认情况下,最后 100 个 HTTP 请求/响应交换)。
info 显示应用程序信息。
integrationgraph 显示 Spring Integration 图。
loggers 显示和修改应用程序中日志记录器的配置。
liquibase 显示已应用的 Liquibase 数据库迁移。
metrics 显示当前应用程序的指标度量信息。
mappings 显示所有 @RequestMapping 路径的整理清单。
scheduledtasks 显示应用程序中的调度任务。
sessions 允许从 Spring Session 支持的会话存储中检索和删除用户会话。当使用 Spring Session 的响应式 Web 应用程序支持时不可用。
shutdown 正常关闭应用程序。
threaddump 执行线程 dump。
heapdump 返回一个 hprof 堆 dump 文件。
jolokia 通过 HTTP 暴露 JMX bean(当 Jolokia 在 classpath 上时,不适用于 WebFlux)。
logfile 返回日志文件的内容(如果已设置 logging.file 或 logging.path 属性)。支持使用 HTTP Range 头来检索部分日志文件的内容。
prometheus 以可以由 Prometheus 服务器抓取的格式暴露指标。

上述端点每一项代表被监控的指标,如果对外开放则监控平台可以查询到对应的端点信息,如果未开放则无法查询对应的端点信息。通过配置可以设置端点是否对外开放功能。使用enable属性控制端点是否对外开放。其中health端点为默认端点,不能关闭。

management:
endpoint:
health: # 端点名称
show-details: always
info: # 端点名称
enabled: true # 是否开放

为了方便开发者快速配置端点,springboot admin设置了13个较为常用的端点作为默认开放的端点,如果需要控制默认开放的端点的开放状态,可以通过配置设置,如下:

management:
endpoints:
enabled-by-default: true # 是否开启默认端点,默认值true

上述端点开启后,就可以通过端点对应的路径查看对应的信息了。但是此时还不能通过HTTP请求查询此信息,还需要开启通过HTTP请求查询的端点名称,使用“*”可以简化配置成开放所有端点的WEB端HTTP请求权限。

management:
endpoints:
web:
exposure:
include: "*"

整体上来说,对于端点的配置有两组信息,一组是endpoints开头的,对所有端点进行配置,一组是endpoint开头的,对具体端点进行配置。

management:
endpoint: # 具体端点的配置
health:
show-details: always
info:
enabled: true
endpoints: # 全部端点的配置
web:
exposure:
include: "*"
enabled-by-default: true

总结

  1. 被监控客户端通过添加actuator的坐标可以对外提供被访问的端点功能
  2. 端点功能的开放与关闭可以通过配置进行控制
  3. web端默认无法获取所有端点信息,通过配置开放端点功能

info端点指标控制

info端点描述了当前应用的基本信息,可以通过两种形式快速配置info端点的信息

  • 配置形式

    在yml文件中通过设置info节点的信息就可以快速配置端点信息

    info:
    appName: @project.artifactId@
    version: @project.version@
    company: 传智教育
    author: itheima

    配置完毕后,对应信息显示在监控平台上

    img

    也可以通过请求端点信息路径获取对应json信息

在这里插入图片描述

  • 编程形式

    通过配置的形式只能添加固定的数据,如果需要动态数据还可以通过配置bean的方式为info端点添加信息,此信息与配置信息共存

    @Component
    public class InfoConfig implements InfoContributor {
    @Override
    public void contribute(Info.Builder builder) {
    //添加单个信息
    builder.withDetail("runTime", new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss").format(new Date()));
    Map infoMap = new HashMap();
    infoMap.put("buildTime", "2006");
    builder.withDetails(infoMap); //添加一组信息
    }
    }

health端点指标控制

health端点描述当前应用的运行健康指标,即应用的运行是否成功。通过编程的形式可以扩展指标信息。

@Component
public class HealthConfig extends AbstractHealthIndicator {
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
boolean condition = true;
if(condition) {
builder.status(Status.UP); //设置运行状态为启动状态
builder.withDetail("runTime", System.currentTimeMillis());
Map infoMap = new HashMap();
infoMap.put("buildTime", "2006");
builder.withDetails(infoMap);
}else{
builder.status(Status.OUT_OF_SERVICE); //设置运行状态为不在服务状态
builder.withDetail("上线了吗?","你做梦");
}
}
}

当任意一个组件状态不为UP时,整体应用对外服务状态为非UP状态。

在这里插入图片描述

metrics端点指标控制

metrics端点描述了性能指标,除了系统自带的监控性能指标,还可以自定义性能指标。

@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;
}
}

在性能指标中就出现了自定义的性能指标监控项

在这里插入图片描述

自定义端点(实用开发篇完结)

可以根据业务需要自定义端点,方便业务监控

@Component
@Endpoint(id="pay",enableByDefault = true)
public class PayEndpoint {
@ReadOperation
public Object getPay(){
Map payMap = new HashMap();
payMap.put("level 1","300");
payMap.put("level 2","291");
payMap.put("level 3","666");
return payMap;
}
}

由于此端点数据spirng boot admin无法预知该如何展示,所以通过界面无法看到此数据,通过HTTP请求路径可以获取到当前端点的信息,但是需要先开启当前端点对外功能,或者设置当前端点为默认开发的端点。

在这里插入图片描述

小结

  1. 端点的指标可以自定义,但是每种不同的指标根据其功能不同,自定义方式不同
  2. info端点通过配置和编程的方式都可以添加端点指标
  3. health端点通过编程的方式添加端点指标,需要注意要为对应指标添加启动状态的逻辑设定
  4. metrics指标通过在业务中添加监控操作设置指标
  5. 可以自定义端点添加更多的指标

端口被占用解决