SpringBoot开发实用篇

热部署

手动启动热部署

项目做完某一个功能测试的时候,修改完功能可以马上见到效果

服务器发现程序有新的改动,服务器实现内部的重启

实现热部署,必须得在spring容器里添加配置

在pom文件中添加开发者工具并使用idea自带的重新构建项目完成热部署激活热备份(Ctrl+F9)

实际是idea重新启动了服务器

  • 开启开发者工具
1
2
3
4
5
6
<!--手动启动热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
  • 激活热部署

idea构建项目

自动启动热部署

打开idea的自动构建项目 idea失去焦点五秒之后自动重新构建项目

新版idea移除这个功能

开启主动部署功能

关于热部署

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

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

热部署范围配置

  • 默认不触发重启的目录列表
    • /META-INF/maven
    • /META-INF/resources
    • /resources
    • /static
    • /public
    • /templates

在springboot的配置文件中找到开发者工具,控制热部署范围配置

开发环境下有效

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

关闭热部署

yml文件配置关闭
1
2
3
4
5
6
devtools:
restart:
# 设置不参与热部署的文件夹
exclude: static/**,public/**,config/application.yml
# 关闭热部署
enabled: false
属性加载优先级覆盖关闭
  • 设置高优先级属性禁用热部署

在springboot启动入口类中设置关闭热部署

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

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

}

}

配置高级

属性绑定

configuration

三种属性绑定

读取单一属性

当要读取yml配置文件中country的值

1
2
3
4
5
6
7
8
9
country: china
province: beijing
city: beijing
area: haiding

user:
name: ma
age: 16

使用注解**@Value**获取

1
2
3
4
5
@Value("${country}")
private String country1;

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

像是是多级属性则一级一级读取

读取数组中数据
1
2
3
4
likes:
- game
- music
- sleep

同样是**@Value**获取 只不过是数组形式获取

1
2
@Value("${likes[1]}")
private String likes;
引用数据
1
2
3
4
5
baseDir: c:\win10
#使用${属性名}引用数据
tempDir: ${baseDir}\temp
#使用引号包裹的字符串,其中的转义字符页可以生效
tempDir2: "${baseDir}\temp \t1 \t2"
读取yml全部属性数据
1
2
3
//自动装配 yml配置文件里的全部属性
@Autowired
private Environment env;

获取数据

1
2
System.out.println(env.getProperty("user.name"));
System.out.println(env.getProperty("likes[1]"));

将一组数据封装为对象

配置数据

在yml中配置数据

1
2
3
4
servers:
idAddress: 192.168.0.1
port: 2345
timeout: -1

定义实体类接收数据生成对象

1
2
3
4
5
6
7
8
9
10
11
//定义数据模型封装yaml文件中对应的数据封装成对象
//定义spring管控的bean
//指定加载spring容器里自定义的bean
@ConfigurationProperties(prefix = "servers")
/@Component
@Data
public class ServerConfig {
private String idAddress;
private int port;
private long timeout;
}
接收数据
  • 在springboot启动入口测试 需要使用getBean来获得数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@SpringBootApplication
@EnableConfigurationProperties(ServerConfig.class)
public class Springboot10ConfigurationApplication {

@Bean
@ConfigurationProperties(prefix = "datasource")
public DruidDataSource druidDataSource(){
DruidDataSource ds = new DruidDataSource();
return ds;
}
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(Springboot10ConfigurationApplication.class,args);
ServerConfig bean = ctx.getBean(ServerConfig.class);
// SpringApplication.run(Springboot10ConfigurationApplication.class, args);
System.out.println(bean);
DruidDataSource ds = ctx.getBean(DruidDataSource.class);
System.out.println(ds.getDriverClassName());
}

}
  • 在congtroller层使用注解**@Autowired**来获得数据
1
2
3
4
5
6
7
8
9
@Autowired
private ServerConfig serverConfig;

@GetMapping
public String test(){
System.out.println(serverfig)

}

第三方bean绑定

@ConfigurationProperties不仅适用于自定义的bean属性还适用于第三方bean绑定

1
2
3
4
5
6
7
@Bean
@ConfigurationProperties(prefix = "datasource")
public DruidDataSource druidDataSource(){
DruidDataSource ds = new DruidDataSource();
return ds;
}
System.out.println(ds.getDriverClassName());
1
2
datasource:
driverClassName: com.mysql.jdbc.DriverMa

@ConfigurationProperties和@EnableConfigurationProperties

  • 开启属性绑定并设置具体目标**@EnableConfigurationProperties(ServerConfig.class)是指定一个类或者多个类为spring容器的bean@Component**作用大致相同

    • 多个类的写法@EnableConfigurationProperties({A.class,b.Blass,C.class})
    • @EnableConfigurationProperties和@Component不能同时使用
    • @Component: 标注Spring管理的Bean,使用@Component注解在一个类上,表示将此类标记为Spring容器中的一个Bean。
  • @ConfigurationProperties具体的做属性绑定

松散绑定
  • @ConfigurationProperties绑定属性支持绑定属性名宽松绑定
  • @Value 不支持宽松绑定
  • 绑定前缀命名命名规则——>建议统一小写命名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@ConfigurationProperties(prefix = "servers")
@Component
@Data
public class ServerConfig {
private String idAddress;
private int port;
private long timeout;
//默认时间单位为毫秒
@DurationUnit(ChronoUnit.HOURS) //自定义时间单位
private Duration serverTimeOut;
//数据存储单位
@DataSizeUnit(DataUnit.MEGABYTES) //自定义数据单位
private DataSize dataSize;
}
1
2
3
4
5
6
7
8
9
10
11
servers:
idAddress: 192.168.0.1 # 驼峰
# ipaddress: 192.168.0.1
# ip_address: 192.168.0.1 # unline
# ip-address: 192.168.0.1 # 烤肉串格式 主流
# IPADDRESS: 192.168.0.1
# IP_ADDRESS: 192.168.0.1 # 常量
port: 2345
timeout: -1
serverTimeOut: 3
dataSize: 10 # 也可以写10MB

开启bean数据校验

  • 添加JSR303规范坐标与hibernate校验框架坐标
1
2
3
4
5
6
7
8
9
10
11
12
13
<!--        1.导入JSR303规范-->
<!-- jdbc-->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>

<!-- 使用hibernate校验器做实现接口-->
<!-- mysql驱动-->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
  • 开启Bean校验功能 使用@Validated注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@ConfigurationProperties(prefix = "servers")
@Component
@Data
//2.开启对当前bean的属性注入校验
@Validated
public class ServerConfig {
private String idAddress;
//3.开启具体校验规则
@Max(value = 8888,message = "最大值不超过8888")
@Min(value = 202,message = "最小值不低于202")
private int port;
private long timeout;
//默认时间单位为毫秒
@DurationUnit(ChronoUnit.HOURS) //自定义时间单位
private Duration serverTimeOut;
//数据存储单位
@DataSizeUnit(DataUnit.MEGABYTES) //自定义数据单位
private DataSize dataSize;
}

进制转换规则

yaml文件中对于数字的定义支持进制书写格式,如需使用字符串请使用引号明确标注

yaml语法规则

测试

加载测试环境专用属性

1
2
test:
prop: vtb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 配置临时变量属性 里面是数组类型可以添加多条
// properties 可以为当前属性添加一组临时属性配置
//@SpringBootTest(properties = {"test.prop=vtb1"})
//使用args配置临时属性 模拟命令行模式 注意前面得加--
//@SpringBootTest(args = {"--test.prop=vtb2"})
//当两个同时出现,由properties覆盖配置文件属性,再由命令行取代properties
@SpringBootTest(properties = {"test.prop=vtb1"},args = {"--test.prop=vtb2"})
public class PropertiesAndArgsTest {

@Value("${test.prop}")
private String msg;
@Test
void testProperties(){
System.out.println(msg);

}
}

引用外部资源

  • @Import(MsgConfig.class)可以导入bean
  • 但是如果需要引用的类写了@Configuration 则springboot自动装配
1
2
3
4
5
6
7
8
9
@Configuration
public class MsgConfig {

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

}
1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootTest
@Import(MsgConfig.class)
public class ConfigurationTest {

@Autowired
private String msg;
@Test
void Configuration(){
System.out.println(msg);
}

}

测试web层

模拟web环境

  • @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) 使用默认端口号进行访问

模拟端口

1
2
3
4
5
6
7
8
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class WebTest {

@Test
void webTest(){

}
}

web环境模拟请求测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//设置web环境
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
// 开启虚拟MVC调用
@AutoConfigureMockMvc
public class WebTest {

@Test
void webTest(@Autowired MockMvc mvc)throws Exception{
//创建一个虚拟请求,当前访问/books
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
//执行对应请求
mvc.perform(builder);

}
}

虚拟请求响应状态匹配

  • 如果与预期值匹配相等则正常运行
  • 如果不相等则报出详细错误
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
void webTest(@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);
}

错误详情

测试响应体内容

  • 测试结果看返回值是否匹配
    • 成功则正常运行
    • 失败则报出集团错误
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    @Test
void testBody(@Autowired MockMvc mvc)throws Exception{
//创建一个虚拟请求,当前访问/books
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/1books");
//执行对应请求
ResultActions actions = mvc.perform(builder);


//设置预期值,与真实值进行比较,成功测试通过,失败测试失败
//定义本次调用的预期值
ContentResultMatchers content = MockMvcResultMatchers.content();
//定义具体的结果 预计本次调用时成功返回内容
ResultMatcher result = content.string("springboot");
//添加预期值到本次调用过程中进行匹配
actions.andExpect(result);
}
}

测试响应体内容(JSON)

  • 匹配Json返回内容是否和预期相等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
void testBodyJson(@Autowired MockMvc mvc)throws Exception{
//创建一个虚拟请求,当前访问/books
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/1books");
//执行对应请求
ResultActions actions = mvc.perform(builder);


//设置预期值,与真实值进行比较,成功测试通过,失败测试失败
//定义本次调用的预期值
ContentResultMatchers content = MockMvcResultMatchers.content();
//定义具体的结果 预计本次调用时成功返回内容
ResultMatcher result = content.json("id1\n" +
"namespringboot\n" +
"typemath\n" +
"descriptiongood book");
//添加预期值到本次调用过程中进行匹配
actions.andExpect(result);
}

测试响应头

  • name中的value是否和预计的相同
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
void testContentType(@Autowired MockMvc mvc)throws Exception{
//创建一个虚拟请求,当前访问/books
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/1books");
//执行对应请求
ResultActions actions = mvc.perform(builder);


//设置预期值,与真实值进行比较,成功测试通过,失败测试失败
//定义本次调用的预期值
HeaderResultMatchers header = MockMvcResultMatchers.header();
//匹配响应头,name为响应头名称,value为预期结果
ResultMatcher string = header.string("Content-Type", "application/json");
//添加预期值到本次调用过程中进行匹配
actions.andExpect(string);
}

数据层测试事务回滚

  • 在测试数据的时候,在测试层添加**@Transactional** springboot会自动执行事务回滚,springboot知道是测试数据不将其写入数据库以免变成脏数据
  • 如果需要数据插入到数据库加上注解**@Rollback(false)**,默认为true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@SpringBootTest
@Transactional //操作事务的注解
@Rollback //默认回滚 不将测试数据添加到数据库
public class DaoTest {
@Autowired
private BookService bookService;

@Test
void DaoTest(){
Book book = new Book();
// book.setId(1);
book.setName("springboot");
book.setType("math");
book.setDescription("good book");
bookService.save(book);
}
}

测试用例设置随机值

  • 在多环境配置中的测试环境添加测试随机值
  • 通过创建实体封装为对象来接收
  • 使用**@Autowired**注入bean来接收数据
  • 设置范围没有规定使用哪种分隔符,()不唯一,可以使用各种符号分割,但不要使用 - 号
1
2
3
4
5
6
testcase:
book:
id: ${random.int(10)} # 设置范围 10以内
name: ${random.value}
uuid: ${random.uuid}
publishTime: ${random.long}
1
2
3
4
5
6
7
8
9
@Data
@Component
@ConfigurationProperties(prefix = "testcase.book")
public class BookCase {
private int id;
private String name;
private String uuid;
private long publishTime;
}
1
2
3
4
5
6
7
8
9
10
11
public class PropertiesAndArgsTest {

@Autowired
private BookCase bookCase;

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

}
}

数据层解决方案SQL

现有数据层解决方案技术选型

Druid+MyBatis-Plus+MySQL

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

数据源内嵌配置

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

  • HikariCP (springboot默认的配置):默认内置数据源对象
  • Tomcat提供DataSource:HikariCP 不可用的情况下,且在web环境下,将使用tomcat服务器配置的数据源对象
  • Commons DBCP:HikariCP 不可用,tomcat数据源也不可用,将使用dbcp数据源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
spring:
# druid配置
datasource:
druid: # 把druid禁用后 默认为Hikari配置
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/test
username: root
password: nian0209
# Hikari配置
datasource:
url: jdbc:mysql://localhost:3306/test
hikari:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: nian0209

内置持久化解决方案-JdbcTemplate

在不使用MyBatis/MyBatis-Plus的时候使用内置的JdbcTemlpate

  • 注意需要导入jdbc坐标

  • 需要将mybatis相关配置关闭

  • jdbc依赖中携带了Hikari数据源依赖

  • 可以在springboot配置文件中配置JdbcTemplate的相关配置

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>

    Hikari数据源依赖

1
2
3
4
5
6
7
8
9
10
11
12
spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/test
username: root
password: nian0209
jdbc:
template:
fetch-size: -1 # 缓存行数
max-rows: 500 # 最大行数
query-timeout: -1 # 查询超时时间

测试

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
@SpringBootTest
public class JdbcTemplateTest {

//由springboot容器注入JdbcTemplate数据源
@Autowired
private JdbcTemplate jdbcTemplate;
//查询 查询格式对于方法架构
@Test
void testJdbc(){
String sql = "select * from tbl_book where id =1";
List<Book> qurey = jdbcTemplate.query(sql, 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;
}
});
System.out.println(qurey);
}
//添加操作
@Test
void save(){
String sql = "Insert into tbl_book values(null,'1','2''3')";
jdbcTemplate.update(sql);
}
}

内嵌数据库

  • SpringBoot提供了三种内嵌数据库共开发者选择,提高开发测试效率
    • H2
    • HSQL
    • Derby

三种数据库均是Java语言编写,可以在springboot环境中运行

NoSQL解决方案

  • 市面上常见的NoSQL解决方案
    • Redis
    • Mongo
    • ES

Redis

  • Redis是一款key-values存储结构的内存级NoSQL数据库
    • 支持多中数据存储格式
    • 支持持久化
    • 支持集群

Redis基本安装(windows)

Redis基本操作

第一次启动redis

SpirngBoot整合Redis

1.导入Redis坐标
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.在springboot配置文件中加入配置(可省略)
1
2
3
4
spring:
redis:
host: localhost
port: 6379
3.注入redisTemplate对象 操作接口对象操作

Redis 两种注入方法

客户端:RedisTemplate以对象作为key和value,内部对数据进行序列化
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
@SpringBootTest
class Springboot12RedisApplicationTests {

@Autowired
private RedisTemplate redisTemplate;

// @Autowired
// private StringRedisTemplate stringRedisTemplate;

@Test
void set() {
ValueOperations ops = redisTemplate.opsForValue();
HashOperations oph = redisTemplate.opsForHash();
ops.set("age",41);
oph.put("info","a","aa");
}


@Test
void get(){
ValueOperations ops = redisTemplate.opsForValue();
HashOperations oph = redisTemplate.opsForHash();
oph.get("info","a");
Object age = ops.get("age");
System.out.println(age);
System.out.println(oph);
}

}
客户端StringRedisTemplate(常用)以字符串作为key和value,与Redis客户端操作等效
1
2
3
4
5
6
7
8
9
10
11
12
@Test
void sets(@Autowired StringRedisTemplate stringRedisTemplate){
ValueOperations ops = stringRedisTemplate.opsForValue();
ops.set("testKey","TestValue");
}

@Test
void gets(@Autowired StringRedisTemplate stringRedisTemplate){
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
String testKey = ops.get("testKey");
System.out.println(testKey);
}

jedis客户端

1.导入jedis坐标
1
2
3
4
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
2.设置client-type
1
2
3
4
5
spring:
redis:
host: localhost
port: 6379
client-type: jedis

客户端:lettcus(默认)与jedis区别

  • jedis连接Redis服务器是直连模式,当多线程模式下使用jedis会存在线程安全问题,解决方案可以通过配置连接池使每个连接专 用,这样整体性能就大受影响。

  • lettcus基于Netty框架进行与Redis服务器连接,底层设计中采用StatefulRedisConnection。 StatefulRedisConnection自身是线程安全的,可以保障并发访问安全问题,所以一个连接可以被多线程复用。当然lettcus也支持多连接实例一起工作

MangoDB

支持结构化的数据存储并且访问速度很快

  • 一个开源,高性能,无模式文档型数据库。NoSQL数据库产品中的一种,是最像关系型数据库的非关系型数据库

  • 存放的数据大多数是修改频率高,临时存储

Mongodb基本安装

  • windows安装mongodb MongoDB Community Download | MongoDB

  • 解压缩后配置文件目录

  • windows下启动Mongo

    • 服务端启动:mongd –dbpath..\data\db
    • 客户端启动:mongo (默认端口:27017 访问地址:127.0.0.1)
  • 使用navicat连接mongo数据库

Mongodb基本操作

超级详细MongoDB 的基本操作(理论+举例)_11.28.的博客-CSDN博客

Mongdb操作

SpirngBoot整合MongDB

1.导入对应坐标
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
2.配置mongodb数据库地址
1
2
3
4
spring:
data:
mongodb:
uri: mongodb://localhost/user
3.使用MongoTemplate接口客户端读写测试
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
@SpringBootTest
class Springboot13MongdbApplicationTests {

@Autowired
private MongoTemplate mongoTemplate;

//插入
@Test
void contextLoads() {
Book book = new Book();
book.setId(1);
book.setName("springboot");
book.setType("math");
book.setDescription("good book");
mongoTemplate.save(book);
}

//查询
@Test
void find(){
List<Book> all = mongoTemplate.findAll(Book.class);
System.out.println(all);
}

}

Elasticesearch(ES)

  • Elasticesearch是一个分布式全文搜索引擎

多段索引

索引操作

添加ik分词器

将ik分词器解衣后存放在es目录下的plugins里

创建索引并指定规则

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
{
"mappings":{
"properties":{
"id":{
"type":"keyword"
},
"name":{
"type":"text",
"analyzer":"ik_max_word",
"copy_to":"all"
},
"type":{
"type":"keyword"
},
"decription":{
"type":"text",
"analyzer":"ik_max_word",
"copy_to":"all"
},
# 设计型字段 将name和decription里的字段统一读入
"all":{
"type":"text",
"analyzer":"ik_max_word"
}
}
}
}

创建文档

PUT http://localhost:9200/books/_doc

查询文档

删除文档

修改文档

springboot整合ES

  • 因为视频中的方法以被springboot弃用,所以使用新的技术 在高版本的springboot中已经整合了High level client 可以使用注入bean来返回客户端

Spring Boot整合Elasticsearch,最新最全教程_Cloud-Future的博客-CSDN博客_springboot整合elasticsearch

SpringBoot 整合 high-level-client_张凯生的博客-CSDN博客

整合第三方技术

缓存

  • 缓存是一种介于数据永久存储介质与属于应用之间的数据临时存储介质

  • 使用缓存可以有效的减少低速数据读取过程的次数(例如:磁盘IO),提高系统性能

  • 缓存不仅可以用于提高永久性存储介质的数据读取效率,还可以提高临时的数据存储空间

模拟缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//模拟缓存
private HashMap<Integer,Book> cache = new HashMap<>();

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

Springboot提高了缓存技术,方便缓存使用

Spring cache 注解详解_子沐月的博客-CSDN博客

史上最全的Spring Boot Cache使用与整合_我俗人的博客-CSDN博客_springboot 使用cache

启用缓存
  • 导入缓存依赖坐标
1
2
3
4
5
<!--cache-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
  • 在springboot启动程序入口设置启用缓存 注解 @EnableCaching
1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
// 开启缓存功能
@EnableCaching
public class Springboot15CacheApplication {

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

}
  • 在需要使用缓存的地方设置开启缓存功能 @Cacheable

    在方法执行前spring先查看缓存中有没有数据,如果有数据,则直接返回数据,如果没有数据,调用方法并将方法返回值放入缓存

    基于内存开启的缓存,当服务器停掉之后,缓存的数据也没有了

1
2
3
4
5
6
@Override
//开启缓存,缓存空间为cacheSpace,查找条件为key=id
@Cacheable(value = "cacheSpace",key ="#id")
public Book getById(Integer id) {
return bookDao.selectById(id);
}
  • @CachePut 将方法的返回值放入缓存中
  • @CacheEvict 将一条或者多条数据从缓存中删除
Springboot 提供的缓存技术除了默认的缓存方案,还可以对其他缓存技术进行整合,统一接口,方便缓存的开发与管理
  • Generic
  • JCache
  • Ehcache
  • Hazelacst
  • infinispan
  • Coushbase
  • Redis
  • Caffenine
  • Simple(默认)
  • memcached(市面上,常用的缓存技术)
缓存使用案例—手机验证码
  • 需求

    • 输入手机号获取验证码,组织文档以短信形式发送给用户(页面模拟)
    • 输入手机号和验证码验证结果
  • 需求分析

    • 提供controller,传入手机号,业务层通过手机号计算出独有的六位验证码数据,存入缓存后返回此数据
    • 提供controller,传入手机号与验证码,业务层通过手机号从缓存中读取验证码与输入的验证码进行比对

springboot整合ehcache缓存

导入坐标依赖

  • 注意springboot的版本稳定为2.5.x
  • 添加ehcache配置文件
  • 在springboot的配置文件中配置ehcache
1
2
3
4
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?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>
1
2
3
4
5
6
spring:
# 缓存技术选择
cache:
type: ehcache
ehcache:
config: classpath:ehcache.xml

转换缓存方案为Redis

springboot中切换缓存方案为Redis

  • 导入依赖
  • 在springboot的配置文件中配置Redis相关内容
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
spring:
redis:
port: 6379
host: localhost
# 缓存技术选择 redis配置 需要开redis服务器
cache:
type: redis
redis:
# 是否缓存空值
cache-null-values: false
# 是否使用前缀 开启false之后 key-prefix的配置不起作用
use-key-prefix: false
# 指定前缀
key-prefix: aa
# 最大活动时间
time-to-live: 10s

memchaed 需要重新配置 window下安装有问题

memcached 安装及集成(转) - Jsonring - 博客园 (cnblogs.com)

缓存供应商变更jetcache

  • jetCache对SpringCache进行了封装,在原有功能的基础上实现了多级缓存,缓存统计,自动刷新,异步调用,数据报表等功能

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

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

加入坐标依赖

1
2
3
4
5
<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-starter-redis</artifactId>
<version>2.6.2</version>
</dependency>

本地缓存也远程缓存共存

  • remote
  • local
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
jetcache:
local:
# 默认
default:
type: linkedhashmap
keyConvertor: fastjson
remote:
# 默认
default:
type: redis
host: localhost
port: 6379
poolConfig:
maxTotal: 50
# 远程配置2
sms:
type: redis
host: localhost
port: 6379
poolConfig:
maxTotal: 50

基本配置

开启使用jetCache 注解:**@EnableCreateCacheAnnotation**

1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
//jetcache启用缓存的主开关
@EnableCreateCacheAnnotation
public class Springboot16JetcacheApplication {

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

}

声明缓存对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Service
public class SMSCodeServiceImpl implements SMSCodeService {
@Autowired
private CodeUtils codeUtils;

// remote

// 设置过期时间 默认为秒 设置本地访问:cacheType = CacheType.LOCAL
@CreateCache(name="jetCache",expire = 3600,timeUnit = TimeUnit.SECONDS,cacheType = CacheType.LOCAL)
private Cache<String,String> jetCache;

// 设置过期时间 默认为秒 方案2
// @CreateCache(area = "sms",name="jetCache",expire = 10,timeUnit = TimeUnit.SECONDS)
// private Cache<String,String> jetCache1;


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

@Override
public boolean checkCode(SMSCode smsCode) {

String code = jetCache.get(smsCode.getTele());
return smsCode.getCode().equals(code);
}
}

启用方法注解

1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootApplication
//jetcache启用缓存的主开关
@EnableCreateCacheAnnotation
//开启方法注解缓存 在方法头上设置开启缓存 指定扫描的包
@EnableMethodCache(basePackages = "ithima")
public class Springboot16JetcacheApplication {

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

}
基本操作
  • 缓存对象必须保障可序列话
  • 设置编码
  • 设置增删查改的缓存操作
1
2
3
4
5
6
7
8
9
import lombok.Data;
import java.io.Serializable;
@Data
public class Book implements Serializable {
private Integer id;
private String type;
private String name;
private String description;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
jetcache:
# 查看缓存统计报告
statIntervaMinutes: 10
local:
default:
type: linkedhashmap
keyConvertor: fastjson
remote:
default:
type: redis
host: localhost
port: 6379
keyConvertor: fastjson
valueEncode: java
valueDecode: java
poolConfig:
maxTotal: 50
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
@Service
public class BookServiceImpl extends ServiceImpl<BookDao, Book> implements BookService {

@Autowired
private BookDao bookDao;

@Override
//开启缓存,缓存空间为cacheSpace,查找条件为key=id 设置为本地
@Cached(name="book_",key="#id",expire = 3600,cacheType = CacheType.REMOTE)
@CacheRefresh(refresh = 10,timeUnit = TimeUnit.SECONDS)
public Book getById(Integer id) {
return bookDao.selectById(id);
}
//更新操作 更新时将缓存中对应的数据一起更新
@CacheUpdate(name="book_",key="#book.id",value="#book")
@Override
public boolean update(Book book) {
return bookDao.updateById(book)>1;
}
//删除操作 删除时将缓存中对应的数据一起删除
@CacheInvalidate(name = "book_",key="#id")
@Override
public boolean delete(Integer id) {
return bookDao.deleteById(id)>1;
}
}

缓存供应商变更:j2cache

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

添加坐标

  • 加入整合坐标
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--j2cache核心坐标-->
<dependency>
<groupId>net.oschina.j2cache</groupId>
<artifactId>j2cache-core</artifactId>
<version>2.8.5-release</version>
</dependency>
<!--springboot整合j2cache坐标-->
<dependency>
<groupId>net.oschina.j2cache</groupId>
<artifactId>j2cache-spring-boot2-starter</artifactId>
<version>2.8.0-release</version>
</dependency>

<!--整合ehcache,使用其他就换其他坐标-->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>

在springboot配置文件中设置j2cache的配置文件

1
2
j2cache:
config-location: j2cache.properties

配置一级缓存和二级缓存以及一级缓存数据到二级缓存的发送方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 1级缓存
# 配置供应商
j2cache.L1.provider_class = ehcache
ehcache.configXml = ehcache.xml

# 设置是否启用二级缓存
j2cache-l2-open = false

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

# 1级缓存中的数据如何到达二级缓存中
# 配置一级缓存数据到二级缓存数据的广播方式:可以使用redis提供的消息订阅模式,也可以使用groups 多播实现
j2cache.broadcast =net.oschina.j2cache.cache.support.redis.SpringRedisPubSubPolicy

# 配置redis模式
redis.mode = single

设置缓存对象,操作数据

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

@Autowired
private CacheChannel cacheChannel;

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

@Override
public boolean checkCode(SMSCode smsCode) {

String code = cacheChannel.get("sms",smsCode.getTele()).asString();
return smsCode.getCode().equals(code);
}
}

任务

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

    • 年度报表
    • 缓存统计报告
    • … …
  • 市面上流行的定时任务技术

    • Quartz
    • Spring Task

Springboot 整合Quartz

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

导入对应坐标

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

定义具体要执行的任务

1
2
3
4
5
6
7
8
public class MyQuartz extends QuartzJobBean {
// 具体要做的事
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {

System.out.println("Quartz job is running....");
}
}

定义工作明细与触发器,并绑定对应关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration
public class QuartzConfig {

@Bean
public JobDetail printJobDetail(){
//绑定具体工作
return JobBuilder.newJob(MyQuartz.class).storeDurably().build();
}

@Bean
public Trigger printTrigger(){
//绑定具体的工作明显
ScheduleBuilder<? extends Trigger> schedBuilder = CronScheduleBuilder.cronSchedule("0/5 * * * * ?");
return TriggerBuilder.newTrigger().forJob(printJobDetail()).withSchedule(schedBuilder).build();
}

}

spring task

设置开启定时任务功能开关

  • 在springboot启动类入口使用注解 @EnableScheduling
1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
//开启定时任务功能
@EnableScheduling
public class springboot18TaskApplication {

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

}

设置定时执行的任务

  • 在方法上直接使用注解来指定要定时执行的任务
1
2
3
4
5
6
7
8
9
@Configuration
public class MyBean {

//设置执行时间
@Scheduled(cron = "0/3 * * * * ?")
public void print(){
System.out.println(Thread.currentThread().getName() + "spring task is running.....");
}
}
  • 设置spring task线程前缀
1
2
3
4
spring:
task:
execution:
thread-name-prefix: spring_task_

springboot 整合JavaMail

发送简单邮件

  • SMTP(Simple Mail Transfer Protocol):简单邮件传输协议,用于发送电子邮件的传输协议
  • POP3(Post Office Protocol -Version3):用于接收电子邮件的标准协议
  • IMAP(Internet Mail Access Protocol):互联网消息协议,是POP3的替代协议
导入对应Javamail坐标
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
配置smtp协议POP3协议
1
2
3
4
5
6
spring:
mail:
# 邮件服务器供应商
host: smtp.qq.com
username: 2976908***@qq.com
password: xxuchqhavybxde**
设置邮件客户端
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
@Service
public class SendMailServiceImpl implements SendMailService {

//发送人
private String from = "2976908***@qq.com";
//接收人
private String to = "129577****@qq.com";
//标题
private String subject = "测试邮件";
//测试邮件正文
private String context = "测试邮件正文内容";

@Autowired
private JavaMailSender javaMailSender;

@Override
public void SendMail() {

SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(from);
message.setTo(to);
message.setSubject(subject);
message.setText(context);
javaMailSender.send(message);
}
}

发送复杂邮件

  • 使用MimeMessageHelper对象
  • 发送html 需要开启 helper.setText(context,true);
  • 发送附件MimeMessageHelper(message,true);
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
@Service
public class SendMailServiceImpl implements SendMailService {

//发送人
private String from = "2976908894@qq.com";
//接收人
private String to = "1295773125@qq.com";
//标题
private String subject = "测试邮件";
//测试邮件正文
private String context = "<a href='https://lulyqqqq.github.io/'>博客</a>";

@Autowired
private JavaMailSender javaMailSender;

@Override
public void SendMail() {
try {
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message,true);
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(context,true);
//添加附件
File f1 = new File("src/main/resources/111.jpg");
helper.addAttachment("图片.jpg",f1);
javaMailSender.send(message);
} catch (MessagingException e) {
e.printStackTrace();
}
}
}

消息

  • 同步消息
  • 异步消息
    • 企业级应用中广泛使用的三种异步消息传递技术
      • JMS(Java message Service):一个规范,等同于JDBC规范,提供了与消息服务相关的API接口 规范消息相关的API
        • JSM消息模型
          • peer-2-peer:点对点模型,消息发送到一个队列中,队列保存消息。队列的消息只能被一个消费者消费,或超时
          • publish-subscribe:发布订阅模型,消息可以被多个消费者消费,生产者和消费者完全独立,不需要感知对方的存在
      • AMQP(advanced message queuing protocol):一种协议(高级消息协议,也是消息代理规范)规范了网络交换的数据格式,兼容JMS 规范消息传递的格式
        • 优点:兼容跨平台性,服务器供应商,生产者,消费者可以使用不同的语言实现
        • AMQP消息模型
          • direct exchange
          • fanout exchange
          • topic exchange
          • headers exchange
          • system exchange
        • AMQP消息种类: byte[] 字节数组类型
        • AMQP实现:RabbitMQ,StormMQ,RocketMQ
      • MQIT(Message Queueing Telemetry Transport)消息队列遥测传输,专为小设备设计,是物联网(IOT)生态系统中主要成分之一
      • Kafka:一种高吞吐量的分布式发布订阅消息系统,提供实时消息功能

消息队列

  • RabbitMQ
  • StormMQ
  • RocketMQ
  • Kafka

Springboot 整合ActiveMQ

加入activemq坐标

1
2
3
4
5
<!--activemq-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>

配置activemq

1
2
3
4
5
6
7
8
9
10
11
12
server:
port: 8080
spring:
# 配置默认的服务地址
activemq:
broker-url: tcp://localhost:61616
# 配置默认保存位置
jms:
# 开启true 设置发布订阅模型 false为点对点模型
pub-sub-domain: true
template:
default-destination: itheima

使用JmsMessagingTemplate对象进行操作

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

@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
@Override
public void SendMessage(String id) {
System.out.println("待发送的短信已纳入处理队列:id = "+id);
// 设置存放地址名称
jmsMessagingTemplate.convertAndSend("order.queue.id",id);

}

@Override
public String doMessage() {
// 在对应地址取出数据
String id = jmsMessagingTemplate.receiveAndConvert("order.queue.id",String.class);
System.out.println("已完成短信发送业务id = "+id);
return id;
}
}

定义listener 自动处理消息队列

  • @JmsListener(destination = “order.queue.id”) @JmsListener注解指定自动解决哪里位置的消息
  • @SendTo 自动处理完后, 将方法的返回值 发送到指定的其他队列中
1
2
3
4
5
6
7
8
9
10
11
@Component
public class MessageListener {

//加上JmsListener会直接在接收到信息任务后直接处理,不需要手动进行处理
@JmsListener(destination = "order.queue.id")
@SendTo("order.queue.other.id") // 自动处理完后, 将方法的返回值 发送到指定的其他队列中
public String receive(String id){
System.out.println("已完成短信发送业务id = "+id);
return "new:" + id;
}
}

RabbitMQ

  • RabbitMQ基于Erlang语编写,需要安装Erlang
  • Erlang
    • 需要下载
    • 安装完成后重启电脑
  • 环境变量配置
    • ERLANG_HOME
    • PATH

安装RabbitMQ

  • 官网下载:傻瓜式安装
启动RabbitMQ
  • 管理员权限启动,在目录下启用cmd输入:rabbitmq-service.bat start

  • 停止服务:rabbitmq-service.bat stop

开启RabbitMQ可视化界面
  • 使用插件形式
    • 打开插件:rabbitmq-plugins.bat enable rabbitmq_management
    • 使用localhost:15672访问可视化界面
    • 登录账号和密码:guest

springboot整合Rabbitmq 直连交换机模式

配置顺序基本相同

导入坐标

1
2
3
4
5
<!--rabbitMQ-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

配置rabbitmq

1
2
3
4
spring:
rabbitmq:
host: localhost
port: 5672 # 对外的服务端口为5672 管理端口为15672

定义消息队列 交换机 绑定

  • 一个交换机可以绑定多个队列 处理
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
@Configuration
public class RabbitConfigDirect {

/**
* 设置队列
* Queue里的三个参数配置
* durable:是否持久化 默认为false
* exclusive:是否是当前连接专用 连接关闭后队列删除 默认为false
* autoDelete:是否自动删除 当上生产者或消费者不再使用此队列 自动删除 默认为true
* @return
*/
@Bean
public Queue directQueue() {
//设置消息队列名称
return new Queue("direct_queue");
}

