SpringBoot

学习来源于多方结合:狂神说、黑马

简介

相关文档

  1. Spring Boot Reference Documentation

为什么从spring升级到spring boot?

spring的缺点:

  1. 配置繁琐

    虽然Spring的组件代码是轻量级的,但它的配置却是重量级的。一开始,Spring用XML配置,而且是很多XML配置。Spring2.5引入了基于注解的组件扫描,这消除了大量针对应用程序自身组件的显式XML配置Spring3.0引入了基于Java的配置,这是一种类型安全的可重构配置方式,可以代替XML。所有这些配置都代表了开发时的损耗。因为在思考Sphr管资 解决业务问题之间需要进行思维切换,所以编写配置挤占了编写应用程序逻辑的时间。和所有框架一样,Spring实用,但它要求的回报也不少

  2. 依赖繁琐

    项目的依赖管理也是一件耗时耗力的事情。在环境搭建时,需要分析要导入哪些库的坐标,而且还需要分析导入与之有依赖关系的其他库的坐标,一旦选错了依赖的版本,随之而来的不兼容问题就会严重阻碍项目的开发进度(比如我在tomcat版本选择上就被折腾好久)

springboot功能

  1. 自动配置
    Spring Boot的自动配置是一个运行时(更准确地说,是应用程序启动时)的过程,考虑了众多因素,才决定Spring配置应该用哪个,不该用哪个。该过程是SpringBoot自动完成的。

  2. 起步依赖
    起步依赖本质上是一个Maven项目对象模型(ProjectObject Model,POM),定义了对其他库的传递依赖这些东西加在一起即支持某项功能。
    简单的说,起步依赖就是将具备某种功能的坐标打包到一起,并提供一些默认的功能。

  3. 辅助功能

    提供了一些大型项自中常见的非功能性特性,如嵌入式服务器、安全、指标,健康检测、外部配置等

第一个SpringBoot程序

原理 -》

  1. 6、Springboot自动装配原理_哔哩哔哩_bilibili

  2. 7、了解下主启动类怎么运行_哔哩哔哩_bilibili

  3. 狂神说SpringBoot02:运行原理初探 (qq.com)

  4. p12- 自动装配原理再理解

  5. b站雷神进行复盘学习

四大入门了解点

parent和starter是关乎包依赖与管理的相关内容,引导类主要用于初始化SpringBoot容器,内嵌tomcat可以免去我们配置服务器的繁琐过程

  1. parent

    1
    2
    3
    4
    5
    6
    7
    8
    <!--todo 父依赖,详细依赖可通过relativePath查看
    以后我们导入依赖默认是不需要写版本;但是如果导入的包没有在依赖中管理着就需要手动配置版本了-->
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.3</version>
    <relativePath/> <!-- lookup parent from repository -->
    </parent>
  2. starter

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <!--todo 帮我们导入了web模块正常运行所依赖的组件-->
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
  3. 引导类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package com.example.springboot01helloworld;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;

    @SpringBootApplication
    public class SpringBoot01HelloWorldApplication {

    public static void main(String[] args) {
    //todo 1. SpringBoot 工程提供引导类来启动程序
    //todo 2. SpringBoot 工程启动后创建并初始化Spring容器
    ConfigurableApplicationContext context = SpringApplication.run(SpringBoot03WebApplication.class, args);
    Hello bean = context.getBean(Hello.class);
    System.out.println("bean ==>" + bean);
    }

    }
  4. 内嵌tomcat

    image-20220903155548340

    若想更换其他服务器,如jetty

    image-20220903155615377

    内嵌Tomcat服务器是SpringBoot辅助功能之一

    内嵌Tomcat工作原理是将Tomcat服务器作为对象运行,并将该对象交给Spring容器管理

    变更内嵌服务器思想是去除现有服务器,添加全新的服务器

SpringBoot配置

01、02

1
2
3
# todo  更改项目的端口号
server.port=8866
# todo banner.txt 可以更改项目启动logo,有趣极了

可以通过自定义banner.txt来创建diy启动图标,命名必须为banner

image-20220919101501221

JSR303

  1. 在pom文件中引入校验包

    1
    2
    3
    4
    5
    6
    <!--todo 引入校验包-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
    <version>2.2.13.RELEASE</version>
    </dependency>
  2. 类中注解表示需要校验

    1
    2
    @Validated //todo 数据校验
    public class Person {}
  3. 需要校验的变量进行注解,message为错误提示

    1
    2
    @Email(message = "邮箱格式错误")
    private String name;

    yaml数据格式

  • 大小写敏感
  • 属性层级关系使用多行描述,每行结尾使用冒号结束
  • 使用缩进表示层级关系, 同层级左侧对齐,只允许使用空格(不允许使用Tab键)
  • 属性值前面添加空格 (属性名与属性值之间使用冒号+空格作为分隔)
  • #表示注释

语法规则:

最好按照规范书写 (比如字符串就用双引号括起来,不要直接书写,因为可能会被解析为进制数字),否则可能会出现意料之外的错误

1
2
3
4
5
6
7
8
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 #日期必须使用yyy-MM-dd格式
datetime: 2018-02-17T15:02:31+08:00 #时间和日期之间使用T连接, 最后使用+代表时区

数组以及对象的书写方式(简写方式类似于json)

1
2
3
4
5
6
7
8
9
10
11
12
13
#对象
student:
name: hh
age: 18
#对象行内写法
student2: {name: hh, age: 18}
#数组
pets:
- dog
- cat
- pig
#数组行内写法
pets2: [dog,cat,pig]

SpringBoot相关配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 项目端口号
server:
port: 8866
# todo 项目根路径
servlet:
context-path: /hello

# todo 使用${属性名}引用频繁复用的数据
baseDir: c:\wein10
tempDir: ${baseDir}\temp

# todo 当需要使用转义符时,请使用双引号字符串(**单引号字符串是会忽略转义字符的**)进行包裹
testString: "\thhh\t1"

# todo 自定义图片,会自动转成二进制码图片,并设置为启动图标
spring:
banner:
image:
location: test.jpg

# todo 日志
logging.level.root: info #info、error、debug

注入到配置类

  1. 创建一个类并注入到spring容器中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //todo 定义为Spring管理的bean
    @Component
    @Data
    public class Person {
    private String name;
    private int age;
    private Boolean happy;
    private Date birthday;
    private Map<String, Object> maps;
    private List<Object> lists;
    private Dog dog;
    }
  2. 在配置文件中定义其相关属性,属性分别对应类的各个成员变量,若无对应值则默认为null

    其中:random.可以产生随机数,这里产生一个随机uuid、person.dogName:hello可以进行二元判断,若person.dogName有赋值则取,若无则使用hello_旺财

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    person:
    #插入语句
    name: nihao${random.uuid}
    age: 18
    happy: false
    birthday: 2022/09/02
    maps: {k1: v1, k2: v2}
    lists: [girl,game,sleep]
    dog:
    #判断语句
    name: ${person.dogName:hello}_旺财
    age: 3
  3. 在类中注入该属性@ConfigurationProperties(prefix = "属性名")

    1
    2
    3
    4
    5
    6
    //todo 定义为Spring管理的bean
    @Component
    //todo 引用指定属性
    @ConfigurationProperties(prefix = "person")
    public class Person {
    }

    若是采用properties类型的配置文件,则使用以下方式配置

