Bootstrap

SpringBoot2 学习笔记——黑马程序员

SpringBoot

视频地址:https://www.bilibili.com/video/BV15b4y1a7yG?p=24&share_source=copy_web

部分学习笔记:https://blog.csdn.net/qq_42324086/article/details/121184789

文章目录

1、SpringBoot入门

1.1、Spring程序与SpringBoot程序对比

在这里插入图片描述

1.2、parent

在这里插入图片描述

  • starter

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

  • parent

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

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

  • 实际开发

    使用任意坐标时,仅书写GAV(groupId, artifactId, version)中的G和A,V由SpringBoot提供,除非SpringBoot未提供对应版本V

    如发生坐标错误,再指定Version(要小心版本冲突)

1.3、引导类

  • 启动方式

    @SpringBootApplication
    public class Springboot0101QuickstartApplication {
    
        public static void main(String[] args) {
            ConfigurableApplicationContext ctx = SpringApplication.run(Springboot0101QuickstartApplication.class, args);
            //获取bean对象
            BookController bean = ctx.getBean(BookController.class);
            System.out.println("bean======>" + bean);
        }
    }
    

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

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

1.4、内置Tomcat

在这里插入图片描述

  • Jetty比Tomcat更轻量级,可扩展性更强(相较于Tomcat),谷歌应用引擎(GAE)已经全面切换为Jetty

  • 内置服务器

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

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

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

2、Rest风格

2.1、什么是Rest

  1. 什么是 rest :

    REST(Representational State Transfer)表现形式状态转换

    传统风格资源描述形式
    http://localhost/user/getById?id=1 (得到id为1的用户)
    http://localhost/user/saveUser (保存用户)

    REST风格描述形式
    http://localhost/user/1 (得到id为1的用户)
    http://localhost/user (保存用户)

  2. 优点:

    隐藏资源的访问行为, 无法通过地址得知对资源是何种操作
    书写简化

  3. 按照REST风格访问资源时使用行为动作区分对资源进行了何种操作

    GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE 用来删除资源

    http://localhost/users 查询全部用户信息 GET (查询)
    http://localhost/users/1 查询指定用户信息 GET (查询)
    http://localhost/users 添加用户信息 POST (新增/保存)
    http://localhost/users 修改用户信息 PUT (修改/更新)
    http://localhost/users/1 删除用户信息 DELETE (删除)

注意:

上述行为是约定方式,约定不是规范,可以打破,所以称REST风格,而不是REST规范
描述模块的名称通常使用复数,也就是加s的格式描述,表示此类资源,而非单个资源,例如: users、

  1. 根据REST风格对资源进行访问称为RESTful

2.2、Rest入门案例

步骤:

①设定http请求动作(动词)

使用 @RequestMapping 注解的 method 属性声明请求的方式

使用 @RequestBody 注解 获取请求体内容。直接使用得到是 key=value&key=value…结构的数据。get 请求方式不适用。

使用@ResponseBody 注解实现将 controller 方法返回对象转换为 json 响应给客户端。

@RequestMapping(value=“/users”,method=RequestMethod.POST)

在这里插入图片描述

②:设定请求参数(路径变量)

使用@PathVariable 用于绑定 url 中的占位符。例如:请求 url 中 /delete/{id},这个{id}就是 url 占位符。
在这里插入图片描述

@RequestMapping

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HhzZsWGO-1657811363433)(SpringBoot.assets/image-20220312164532116.png)]

@PathVariable

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yaepxdng-1657811363434)(SpringBoot.assets/image-20220312164552778.png)]

@RequestBody @RequestParam @PathVariable
在这里插入图片描述

2.3、Restful快速开发

使用 @RestController 注解开发 RESTful 风格
在这里插入图片描述

使用 @GetMapping @PostMapping @PutMapping @DeleteMapping 简化 @RequestMapping 注解开发

在这里插入图片描述

3、配置文档

3.1、基础配置

  1. 修改配置
    修改服务器端口
    server.port=80
    关闭运行日志图标(banner)
    spring.main.banner-mode=off
    设置日志相关
    logging.level.root=debug
# 服务器端口配置
server.port=80

# 修改banner
# spring.main.banner-mode=off
# spring.banner.image.location=logo.png

# 日志
logging.level.root=info
  1. SpringBoot内置属性查询
    https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties
    官方文档中参考文档第一项:Application Propertie

3.2、配置文件类型

  • 配置文件格式
    在这里插入图片描述

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

    application.properties

    server.port=80
    

    application.yml

    server:
      port: 81
    

    application.yaml

    server:
      port: 82
    

在这里插入图片描述

3.3、配置文件加载优先级

  • SpringBoot配置文件加载顺序
    application.properties > application.yml > application.yaml
  • 常用配置文件种类
    application.yml

3.4、yaml数据格式

yaml

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

优点:
    容易阅读
    容易与脚本语言交互
    以数据为核心,重数据轻格式

YAML文件扩展名
    .yml(主流)
    .yaml

yaml语法规则
基本语法

key: value -> value 前面一定要有空格
大小写敏感
属性层级关系使用多行描述,每行结尾使用冒号结束
使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(不允许使用Tab键)
属性值前面添加空格(属性名与属性值之间使用冒号+空格作为分隔)
# 表示注释
核心规则:数据前面要加空格与冒号隔开
server:
  servlet:
    context-path: /hello
  port: 82

数据类型

  • 字面值表示方式
    在这里插入图片描述
# 字面值表示方式

boolean: TRUE       #TRUE,true,True,FALSE,false , False 均可
float: 3.14         #6.8523015e+5 # 支持科学计数法
int: 123            #0b1010_0111_0100_1010_1110 # 支持二进制、八进制、十六进制
# null: ~             # 使用 ~ 表示 null
string: HelloWorld  # 字符串可以直接书写
string2: "Hello World"  # 可以使用双引号包裹特殊字符
date: 2018-02-17        # 日期必须使用 yyyy-MM-dd 格式
datetime: 2018-02-17T15:02:31+08:00   # 时间和日期之间使用 T 连接,最后使用 + 代表时区
  • 数组表示方式:在属性名书写位置的下方使用减号作为数据开始符号,每行书写一个数据,减号与数据间空格分隔

在这里插入图片描述

subject:
  - Java
  - 前端
  - 大数据

enterprise:
  name: zhangsan
  age: 16

subject2:
  - Java
  - 前端
  - 大数据
likes: [王者荣耀,刺激战场] # 数组书写缩略格式


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

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

# 对象数组缩略格式
users3: [ { name:Tom , age:4 } , { name:Jerry , age:5 } ]

3.5、读取yaml单一属性数据

  • 使用@Value读取单个数据,属性名引用方式:${一级属性名.二级属性名……}
    在这里插入图片描述
    @Value("${country}")
    private String country1;

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

    @Value("${likes[1]}")
    private String likes1;

    @Value("${users[1].name}")
    private String name1;


    @GetMapping
    public String getById() {
        System.out.println("springboot is running2...");
        System.out.println("country1=>" + country1);
        System.out.println("age1=>" + age1);
        System.out.println("likes1=>" + likes1);
        System.out.println("name1=>" + name1);
        return "springboot is running2...";
    }

3.6、yaml文件中的变量应用

  • 在配置文件中可以使用属性名引用方式引用属性
    在这里插入图片描述
    在这里插入图片描述

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

    lesson: "Spring\tboot\nlesson"
    

3.7、读取yaml全部属性数据

  • 封装全部数据到Environment对象
  • 注意 要导这个 包
  • import org.springframework.core.env.Environment
    在这里插入图片描述
    在这里插入图片描述

3.8、读取yaml应用类型属性数据

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

在这里插入图片描述

  • 自定义对象封装指定数据的作用
    在这里插入图片描述
# 创建类,用于封装下面的数据
# 由spring帮我们去加载数据到对象中,一定要告诉spring加载这组信息
# 使用时候从spring中直接获取信息使用

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

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

    private String driver;
    private String url;
    private String username;
    private String password;
	
	//省略get/set/tostring 方法
}    

使用自动装配封装指定数据

 @Autowired
 private MyDataSource myDataSource;

输出查看

System.out.println(myDataSource);

4、SpringBoot整合JUnit

4.1、整合JUnit

  • 添加Junit的起步依赖 Spring Initializr 创建时自带

    <!--测试的起步依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    
  • SpringBoot整合JUnit

    @SpringBootTest
    class Springboot07JunitApplicationTests {
    	@Autowired
    	private BookService bookService;
    	@Test
    	public void testSave(){
    		bookService.save();
    	}
    }
    
  • @SpringBootTest
    名称:@SpringBootTest
    类型:测试类注解
    位置:测试类定义上方
    作用:设置JUnit加载的SpringBoot启动类
    范例:

    @SpringBootTest
    class Springboot05JUnitApplicationTests {}
    

4.2、整合JUnit——classes属性

在这里插入图片描述

@SpringBootTest(classes = Springboot04JunitApplication.class)
//@ContextConfiguration(classes = Springboot04JunitApplication.class)
class Springboot04JunitApplicationTests {
    //1.注入你要测试的对象
    @Autowired
    private BookDao bookDao;

    @Test
    void contextLoads() {
        //2.执行要测试的对象对应的方法
        bookDao.save();
        System.out.println("two...");
    }
}

注意:

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

5、SpringBoot整合MyBatis、MyBatisPlus

5.1、整合MyBatis

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

②:选择当前模块需要使用的技术集(MyBatis、MySQL)

③:设置数据源参数

#DB Configuration:
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/springboot_db
    username: root
    password: 123456

④:创建user表
在 springboot_db 数据库中创建 user 表

-- ----------------------------
-- Table structure for `user`
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) DEFAULT NULL,
  `password` varchar(50) DEFAULT NULL,
  `name` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'zhangsan', '123', '张三');
INSERT INTO `user` VALUES ('2', 'lisi', '123', '李四');

⑤:创建实体Bean

public class User {
    // 主键
    private Long id;
    // 用户名
    private String username;
    // 密码
    private String password;
    // 姓名
    private String name;
  
    //此处省略getter,setter,toString方法 .. ..
    
}

⑥: 定义数据层接口与映射配置

@Mapper
public interface UserDao {

    @Select("select * from user")
    public List<User> getAll();
}

⑦:测试类中注入dao接口,测试功能

@SpringBootTest
class Springboot05MybatisApplicationTests {

    @Autowired
    private UserDao userDao;

    @Test
    void contextLoads() {
        List<User> userList = userDao.getAll();
        System.out.println(userList);
    }

}

⑧:运行如下

[User{id=1, username='zhangsan', password='123', name='张三'}, User{id=2, username='lisi', password='123', name='李四'}]

5.2、常见问题处理

SpringBoot版本低于2.4.3(不含),Mysql驱动版本大于8.0时,需要在url连接串中配置时区

jdbc:mysql://localhost:3306/springboot_db?serverTimezone=UTC

或在MySQL数据库端配置时区解决此问题

1.MySQL 8.X驱动强制要求设置时区

修改url,添加serverTimezone设定
修改MySQL数据库配置(略)

2.驱动类过时,提醒更换为com.mysql.cj.jdbc.Driver

5.3、整合MyBatisPlus

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

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