/**
* 设置交换机
*/
@Bean
public DirectExchange directExchange(){
//设置交换机名称
return new DirectExchange("directExchange");
}

/**
* 绑定消息队列与交换机 起名字为direct
*/
@Bean
public Binding bindingDirect(){
return BindingBuilder.bind(directQueue()).to(directExchange()).with("direct");
}
}

生产与消费消息(direct)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Service
public class MessageRabbitmqDirectImpl implements MessageService {


@Autowired
private AmqpTemplate amqpTemplate;

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

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

使用消息监听器对消息队列进行监听

  • 当有两个或多个监听器监听一个消息队列时,两个监听器是轮换监听
  • 安装字符串排序监听
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @ClassName: MessageListener
* @author: mafangnian
* @date: 2022/7/15 22:30
* @Blog: null
* 当有两个监听器监听一个消息队列时,两个监听器是轮换监听
*/
@Component
public class MessageListener {

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

springboot整合Rabbitmq 主题交换机模式

定义消息队列

可以采用模糊匹配的方式匹配消息队列进行监听器处理

消息队列

匹配规则

生产与消费消息(topic)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Service
public class MessageRabbitmqTopicImpl implements MessageService {

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

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

消息监听器监听消息队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class MessageListener {

//监听一号消息队列 topic_queue
@RabbitListener(queues = "topic_queue")
public void receive(String id){
System.out.println("已完成短信发送业务(rabbitmq topic)id = "+id);
}

//监听二号消息队列 topic_queue2
@RabbitListener(queues = "topic_queue2")
public void receive1(String id){
System.out.println("已完成短信发送业务(rabbitmq topic)id = "+id);
}
}

重点

当发消息队列的时候(routingKey)起名遵循了两个消息队列的(routingKey)的模糊条件时,设置了监听器的消息队列匹配成功之后,均执行监听器操作

1
2
3
4
5
@Override
public void SendMessage(String id) {
System.out.println("待发送的短信已纳入处理队列(rabbitmq topic):id = "+id);
amqpTemplate.convertAndSend("topicExchange","topic.id.orders",id);
}
1
2
3
4
5
6
7
8
9
@Bean
public Binding bindingTopic(){
return BindingBuilder.bind(topicQueue()).to(topicExchange()).with("topic.*.orders");
}

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

RocketMQ

安装:解压缩