1
2
3
4
5
6
7
8
9
//todo 定义为Spring管理的bean
@Component
//todo 引用指定属性
@PropertySource(value = "classpath:test.properties")//todo 加载指定配置文件和@Value("${name}")配合
public class Person {
@Value("${name}")
private String name;
//...
}

test.properties

1
name=pig

多环境配置以及配置环境设置

当有多个application.yml配置文件时,应创建的目录以及优先级(按图中所示进行优先级排序)如下

image-20220903094856338

作用:

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

各级文件作用

  1. 项目类路径配置文件:服务于开发人员本机开发与测试
  2. 项目类路径config目录中配置文件:服务于项目经理整体调控
  3. 工程路径配置文件:服务于运维人员配置涉密线上环境
  4. 工程路径config目录中配置文件:服务于运维经理整体调控

properties、yml、yaml的优先级(配置文件)

  • properties > yml > yaml
  • 同个配置会按上面排序进行优先级,不同配置,即使处于不同文件里,都可以生效

多环境配置

  1. 方案一:声明不同文件进行存储

    测试环境

    application-test.properties

    1
    server.port=8086

    开发环境

    application-dev.properties

    1
    server.port=8088

    主文件使用

    application.yml

    1
    2
    3
    spring:
    profiles:
    active: test

    结果:image-20220903100637165

    优点

    1. 可以使用独立配置文件定义环境属性
    2. 独立配置文件便于线.上系统维护更新并保障系统安全性
  2. 方案二

    直接在当个文件中定义不同环境

    application.yml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    server:
    port: 8084
    spring:
    profiles:
    active: test

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

    #测试环境
    ---
    server:
    port: 8099
    spring:
    profiles: test

根据功能对配置文件中的信息进行拆分,并制作成独立的配置文件

命名规则如下

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

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

1
2
3
4
spring:
profiles:
active: dev
include: devMVC,devDB

针对不同环境对应的不同配置文件,可以使用下面的方法

1
2
3
4
5
6
7
spring:
profiles:
active: dev
#include: devMVC,devDB
group:
dev: devMVC, devDB
pro: proMVC, proDB

注意,上述两种方式在加载时文件的加载顺序不一样

如果在虚拟机可以使用以下方式指定环境:-Dspringprofiles.active=dev

在命令行的话可以通过参数java-jarxxx.jar --spring.profiles.active=dev去激活想要的配置

Maven与SpringBoot多环境冲突现象解决方案

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

    pom.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <profiles>
    <profile>
    <id>env_dev</id>
    <properties>
    <profile.active>dev</profile.active>
    </properties>

    </profile>
    <profile>
    <id>env_pro</id>
    <properties>
    <profile.active>pro</profile.active>
    </properties>
    <activation>
    <activeByDefault>true</activeByDefault>
    </activation>
    </profile>
    </profiles>

    application.yml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    server:
    port: 8868
    spring:
    profiles:
    active: @profile.active@
    #include: devMVC,devDB
    group:
    dev: devMVC, devDB
    pro: proMVC, proDB

  2. 基于SpringBoot 读取Maven配置属性的前提下,如果在Idea下测试工程时pom.xml每次更新需要手动compile方可生效

profile外部配置方式

打包后代码里面的config和根路径的properties是不会一起打包的,但可以在jar包路径下同个相对路径进行配置,优先级同开发阶段一致

Web开发

jar: webapp

自动装配

  1. 在SpringBoot中,我们可以使用以下方法处理静态资源
    • webjars
    • public
    • static
    • /**
    • resources
  2. 优先级resources > static > public

可以通过自定义默认路径处理静态资源文件

application.properties

1
spring.mvc.static-path-pattern=/hello/,classpath:/example/

整合junit

04

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* todo SpringBoot的测试类应该使用SpringBootTest进行注解
* 当测试类不在引导包同个包下或其子包下就会报错
* 解决方式就是显式指定引导包@SpringBootTest(classes = SpringBoot04JunitApplication.class)
* 底层是通过@ContextConfiguration(classes = SpringBoot04JunitApplication.class)去定位包
* 总结:
* 1.测试类如果存在于引导类所在包或子包中无需指定引导类
* 2.测试类如果不存在于引导类所在的包或子包中需要通过classes属性指定引导类
*/
@SpringBootTest
//@ContextConfiguration(classes = SpringBoot04JunitApplication.class)
class SpringBoot04JunitApplicationTests {

@Autowired
private BookDao bookDao;
@Test
void contextLoads() {
bookDao.sayHi();
}

}

整合mybatisc

05

整合到springboot的依赖一般使用方式为

  1. 导包
  2. 配置
  3. 使用

整合mybatis-plus

06

整合Druid

05

综合案例SSM

07

步骤

  • 实体类开发:使 用Lombok快速制作实体类

  • Dao开发:整合MyBatisPlus, 制作数据层测试类

  • Service开发:基于MyBatisPlus进行增量开发,制作业务层测试类 ➡ BookServiceImpl

    • Service接口名称定义成业务名称,并与Dao接口名称进行区分
    • 制作测试类测试Service功能是否有效
    • 快速开发方案 ➡ BookService2Impl
      • 使用MyBatisPlus提供有 业务层通用接口(ISerivce) 与业务层通用实现类(ServiceImpl<M,T>)
      • 在通用类 基础上做功能重载或功能追加
      • 注意重载时不要覆盖原始操作,避免原始提供的功能丢失
  • Controller开发:基于Restfu1开发,使用PostMan测试接口功能 -> BookController

  • Controller开发: 前后端开发协议制作 -> BookController02

    1. 设计统一的返回值结果类型便于前端开发读取数据

    2. 返回值结果类型可以根据需求自行设定,没有固定格式

    3. 返回值结果模型类用于后端与前端进行数据格式统一,也称为前后端数据协议

      1
      2
      3
      4
      {
      "flag": true,
      "data": []
      }

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

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

    • 列表、 新增、修改、删除.分页、查询
  • 项目异常处理

  • 按条件查询:页面功能调整、Controller修正功能、Service修正功能

运维实用