注意事项: 由于SpringBoot中未收录MyBatis-Plus的坐标版本,需要指定对应的Version

②:定义数据层接口与映射配置,继承BaseMapper

@Mapper
public interface UserDao extends BaseMapper<User> {

}

③:其他同SpringBoot整合MyBatis
(略)

④:测试类中注入dao接口,测试功能

@SpringBootTest
class Springboot06MybatisPlusApplicationTests {

    @Autowired
    private UserDao userDao;

    @Test
    void contextLoads() {
        List<User> users = userDao.selectList(null);
        System.out.println(users);
    }

}

⑤: 运行如下:

[User{id=1, username='zhangsan', password='123', name='张三'}, User{id=2, username='lisi', password='123', name='李四'}]

注意: 如果你的数据库表有前缀要在 application.yml 添加如下配制

#设置Mp相关的配置
mybatis-plus:
  global-config:
    db-config:
      table-prefix: tbl_

6、SpringBoot整合Druid

①: 导入Druid对应的starter

   <dependency>
       <groupId>com.alibaba</groupId>
       <artifactId>druid-spring-boot-starter</artifactId>
       <version>1.2.6</version>
   </dependency>
#DB Configuration:
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/springboot_db?serverTimezone=UTC
    username: root
    password: Lemon
    type: com.alibaba.druid.pool.DruidDataSource

②: 指定数据源类型 (这种方式只需导入一个 Druid 的坐标)

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

或者 变更Druid的配置方式(推荐) 这种方式需要导入 Druid对应的starter

7、SSMP

7.1、数据配置

1. 案例实现方案分析
	实体类开发————使用Lombok快速制作实体类
	Dao开发————整合MyBatisPlus,制作数据层测试类
	Service开发————基于MyBatisPlus进行增量开发,制作业务层测试类
	Controller开发————基于Restful开发,使用PostMan测试接口功能
	Controller开发————前后端开发协议制作
	页面开发————基于VUE+ElementUI制作,前后端联调,页面数据处理,页面消息处理
		列表、新增、修改、删除、分页、查询
	项目异常处理
	按条件查询————页面功能调整、Controller修正功能、Service修正功能
2. SSMP案例制作流程解析
	先开发基础CRUD功能,做一层测一层
	调通页面,确认异步提交成功后,制作所有功能
	添加分页功能与查询功能
DROP TABLE IF EXISTS `tbl_book`;
CREATE TABLE `tbl_book` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `type` varchar(20) DEFAULT NULL,
  `name` varchar(50) DEFAULT NULL,
  `description` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;

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

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>

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

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.2.6</version>
    </dependency>

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

    <!--lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>

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

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.2.6</version>
    </dependency>

</dependencies>
server:
  port: 80
# druid 数据源配制
spring:
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC
      username: root
      password: Lemon

# mybatis-plus
mybatis-plus:
  global-config:
    db-config:
      table-prefix: tbl_
      id-type: auto # 主键策略

Book.class

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

BookDao.class

@Mapper
public interface BookDao extends BaseMapper<Book> {

    /**
     * 查询一个
     * 这是 Mybatis 开发
     * @param id
     * @return
     */
    @Select("select * from tbl_book where id = #{id}")
    Book getById(Integer id);
}

测试类

@SpringBootTest
public class BookDaoTestCase {

    @Autowired
    private BookDao bookDao;

    @Test
    void testGetById() {
        System.out.println(bookDao.getById(1));
        System.out.println(bookDao.selectById(1));
    }

    @Test
    void testSave() {
        Book book = new Book();
        book.setType("测试数据123");
        book.setName("测试数据123");
        book.setDescription("测试数据123");
        bookDao.insert(book);
    }

    @Test
    void testUpdate() {
        Book book = new Book();
        book.setId(13);
        book.setType("测试数据asfd");
        book.setName("测试数据123");
        book.setDescription("测试数据123");
        bookDao.updateById(book);
    }

    @Test
    void testDelete() {
        bookDao.deleteById(13);
    }

    @Test
    void testGetAll() {
        System.out.println(bookDao.selectList(null));
    }

    @Test
    void testGetPage() {
    }

    @Test
    void testGetBy() {
    }
}

开启MybatisPlus运行日志

# mybatis-plus
mybatis-plus:
  global-config:
    db-config:
      table-prefix: tbl_
      id-type: auto # 主键策略
  configuration:
    # 开启MyBatisPlus的日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

7.2、分页

分页操作需要设定分页对象IPage

@Test
void testGetPage() {
    IPage page = new Page(1, 5);
    bookDao.selectPage(page, null);
}
  • IPage对象中封装了分页操作中的所有数据

    数据

    当前页码值

    每页数据总量

    最大页码值

    数据总量

  • 分页操作是在MyBatisPlus的常规操作基础上增强得到,内部是动态的拼写SQL语句,因此需要增强对应的功能

使用MyBatisPlus拦截器实现

@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        //1. 定义 Mp 拦截器
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        //2. 添加具体的拦截器 分页拦截器
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return interceptor;
    }
}

测试

@Test
void testGetPage() {
    IPage page = new Page(1, 5);
    bookDao.selectPage(page, null);
    System.out.println(page.getCurrent());
    System.out.println(page.getSize());
    System.out.println(page.getPages());
    System.out.println(page.getTotal());
    System.out.println(page.getRecords());
}

7.3、数据层标准开发

  • 使用QueryWrapper对象封装查询条件,推荐使用LambdaQueryWrapper对象,所有查询操作封装成方法调用
@Test
void testGetBy2() {
    LambdaQueryWrapper<Book> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    lambdaQueryWrapper.like(Book::getName, "Spring");
    bookDao.selectList(lambdaQueryWrapper);
}
@Test
void testGetBy() {
    QueryWrapper<Book> queryWrapper = new QueryWrapper<>();
    queryWrapper.like("name", "Spring");
    bookDao.selectList(queryWrapper);
}
  • 支持动态拼写查询条件
@Test
void testGetBy2() {
    String name = "1";
    LambdaQueryWrapper<Book> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //if (name != null) lambdaQueryWrapper.like(Book::getName,name);
    lambdaQueryWrapper.like(Strings.isNotEmpty(name), Book::getName, name);
    bookDao.selectList(lambdaQueryWrapper);
}

7.4、业务层标准开发(基础CRUD)

  • Service层接口定义与数据层接口定义具有较大区别,不要混用
    selectByUserNameAndPassword(String username,String password); 数据层接口
    login(String username,String password); Service层接口

  • 接口定义

    public interface BookService {
    
        Boolean save(Book book);
    
        Boolean update(Book book);
    
        Boolean delete(Integer id);
    
        Book getById(Integer id);
    
        List<Book> getAll();
    
        IPage<Book> getPage(int currentPage,int pageSize);
    }
    
  • 实现类定义

@Service
public class BookServiceImpl implements BookService {

    @Autowired
    private BookDao bookDao;

    @Override
    public Boolean save(Book book) {
        return bookDao.insert(book) > 0;
    }

    @Override
    public Boolean update(Book book) {
        return bookDao.updateById(book) > 0;
    }

    @Override
    public Boolean delete(Integer id) {
        return bookDao.deleteById(id) > 0;
    }

    @Override
    public Book getById(Integer id) {
        return bookDao.selectById(id);
    }

    @Override
    public List<Book> getAll() {
        return bookDao.selectList(null);
    }

    @Override
    public IPage<Book> getPage(int currentPage, int pageSize) {
        IPage page = new Page(currentPage, pageSize);
        bookDao.selectPage(page, null);
        return page;
    }
}
  • 测试类定义
@SpringBootTest
public class BookServiceTestCase {

    @Autowired
    private BookService bookService;

    @Test
    void testGetById() {
        System.out.println(bookService.getById(4));
    }

    @Test
    void testSave() {
        Book book = new Book();
        book.setType("测试数据123");
        book.setName("测试数据123");
        book.setDescription("测试数据123");
        bookService.save(book);
    }

    @Test
    void testUpdate() {
        Book book = new Book();
        book.setId(14);
        book.setType("测试数据asfd");
        book.setName("测试数据123");
        book.setDescription("测试数据123");
        bookService.update(book);
    }

    @Test
    void testDelete() {
        bookService.delete(14);
    }

    @Test
    void testGetAll() {
        System.out.println(bookService.getAll());
    }

    @Test
    void testGetPage() {
        IPage<Book> page = bookService.getPage(2, 5);
        System.out.println(page.getCurrent());
        System.out.println(page.getSize());
        System.out.println(page.getPages());
        System.out.println(page.getTotal());
        System.out.println(page.getRecords());
    }
}

7.5、业务层快速开发(基于MyBatisPlus构建)

  • 快速开发方案

    使用MyBatisPlus提供有业务层通用接口(ISerivce)与业务层通用实现类(ServiceImpl<M,T>)

    在通用类基础上做功能重载或功能追加

    注意重载时不要覆盖原始操作,避免原始提供的功能丢失

  • 接口定义

public interface IBookService extends IService<Book> {
}
  • 接口追加功能
public interface IBookService extends IService<Book> {

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

    Boolean insert(Book book);

    Boolean modify(Book book);

    Book get(Integer id);
}

在这里插入图片描述

  • 实现类定义
@Service
public class BookServiceImpl extends ServiceImpl<BookDao, Book> implements IBookService {
}
  • 实现类追加功能
@Service
public class BookServiceImpl extends ServiceImpl<BookDao, Book> implements IBookService {

    @Autowired
    private BookDao bookDao;

    public Boolean insert(Book book) {
        return bookDao.insert(book) > 0;
    }

    public Boolean modify(Book book) {
        return bookDao.updateById(book) > 0;
    }

    public Boolean delete(Integer id) {
        return bookDao.deleteById(id) > 0;
    }

    public Book get(Integer id) {
        return bookDao.selectById(id);
    }
}
  • 测试类定义
@SpringBootTest
public class BookServiceTest {

    @Autowired
    private IBookService bookService;

    @Test
    void testGetById() {
        System.out.println(bookService.getById(4));
    }

    @Test
    void testSave() {
        Book book = new Book();
        book.setType("测试数据123");
        book.setName("测试数据123");
        book.setDescription("测试数据123");
        bookService.save(book);
    }

    @Test
    void testUpdate() {
        Book book = new Book();
        book.setId(14);
        book.setType("===========");
        book.setName("测试数据123");
        book.setDescription("测试数据123");
        bookService.updateById(book);
    }

    @Test
    void testDelete() {
        bookService.removeById(14);
    }

    @Test
    void testGetAll() {
        System.out.println(bookService.list());
    }

    @Test
    void testGetPage() {
        IPage<Book> page = new Page<>(2, 5);
        bookService.page(page);
        System.out.println(page.getCurrent());
        System.out.println(page.getSize());
        System.out.println(page.getPages());
        System.out.println(page.getTotal());
        System.out.println(page.getRecords());
    }
}

7.7、表现层标准开发

  • 基于Restful进行表现层接口开发
  • 使用Postman测试表现层接口功能
@RestController
@RequestMapping("/books")
public class BookController {

    @Autowired
    private IBookService bookService;

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

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

    @PutMapping
    public Boolean update(@RequestBody Book book) {
        return bookService.modify(book);
    }

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

    @GetMapping("{id}")
    public Book getById(@PathVariable Integer id) {
        return bookService.getById(id);
    }