  • 默认服务端口:9876
  • 环境变量配置:ROCKETMQ_HOME
  • PATH
  • NAMESRV_ADDR(建议):127.0.0.1:9876

启动命名服务器和broker

  • 配置环境变量完成后直接双击文件
    • 命名服务器:mqnamesrv.cmd
    • broker服务器:mqbroker.cmd
  • 启动服务器

测试服务器

recketmq自带测试程序 需要使用测试的话,在包路径下找对应的测试类

  • 使用tools org.apache.rocketmq.example.quickstart.Producer (产生若干条消息) 如果服务器有问题,则无法产生
  • 使用tools org.apache.rocketmq.example.quickstart.Consumer 消费消息

springboot整合rocketmq

需要启动rocketmq服务器

导入坐标
1
2
3
4
5
6
<!--rocketmq-->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.2.1</version>
</dependency>
配置rocketmq
1
2
3
4
5
rocketmq:
name-server: localhost:9876
# 设置生产者所属组
producer:
group: group_rocketmq
设置发送消息对象 RocketMQTemplate

生产消息

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
@Service
public class MessageServiceRocketmqImpl implements MessageService {

@Autowired
private RocketMQTemplate rocketMQTemplate;

@Override
public void SendMessage(String id) {
System.out.println("待发送的短信已纳入处理队列(rocketmq):id = "+id);
// 同步消息
// rocketMQTemplate.convertAndSend("order_id",id);

//异步消息产生之后的回调操作 告诉你消息执行情况
SendCallback callback = new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.println("消息发送成功!");
}

@Override
public void onException(Throwable throwable) {
System.out.println("消息发送失败!");
}
};
//异步消息
//三个参数 1.发送的位置即主题名称 2.发送的消息 3.发送后的结果
rocketMQTemplate.asyncSend("order_id",id,callback);
}
}
配置监听器 消费消息
1
2
3
4
5
6
7
8
9
10
@Component
//设置主题名称 消费者所属组
//设置消费者 必须和生产者一个组才能匹配 消费消息 consumerGroup = "group_rocketmq"
@RocketMQMessageListener(topic = "order_id",consumerGroup = "group_rocketmq")
public class MessageListener implements RocketMQListener<String> {
@Override
public void onMessage(String id) {
System.out.println("已完成短信发送业务(rocketmq) = "+id);
}
}

Kafka