打包(windows)

  1. 对项目进行打包

    1. 使用idea

      可以通过箭头所指方向对test指令进行屏蔽,避免打包过程执行一堆不希望执行的test操作

      image-20220905143835365

    2. 使用cmd

      1
      mvn package
  2. 打包文件为一个jar包,执行该文件可以通过cmd进行

    1
    java -jar jar包名.jar

    指定端口(可以通过–命令去更改属性,属性名格式同yml配置文件)

    1
    java -jar jar包名.jar --server.port=8868
  3. 注意:jar支持命令行启动需要依赖maven插件支持,请确认打包时是否具有SpringBoot对应的maven插件,否则运行该包会出现异常(不过一般情况下创建项目就会自动引入该包了)

    1
    2
    3
    4
    5
    6
    7
    8
    <build>
    <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
    </plugins>
    </build>

    win环境下端口的命令行操作

  4. 查询端口
    netstat -ano

  5. 查询指定端口
    netstat -ano |findstr "端口号"

  6. 根据进程PID查询进程名称
    tasklist |findstr "进程PID号"

  7. 根据PID杀死任务
    taskkill /F /PID "进程PID号”

  8. 根据进程名称杀死任务
    taskkill -f -t -im "进程名称"

临时属性

  1. 使用jar命令启动SpringBoot工程时可以使用临时属性替换配置文件中的属性
  2. 临时属性添加方式: java -jar工程名.jar --属性名=值
  3. 多个临时属性之间使用空格分隔
  4. 临时属性必须是当前boot工程支持的属性,否则设置无效

临时属性在idea下的测试

image-20220906085212113

在程序的主入口函数,我们可以看到args形参,args为外部临时环境的传入参数存储的数组

若不想外部设置环境参数可以在run方法内去掉args

1
2
3
4
5
6
7
8
9
10
11
@SpringBootApplication
public class SSMbuildApplication {

public static void main(String[] args) {
args = new String[1];
args[0] = "--server.port=8868";
System.out.println(args); //todo args为外部临时环境的传入参数存储的数组,若不想外部设置环境参数可以在run方法内去掉args
SpringApplication.run(SSMbuildApplication.class, args);
}

}

打包(Linux)

运维实用篇-53-Boot工程快速启动(Linux版)_哔哩哔哩_bilibili

自定义配置文件

当前路径

--spring.config.name=文件名

指定路径

--spring.config.location=classpath:/文件名.文件后缀

多环境开发

日志

日志输出格式

image-20220906221659366

开发实用

热部署

手动启动热部署

  1. 首先定义启用热部署的坐标-开发者工具

    1
    2
    3
    4
    5
    <!--todo 热部署-sp相关开发者工具坐标添加-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    </dependency>
  2. 使用方式:在修改源程序后,选择以下选项进行手动热部署

    image-20220907091341180

    或者,快捷键:ctrl + f9

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

自动启动热部署

  1. 在设置中打开相关选项

    image-20220907093447053

  2. ctrl + alt + shift + / 选择第一个选项,打开下面窗口,将下面选项勾选

    image-20220907093647618

  3. 重启使用即可

热部署范围配置

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

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

配置不参与热部署的文件或文件夹

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