    @GetMapping("{currentPage}/{pageSize}")
    public IPage<Book> getPage(@PathVariable Integer currentPage, @PathVariable int pageSize) {
        return bookService.getPage(currentPage, pageSize);
    }

}

添加 分页的业务层方法

IBookService

 IPage<Book> getPage(int currentPage,int pageSize);

BookServiceImpl

@Override
public IPage<Book> getPage(int currentPage, int pageSize) {

    IPage page = new Page(currentPage, pageSize);
    bookDao.selectPage(page, null);

    return page;
}

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

7.8、表现层数据一致性处理(R对象)

统一格式

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

  • 设计表现层返回结果的模型类,用于后端与前端进行数据格式统一,也称为前后端数据协议
@Data
public class R {
    private Boolean flag;
    private Object data;

    public R() {
    }

    /**
     * 不返回数据的构造方法
     *
     * @param flag
     */
    public R(Boolean flag) {
        this.flag = flag;
    }

    /**
     * 返回数据的构造方法
     *
     * @param flag
     * @param data
     */
    public R(Boolean flag, Object data) {
        this.flag = flag;
        this.data = data;
    }
}
  • 表现层接口统一返回值类型结果
@RestController
@RequestMapping("/books")
public class BookController {

    @Autowired
    private IBookService bookService;

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

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

    }

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

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

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

    @GetMapping("{currentPage}/{pageSize}")
    public R getPage(@PathVariable Integer currentPage, @PathVariable int pageSize) {
        return new R(true, bookService.getPage(currentPage, pageSize));
    }

}

在这里插入图片描述

前端部分省略

8、Springboot工程打包与运行

8.1、程序为什么要打包

将程序部署在独立的服务器上
在这里插入图片描述

8.2、SpringBoot项目快速启动(Windows版)

步骤

①:对SpringBoot项目打包(执行Maven构建指令package)
执行 package 打包命令之前 先执行 mvn clean 删除 target 目录及内容

mvn package

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Eg4DFXqd-1657811363451)(SpringBoot.assets/image-20220317212408717.png)]

打包完成 生成对应的 jar 文件
在这里插入图片描述

可能出现的问题: IDEA下 执行 Maven 命令控制台中文乱码
Ctr+Alt+S 打开设置,在Build,Execution ,Deployment找到Build Tools下Maven项下的Runner ,在VM Options 添加
-Dfile.encoding=GB2312 ,点击OK。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z5rHQGqK-1657811363452)(SpringBoot.assets/image-20220317212514627.png)]

②:运行项目(执行启动指令) java -jar <打包文件名>

java –jar springboot.jar

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

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

在这里插入图片描述

地址栏输入 cmd 回车
在这里插入图片描述

执行 java -jar springboot_08_ssmp-0.0.1-SNAPSHOT.jar
在这里插入图片描述

打包优化:跳过 test 生命周期
在这里插入图片描述
在这里插入图片描述

8.3、打包插件

如果没有配制spring boot 打包插件可能遇到下面的问题:
在这里插入图片描述

使用SpringBoot提供的maven插件可以将工程打包成可执行jar包

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

可执行jar包目录
在这里插入图片描述

ar包描述文件(MANIFEST.MF)

普通工程

Manifest-Version: 1.0
Implementation-Title: springboot_08_ssmp
Implementation-Version: 0.0.1-SNAPSHOT
Build-Jdk-Spec: 1.8
Created-By: Maven Jar Plugin 3.2.0

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

Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Implementation-Title: springboot_08_ssmp
Implementation-Version: 0.0.1-SNAPSHOT
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Start-Class: com.example.SSMPApplication 启动类
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.5.6
Created-By: Maven Jar Plugin 3.2.0
Main-Class: org.springframework.boot.loader.JarLauncher  jar启动器

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

  • Windonws端口被占用
# 查询端口
netstat -ano
# 查询指定端口
netstat -ano |findstr "端口号"
# 根据进程PID查询进程名称
tasklist |findstr "进程PID号"
# 根据PID杀死任务
taskkill /F /PID "进程PID号"
# 根据进程名称杀死任务
taskkill -f -t -im "进程名称"

8.4、Boot工程快速启动

在这里插入图片描述

  • 基于Linux(CenterOS7)

  • 安装JDK,且版本不低于打包时使用的JDK版本

    • 可以使用 yum 安装
  • 安装 MySQL

    • 可以参考: https://blog.csdn.net/qq_42324086/article/details/120579197
  • 安装包保存在/usr/local/自定义目录中或$HOME下

  • 其他操作参照Windows版进行

启动成功无法访问

添加 80 端口

  • 添加 端口
firewall-cmd --zone=public --permanent --add-port=80/tcp

重启

systemctl restart firewalld

后台启动命令

nohup java -jar springboot_08_ssmp-0.0.1-SNAPSHOT.jar > server.log 2>&1 &

停止服务

  • ps -ef | grep “java -jar”
  • kill -9 PID
  • cat server.log (查看日志)
[root@cjbCentos01 app]# ps -ef | grep "java -jar"
UID         PID   PPID  C STIME TTY          TIME CMD
root       6848   6021  7 14:45 pts/2    00:00:19 java -jar springboot_08_ssmp-0.0.1-SNAPSHOT.jar
root       6919   6021  0 14:49 pts/2    00:00:00 grep --color=auto java -jar
[root@cjbCentos01 app]# kill -9 6848
[root@cjbCentos01 app]# ps -ef | grep "java -jar"
root       7016   6021  0 14:52 pts/2    00:00:00 grep --color=auto java -jar
[1]+  已杀死               nohup java -jar springboot_08_ssmp-0.0.1-SNAPSHOT.jar > server.log 2>&1
[root@cjbCentos01 app]# 

8.6、临时属性

8.6.1、临时属性

在这里插入图片描述

  • 带属性数启动SpringBoot
java -jar springboot_08_ssmp-0.0.1-SNAPSHOT.jar --server.port=8080
  • 携带多个属性启动SpringBoot,属性间使用空格分隔

属性加载优先顺序

  1. 参看 https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config
    在这里插入图片描述
8.6.2、开发环境

在这里插入图片描述

  • 带属性启动SpringBoot程序,为程序添加运行属性
    在这里插入图片描述
    在这里插入图片描述

在启动类中 main 可以通过 System.out.println(Arrays.toString(args)); 查看配制的属性

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

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

不携带参数启动SpringBoot程序

public static void main(String[] args) {
    //可以在启动boot程序时断开读取外部临时配置对应的入口,也就是去掉读取外部参数的形参
	SpringApplication.run(SSMPApplication.class);
}

8.7、配置环境

在这里插入图片描述

8.7.1、配置文件分类
  • SpringBoot中4级配置文件

    • 1级:file :config/application.yml 【最高】

    • 2级:file :application.yml

    • 3级:classpath:config/application.yml

    • 4级:classpath:application.yml 【最低】

  • 作用:

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

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

8.7.2、自定义配置文件

在这里插入图片描述

通过启动参数加载配置文件(无需书写配置文件扩展名) --spring.config.name=eban
在这里插入图片描述

properties与yml文件格式均支持

  • 通过启动参数加载指定文件路径下的配置文件 --spring.config.location=classpath:/ebank.yml
    在这里插入图片描述

properties与yml文件格式均支持

  • 通过启动参数加载指定文件路径下的配置文件时可以加载多个配置,后面的会覆盖前面的
--spring.config.location=classpath:/ebank.yml,classpath:/ebank-server.yml

在这里插入图片描述

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

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

  • 单服务器项目:使用自定义配置文件需求较低

  • 多服务器项目:使用自定义配置文件需求较高,将所有配置放置在一个目录中,统一管理

  • 基于SpringCloud技术,所有的服务器将不再设置配置文件,而是通过配置中心进行设定,动态加载配置信息

8.7.3、多环境开发(yaml)

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

#应用环境
#公共配制
spring:
  profiles:
    active: dev


#设置环境
#开发环境
---
spring:
  config:
    activate:
      on-profile: dev
server:
  port: 81

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

#测试环境
---
spring:
  profiles: test
server:
  port: 82
8.7.4、多环境开发文件(yaml)

在这里插入图片描述

多环境开发(YAML版)多配置文件格式
主启动配置文件application.yml

#应用环境
#公共配制
spring:
  profiles:
    active: test

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

server:
  port: 81

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

server:
  port: 82

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

server:
  port: 83
8.7.5、多环境分组管理
  • 根据功能对配置文件中的信息进行拆分,并制作成独立的配置文件,命名规则如下

    • application-devDB.yml

    • application-devRedis.yml

    • application-devMVC.yml

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

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

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

    The following profiles are active: devDB,devMVC,dev
    
  • 从Spring2.4版开始使用group属性替代include属性,降低了配置书写量

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

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

注意事项:
使用group属性,会覆盖 主环境dev (active) 的内容,最后加载的环境属性生效

The following profiles are active: dev,devDB,devMVC
8.7.6、多环境开发控制

在这里插入图片描述
Maven与SpringBoot多环境兼容
①:Maven中设置多环境属性

<!--Maven中设置多环境属性-->
<profiles>
    <profile>
        <id>dev_env</id>
        <properties>
            <profile.active>dev</profile.active>
        </properties>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
    </profile>
    <profile>
        <id>pro_env</id>
        <properties>
            <profile.active>pro</profile.active>
        </properties>

    </profile>
    <profile>
        <id>test_env</id>
        <properties>
            <profile.active>test</profile.active>
        </properties>
    </profile>
</profiles>

②:SpringBoot中引用Maven属性

spring:
  profiles:
    active: @profile.active@
    group:
      "dev": devDB,devMVC
      "pro": proDB,proMVC

在这里插入图片描述

③:执行Maven打包指令,并在生成的boot打包文件.jar文件中查看对应信息
问题:修改pom.xml 文件后,启动没有生效 手动 compile 即可
在这里插入图片描述

或者 设置 IDEA进行自动编译
在这里插入图片描述

9、日志

9.1、日志基础操作

日志(log)作用

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

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

  • 先引入 Lombok 工具类

      <!--lombok-->
      <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
      </dependency>
    

    ①:添加日志记录操作

    @RestController
    @RequestMapping("/books")
    public class BookController {
        private static final Logger log = LoggerFactory.getLogger(BookController.class);
    
        @GetMapping
        public String getById() {
            System.out.println("springboot is running...");
            log.debug("debug ...");
            log.info("info ...");
            log.warn("warn ...");
            log.error("error ...");
            return "springboot is running...";
        }
    }
    
  • 日志级别

TRACE:运行堆栈信息,使用率低
DEBUG:程序员调试代码使用
INFO:记录运维过程数据
WARN:记录运维过程报警数据
ERROR:记录错误堆栈信息
FATAL:灾难信息,合并计入ERRO

②:设置日志输出级别

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

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

logging:
  # 设置分组
  group:
    # 自定义组名,设置当前组中所包含的包
    ebank: com.example.controller,com.example.service,com.example.dao
    iservice: com.alibaba
  level:
    root: info
    # 设置某个包的日志级别
#    com.example.controller: debug
    # 为对应组设置日志级别
    ebank: warn