  • 消息中间件

安装解压缩:windows建议使用2.x之类的版本 3.x的版本存在bug

启动Kafka服务器

  • 在Kafka的bin目录下的windows里启动cmd

    • 先启动注册器,在启动命名服务器
  • 启动zookeeper(注册器)

    • zookeeper-server-start.bat ../../config/zookeeper.properties (需要指定启动的配置文件 在config目录下)
    • 默认端口:2181
  • 启动kafka

    • kafka-server-start.bat ../../config/server.properties (同样指定配置文件 在config目录下)
    • 默认端口:9092

测试kafka服务器

  • 需要自己创建topic来测试
    • 创建topic: kafka-topics.bat –zookeeper 127.0.0.1:2181 –create –replication-factor 1 –partitions 1 –topic itheima(最后一个参数设置topic名称)
  • 查看topic
    • kafka-topics.bat –zookeeper 127.0.0.1:2181 –list
  • 测试
    • 生产者功能测试
      • kafka-console-producer.bat –broker-list localhost:9092 –topic itheima
    • 消费者功能测试
      • kafka-console-consumer.bat –bootstrap-server localhost:9092 –topic itheima –from-beginning
  • 删除topic
    • kafka-topics.bat –delete –zookeeper localhost:2181 –topic itheima

springboot整合kafka

导入坐标
1
2
3
4
5
<!--kafka-->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
配置地址信息
1
2
3
4
5
6
7
spring:
kafka:
# 指定访问服务器地址
bootstrap-servers: localhost:9092
consumer:
# 配置消费者所属组
group-id: order
生产消息
1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class MessageServiceKafkaImpl implements MessageService {

@Autowired
private KafkaTemplate<String,String> kafkaTemplate;

@Override
public void SendMessage(String id) {
System.out.println("待发送的短信已纳入处理队列(kafka):id = "+id);
kafkaTemplate.send("itheima",id);
}
}
监听器消费消息
1
2
3
4
5
6
7
8
@Component
public class MessageListener {

@KafkaListener("itheima") //指定
public void onMessage(ConsumerRecord<String,String> record){
System.out.println("已完成短信发送业务id(kafka) = " + record.value);
}
}

监控

监控的意义

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

监控的实施方式

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

可视化监控平台