其中:

  1. 文件夹设置方式为:文件夹名/**
  2. 文件设置方式:文件相对路径/文件名.后缀

关闭热部署

  1. 方式一:yml文件配置,缺点是你配置的可能会被别人覆盖

    1
    2
    3
    4
    5
    6
    spring:
    #设置不参与热部署的文件或文件夹
    devtools:
    restart:
    #禁用热部署
    enabled: false
  2. 方式二:在应用主启动程序禁用该选项

    1
    2
    3
    4
    5
    6
    7
    8
    @SpringBootApplication
    public class SpringBoot10HotDeployApplication {
    public static void main(String[] args) {
    //todo 禁用热部署
    System.setProperty("spring.devtools.restart.enabled", "false");
    SpringApplication.run(SpringBoot10HotDeployApplication.class, args);
    }
    }

    配置高级

@ConfigurationProperties

将配置文件属性注入到自定义类中

  1. 自定义类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    package com.example.config;

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

    //@Component
    @Data
    @ConfigurationProperties(prefix = "servers")
    public class ServerConfig {
    private String ipAddress;
    private int port;
    private long timeout;
    }
  2. 配置文件属性

    1
    2
    3
    4
    servers:
    ipAddress: https://192.168.0.0
    port: 8888
    timeout: 3000
  3. 在主应用程序进行测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @SpringBootApplication
    @EnableConfigurationProperties(ServerConfig.class)
    public class SpringBoot11ConfigurationApplication {

    public static void main(String[] args) {

    ConfigurableApplicationContext ctx = SpringApplication.run(SpringBoot11ConfigurationApplication.class, args);
    ServerConfig bean = ctx.getBean(ServerConfig.class);
    System.out.println(bean);
    }

    }

    引入第三方包进行属性配置(以druid为例)

  4. 引包

    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.11</version>
    </dependency>
  5. 在SpringBoot11ConfigurationApplication进行注入

    1
    2
    3
    4
    5
    6
    7
    @Bean
    @ConfigurationProperties(prefix = "datasource")//配置文件注入
    public DruidDataSource dataSource() {
    DruidDataSource ds = new DruidDataSource();
    //ds.setDriverClassName("test123");//java方式注入
    return ds;
    }
  6. 如选择配置文件注入,则在配置文件设置属性

    1
    2
    datasource:
    driverClassName: test123456
  7. 测试,同自定义类方式一致

    1
    2
    DruidDataSource bean1 = ctx.getBean(DruidDataSource.class);
    System.out.println(bean1.getDriverClassName());

@EnableConfigurationProperties

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

拿的前面自定义类的样例

1
2
3
4
5
//@Component //被注入目标定义EnableConfigurationProperties则可以省去,否则出现两个同名bean会报错
@Data
@ConfigurationProperties(prefix = "servers")
public class ServerConfig {
}
1
2
3
4
@SpringBootApplication
@EnableConfigurationProperties(ServerConfig.class) //将指定类注入
public class SpringBoot11ConfigurationApplication {
}

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

测试遇到问题的解决方式

image-20220907170959496

宽松绑定/松散绑定

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

1
2
3
4
5
6
7
8
9
servers:
#ipAddress: https://192.168.0.0 #驼峰
#ipaddress: https://192.168.0.0
#ip-address: https://192.168.0.0 #烤肉串模式
#ip_address: https://192.168.0.0 #unline
#IP-ADDRESS: https://192.168.0.0
#IP_ADDRESS: https://192.168.0.0 #常量
#IPADDRESS: https://192.168.0.0
I-P-a_ddre_ss: https://192.168.0.1

以上所有格式都可以被识别,但最推荐的为烤肉串模式,其他有注解的格式也推荐

注意:

  1. 宽松绑定不支持注解@Value引用单个属性的方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @SpringBootTest
    class SpringBoot11ConfigurationApplicationTests {
    @Value("${servers.ipAddress}") //宽松绑定不支持注解@Value引用单个属性的方式
    private String msg;

    @Test
    void contextLoads() {
    System.out.println(msg);
    }
    }
  2. 绑定前缀名命名规范:仅能使用纯小写字母、数字、下划线作为合法的字符

    1
    2
    3
    4
    5
    6
    @ConfigurationProperties(prefix = "datasource")//绑定前缀名命名规范:仅能使用纯小写字母、数字、下划线作为合法的字符
    public DruidDataSource dataSource() {
    DruidDataSource ds = new DruidDataSource();
    //ds.setDriverClassName("test123");
    return ds;
    }

    常用计量单位绑定

1
2
3
4
5
6
//设置数据格式:时间数据格式
@DurationUnit(ChronoUnit.DAYS)
private Duration serverTimeout;
//设置数据类型大小格式:MB
@DataSizeUnit(DataUnit.MEGABYTES)
private DataSize dataSize;

数据校验

补充JSR303

步骤

  1. 导入JSR303与Hibernate校验框架坐标

    1
    2
    3
    4
    5
    6
    7
    8
    <dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    </dependency>
    <dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    </dependency>
  2. 使用@Validated注解启用校验功能

    1
    2
    3
    4
    @Data
    @ConfigurationProperties(prefix = "servers")
    @Validated
    public class ServerConfig {}
  3. 使用具体校验规则规范数据校验格式

    1
    2
    3
    4
    5
    6
    7
    8
    @Data
    @ConfigurationProperties(prefix = "servers")
    @Validated
    public class ServerConfig {
    private String ipAddress;
    @Max(value = 1000, message = "最大值不能超过8888") //校验
    private int port;
    }

    获取配置文件属性值

第一种方式:直接通过注解获取

1
2
3
4
5
@Value"$(person.name)"
private String name2;

@Value("$(person.age)")
private int age;

第二种:通过注入enviroment环境变量去获取并读取

1
2
@Autowired
private Environment env;

第三种:通过ConfigurationProperties注解获取引用的数据

1
2
3
@Component
@ConfigurationProperties(prefix="person")
public class Person{}

测试

加载测试专用属性

  1. 注入属性

    1
    @SpringBootTest(properties = "test.prop = value2")
  2. 注入命令行形式属性

    1
    @SpringBootTest(args = {"--test.prop=value3"})
  3. 直接读取配置文件属性

    1
    2
    @Value("${test.prop}") //todo 1.手动注入配置文件
    private String testV; //todo 读取属性

    这三种的优先级为1>2>3

加载测试专用配置

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

  1. 先定义一个配置型测试类

    1
    2
    3
    4
    5
    6
    7
    8
    @Configuration
    public class MsgConfig {
    //todo 定义一个字符串型的bean
    @Bean
    public String msg() {
    return "bean msg";
    }
    }
  2. 测试类注入该bean,使用注解Import加载当前测试类专用配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @SpringBootTest
    @Import({MsgConfig.class})
    public class ConfigurationTest {
    @Autowired
    private String msg;

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

    Web环境模拟测试

在需要测试的类中的@SpringBootTest注解里面添加以下属性webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT

随机端口

1
2
3
4
5
6
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class WebTest {
@Test
public void test() {
}
}

自定义端口

1
2
3
4
5
6
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class WebTest {
@Test
public void test() {
}
}

其他测试 -> p80-p83(黑马程序员SpringBoot2全套视频教程

image-20220908114808052

image-20220908114815467

数据层测试回滚

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

1
2
3
@SpringBootTest
@Transactional //todo 添加事务,默认会设置为自动回滚,所以下面的rollback可加可不加
public class BoosServiceTest{}

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

1
2
3
4
@SpringBootTest
@Transactional //todo 添加事务,默认会设置为自动回滚,所以下面的rollback可加可不加
@Rollback(true) //todo 设置事务回滚,默认即为true
public class BoosServiceTest{}

测试用例数据设定

定义数据

1
2
3
4
5
6
7
test:
book:
id: ${random.int}
name: ${random.value}
uuid: ${random.uuid}
time: ${random.long}
value: ${random.int(1,10)} #数据范围(1,10)

新建实体类,注册为组件并将配置文件属性注入

1
2
3
4
5
6
7
8
9
10
@Data
@Component
@ConfigurationProperties(prefix = "test.book")
public class TestBook {
private int id;
private String name;
private String uuid;
private long time;
private int value;
}

测试

1
2
3
4
5
6
7
8
9
@SpringBootTest
public class dataTest {
@Autowired
private TestBook testBook;
@Test
public void testData() {
System.out.println(testBook);
}
}

数据层解决方案

SQL

数据源

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

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

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

1
2
3
4
5
6
7
8
9
# todo 配置数据库相关信息
spring:
datasource:
#url: jdbc:mysql://localhost:3306/ssmbuild?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
url: jdbc:mysql://localhost:3306/ssmbuild
hikari:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 333333
JDBCTemplate-数据层解决方案

todo:内置持久化解决方案一JdbcTemplate

  1. 导包

    1
    2
    3
    4
    5
    <!--todo 引入jdbcTemplate-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
  2. 默认查询使用方式

    1
    2
    3
    4
    5
    6
    @Test
    void test1(@Autowired JdbcTemplate jdbcTemplate) {
    String sql = "select * from books";
    List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
    System.out.println(maps);
    }
  3. 自定义返回格式查询方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @Test
    void test2(@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);
    }
  4. 新增数据

    1
    2
    3
    4
    5
    @Test
    void test3(@Autowired JdbcTemplate jdbcTemplate) {
    String sql = "insert into tbl_book values(null ,'1','2','3')";
    jdbcTemplate.update(sql);
    }
    内嵌数据库

实用开发篇-88-H2数据库_哔哩哔哩_bilibili

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

  • H2
  • HSQL
  • Derby

image-20220909154305131

image-20220909154316369

image-20220909154321034

NoSQL

市面上常见的NoSQL解决方案

整合第三方技术

缓存

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

模拟缓存:使用成员变量存储已查询数据,下次查询时直接取出即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Service
public class BookServiceImpl implements BookService {
private HashMap<Integer, Book> cacheBooks = new HashMap<Integer, Book>();

@Override
public Book getBookById(int id) {
//todo 模拟缓存
Book book = cacheBooks.get(id);
if(book == null) {
Book queryBook = bookMapper.selectById(id);
cacheBooks.put(id,queryBook);
return queryBook;
}else {
return book;
}
}
}

spring boot缓存技术

使用步骤:

  1. 启用缓存

    导入坐标

    1
    2
    3
    4
    5
    <!--todo 1.缓存依赖-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>

    主入口函数开启缓存

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @SpringBootApplication
    @EnableCaching //todo 2.开启缓存
    public class SpringBoot14CacheApplication {

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

    }
  2. Service设置进入缓存的数据

  3. 设置读取缓存的数据

    1
    2
    3
    4
    5
    @Override
    @Cacheable(value = "cacheSpace", key = "#id")//todo 3.开启缓存,value:缓存存储空间,key:缓存存储位置
    public Book getBookById(int id) {
    return bookMapper.selectById(id);
    }

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

  • Generic
  • JCache
  • Ehcache
  • Hazelcast
  • Infinispan
  • Couchbase
  • Redis
  • Caffenine
  • simple (默认)
  • memcached
生成验证码
  1. 创建验证码实体类

    1
    2
    3
    4
    5
    6
    @Data
    //todo 1.创建SMS验证码实体类
    public class SMSCode {
    private String tele;
    private String code;
    }
  2. Service接口层编写发送验证码以及验证验证码方法

    1
    2
    3
    4
    public interface SMSCodeService {
    String sendCodeToSMS(String tele);
    boolean checkCode(SMSCode smsCode);
    }
  3. 创建工具类根据手机号码生成验证码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    @Component
    public class CodeUtils {
    private String[] patch = {"000000", "00000", "0000", "000", "00", "0", ""};

    public String generator(String tele) {
    int hash = tele.hashCode();
    int encryption = 20220913;
    long result = hash ^ encryption; //异或操作
    long nowTime = System.currentTimeMillis(); //保证唯一性
    result = result ^ nowTime;
    long code = result % 1000000;
    //return code + "";

    code = code < 0 ? -code: code; //取正数
    String codeStr = code + "";
    int len = codeStr.length();
    return patch[len] + codeStr; //自动补零
    }

    @Cacheable(value = "smsCode", key = "#tele")
    public String get(String tele) {
    return null; //有值则返回对应值,否则默认返回null
    }
    }
  4. Controller层实现接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @RestController
    @RequestMapping("/msg2")
    public class MsgController2 {
    @Autowired
    private SMSCodeService smsCodeService;

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

    @PostMapping
    public boolean checkCode(SMSCode smsCode) {
    return smsCodeService.checkCode(smsCode);
    }
    }
  5. Service实现类层完成方法编写

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @Service
    public class SMSCodeServiceImpl implements SMSCodeService{

    @Autowired
    private CodeUtils codeUtils;

    @Override
    @CachePut(value = "smsCode", key = "#tele")//todo 4.将生成的验证码存入缓存中,这里使用CachePut,每次重新请求都会重新写入验证码
    public String sendCodeToSMS(String tele) {
    String code = codeUtils.generator(tele);
    return code;
    }

    @Override
    public boolean checkCode(SMSCode smsCode) {
    //取出内存中的验证码与传递的验证码进行对比,相同返回true
    String code = smsCode.getCode();
    String cacheCode = codeUtils.get(smsCode.getTele());
    return code.equals(cacheCode);
    }
    }
    ehcache实现验证码

Ehcache

使用springboot官方提供的部分缓存技术,不需要修改到源码即可进行缓存相关处理

  1. 引入坐标

    1
    2
    3
    4
    5
    <!--todo 引入ehcache-->
    <dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    </dependency>
  2. 主配置文件配置对应缓存

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    spring:
    #todo 缓存设置
    cache:
    #缓存类型,默认为simple
    type: ehcache
    #缓存存储位置
    ehcache:
    config: classpath:ehcache.xml
    #报错解决Cache configuration does not exist 'ServletContext resource [/ehcache.xml]'
    #https://blog.csdn.net/oYuWoXiangGuan3/article/details/109290314
  3. 配置缓存相关设置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    <?xml version="1.0" encoding="UTF-8"?>
    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">

    <!-- 磁盘缓存位置 -->
    <diskStore path="D:\ehcache"/>
    <!--https://www.cnblogs.com/myseries/p/11370109.html-->
    <!--默认缓存策略-->
    <!-- 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">
    <!--<persistence strategy="localTempSwap"/>-->
    </defaultCache>

    <!-- 自定义缓存 -->
    <cache name="smsCode"
    eternal="false"
    diskPersistent="false"
    maxElementsInMemory="1000"
    overflowToDisk="false"
    timeToIdleSeconds="60"
    timeToLiveSeconds="60"
    memoryStoreEvictionPolicy="LRU"/>
    </ehcache>
    redis实现验证码缓存

步骤同ehcache一致,只是redis的相关配置不需要另起文件进行配置,只需要在yml主配置文件里面进行配置即可

  1. 导入依赖

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
  2. 主配置文件引入redis缓存

    1
    2
    3
    spring:
    cache:
    type: redis
  3. redis相关配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    spring:
    cache:
    redis:
    cache-null-values: false #空值
    key-prefix: test_ #key前缀名
    use-key-prefix: false #key前缀
    time-to-live: 10s #数据过期时间
    redis:
    host: localhost
    port: 6379
    memcached

memcached - a distributed memory object caching system

Windows 下安装 Memcached | 菜鸟教程 (runoob.com)

memcached客户端选择

  • Memcached Client for Java: 最早期客户端,稳定可靠,用户群广
  • SpyMemcached:效率更高
  • Xmemcached: 并发处理更好

SpringBoot未提供对memcached的整合,需要使用硬编码方式实现客户端初始化管理,实现方式如下:以验证码为例

  1. 导入坐标

    1
    2
    3
    4
    5
    6
    7
    <!--todo 引入memcached-->
    <!-- https://mvnrepository.com/artifact/com.googlecode.xmemcached/xmemcached -->
    <dependency>
    <groupId>com.googlecode.xmemcached</groupId>
    <artifactId>xmemcached</artifactId>
    <version>2.4.7</version>
    </dependency>
  2. 编写config类用于创建并返回一个memcachedClient的bean

    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
    package com.example.config;


    import net.rubyeye.xmemcached.MemcachedClient;
    import net.rubyeye.xmemcached.MemcachedClientBuilder;
    import net.rubyeye.xmemcached.XMemcachedClientBuilder;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;

    import java.io.IOException;

    @Configuration
    public class XMemcachedConfig {

    @Autowired
    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;
    }
    }
  3. 在主配置文件编写相关缓存配置属性并在后面创建MemcachedProperties类时注入

    1
    2
    3
    4
    5
    # todo memcached
    memcached:
    servers: localhost:11211
    poolSize: 10
    opTimeout: 3000
  4. 编写MemcachedProperties类用于该缓存的相关配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    package com.example.config;

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

    @Component
    @ConfigurationProperties(prefix = "memcached")
    @Data
    public class XMemcachedProperties {
    private String servers;
    private int poolSize;
    private long opTimeout;
    }
  5. SMSCodeServiceImpl 实现验证码创建和验证功能

    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
    @Service
    public class SMSCodeServiceImpl implements SMSCodeService {
    @Autowired
    private CodeUtils codeUtils;
    @Autowired
    private MemcachedClient memcachedClient;
    @Override
    public String sendCodeToSMS(String tele) {
    String code = codeUtils.generator(tele);
    try {
    memcachedClient.set(tele,8,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);
    }
    }
    影响数据淘汰的相关配置

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

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

jetCache对SpringCache进行了封装,在原有功能基础上实现了多级缓存、缓存统计、自动刷新、异步调用、数据报表等功能,可以整合与统一多种缓存的配置方式,免去上面不同缓存供应商的不同设置方法

jetCache设定了本地缓存与远程缓存的多级缓存解决方案

  • 本地缓存 (local)
    • LinkedHashMap
    • Caffeine
  • 远程缓存(remote)
    • Redis
    • Tair

使用体验

一、远程缓存体验

  1. 导入依赖(不要导入2.7及以上版本,因为在这个案例不适用)

    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.alicp.jetcache</groupId>
    <artifactId>jetcache-starter-redis</artifactId>
    <version>2.6.5</version>
    </dependency>
  2. 主配置文件进行jetcache配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # todo 2. jetcache相关设置
    jetcache:
    areaInCacheName: false #是否将域名设置在缓存键名前缀
    statIntervalMinutes: 1 #缓存统计
    #远程缓存
    remote:
    default:
    type: redis
    host: localhost
    port: 6379
    keyConvertor: fastjson
    valueEncode: java #序列化前
    valueDecode: java #序列化后
    poolConfig:
    maxTotal: 50 #容纳池

    default为缓存的默认配置,你也可以针对个性化需求进行别名配置,如下,进行一个名为sms的缓存配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    jetcache:
    #远程缓存
    remote:
    sms:
    type: redis
    host: localhost
    port: 6379
    poolConfig:
    maxTotal: 50
  3. 在应用主入口开启缓存功能

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @SpringBootApplication
    @EnableCreateCacheAnnotation //todo 开启该缓存功能
    public class SpringBoot15JetcacheApplication {

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

    }
  4. 导入并使用缓存

    1
    2
    @CreateCache(area = "sms", name = "jetCache", expire = 5, timeUnit = TimeUnit.SECONDS,cacheType = CacheType.REMOTE)
    private Cache<String, String> jetCache;
  5. 存入缓存

    1
    2
    3
    4
    5
    6
    @Override
    public String sendCodeToSMS(String tele) {
    String code = codeUtils.generator(tele);
    jetCache.put(tele, code);
    return code;
    }
  6. 取出缓存

    1
    2
    3
    4
    5
    6
    @Override
    public boolean checkCode(SMSCode smsCode) {
    //取出内存中的验证码与传递的验证码进行对比,相同返回true
    String code = jetCache.get(smsCode.getTele());
    return smsCode.getCode().equals(code);
    }

    二、本地缓存体验

  7. 设置本地缓存

    1
    2
    3
    4
    5
    6
    7
    8
    9
    jetcache:  
    #本地缓存
    local:
    default:
    type: linkedhashmap
    keyConvertor: fastjson
    sms:
    type: linkedhashmap
    keyConvertor: fastjson
  8. 使用:将cacheType = CacheType.REMOTE改为cacheType = CacheType.LOCAL即可

三、方法使用缓存

  1. 在主应用入口开启方法缓存功能@EnableMethodCache(basePackages = "开启缓存的包名")

    1
    2
    3
    4
    5
    6
    7
    8
    @SpringBootApplication
    @EnableCreateCacheAnnotation //todo 3.开启该缓存功能
    @EnableMethodCache(basePackages = "com.example") //todo 方法缓存开启
    public class SpringBoot15JetcacheApplication {
    public static void main(String[] args) {
    SpringApplication.run(SpringBoot15JetcacheApplication.class, args);
    }
    }
  2. 在需要缓存的实体类需进行序列化操作

    1
    2
    3
    4
    5
    6
    7
    8
    //todo 缓存实体类需序列化
    @Data
    public class Book implements Serializable {
    private int id;
    private String type;
    private String name;
    private String description;
    }
  3. 查询时存入缓存

    1
    2
    3
    4
    5
    6
    @Override
    @Cached(name = "book_", key = "#id", expire = 3600, cacheType = CacheType.REMOTE) //todo 缓存设置
    //@CacheRefresh(refresh = 10)//todo 缓存更新时间
    public Book getBookById(int id) {
    return bookMapper.selectById(id);
    }
  4. 更新数据时同步更新缓存

    1
    2
    3
    4
    5
    @Override
    @CacheUpdate(name = "book_", key = "#book.id", value = "#book")//todo 更新数据时更新缓存
    public boolean update(Book book) {
    return bookMapper.updateById(book) > 0;
    }
  5. 删除数据时删除对应缓存

    1
    2
    3
    4
    5
    @Override
    @CacheInvalidate(name = "book_", key = "#id")//todo 删除数据时删除对应缓存
    public boolean delete(int id) {
    return bookMapper.deleteById(id) > 0;
    }
    j2cache

J2Cache是开源的两级缓存框架(要求至少 Java 8)。第一级缓存使用内存(同时支持 Ehcache 2.x、Ehcache 3.x 和 Caffeine),第二级缓存使用 Redis(推荐)/Memcached 。 由于大量的缓存读取会导致 L2 的网络成为整个系统的瓶颈,因此 L1 的目标是降低对 L2 的读取次数。 该缓存框架主要用于集群环境中。单机也可使用,用于避免应用重启导致的缓存冷启动后对后端业务的冲击。

jetcache和j2cache的区别

使用体验

  1. 导包

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <!--todo 导入依赖-->
    <!-- https://mvnrepository.com/artifact/net.oschina.j2cache/j2cache-core -->
    <dependency>
    <groupId>net.oschina.j2cache</groupId>
    <artifactId>j2cache-core</artifactId>
    <version>2.8.5-release</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/net.oschina.j2cache/j2cache-spring-boot2-starter -->
    <dependency>
    <groupId>net.oschina.j2cache</groupId>
    <artifactId>j2cache-spring-boot2-starter</artifactId>
    <version>2.8.0-release</version>
    </dependency>

    因为后面的一级缓存需要用到ehcache,所以这里导入ehcache

    1
    2
    3
    4
    5
    <!--todo 引入ehcache-->
    <dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    </dependency>
  2. 在主入口配置文件配置j2cache

    1
    2
    3
    #配置j2cache
    j2cache:
    config-location: j2cache.properties
  3. 配置j2cache.properties

    分别需要配置一级缓存以及二级缓存并设置对应的配置文件,以及一级缓存到达二级缓存的方式

    1. 一级缓存

      1
      2
      3
      4
      5
      # 一级缓存
      #指定一级缓存类型
      j2cache.L1.provider_class = ehcache
      #指定ehcache配置文件位置
      ehcache.configXml = ehcache.xml
    2. 二级缓存

      这里面的二级缓存以及第三步引用的类可以从libary查找,直接进到该方法并复制完整类名即可

      image-20220915120718044

      二级缓存的相关配置可以不用创建新文件去进行,可直接用redis.的方式配置

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      # 二级缓存
      j2cache.L2.provider_class = net.oschina.j2cache.cache.support.redis.SpringRedisProvider
      j2cache.L2.config_section = redis
      redis.hosts = localhost:6379
      #设置单例模式
      redis.mode = single
      #存储空间
      redis.namespace = j2cache
      #是否启用二级缓存
      j2cache.L2-cache-open = false
    3. 一级缓存到二级缓存配置

      1
      2
      # 一级缓存数据到达二级缓存
      j2cache.broadcast = net.oschina.j2cache.cache.support.redis.SpringRedisPubSubPolicy
    4. 全部代码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      #todo j2cache配置

      # 一级缓存
      #指定一级缓存类型
      j2cache.L1.provider_class = ehcache
      #指定ehcache配置文件位置
      ehcache.configXml = ehcache.xml

      # 二级缓存
      j2cache.L2.provider_class = net.oschina.j2cache.cache.support.redis.SpringRedisProvider
      j2cache.L2.config_section = redis
      redis.hosts = localhost:6379
      #设置单例模式
      redis.mode = single
      #存储空间
      redis.namespace = j2cache
      #是否启用二级缓存
      j2cache.L2-cache-open = false

      # 一级缓存数据到达二级缓存
      j2cache.broadcast = net.oschina.j2cache.cache.support.redis.SpringRedisPubSubPolicy
  4. 配置一级缓存对应的配置文件ehcache.xml,同之前教程一致

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    <?xml version="1.0" encoding="UTF-8"?>
    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">

    <!-- 磁盘缓存位置 -->
    <diskStore path="D:\ehcache"/>
    <!-- 默认缓存 -->
    <defaultCache
    eternal="false"
    diskPersistent="false"
    maxElementsInMemory="1000"
    overflowToDisk="false"
    timeToIdleSeconds="10"
    timeToLiveSeconds="10"
    memoryStoreEvictionPolicy="LRU">
    <!--<persistence strategy="localTempSwap"/>-->
    </defaultCache>

    <!-- 自定义缓存 -->
    <cache name="smsCode"
    eternal="false"
    diskPersistent="false"
    maxElementsInMemory="1000"
    overflowToDisk="false"
    timeToIdleSeconds="60"
    timeToLiveSeconds="60"
    memoryStoreEvictionPolicy="LRU"/>
    </ehcache>
  5. 使用缓存,直接在需要使用的地方引入该缓存实现类即可

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    //todo 使用缓存
    @Service
    public class SMSCodeServiceImpl implements SMSCodeService {

    @Autowired
    private CodeUtils codeUtils;

    //todo 引入缓存
    @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);
    }

    }

    任务

定时任务是企业级应用中的常见操作

  • 年度报表
  • 缓存统计报告

市面上流行的定时任务技术

  • Quartz
  • Spring Task
Timer

使用Java内置apiTimer进行定时器操作

  1. 创建定时器对象

    1
    Timer timer = new Timer(); //todo 创建定时器
  2. 创建目标任务

    1
    2
    3
    4
    5
    6
    TimerTask task = new TimerTask() { //todo 创建定时任务
    @Override
    public void run() {
    System.out.println("timer task run...");
    }
    };
  3. 定时器操作任务

    1
    timer.schedule(task,0,2000);//todo 定时器执行定时任务
    Quartz

相关概念:

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

执行定时任务

  1. 导入quartz坐标

    1
    2
    3
    4
    5
    <!--todo 导入quartz-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
    </dependency>
  2. 编写工作-执行任务

    工作需继承QuartzJobBean类

    1
    2
    3
    4
    5
    6
    public class MyQuartz extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
    System.out.println("quartz task run..."); // TODO: 2022/9/15 具体执行的工作
    }
    }
  3. 编写工作明细-绑定任务

    创建Confiiguration类

    1
    2
    3
    @Configuration
    public class QuartzConfig {
    }

    编写工作明细,绑定具体工作,返回一个bean

    1
    2
    3
    4
    5
    @Bean
    public JobDetail printJobDetail() {
    // TODO: 2022/9/15 绑定具体的工作
    return JobBuilder.newJob(MyQuartz.class).storeDurably().build();
    }
  4. 编写触发器-绑定工作明细

    1
    2
    3
    4
    5
    6
    7
    @Bean
    public Trigger printJobTrigger() {
    // TODO: 2022/9/16 定时器的参数设置
    ScheduleBuilder schedBuild = CronScheduleBuilder.cronSchedule("0/5 * * * * ?");
    // TODO: 2022/9/15 绑定具体的工作明细
    return TriggerBuilder.newTrigger().forJob(this.printJobDetail()).withSchedule(schedBuild).build();
    }
    Spring Task
  5. 主应用入口开启定时器功能

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @SpringBootApplication
    @EnableScheduling //todo 开启定时器功能
    public class SpringBoot17TaskApplication {

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

    }
  6. 编写任务并注入spring

    1
    2
    3
    4
    5
    6
    @Component //注入spring
    public class MySpringTask {
    public void print() {
    System.out.println(Thread.currentThread().getName() + "_spring task run ...");
    }
    }
  7. 在需要执行的方法配置定时任务相关设置

    1
    2
    3
    4
    @Scheduled(cron = "0/1 * * * * ?") //todo 定时任务执行间隔时间
    public void print() {
    System.out.println(Thread.currentThread().getName() + "_spring task run ...");
    }

    可以针对定时任务进行相关个性化配置

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

邮件

消息队列(重要,后面补)

监控

监控的意义

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

可视化监控平台(Spring Boot Admin)

监控的实施方式:

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

image-20220917090938743

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

使用方式:

  1. Admin服务端配置方式

    1. 导入坐标

      1
      2
      3
      4
      5
      <dependency>
      <groupId>de.codecentric</groupId>
      <artifactId>spring-boot-admin-starter-server</artifactId>
      <version>2.7.3</version>
      </dependency>
    2. 主应用入口开启监控功能@EnableAdminServer

      1
      2
      3
      4
      5
      6
      7
      8
      @SpringBootApplication
      @EnableAdminServer
      public class SpringBoot18MonitorApplication {

      public static void main(String[] args) {
      SpringApplication.run(SpringBoot18MonitorApplication.class, args);
      }
      }
    3. 配置服务端端口

      1
      2
      server:
      port: 8081
  2. Admin客户端配置方式

    1. 导入坐标

      1
      2
      3
      4
      5
      <dependency>
      <groupId>de.codecentric</groupId>
      <artifactId>spring-boot-admin-starter-client</artifactId>
      <version>2.7.3</version>
      </dependency>
    2. 配置文件配置监控目标服务器地址

      1
      2
      3
      4
      5
      6
      # 18-监控目标服务器地址
      spring:
      boot:
      admin:
      client:
      url: http://localhost:8081
    3. 开放相关健康指标数据

      1
      2
      3
      4
      management:
      endpoint:
      health:
      show-details: always #开放信息详情
    4. 开放所有相关指标数据

      1
      2
      3
      4
      5
      management:
      endpoints:
      web:
      exposure:
      include: "*" #暴露所有监控web端点

      监控原理(待完善)

  • Actuator提供了SpringBoot生产就绪功能,通过端点的配置与访问,获取端点信息
  • 端点描述了一组监控信息,SpringBoot提供了多个内置端点,也可以根据需要自定义端点信息
  • 访问当前应用所有端点信息: /actuator
  • 访问端点详细信息: /actuator/端点名称
ID 描述 默认启用
loggers 显示和修改应用程序中日志记录器的配置
liquibase 显示已应用的Liquibase 数据库迁移
metrics 显示当前应用程序的指标度量信息
mappings 显示所有@RequestMapping路径的整理清单
scheduledtasks 显示应用程序中的调度任务
sessions 允许从Spring Session支持的会话存储中检索和删除用户会话。当使用Spring Session的响应式Web应用程序支持时不可用
shutdown 正常关闭应用程序
threaddump 执行线程dump

启用指定端点,其中health是必须启动的端点

1
2
3
4
5
6
7
8
management:
#启用指定端点
endpoint:
health:
enabled: true
show-details: always #开放信息详情
beans:
enabled: true

启用所有端点

1
2
3
management:
endpoints:
enabled-by-default: true #启用所有端点

web开放端口:

1
2
3
4
5
6
management:
endpoints:
web:
exposure:
#include: "*" #暴露所有监控web端点
include: health, info #暴露部分监控web端点

控制台提示信息:2022-09-17 11:05:29.043 INFO 22732 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 2 endpoint(s) beneath base path '/actuator'

java自带监视与管理平台:开启方式:控制台输入jconsole

自定义监控指标

info端点指标控制

  1. 开启info端点指标

    1
    2
    3
    4
    5
    management:
    #开启info端点指标
    info:
    env:
    enabled: true
  2. 静态信息定义

    1
    2
    3
    4
    5
    6
    #静态信息info
    info:
    appName: @project.artifactId@
    version: @project.version@
    company: Dong-yyy
    author: Dong
  3. 动态信息定义

    1. 编写类注入spring并实现InfoContributor接口

      1
      2
      3
      4
      @Component
      public class InfoConfig implements InfoContributor {

      }
    2. 重写contribute方法,使用builder.withDetail方法写入info数据(键值对形式写入),也可以直接传入map

      1
      2
      3
      4
      5
      6
      7
      @Override
      public void contribute(Info.Builder builder) {
      builder.withDetail("runTime", System.currentTimeMillis()); //直接写入数据
      Map infoMap = new HashMap();
      infoMap.put("buildTime", "2022");
      builder.withDetails(infoMap);//传入map
      }

health端点指标控制

health会对引入的依赖进行监控,若有一个有问题则会显示DOWN

可以自定义对组件的状态进行监听

  1. 创建类注入Spring并继承AbstractHealthIndicator类

    1
    2
    3
    4
    @Component
    public class HealthConfig extends AbstractHealthIndicator {

    }
  2. 实现doHealthCheck方法

    分别对不同状态进行信息展示以及health状态控制

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @Override
    protected void doHealthCheck(Health.Builder builder) throws Exception {
    // TODO: 2022/9/17 模拟根据组件状态赋值
    Boolean condition = false;
    if (condition) {
    // TODO: 2022/9/17 设置HealthConfig信息
    builder.withDetail("runTime", System.currentTimeMillis()); //直接写入数据
    Map infoMap = new HashMap();
    infoMap.put("buildTime", "2022");
    builder.withDetails(infoMap);//传入map
    builder.up();//设置状态为上线
    }else {
    builder.withDetail("状态","有毒☠");
    //builder.down();//设置状态为下线
    builder.status(Status.DOWN);//建议写的通俗易懂
    }
    }

metrics(性能)端点指标控制

可以自定义性能中的操作

  1. 再需要操作的业务层service定义一个变量记录执行次数
  2. 在无参构造函数种去初始化该操作
  3. 需要绑定的操作去增加执行次数
1
2
3
4
5
6
7
8
9
10
11
private Counter counter;

public BookServiceImpl(MeterRegistry meterRegistry) {
counter = meterRegistry.counter("用户操作次数");
}

@Override
public boolean delete(int id) {
counter.increment(); //todo 模拟每次执行记录条数
return bookMapper.deleteById(id) > 0;
}

image-20220917154913416

自定义端点

  1. 定义一个配置类注入spring并加上端点注解Endpoint

    1
    2
    3
    4
    5
    @Component
    @Endpoint(id = "pay" ,enableByDefault = true)
    public class PayEndpoint {

    }
  2. 配置读取端点调用方法的注解@ReadOperation

    1
    2
    3
    4
    5
    @ReadOperation
    public Object getPay() {
    System.out.println("hhh"); //控制台打印信息

    }
  3. 书写返回数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @ReadOperation
    public Object getPay() {
    System.out.println("hhh"); //控制台打印信息
    Map hashMap = new HashMap();
    hashMap.put("还没结束","不能停啊");
    hashMap.put("坚持住","你可以的");
    hashMap.put("成功了","哈哈哈😄");
    return hashMap; //接口返回信息

    }

    访问http://desktop-s56pmei:8082/actuator/pay

image-20220917160917971

原理

起步依赖原理

就是查看springboot的依赖

  • 在spring-boot-starter-parent中定义了各种技术的版本信息,组合了一套最优搭配的技术版本。
  • 在各种starter中,定义了完成该功能需要的坐标合集,其中大部分版本信息来自于父工程。
  • 我们的工程继承parent,引入starter后,通过依赖传递,就可以简单方便获得需要的jar包,并且不会存在版本冲突等问题

自动装配原理

视频链接:02-SpringBoot自动配置-Condition-1_哔哩哔哩_bilibili