9.2、快速创建日志对象

  • 使用lombok提供的注解@Slf4j简化开发,减少日志对象的声明操作

    @Slf4j
    //Rest模式
    @RestController
    @RequestMapping("/books")
    public class BookController {
    
        @GetMapping
        public String getById(){
            System.out.println("springboot is running...2");
    
            log.debug("debug...");
            log.info("info...");
            log.warn("warn...");
            log.error("error...");
    
            return "springboot is running...2";
        }
    
    }
    

9.3、日志输出格式控制

在这里插入图片描述

  • PID:进程ID,用于表明当前操作所处的进程,当多服务同时记录日志时,该值可用于协助程序员调试程序
  • 所属类/接口名:当前显示信息为SpringBoot重写后的信息,名称过长时,简化包名书写为首字母,甚至直接删除
  • 设置日志输出格式
logging:
  pattern:
    console: "%d - %m%n"

%d:日期
%m:消息
%n:换行
在这里插入图片描述

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

9.4、文件记录日志

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

在这里插入图片描述

10、热部署

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency>
spring:
  # 热部署范围配置
  devtools:
    restart:
      # 设置不参与热部署的文件和文件夹(即修改后不重启)
      exclude: static/**,public/**,config/application.yml
      #是否可用
      enabled: false

如果配置文件比较多的时候找热部署对应配置比较麻烦,可以在springboot启动类的main方法中设置,此处设置的优先级将比配置文件高,一定会生效。

System.setProperty("spring.devtools.restart.enabled", "false");

11、属性绑定

  1. 先在要配置的类上面加@Component注解将该类交由spring容器管理;
  2. @ConfigurationProperties(prefix="xxx"),xxx跟application.yml配置文件中的属性对应;
  3. 如果多个配置类想统一管理也可以通过@EnableConfigurationProperties({xxx.class, yyy.class})的方式完成配置,不过该注解会与@Component配置发生冲突,二选一即可;
  4. 第三方类对象想通过配置进行属性注入,可以通过创建一个方法,在方法体上加@Bean和@ConfigurationProperties(prefix="xxx")注解,然后方法返回这个第三方对象的方式。
  5. 使用@ConfigurationProperties(prefix="xxx")注解后idea工具会报一个警告Spring Boot Configuration Annotation Processor not configured
@ConfigurationProperties(prefix="xxx")
@Data
public class ServerConfig {
    private String inAddress;
    private int port;
    private long timeout;
}

@ConfigurationProperties绑定属性支持属性名宽松绑定,又叫松散绑定。

比如要将ServerConfig.class作为配置类,并通过配置文件application.yml绑定属性

ServerConfig.class
serverConfig:
  # ipAddress: 192.168.0.1 # 驼峰模式
  # ipaddress: 192.168.0.1
  # IPADDRESS: 192.168.0.1
  ip-address: 192.168.0.1 # 主流配置方式,烤肉串模式
  # ip_address: 192.168.0.1 # 下划线模式
  # IP_ADDRESS: 192.168.0.1 # 常量模式
  # ip_Add_rEss: 192.168.0.1
  # ipaddress: 192.168.0.1
  port: 8888
  timeout: -1

以ipAddress属性为例,上面的多种配置方式皆可生效,这就是松散绑定。而@Value不支持松散绑定,必须一一对应。

@ConfigurationProperties(prefix="serverconfig")中的prefix的值为serverconfig或者server-config,如果是serverConfig就会报错,这与松散绑定的前缀命名规范有关:仅能使用纯小写字母、数字、中划线作为合法的字符

12、常用计量单位应用

//@Component
@ConfigurationProperties(prefix = "server-config")
@Data
public class ServerConfig {
   private String ipAddress;
   private int port;
   @DurationUnit(ChronoUnit.MINUTES)
   private Duration timeout;

   @DataSizeUnit(DataUnit.MEGABYTES)
   private DataSize dataSize;
}

引入Bean属性校验框架的步骤:

  1. pom.xml中添加JSR303规范和hibernate校验框架的依赖:
<!--导入JSR303规范-->
<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
</dependency>
<!--使用hibernate框架提供的校验器-->
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
</dependency>
  1. 在要校验的类上加@Validated注解

  2. 设置具体的校验规则,如:@Max(value=8888, message="最大值不能超过8888")

@ConfigurationProperties(prefix = "server-config")
@Data
// 2.开启对当前bean的属性注入校验
@Validated
public class ServerConfig {
    private String ipAddress;
    // 设置具体的规则
    @Max(value = 8888, message = "最大值不能超过8888")
    @Min(value = 1000, message = "最小值不能低于1000")
    private int port;
    @DurationUnit(ChronoUnit.MINUTES)
    private Duration timeout;

    @DataSizeUnit(DataUnit.MEGABYTES)
    private DataSize dataSize;
}

进制转换中的一些问题:

application.yml文件中对数据库有如下配置:

datasource:
  driverClassName: com.mysql.cj.jdbc.Driver123
  # 不加引号读取的时候默认解析为了8进制数,转成十进制就是87
  # 所以想让这里正确识别,需要加上引号
  # password: 0127
  password: "0127"

13、测试类

13.1、加载专用属性

@SpringBootTest注解中可以设置properties和args属性,这里的args属性的作用跟idea工具中自带的程序参数类似,只不过这里的配置是源码级别的,会随着源码的移动而跟随,而idea中的程序参数的配置会丢失。并且这里的args属性的配置的作用范围比较小,仅在当前测试类生效。

application.yml

test:
  prop: testValue
// properties属性可以为当前测试用例添加临时的属性配置
//@SpringBootTest(properties = {"test.prop=testValue1"})
// args属性可以为当前测试用例添加临时的命令行参数
//@SpringBootTest(args = {"--test.prop=testValue2"})
// 优先级排序: args > properties > 配置文件
@SpringBootTest(args = {"--test.prop=testValue2"}, properties = {"test.prop=testValue1"})
class PropertiesAndArgsTest {
    @Value("${test.prop}")
    private String prop;
    @Test
    public void testProperties() {
        System.out.println("prop = " + prop);
    }
}

13.2、加载专用类

某些测试类中需要用到第三方的类,而其他测试类则不需要用到,这里可以在类上加载@Import({xxx.class, yyy.class})

@RestController
@RequestMapping("/books")
public class BookController {
    /*@GetMapping("/{id}")
    public String getById(@PathVariable int id) {
        System.out.println("id = " + id);
        return "getById...";
    }*/

    @GetMapping("/{id}")
    public Book getById(@PathVariable int id) {
        System.out.println("id = " + id);
        Book book = new Book();
        book.setId(5);
        book.setName("springboot");
        book.setType("springboot");
        book.setDescription("springboot");
        return book;
    }
}

相应测试类

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
// 开启虚拟mvc调用
@AutoConfigureMockMvc
public class WebTest {
    @Test
    public void testRandomPort() {
    }

    @Test
    public void testWeb(@Autowired MockMvc mvc) throws Exception {
        // 创建虚拟请求,当前访问 /books
        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books/5");
        mvc.perform(builder);
    }

    @Test
    public void testStatus(@Autowired MockMvc mvc) throws Exception {
        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books1/6");
        ResultActions action = mvc.perform(builder);
        // 设定预期值,与真实值进行比较,成功测试通过,失败测试不通过
        // 定义本次调用的预期值
        StatusResultMatchers srm = MockMvcResultMatchers.status();
        // 预计本次调用成功的状态码:200
        ResultMatcher ok = srm.isOk();
        // 添加预计值到本次调用过程中进行匹配
        action.andExpect(ok);
    }

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

    @Test
    public void testJson(@Autowired MockMvc mvc) throws Exception {
        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books/7");
        ResultActions action = mvc.perform(builder);
        // 设定预期值,与真实值进行比较,成功测试通过,失败测试不通过
        // 定义本次调用的预期值
        ContentResultMatchers jsonMatcher = MockMvcResultMatchers.content();
        ResultMatcher rm = jsonMatcher.json("{\"id\":5,\"name\":\"springboot\",\"type\":\"springboot\",\"description\":\"springboot1\"}");
        action.andExpect(rm);
    }

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

    @Test
    // 完整测试
    public void testGetById(@Autowired MockMvc mvc) throws Exception {
        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books/8");
        ResultActions action = mvc.perform(builder);

        // 1、比较状态码
        StatusResultMatchers statusResultMatchers = MockMvcResultMatchers.status();
        ResultMatcher statusResultMatcher = statusResultMatchers.isOk();
        action.andExpect(statusResultMatcher);

        // 2、比较返回值类型
        HeaderResultMatchers headerResultMatchers = MockMvcResultMatchers.header();
        ResultMatcher headerResultMatcher = headerResultMatchers.string("Content-Type", "application/json");
        action.andExpect(headerResultMatcher);

        /// 3、比较json返回值
        ContentResultMatchers contentResultMatchers = MockMvcResultMatchers.content();
        ResultMatcher jsonResultMatcher = contentResultMatchers.json("{\"id\":5,\"name\":\"springboot\",\"type\":\"springboot\",\"description\":\"springboot\"}");
        action.andExpect(jsonResultMatcher);
    }
}

13.3、业务层测试事务回滚

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

    @SpringBootTest 
    @Transactional public class DaoTest {    
        @Autowired     
        private BookService bookService; }
    
  • l如果想在测试用例中提交事务,可以通过@Rollback注解设置

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

13.4、测试用例数据设定

  • 测试用例数据通常采用随机值进行测试,使用SpringBoot提供的随机数为其赋值
testcast:
  book:
    id: ${random.int}           # 随机整数
    id2: ${random.int(10)}      # 10以内随机数
    type: ${random.int(10,20)}  # 10到20随机数
    uuid: ${random.uuid}        # 随机uuid
    name: ${random.value}       # 随机字符串,MD5字符串,32位
    publishTime: ${random.long} # 随机整数(long范围)
u${random.int}表示随机整数

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

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

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

14、数据层解决方案

14.1、SQL

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

Druid + MyBatis-Plus + MySQL

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

格式一:

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

格式二:

spring:
	datasource:
		druid:
			driver-class-name: com.mysql.cj.jdbc.Driver
			url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
			username: root
			password: root
  • SpringBoot提供了3种内嵌的数据源对象供开发者选择
    • HikariCP
    • Tomcat提供DataSource
    • Commons DBCP
spring:
	datasource:
		driver-class-name: com.mysql.cj.jdbc.Driver
		url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
		username: root
		password: root
spring:
	datasource:
		druid:
			driver-class-name: com.mysql.cj.jdbc.Driver
			url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
			username: root
			password: root
  • SpringBoot提供了3种内嵌的数据源对象供开发者选择

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

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

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

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

spring:
	datasource:
		driver-class-name: com.mysql.cj.jdbc.Driver
		url: jdbc:mysql://localhost:3306/ssm_db
		username: root
		password: root
		hikari:
			maximum-pool-size: 50
  • 内置持久化解决方案——JdbcTemplate
@SpringBootTest
class Springboot15SqlApplicationTests {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Test
    void testJdbc(){
        String sql = "select * from tbl_book where id = 1";
        List<Book> query = 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(query);
    }
}
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
spring:
	jdbc:
		template:
			query-timeout: -1   # 查询超时时间
			max-rows: 500       # 最大行数
			fetch-size: -1      # 缓存行数
  • SpringBoot提供了3种内嵌数据库供开发者选择,提高开发测试效率

    • H2
    • HSQL
    • Derby
  • 导入H2相关坐标

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
    
  • 设置当前项目为web工程,并配置H2管理控制台参数