  • Spring Boot Admin,开源社区项目,用于管理和监控springboot应用程序。客户端注册到服务端,通过HTTP请求方式,服务端定期从客户端获取对应的信息,并提供ui界面展示对应信息
  • 平台版本必须和springboot版本相一致

做成web形式,使用可视化界面查看 配置个web程序端口

  • 在服务端开启监控服务
1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
//开启监控服务
@EnableAdminServer
public class Springboot21AdminServerApplication {

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

}
  • 创建Admin服务端 对应坐标
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
<dependencies>

<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>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
  • 配置客户端
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
<dependencies>

<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>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
  • 客户端匹配服务端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
spring:
boot:
admin:
client:
# 谁来监控
url: http://localhost:8080
management:
endpoint:
health:
show-details: always
endpoints:
web:
# 打开所有可查看的配置
exposure:
include: "*"

监控原理 —–Actuator

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

端点暴露

./springboot开发实用篇/9

暴露端点功能

Info端点加载数据

  • 静态数据
  • 动态数据
1
2
3
4
5
6
# 设置端点里内容 info 设置内容
info:
appName: @02@
version: @9.9@
company: Good Company
author: XD
1
2
3
4
5
6
7
8
9
// 通过spring容器设置数据
@Component
public class InfoConfig implements InfoContributor {
@Override
public void contribute(Info.Builder builder) {
builder.withDetail("runTime",System.currentTimeMillis());

}
}

Spring Boot Admin 可视化界面

health端点数据自定义

继承实现数据自定义

状态的设置,如果状态不是为up则会影响最终实例的状态

1
2
3
4
5
6
7
8
9
10
11
12
@Component
public class HealthConfig extends AbstractHealthIndicator {
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
builder.withDetail("runTime",System.currentTimeMillis());
Map infoMap = new HashMap();
infoMap.put("buildTime","2006");
builder.withDetails(infoMap);
//设置状态
builder.up();
}
}

自定义health指标

性能指标自定义

执行了多少次结果会在可视化界面上展示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private Counter counter;

public BookServiceImpl(MeterRegistry meterRegistry){

counter = meterRegistry.counter("用户操作付费次数:")
}


@Override
public boolean delete(Integer id) {
//每次执行删除操作相当于执行了一次付费操作
counter.increment();
return bookDao.deleteById(id) > 0;
}

自定义监控指标

自定义端点

通过http://localhost:8080/actuator/pay 访问

在可视化界面 Spring Boot Admin上可以查看

在使用了这个链接之后页面显示return回来的值

1
2
3
4
5
6
7
8
9
10
11
12
@Component
@Endpoint(id = "pay",enableByDefault = true) //通过id访问
public class payEndPoint {
@ReadOperation
public Object getPay(){
Map payMap = new HashMap();
payMap.put("lever 1",100);
payMap.put("lever 2",50);
payMap.put("lever 3",10);
return payMap;
}
}

总结:部分技术尚未掌握,大抵只是通过springboot整合其他技术,了解其他技术大概是怎么样使用,对各个模块有了更深层次的印象;可以加快速度学习,但是笔记总要回顾,需要不定时会看,巩固基础

开发实用篇告一段落