server:
	port: 80
spring:
	h2:
		console:
			path: /h2
				enabled: true

访问用户名sa,默认密码123456

操作数据库(创建表)

create table tbl_book (id int,name varchar,type varchar,description varchar)
#设置访问数据源
server:
	port: 80
spring:
	datasource:
		driver-class-name: org.h2.Driver
		url: jdbc:h2:~/test
		username: sa
		password: 123456
h2:
	console:
		path: /h2
		enabled: true

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

server:
	port: 80
spring:
	h2:
		console:
			path: /h2
			enabled: false

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

server:
	port: 80
spring:
	datasource:
#    driver-class-name: org.h2.Driver
		url: jdbc:h2:~/test
		username: sa
		password: 123456
	h2:
		console:
		path: /h2
		enabled: true

在这里插入图片描述

14.2、MongoDB

MongoDB是一个开源、高性能、无模式的文档型数据库。NoSQL数据库产品中的一种,是最像关系型数据库的非关系型数据库
在这里插入图片描述

14.2.1、MongoDB的使用
  • Windows版Mongo下载

https://www.mongodb.com/try/download

  • Windows版Mongo安装

解压缩后设置数据目录

  • Windows版Mongo启动

服务端启动

在bin目录下

mongod --dbpath=..\data\db

客户端启动

mongo --host=127.0.0.1 --port=27017

14.2.2、MongoDB可视化客户端

在这里插入图片描述

  • 新增

    db.集合名称.insert/save/insertOne(文档)

  • 修改

    db.集合名称.remove(条件)

  • 删除

    db.集合名称.update(条件,{操作种类:{文档}})
    在这里插入图片描述
    在这里插入图片描述

14.2.3、Springboot集成MongoDB

导入MongoDB驱动

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

配置客户端

spring:
	data:
		mongodb:
			uri: mongodb://localhost/itheima

客户端读写MongoDB

@Test
void testSave(@Autowired MongoTemplate mongoTemplate){
    Book book = new Book();
    book.setId(1);
    book.setType("springboot");
    book.setName("springboot");
    book.setDescription("springboot");
    mongoTemplate.save(book);
}
@Test
void testFind(@Autowired MongoTemplate mongoTemplate){
    List<Book> all = mongoTemplate.findAll(Book.class);
    System.out.println(all);
}

14.3、ElasticSearch(ES)

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

在这里插入图片描述

在这里插入图片描述

14.3.1、ES下载
  • Windows版ES下载

https://www.elastic.co/cn/downloads/elasticsearch

  • Windows版ES安装与启动

运行:elasticsearch.bat

14.3.2、ES索引、分词器
  • 创建/查询/删除索引

put:http://localhost:9200/books

get:http://localhost:9200/books

delete:http://localhost:9200/books

  • IK分词器

    下载:https://github.com/medcl/elasticsearch-analysis-ik/releases

  • 创建索引并指定规则

{
    "mappings":{
        "properties":{
            "id":{
                "type":"keyword"
            },
            "name":{
                "type":"text",                "analyzer":"ik_max_word",                "copy_to":"all"
            },
            "type":{
                "type":"keyword"
            },
            "description":{
                "type":"text",	                "analyzer":"ik_max_word",                "copy_to":"all"
            },
            "all":{
                "type":"text",	                "analyzer":"ik_max_word"
            }
        }
    }
}
14.3.3、文档操作(增删改查)
  • 创建文档

    post:http://localhost:9200/books/_doc(使用系统生成的id)

    post:http://localhost:9200/books/_create/1(使用指定id)

    post:http://localhost:9200/books/_doc/1(使用指定id,不存在创建,存在更新,版本递增)

    {
        "name":"springboot",
        "type":"springboot",
        "description":"springboot"
    }
    
  • 查询文档

    get:http://localhost:9200/books/_doc/1

    get:http://localhost:9200/books/_search

  • 条件查询

    get:http://localhost:9200/books/_search?q=name:springboot

  • 删除文档

    delete:http://localhost:9200/books/_doc/1

  • 修改文档(全量修改)

    put:http://localhost:9200/books/_doc/1

    {
        "name":"springboot",
        "type":"springboot",
        "description":"springboot"
    }
    
  • 修改文档(部分修改)

    post:http://localhost:9200/books/_update/1

    {
        "doc":{
            "name":"springboot"
        }
    }
    
14.3.4、Springboot集成ES
  • 导入坐标

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    </dependency>
    
  • 配置

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

    @SpringBootTest
    class Springboot18EsApplicationTests {
        @Autowired
        private ElasticsearchRestTemplate template;
    }
    
  • SpringBoot平台并没有跟随ES的更新速度进行同步更新,ES提供了High Level Client操作ES

    导入坐标

    <dependency>
        <groupId>org.elasticsearch.client</groupId>
        <artifactId>elasticsearch-rest-high-level-client</artifactId>
    </dependency>
    

    无需配置

  • 客户端

    @Test
    void test() throws IOException {
        HttpHost host = HttpHost.create("http://localhost:9200");
        RestClientBuilder builder = RestClient.builder(host);
        RestHighLevelClient client = new RestHighLevelClient(builder);
        //客户端操作
        CreateIndexRequest request = new CreateIndexRequest("books");
        //获取操作索引的客户端对象,调用创建索引操作
        client.indices().create(request, RequestOptions.DEFAULT);
        //关闭客户端
        client.close();
    }
    
  • 客户端改进

    @SpringBootTest
    class Springboot18EsApplicationTests {
        @Autowired
        private BookDao bookDao;
        @Autowired
        RestHighLevelClient client;
        @BeforeEach
        void setUp() {
            this.client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://localhost:9200")));
        }
        @AfterEach
        void tearDown() throws IOException {
            this.client.close();
        }
        @Test
        void test() throws IOException {
            //客户端操作
            CreateIndexRequest request = new CreateIndexRequest("books");
            //获取操作索引的客户端对象,调用创建索引操作
            client.indices().create(request, RequestOptions.DEFAULT);
        }
    }
    
14.3.5、索引
  • 创建索引

    //创建索引
    @Test
    void testCreateIndexByIK() throws IOException {
        HttpHost host = HttpHost.create("http://localhost:9200");
        RestClientBuilder builder = RestClient.builder(host);
        RestHighLevelClient client = new RestHighLevelClient(builder);
        //客户端操作
        CreateIndexRequest request = new CreateIndexRequest("books");
        //设置要执行操作
        String json = "";
        //设置请求参数,参数类型json数据
        request.source(json,XContentType.JSON);
        //获取操作索引的客户端对象,调用创建索引操作
        client.indices().create(request, RequestOptions.DEFAULT);
        //关闭客户端
        client.close();
    }
    
    String json = "{\n" +
        "    \"mappings\":{\n" +
        "        \"properties\":{\n" +
        "            \"id\":{\n" +
        "                \"type\":\"keyword\"\n" +
        "            },\n" +
        "            \"name\":{\n" +
        "                \"type\":\"text\",\n" +
        "                \"analyzer\":\"ik_max_word\",\n" +
        "                \"copy_to\":\"all\"\n" +
        "            },\n" +
        "            \"type\":{\n" +
        "                \"type\":\"keyword\"\n" +
        "            },\n" +
        "            \"description\":{\n" +
        "                \"type\":\"text\",\n" +
        "                \"analyzer\":\"ik_max_word\",\n" +
        "                \"copy_to\":\"all\"\n" +
        "            },\n" +
        "            \"all\":{\n" +
        "                \"type\":\"text\",\n" +
        "                \"analyzer\":\"ik_max_word\"\n" +
        "            }\n" +
        "        }\n" +
        "    }\n" +
        "}";
    
  • 添加文档

    //添加文档
    @Test
    void testCreateDoc() throws IOException {
        Book book = bookDao.selectById(1);
        IndexRequest request = new IndexRequest("books").id(book.getId().toString());
        String json = JSON.toJSONString(book);
        request.source(json,XContentType.JSON);
        client.index(request, RequestOptions.DEFAULT);
    }
    
  • 批量添加文档

    //批量添加文档
    @Test
    void testCreateDocAll() throws IOException {
        List<Book> bookList = bookDao.selectList(null);
        BulkRequest bulk = new BulkRequest();
        for (Book book : bookList) {
            IndexRequest request = new IndexRequest("books").id(book.getId().toString());
            String json = JSON.toJSONString(book);
            request.source(json,XContentType.JSON);
            bulk.add(request);
        }
        client.bulk(bulk,RequestOptions.DEFAULT);
    }
    
  • 按id查询文档

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

@Test
void testSearch() throws IOException {
    SearchRequest request = new SearchRequest("books");
    SearchSourceBuilder builder = new SearchSourceBuilder();
    builder.query(QueryBuilders.termQuery("all",“java"));
    request.source(builder);
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    SearchHits hits = response.getHits();
    for (SearchHit hit : hits) {
    String source = hit.getSourceAsString();
    Book book = JSON.parseObject(source, Book.class);
    System.out.println(book);
    }
}

15、缓存

15.1、缓存简介

缓存是一种介于数据永久存储介质与数据应用之间的数据临时存储介质
在这里插入图片描述

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

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

  • 缓存不仅可以用于提高永久性存储介质的数据读取效率,还可以提供临时的数据存储空间
    在这里插入图片描述

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

15.2、缓存使用

  • 启用缓存

  • 设置进入缓存的数据

  • 设置读取缓存的数据

  • 导入缓存技术对应的starter

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    
  • 启用缓存

    @SpringBootApplication
    @EnableCaching
    public class Springboot19CacheApplication {
        public static void main(String[] args) { 
            SpringApplication.run(Springboot19CacheApplication.class, args);
        }
    }
    
  • 设置当前操作的结果数据进入缓存

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

15.3、其他缓存

  • SpringBoot提供的缓存技术除了提供默认的缓存方案,还可以对其他缓存技术进行整合,统一接口,方便缓存技术的开发与管理
    • Generic
    • JCache
    • Ehcache
    • Hazelcast
    • Infinispan
    • Couchbase
    • Redis
    • Caffeine
    • Simple(默认)
    • memcached
    • jetcache(阿里)

15.4、缓存使用案例——手机验证码

  • 需求

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

    • 输入手机号和验证码验证结果

  • 需求分析

    • 提供controller,传入手机号,业务层通过手机号计算出独有的6位验证码数据,存入缓存后返回此数据

    • 提供controller,传入手机号与验证码,业务层通过手机号从缓存中读取验证码与输入验证码进行比对,返回比对结果

15.4.1、Cache
  • 开启缓存

    @SpringBootApplication
    @EnableCaching
    public class Springboot19CacheApplication {
        public static void main(String[] args) {
            SpringApplication.run(Springboot19CacheApplication.class, args);
        }
    }
    
  • 业务层接口

    public interface SMSCodeService {
        /**
        * 传入手机号获取验证码,存入缓存
        * @param tele
        * @return
        */
        String sendCodeToSMS(String tele);
    
        /**
         * 传入手机号与验证码,校验匹配是否成功
         * @param smsCode
         * @return
         */
        boolean checkCode(SMSCode smsCode);
    }
    
  • 业务层设置获取验证码操作,并存储缓存,手机号为key,验证码为value

    @Autowired
    private CodeUtils codeUtils;
    @CachePut(value = "smsCode",key="#tele")
    public String sendCodeToSMS(String tele) {
        String code = codeUtils.generator(tele);
        return code;
    }
    
  • 业务层设置校验验证码操作,校验码通过缓存读取,返回校验结果

    @Autowired
    private CodeUtils codeUtils;
    public boolean checkCode(SMSCode smsCode) {
        //取出内存中的验证码与传递过来的验证码比对,如果相同,返回true
        String code = smsCode.getCode();
        String cacheCode = codeUtils.get(smsCode.getTele());
        return code.equals(cacheCode);
    }
    
    @Component
    public class CodeUtils {
        @Cacheable(value = "smsCode",key="#tele")
        public String get(String tele){
            return null;
        }
    }
    
15.4.2、Ehcache
  • 加入Ehcache坐标(缓存供应商实现)

    <dependency>
        <groupId>net.sf.ehcache</groupId>
        <artifactId>ehcache</artifactId>
    </dependency>
    
  • 缓存设定为使用Ehcache

    spring:
    	cache:
    		type: ehcache
    		ehcache:
    			config: ehcache.xml
    
  • 提供ehcache配置文件ehcache.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
             updateCheck="false">
        <!--默认缓存策略 -->
        <!-- 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>
    
15.4.3、Redis
  • 加入Redis坐标(缓存供应商实现)
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  • 配置Redis服务器,缓存设定为使用Redis

    spring:
    	redis:
    		host: localhost
        	port: 6379
    	cache:
    		type: redis
    
  • 设置Redis相关配置

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

  • 地址:https://www.runoob.com/memcached/window-install-memcached.html

  • 安装memcached

    • 使用管理员身份运行cmd指令

    • 安装

      memcached.exe -d install

  • 运行

    • 启动服务

      memcached.exe -d start

    • 定制服务

      memcached.exe -d stop

  • memcached客户端选择

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

  • 加入Xmemcached坐标(缓存供应商实现)

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

    memcached:
    	# memcached服务器地址
    	servers: localhost:11211
    	# 连接池的数量
    	poolSize: 10
    	# 设置默认操作超时
    	opTimeout: 3000
    
  • 创建读取属性配置信息类,加载配置

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

    @Configuration
    public class XMemcachedConfig {
        @Autowired
        private XMemcachedProperties xMemcachedProperties;
        @Bean
        public MemcachedClient getMemcachedClinet() throws IOException {
            MemcachedClientBuilder builder = new XMemcachedClientBuilder(xMemcachedProperties.getServers());
            MemcachedClient memcachedClient = builder.build();
            return memcachedClient;
        }
    }
    
  • 配置memcached属性

    @Service
    public class SMSCodeServiceMemcacheImpl implements SMSCodeService {
        @Autowired
        private CodeUtils codeUtils;
        @Autowired
        private MemcachedClient memcachedClient;
        @Override
        public String sendCodeToSMS(String tele) {
            String code = this.codeUtils.generator(tele);
            //将数据加入memcache
            try {
                memcachedClient.set(tele,0,code);		// key,timeout,value
            } catch (Exception e) {
                e.printStackTrace();
            }
            return code;
        }
    }
    
    @Service
    public class SMSCodeServiceMemcacheImpl implements SMSCodeService {
        @Autowired
        private CodeUtils codeUtils;
        @Autowired
        private MemcachedClient memcachedClient;
        @Override
        public boolean checkCode(CodeMsg codeMsg) {
            String value = null;
            try {
                value = memcachedClient.get(codeMsg.getTele()).toString();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return codeMsg.getCode().equals(value);
        }
    }
    
15.4.5、jetcache
  • jetCache对SpringCache进行了封装,在原有功能基础上实现了多级缓存、缓存统计、自动刷新、异步调用、数据报表等功能

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

    • 本地缓存(local)

      • LinkedHashMap
      • Caffeine
    • 远程缓存(remote)

      • Redis
      • Tair
  • 加入jetcache坐标

    <dependency>
        <groupId>com.alicp.jetcache</groupId>
        <artifactId>jetcache-starter-redis</artifactId>
        <version>2.6.2</version>
    </dependency>
    
  • 配置远程缓存必要属性

    jetcache:
    	remote:
    		default:
    			type: redis
    			host: localhost
    			port: 6379
    			poolConfig:
    				maxTotal: 50
    
    jetcache:
    	remote:
    		default:
    			type: redis
    			host: localhost
    			port: 6379
    			poolConfig:
    				maxTotal: 50
        sms:
        type: redis
        host: localhost
        port: 6379
        poolConfig:
        	maxTotal: 50
    
  • 配置本地缓存必要属性

    jetcache:
    	local:
    		default:
    			type: linkedhashmap
    			keyConvertor: fastjson
    
  • 配置范例

    jetcache:
    	statIntervalMinutes: 15
        areaInCacheName: false
    	local:
    		default:
    		type: linkedhashmap
    		keyConvertor: fastjson
    		limit: 100
    	remote:
    		default:
    			host: localhost
    			port: 6379
    			type: redis
    			keyConvertor: fastjson
    			valueEncoder: java
    			valueDecoder: java
    			poolConfig:
    				minIdle: 5
    				maxIdle: 20
    				maxTotal: 50
    
  • 配置属性说明
    在这里插入图片描述

  • 开启jetcache注解支持

    @SpringBootApplication
    @EnableCreateCacheAnnotation
    public class Springboot19CacheApplication {
        public static void main(String[] args) {
            SpringApplication.run(Springboot19CacheApplication.class, args);
        }
    }
    
  • 声明缓存对象

    @Service
    public class SMSCodeServiceImpl implements SMSCodeService {
        @Autowired
        private CodeUtils codeUtils;
        @CreateCache(name = "smsCache", expire = 3600)
        private Cache<String, String> jetSMSCache;
    }
    
  • 操作缓存

    @Service
    public class SMSCodeServiceImpl implements SMSCodeService {
        @Override
        public String sendCodeToSMS(String tele) {
            String code = this.codeUtils.generator(tele);
            jetSMSCache.put(tele,code);
            return code;
        }
        @Override
        public boolean checkCode(CodeMsg codeMsg) {
            String value = jetSMSCache.get(codeMsg.getTele());
            return codeMsg.getCode().equals(value);
        }
    }
    
  • 启用方法注解

    @SpringBootApplication
    @EnableCreateCacheAnnotation
    @EnableMethodCache(basePackages = "com.itheima")
    public class Springboot20JetCacheApplication {
        public static void main(String[] args) {
            SpringApplication.run(Springboot20JetCacheApplication.class, args);
        }
    }
    
  • 使用方法注解操作缓存

    @Service
    public class BookServiceImpl implements BookService {
        @Autowired
        private BookDao bookDao;
        @Cached(name = "smsCache_", key = "#id", expire = 3600)
        @CacheRefresh(refresh = 10,timeUnit = TimeUnit.SECONDS)
        public Book getById(Integer id) {
            return bookDao.selectById(id);
        }
    }
    
    @Service
    public class BookServiceImpl implements BookService {
        
        @CacheUpdate(name = "smsCache_", key = "#book.id", value = "#book")
        public boolean update(Book book) {
            return bookDao.updateById(book) > 0;
        }
    
        @CacheInvalidate(name = "smsCache_", key = "#id")
        public boolean delete(Integer id) {
            return bookDao.deleteById(id) > 0;
        }
    }
    
  • 缓存对象必须保障可序列化

    @Data
    public class Book implements Serializable {
    }
    
    jetcache:
    	remote:
    		default:
    			type: redis
    			keyConvertor: fastjson
    			valueEncoder: java
    			valueDecoder: java
    
  • 查看缓存统计报告

    jetcache:
    	statIntervalMinutes: 15
    
15.4.6、j2cache
  • j2cache是一个缓存整合框架,可以提供缓存的整合方案,使各种缓存搭配使用,自身不提供缓存功能

  • 基于 ehcache + redis 进行整合

  • 加入j2cache坐标,加入整合缓存的坐标

    <dependency>
        <groupId>net.oschina.j2cache</groupId>
        <artifactId>j2cache-spring-boot2-starter</artifactId>
        <version>2.8.0-release</version>
    </dependency>
    <dependency>
        <groupId>net.oschina.j2cache</groupId>
        <artifactId>j2cache-core</artifactId>
        <version>2.8.4-release</version>
    </dependency>
    <dependency>
        <groupId>net.sf.ehcache</groupId>
        <artifactId>ehcache</artifactId>
    </dependency>
    
  • 配置使用j2cache(application.yml)

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

    # 配置1级缓存
    j2cache.L1.provider_class = ehcache
    ehcache.configXml = ehcache.xml
    
    # 配置1级缓存数据到2级缓存的广播方式:可以使用redis提供的消息订阅模式,也可以使用jgroups多播实现
    j2cache.broadcast = net.oschina.j2cache.cache.support.redis.SpringRedisPubSubPolicy
    
    # 配置2级缓存
    j2cache.L2.provider_class = net.oschina.j2cache.cache.support.redis.SpringRedisProvider
    j2cache.L2.config_section = redis
    redis.hosts = localhost:6379
    
  • 设置使用缓存

    @Service
    public class SMSCodeServiceImpl implements SMSCodeService {
        @Autowired
        private CodeUtils codeUtils;
        @Autowired
        private CacheChannel cacheChannel;
    }
    
    @Service
    public class SMSCodeServiceImpl implements SMSCodeService {
        @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);
        }
    }
    

16、定时

任务

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

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

    • Quartz
    • Spring Task

16.1、SpringBoot整合Quartz

  • 相关概念

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

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-quartz</artifactId>
    </dependency>
    
  • 定义具体要执行的任务,继承QuartzJobBean

    public class QuartzTaskBean extends QuartzJobBean {
        @Override
        protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
            System.out.println(“quartz job run... ");
                               }
    }
    
  • 定义工作明细与触发器,并绑定对应关系

    @Configuration
    public class QuartzConfig {
        @Bean
        public JobDetail printJobDetail(){
            return JobBuilder.newJob(QuartzTaskBean.class).storeDurably().build();
        }
        @Bean
        public Trigger printJobTrigger() {
            CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/3 * * * * ?");
            return TriggerBuilder.newTrigger().forJob(printJobDetail())
                .withSchedule(cronScheduleBuilder).build();
        }
    }
    

16.2、Spring Task

  • 开启定时任务功能

    @SpringBootApplication
    @EnableScheduling
    public class Springboot22TaskApplication {
        public static void main(String[] args) {
            SpringApplication.run(Springboot22TaskApplication.class, args);
        }
    }
    
  • 设置定时执行的任务,并设定执行周期

    @Component
    public class ScheduledBean {
        @Scheduled(cron = "0/5 * * * * ?")
        public void printLog(){
            System.out.println(Thread.currentThread().getName()+":run...");
        }
    }
    
  • 定时任务相关配置

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

16.3、SpringBoot整合JavaMail

  • SMTP(Simple Mail Transfer Protocol):简单邮件传输协议,用于发送电子邮件的传输协议

  • POP3(Post Office Protocol - Version 3):用于接收电子邮件的标准协议

  • IMAP(Internet Mail Access Protocol):互联网消息协议,是POP3的替代协议

  • 导入SpringBoot整合JavaMail的坐标

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-mail</artifactId>
    </dependency>
    
  • 配置JavaMail

    spring:
    	mail:
    		host: smtp.qq.com
    		username: *********@qq.com
    		password: *********
    

在这里插入图片描述

  • 开启定时任务功能

    @Service
    public class SendMailServiceImpl implements SendMailService {
        private String from =********@qq.com";	 // 发送人
            private String to = "********@126.com";	 // 接收人
        private String subject = "测试邮件";		 // 邮件主题
        private String text = "测试邮件正文";	 	// 邮件内容
    }
    
  • 开启定时任务功能

    @Service
    public class SendMailServiceImpl implements SendMailService {
        @Autowired
        private JavaMailSender javaMailSender;
        @Override
        public void sendMail() {
            SimpleMailMessage mailMessage = new SimpleMailMessage();
            mailMessage.setFrom(from);
            mailMessage.setTo(to);
            mailMessage.setSubject(subject);
            mailMessage.setText(text);
            javaMailSender.send(mailMessage);
        }
    }
    
  • 附件与HTML文本支持

    private String text = "<a href='https://www.itcast.cn/'>传智教育</a>";
    @Override
    public void sendMail() {
        try {
            MimeMessage mimeMessage = javaMailSender.createMimeMessage();
            MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage,true);
            mimeMessageHelper.setFrom(from);
            mimeMessageHelper.setTo(to);
            mimeMessageHelper.setSubject(subject);
            mimeMessageHelper.setText(text,true);
            File file = new File("logo.png");
            mimeMessageHelper.addAttachment("美图.png",file);
            javaMailSender.send(mimeMessage);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    

17、消息

在这里插入图片描述

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

17.1、JMS

  • JMS(Java Message Service):一个规范,等同于JDBC规范,提供了与消息服务相关的API接口

  • JMS消息模型

    • peer-2-peer:点对点模型,消息发送到一个队列中,队列保存消息。队列的消息只能被一个消费者消费,或超时
    • publish-subscribe:发布订阅模型,消息可以被多个消费者消费,生产者和消费者完全独立,不需要感知对方的存在
  • JMS消息种类

    • TextMessage
    • MapMessage
    • BytesMessage
    • StreamMessage
    • ObjectMessage
    • Message (只有消息头和属性)
  • JMS实现:ActiveMQ、Redis、HornetMQ、RabbitMQ、RocketMQ(没有完全遵守JMS规范

17.2、AMQP

  • AMQP(advanced message queuing protocol):一种协议(高级消息队列协议,也是消息代理规范),规范了网络交换的数据格式,兼容JMS

  • 优点:具有跨平台性,服务器供应商,生产者,消费者可以使用不同的语言来实现

  • AMQP消息模型

    • direct exchange
    • fanout exchange
    • topic exchange
    • headers exchange
    • system exchange
  • AMQP消息种类:byte[]

  • AMQP实现:RabbitMQ、StormMQ、RocketMQ

17.3、MQTT

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

17.4、Kafka

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

17.5、消息案例

  • 购物订单业务
    • 登录状态检测
    • 生成主单
    • 生成子单
    • 库存检测与变更
    • 积分变更
    • 支付
    • 短信通知(异步)
    • 购物车维护
    • 运单信息初始化
    • 商品库存维护
    • 会员维护

17.6、ActiveMQ

17.6.1、SpringBoot整合ActiveMQ
  • 导入SpringBoot整合ActiveMQ坐标

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-activemq</artifactId>
    </dependency>
    
  • 配置ActiveMQ(采用默认配置)

    spring:
    	activemq:
    		broker-url: tcp://localhost:61616
    	jms:
    		pub-sub-domain: true
    		template:
    			default-destination: itheima
    
  • 生产与消费消息(使用默认消息存储队列)

    @Service
    public class MessageServiceActivemqImpl implements MessageService {
        @Autowired
        private JmsMessagingTemplate jmsMessagingTemplate;
        public void sendMessage(String id) {
            System.out.println("使用Active将待发送短信的订单纳入处理队列,id:"+id);
            jmsMessagingTemplate.convertAndSend(id);
        }
        public String doMessage() {
            return jmsMessagingTemplate.receiveAndConvert(String.class);
        }
    }
    
  • 生产与消费消息(指定消息存储队列)

    @Service
    public class MessageServiceActivemqImpl implements MessageService {
        @Autowired
        private JmsMessagingTemplate jmsMessagingTemplate;
    
        public void sendMessage(String id) {
            System.out.println("使用Active将待发送短信的订单纳入处理队列,id:"+id);
            jmsMessagingTemplate.convertAndSend("order.sm.queue.id",id);
        }
        public String doMessage() {
            return jmsMessagingTemplate.receiveAndConvert("order.sm.queue.id",String.class);
        }
    }
    
  • 使用消息监听器对消息队列监听

    @Component
    public class MessageListener {
        @JmsListener(destination = "order.sm.queue.id")
        public void receive(String id){
            System.out.println("已完成短信发送业务,id:"+id);
        }
    }
    
  • 流程性业务消息消费完转入下一个消息队列

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

17.7、RabbitMQ

  • RabbitMQ基于Erlang语言编写,需要安装Erlang

  • Erlang

  • 下载地址:https://rabbitmq.com/install-windows.html

  • 安装:一键傻瓜式安装

  • 启动服务

    rabbitmq-service.bat start

  • 关闭服务

    rabbitmq-service.bat stop

  • 查看服务状态

    rabbitmqctl status

  • 服务管理可视化(插件形式)

  • 查看已安装的插件列表

  • 开启服务管理插件

    rabbitmq-plugins.bat enable rabbitmq_management

  • 访问服务器

    http://localhost:15672

    • 服务端口:5672,管理后台端口:15672
    • 用户名&密码:guest
17.7.1、SpringBoot整合RabbitMQ
  • 导入SpringBoot整合RabbitMQ坐标

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
    
  • 配置RabbitMQ (采用默认配置)

    spring:
    	rabbitmq:
    		host: localhost
    		port: 5672
    
  • 定义消息队列(direct)

    @Configuration
    public class RabbitDirectConfig {
        @Bean
        public Queue queue(){
            return new Queue("simple_queue");
        }
    }
    
    @Configuration
    public class RabbitDirectConfig {
        @Bean
        public Queue queue(){
            // durable:是否持久化,默认false
            // exclusive:是否当前连接专用,默认false,连接关闭后队列即被删除
            // autoDelete:是否自动删除,当生产者或消费者不再使用此队列,自动删除
            return new Queue("simple_queue",true,false,false);
        }
    }
    
    @Configuration
    public class RabbitDirectConfig {
        @Bean
        public Queue directQueue(){
            return new Queue("direct_queue");	   
        }
        @Bean
        public Queue directQueue2(){
            return new Queue("direct_queue2");    
        }
        @Bean
        public DirectExchange directExchange(){
            return new DirectExchange("directExchange");
        }
        @Bean
        public Binding bindingDirect(){
            return BindingBuilder.bind(directQueue()).to(directExchange()).with("direct");
        }
        @Bean
        public Binding bindingDirect2(){
            return BindingBuilder.bind(directQueue2()).to(directExchange()).with("direct2");
        }
    }
    
  • 生产与消费消息(direct)

    @Service
    public class MessageServiceRabbitmqDirectImpl implements MessageService {
        @Autowired
        private AmqpTemplate amqpTemplate;
        @Override
        public void sendMessage(String id) {
            System.out.println("使用Rabbitmq将待发送短信的订单纳入处理队列,id:"+id);
            amqpTemplate.convertAndSend("directExchange","direct",id);
        }
    }
    
  • 使用消息监听器对消息队列监听(direct)

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

    @Component
    public class RabbitMessageListener2 {
        @RabbitListener(queues = "direct_queue")
        public void receive(String id){
            System.out.println("已完成短信发送业务(two),id:"+id);
        }
    }
    
  • 定义消息队列(topic)

    @Configuration
    public class RabbitTopicConfig {
        @Bean
        public Queue topicQueue(){        return new Queue("topic_queue");    }
        @Bean
        public Queue topicQueue2(){       return new Queue("topic_queue2");   }
        @Bean
        public TopicExchange topicExchange(){
            return new TopicExchange("topicExchange");
        }
        @Bean
        public Binding bindingTopic(){
            return BindingBuilder.bind(topicQueue()).to(topicExchange()).with("topic.*.*");
        }
        @Bean
        public Binding bindingTopic2(){
            return BindingBuilder.bind(topicQueue2()).to(topicExchange()).with("topic.#");
        }
    }
    
  • 绑定键匹配规则

    • *(星号): 用来表示一个单词 ,且该单词是必须出现的
    • #(井号): 用来表示任意数量

在这里插入图片描述

  • 生产与消费消息(topic)

    @Service
    public class MessageServiceRabbitmqTopicmpl implements MessageService {
        @Autowired
        private AmqpTemplate amqpTemplate;
        @Override
        public void sendMessage(String id) {
            System.out.println("使用Rabbitmq将待发送短信的订单纳入处理队列,id:"+id);
            amqpTemplate.convertAndSend("topicExchange","topic.order.id",id);
        }
    }
    
  • 使用消息监听器对消息队列监听(topic)

    @Component
    public class RabbitTopicMessageListener {
        @RabbitListener(queues = "topic_queue")
        public void receive(String id){
            System.out.println("已完成短信发送业务,id:"+id);
        }
        @RabbitListener(queues = "topic_queue2")
        public void receive2(String id){
            System.out.println("已完成短信发送业务(two),id:"+id);
        }
    }
    

17.8、RocketMQ

  • 下载地址:https://rocketmq.apache.org/

  • 安装:解压缩

    • 默认服务端口:9876
  • 环境变量配置

  • ROCKETMQ_HOME

  • PATH

  • NAMESRV_ADDR (建议): 127.0.0.1:9876

  • 命名服务器与broker
    在这里插入图片描述

  • 启动命名服务器

    mqnamesrv

  • 启动broker

    mqbroker

  • 服务器功能测试:生产者

    tools org.apache.rocketmq.example.quickstart.Producer

  • 服务器功能测试:消费者

    tools org.apache.rocketmq.example.quickstart.Consumer

17.8.1、SpringBoot整合RocketMQ
  • 导入SpringBoot整合RocketMQ坐标
<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
    <version>2.2.1</version>
</dependency>
  • 配置RocketMQ (采用默认配置)

    rocketmq:
    	name-server: localhost:9876
    	producer:
    		group: group_rocketmq
    
  • 生产消息

    @Service
    public class MessageServiceRocketmqImpl implements MessageService {
        @Autowired
        private RocketMQTemplate rocketMQTemplate;
        @Override
        public void sendMessage(String id) {
            rocketMQTemplate.convertAndSend("order_sm_id",id);
            System.out.println("使用Rabbitmq将待发送短信的订单纳入处理队列,id:"+id);
        }
    }
    
  • 生产异步消息

    @Service
    public class MessageServiceRocketmqImpl implements MessageService {
        @Autowired
        private RocketMQTemplate rocketMQTemplate;
        @Override
        public void sendMessage(String id) {
            SendCallback callback = new SendCallback() {
                @Override
                public void onSuccess(SendResult sendResult) {
                    System.out.println("消息发送成功");
                }
                @Override
                public void onException(Throwable throwable) {
                    System.out.println("消息发送失败!!!!!!!!!!!");
                }
            };
            System.out.println("使用Rabbitmq将待发送短信的订单纳入处理队列,id:"+id);
            rocketMQTemplate.asyncSend("order_sm_id",id,callback);
        }
    }
    
  • 使用消息监听器对消息队列监听

    @Component
    @RocketMQMessageListener(topic="order_sm_id",consumerGroup = "group_rocketmq")
    public class RocketmqMessageListener implements RocketMQListener<String> {
        @Override
        public void onMessage(String id) {
            System.out.println("已完成短信发送业务,id:"+id);
        }
    }
    

17.9、Kafka

  • 下载地址:https://kafka.apache.org/downloads

  • windows 系统下3.0.0版本存在BUG,建议使用2.X版本

  • 安装:解压缩

  • 启动zookeeper

    zookeeper-server-start.bat ..\..\config\zookeeper.properties

    • 默认端口:2181
  • 启动kafka

    kafka-server-start.bat ..\..\config\server.properties

    • 默认端口:9092
  • 创建topic

    kafka-topics.bat --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic itheima

  • 查看topic

    kafka-topics.bat --zookeeper 127.0.0.1:2181 --list

  • 删除topic

    kafka-topics.bat --delete --zookeeper localhost:2181 --topic itheima

  • 生产者功能测试

    kafka-console-producer.bat --broker-list localhost:9092 --topic itheima

  • 消费者功能测试

    kafka-console-consumer.bat --bootstrap-server localhost:9092 --topic itheima --from-beginning

17.9.1、SpringBoot整合Kafka
  • 导入SpringBoot整合Kafka坐标

    <dependency>
       <groupId>org.springframework.kafka</groupId>
        <artifactId>spring-kafka</artifactId>
    </dependency>
    
  • 配置Kafka (采用默认配置)

    spring:
    	kafka:
    		bootstrap-servers: localhost:9092
    		consumer:
    			group-id: order
    
  • 生产消息

    @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("kafka_topic",id);    }
    }
    
  • 使用消息监听器对消息队列监听

    @Component
    public class KafkaMessageListener{
        @KafkaListener(topics = {"kafka_topic"})
        public void onMessage(ConsumerRecord<?, ?> record) {
            System.out.println("已完成短信发送业务,id:"+record.value());
        }
    }
    

18、监控

18.1、简介

监控的意义:

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

监控的实施方式:

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

18.2、可视化监控平台

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

  • Admin服务端

    <properties>
        <spring-boot-admin.version>2.5.4</spring-boot-admin.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-starter-server</artifactId>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>de.codecentric</groupId>
                <artifactId>spring-boot-admin-dependencies</artifactId>
                <version>${spring-boot-admin.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
  • Admin客户端

    <properties>
        <spring-boot-admin.version>2.5.4</spring-boot-admin.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-starter-client</artifactId>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>de.codecentric</groupId>
                <artifactId>spring-boot-admin-dependencies</artifactId>
                <version>${spring-boot-admin.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
  • Admin服务端

    <dependency>
        <groupId>de.codecentric</groupId>
        <artifactId>spring-boot-admin-starter-server</artifactId>
        <version>2.5.4</version>
    </dependency>
    
  • Admin客户端

    <dependency>
        <groupId>de.codecentric</groupId>
        <artifactId>spring-boot-admin-starter-client</artifactId>
        <version>2.5.4</version>
    </dependency>
    
  • Admin服务端

    server:
    	port: 8080
    
  • 设置启用Spring-Admin

    @SpringBootApplication
    @EnableAdminServer
    public class Springboot25ActuatorServerApplication {
        public static void main(String[] args) {
            SpringApplication.run(Springboot25ActuatorServerApplication.class, args);
        }
    }
    
  • Admin客户端

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

在这里插入图片描述

18.3、监控原理

  • Actuator提供了SpringBoot生产就绪功能,通过端点的配置与访问,获取端点信息

  • 端点描述了一组监控信息,SpringBoot提供了多个内置端点,也可以根据需要自定义端点信息

  • 访问当前应用所有端点信息:/actuator

  • 访问端点详细信息:/actuator**/**端点名称
    在这里插入图片描述

在这里插入图片描述

  • Web程序专用端点
    在这里插入图片描述

  • 启用指定端点

    management:
    endpoint:
    health:   # 端点名称
    enabled: true      show-details: always    beans:    # 端点名称      enabled: true
    
    
  • 启用所有端点

    management:
    	endpoints:
    		enabled-by-default: true
    
    
  • 暴露端点功能

    • 端点中包含的信息存在敏感信息,需要对外暴露端点功能时手动设定指定端点信息
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述

18.4、自定义监控指标

  • 为info端点添加自定义指标

    info:
    	appName: @project.artifactId@
    	version: @project.version@
    	author: itheima
    
    @Component
    public class AppInfoContributor implements InfoContributor {
        @Override
        public void contribute(Info.Builder builder) {
            Map<String,Object> infoMap = new HashMap<>();
            infoMap.put("buildTime","2006");
            builder.withDetail("runTime",System.currentTimeMillis())
                .withDetail("company","传智教育");
            builder.withDetails(infoMap);
        }
    }
    
  • 为Health端点添加自定义指标

    @Component
    public class AppHealthContributor extends AbstractHealthIndicator {
        @Override
        protected void doHealthCheck(Health.Builder builder) throws Exception {
            boolean condition = true;
            if(condition){
                Map<String,Object> infoMap = new HashMap<>();
                infoMap.put("buildTime","2006");
                builder.withDetail("runTime",System.currentTimeMillis())
                    .withDetail("company","传智教育");
                builder.withDetails(infoMap);
                builder.status(Status.UP);
            }else{
                builder.status(Status.DOWN);
                 }
        }
    }
    
  • 为Metrics端点添加自定义指标

    @Service
    public class BookServiceImpl extends ServiceImpl<BookDao, Book> implements IBookService {
        private Counter counter;
        public BookServiceImpl(MeterRegistry meterRegistry){
            counter = meterRegistry.counter("用户付费操作次数:");
        }
        @Override
        public boolean delete(Integer id) {
            counter.increment();
            return bookDao.deleteById(id) > 0;
        }
    }
    
  • 自定义端点

    @Component
    @Endpoint(id="pay")
    public class PayEndPoint {
        @ReadOperation
        public Object getPay(){
            //调用业务操作,获取支付相关信息结果,最终return出去
            Map payMap = new HashMap();
            payMap.put("level 1",103);
            payMap.put("level 2",315);
            payMap.put("level 3",666);
            return payMap;
        }
    }
    

pom
import



- Admin客户端

```xml
<properties>
    <spring-boot-admin.version>2.5.4</spring-boot-admin.version>
</properties>
<dependencies>
    <dependency>
        <groupId>de.codecentric</groupId>
        <artifactId>spring-boot-admin-starter-client</artifactId>
    </dependency>
</dependencies>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-dependencies</artifactId>
            <version>${spring-boot-admin.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
  • Admin服务端

    <dependency>
        <groupId>de.codecentric</groupId>
        <artifactId>spring-boot-admin-starter-server</artifactId>
        <version>2.5.4</version>
    </dependency>
    
  • Admin客户端

    <dependency>
        <groupId>de.codecentric</groupId>
        <artifactId>spring-boot-admin-starter-client</artifactId>
        <version>2.5.4</version>
    </dependency>
    
  • Admin服务端

    server:
    	port: 8080
    
  • 设置启用Spring-Admin

    @SpringBootApplication
    @EnableAdminServer
    public class Springboot25ActuatorServerApplication {
        public static void main(String[] args) {
            SpringApplication.run(Springboot25ActuatorServerApplication.class, args);
        }
    }
    
  • Admin客户端

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

    [外链图片转存中…(img-cXWfQSEx-1657811363485)]

18.3、监控原理

  • Actuator提供了SpringBoot生产就绪功能,通过端点的配置与访问,获取端点信息

  • 端点描述了一组监控信息,SpringBoot提供了多个内置端点,也可以根据需要自定义端点信息

  • 访问当前应用所有端点信息:/actuator

  • 访问端点详细信息:/actuator**/**端点名称

    [外链图片转存中…(img-KSdMaD18-1657811363486)]

    [外链图片转存中…(img-UlUbALwe-1657811363487)]

  • Web程序专用端点

    [外链图片转存中…(img-maGlhCAb-1657811363487)]

  • 启用指定端点

    management:
    endpoint:
    health:   # 端点名称
    enabled: true      show-details: always    beans:    # 端点名称      enabled: true
    
    
  • 启用所有端点

    management:
    	endpoints:
    		enabled-by-default: true
    
    
  • 暴露端点功能

    • 端点中包含的信息存在敏感信息,需要对外暴露端点功能时手动设定指定端点信息

      [外链图片转存中…(img-6UeTKYgJ-1657811363488)]

[外链图片转存中…(img-bLPYJP4S-1657811363489)]

[外链图片转存中…(img-RwCI70cm-1657811363490)]

18.4、自定义监控指标

  • 为info端点添加自定义指标

    info:
    	appName: @project.artifactId@
    	version: @project.version@
    	author: itheima
    
    @Component
    public class AppInfoContributor implements InfoContributor {
        @Override
        public void contribute(Info.Builder builder) {
            Map<String,Object> infoMap = new HashMap<>();
            infoMap.put("buildTime","2006");
            builder.withDetail("runTime",System.currentTimeMillis())
                .withDetail("company","传智教育");
            builder.withDetails(infoMap);
        }
    }
    
  • 为Health端点添加自定义指标

    @Component
    public class AppHealthContributor extends AbstractHealthIndicator {
        @Override
        protected void doHealthCheck(Health.Builder builder) throws Exception {
            boolean condition = true;
            if(condition){
                Map<String,Object> infoMap = new HashMap<>();
                infoMap.put("buildTime","2006");
                builder.withDetail("runTime",System.currentTimeMillis())
                    .withDetail("company","传智教育");
                builder.withDetails(infoMap);
                builder.status(Status.UP);
            }else{
                builder.status(Status.DOWN);
                 }
        }
    }
    
  • 为Metrics端点添加自定义指标

    @Service
    public class BookServiceImpl extends ServiceImpl<BookDao, Book> implements IBookService {
        private Counter counter;
        public BookServiceImpl(MeterRegistry meterRegistry){
            counter = meterRegistry.counter("用户付费操作次数:");
        }
        @Override
        public boolean delete(Integer id) {
            counter.increment();
            return bookDao.deleteById(id) > 0;
        }
    }
    
  • 自定义端点

    @Component
    @Endpoint(id="pay")
    public class PayEndPoint {
        @ReadOperation
        public Object getPay(){
            //调用业务操作,获取支付相关信息结果,最终return出去
            Map payMap = new HashMap();
            payMap.put("level 1",103);
            payMap.put("level 2",315);
            payMap.put("level 3",666);
            return payMap;
        }
    }
    
;