SpringBoot

概念

  • 什么是SpringBoot

    SpringBoot 提供了一种快速使用Spring 的方式,基于约定优于配置的思想,可以让开发人员不必再配置与逻辑业务之间进行思维的切换,全身心的投入到逻辑业务的代码编写中,从而大大提高了开发的效率,一定程度上缩短了项目周期

创建 SpringBoot 项目

  • 配置服务地址

    Server URL:https://start.springboot.io/
    Server URL
  • 选择Spring-Web 依赖项

    speing-web
    speing-web
  • Service URL

    • https://start.springboot.io/(推荐)
    • https://start.aliyun.com/(存在springboot 版本问题,需要手动更新创建好项目的版本号)
    • https://start.spring.io/(官网提供)
  • 以上两个地址都可以在浏览器内打开,并通过向导完成项目初始化,下载后可以导入到IDEA 进行使用

  • pom 解析

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!-- 父 -->
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.6.7</version>
    <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <!-- 本项目 -->
    <groupId>com.coderitl</groupId>
    <artifactId>springboot</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot</name>
    <description>springboot</description>
    <properties>
    <java.version>1.8</java.version>
    </properties>

    <dependencies>
    <!-- SpringBoot 框架 web 项目起步依赖 -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>


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

    </dependencies>

    <build>
    <plugins>

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

    </project>

  • 注意点

    1
    SpringBoot 项目代码必须放到 SpringbootApplication 类所在的同级目录或下级目录
    • 原因:是由于SpringBoot 的默认包扫描
  • 目录结构

    springboot 目录结构
    springboot目录结构
  • 创建一个springmvc controller

    1
    2
    3
    4
    5
    6
    7
    8
    @Controller
    public class HelloWBoot {
    @RequestMapping("/doSome")
    @ResponseBody
    public String doSome() {
    return "Hello SpringBoot Application";
    }
    }
  • 启动信息查看

    查看启动信息
    查看启动信息
  • 直接去访问控制器的请求,直接查看是一个Error 地址

    直接访问显示的页面,并不是启动失败 访问spring-mvc的 /doSome 请求
    直接访问显示的页面,并不是启动失败 spring-mvc 请求
  • 核心配置文件两种形式(两者同时出现以properties 为主,yml 互补差异内容)

    • application.properties

      1
      2
      3
      4
      5
      6
      7
      # 设置内嵌 Tomcat 启动端口
      server.port=8080 # 两者同时配置端口,以此处为优先
      # 设置上下文 根
      # server.servlet.context-path 如果未配置,将以 yml 作为互补内容

      ...

    • application.yml(推荐)

      1
      2
      3
      4
      5
      6
      7
      8
      # http://localhost:8080/mydev/xxx
      server:
      port: 8080 # 设置内嵌 Tomcat 启动端口
      servlet:
      context-path: /mydev # 设置上下文路径

      ...

  • 多环境配置

    1
    2
    3
    4
    5
    6
    7
    # 主配置文件
    spring:
    profiles:
    # 激活的配置文件名称
    active: dev
    # 包含其他配置文件
    include: devDB,devRedis
    1
    2
    3
    4
    5
    6
    # dev 环境
    # 给配置文件起名字,让主配置引用
    spring:
    config:
    activate:
    on-profile: dev
    文件结构
  • SpringBoot 2.4 版本开始使用group 属性代替include 属性,降低了配置书写量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 主配置文件对其他环境配置文件的引用
    spring:
    profiles:
    # dev 和 group:dev 的需要对应
    active: dev
    # devDB 等为自定义名称,约定大于配置(application-devDB的格式)
    group:
    # devDB,devRedis 有加载优先级,靠后的配置会覆盖前面的配置
    "dev": devDB,devRedis
    "pro": proDB,proRedis
    "test": testDB,testRedis
  • 文件隐藏

    文件隐藏
    文件隐藏

热部署配置

  • 添加依赖

    1
    2
    3
    4
    5
    6
    <!-- 热部署 -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
    </dependency>
  • 配置IDEA

    配置项目自动编译 项目重启: 低版本 ctrl + alt + shift + / 2021.3.2 迁移到如下
    配置项目自动编译 允许项目重启
  • 重新启动IDEA

RestFul

  • 认识

    REST(Representational State Transfer)简称REST

    一种互联网软件架构设计的风格,但它不是标准,它只是提出了一组客户端和服务器交互时的架构理念和设计原则,基于这种理念和原则设计的接口可以更简洁,更有层次

    比如要访问一个http 接口:http://localhost:8080/boot/order?id=2022$status=1

    采用RESTFul 风格则地址为: http://localhost:8080/boot/order/2022/1

  • @PathVariable

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /**
    * 名称: @PathVariable
    * 参数: 形参注解
    * 位置: SpringMVC 控制器方法形参定义前面
    * 作用: 绑定路径参数与处理器方法形参间的关系,要求路径参数名与形参名 一 一 对应
    */
    @ResponseBody
    @RequestMapping(value = "/getById/{id}", method = RequestMethod.GET)
    public String getById(@PathVariable Integer id) {
    System.out.println("user save..." + id);
    return "{'message':'getById'}";
    }
  • @RequestBody、@RequestParam、@PathVariable

    • 区别

      • @RequestParam(请求体参数) 用于接受url 地址传参或表单传参
      • @RequestBody 用于接受json 数据(POST 请求)
      • @PathVariable 用于接受路径参数,使用{参数名称} 描述路径参数
    • 应用

      • 后期开发中,发送请求参数超过1 个时,json 格式为主,@RequestBody 应用较广
      • 如果发送非json 格式数据,选用@RequestParam 接受请求参数
      • 采用RESTFul 进行开发,当参数数量较少时,例如一个,可以采用@PathVariable 接受请求路径变量,通常用于传递id
    • 简化

      简化
      简化
    • 测试请求

      请求测试
      请求测试
    • 方法注解

      1
      2
      3
      4
      5
      6
      7
      8
      @DeleteMapping
      @GetMapping
      @PostMapping
      @PutMapping

      位置: 基于 springMVC 的 RESTFul 开发控制器方法定义上方
      作用: 设置当前控制器方法请求访问路径与请求动作,每种对应一个请求动作
      value(默认): 请求访问路径

单一属性读取

  • 配置文件

    1
    2
    # 自定义单一属性
    developmentEnvironment: IDEA2021.3.2
    • 读取

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      // 读取 yml 单一数据 developmentEnvironment
      @Value("${developmentEnvironment}")
      private String devEnv;

      ...
      @xxx
      {
      return devEnv;
      }

    • 结果

      读取结果
      读取结果
  • yml 内容多层级

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    hexo:
    categories:
    - Java
    - SpringBoot

    ItUser:
    name: [ coder-itl,student ] # 上述 hexo 可以写为数组形式
    age: 18

    dataSource:
    driver: com.mysql.jdbc.Driver
    url: localhost:3306/springdb
    username: root
    password: root

  • 读取

    分级结构读取一 分级结构读取二
    分级结构 分级结构读取
  • yml 内部引用

    1
    2
    3
    4
    5
    6
    7
    baseDir: c:\win11
    tempDir: ${baseDir}\code

    # 有转义字符的使用 单引号('')包裹即可  等等
    baseDir: 'a\tb'
    输出: a b

    • 输出

      内部引用
      内部引用
  • 自动装配所有

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    package com.coderitl.springboot.controller;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.core.env.Environment;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;


    @RestController
    @RequestMapping("/users")
    public class UserController {
    // 自动装配将所有的数据封装到一个对象 Environment 中
    @Autowired
    private Environment env;

    // @RequestMapping(value = "/delete/{id}", method = RequestMethod.DELETE)
    @GetMapping("/{id}")
    public String delete(@PathVariable Integer id) {
    System.out.println("user delete..." + id);
    // 获取输出必须先访问请求才可以看到对应结果
    System.out.println(env.getProperty("server.port"));
    return "code: 200";
    }
    }

    • 获取属性

      属性获取
      属性获取
  • 数据封装

    1
    2
    3
    4
    5
    6
    # yml
    dataSource:
    driver: com.mysql.jdbc.Driver
    url: localhost:3306/springdb
    username: root
    password: root
    • 数据封装

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      package com.coderitl.springboot.vo;

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

      /**
      * dataSource 需要写为 prefix="data-source" 才有效
      * datasource:
      * driver: com.mysql.jdbc.Driver
      * url: localhost:3306/springdb
      * username: root
      * password: root
      */
      // 1. 定义数据模型封装 yml 文件中对应的数据 2. 定义为 spring 管控的 bean 3. 指定加载的数据
      @Component
      @ConfigurationProperties(prefix = "datasource")
      public class MyDataSource {
      private String driver;
      private String url;
      private String username;
      private String password;


      public String getDriver() {
      return driver;
      }

      public void setDriver(String driver) {
      this.driver = driver;
      }

      public String getUrl() {
      return url;
      }

      public void setUrl(String url) {
      this.url = url;
      }

      public String getUsername() {
      return username;
      }

      public void setUsername(String username) {
      this.username = username;
      }

      public String getPassword() {
      return password;
      }

      public void setPassword(String password) {
      this.password = password;
      }

      @Override
      public String toString() {
      return "MyDataSource{" +
      "driver='" + driver + '\'' +
      ", url='" + url + '\'' +
      ", username='" + username + '\'' +
      ", password='" + password + '\'' +
      '}';
      }
      }

    • 获取测试

      名称使用问题 获取测试
      名称使用问题 获取测试

集成JUNIT

  • 步骤

    • 加入依赖

      1
      2
      3
      4
      5
      6

      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
      </dependency>
    • dao 测试

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      package com.coderitl.springboot.dao.impl;

      import com.coderitl.springboot.dao.UserDao;
      import org.springframework.stereotype.Repository;


      @Repository
      public class UserDaoImpl implements UserDao {
      @Override
      public void save() {
      System.out.println("save is running.........");
      }
      }


      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      // 测试

      package com.coderitl.springboot;

      import com.coderitl.springboot.dao.UserDao;
      import org.junit.jupiter.api.Test;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.boot.test.context.SpringBootTest;

      @SpringBootTest
      class SpringbootApplicationTests {
      // 1. 注入你要测试的对象
      @Autowired
      private UserDao userDao;

      @Test
      void contextLoads() {
      // 2. 执行要测试的对象对应的方法
      userDao.save();
      }

      }

  • 测试文件位置问题

    • 改动SpringbootApplicationTests 的位置,如果不在SpringbootApplication 引导类包的下级,运行将会报错

      测试类位置引起报错
      测试类位置引起报错
    • 解决方案

      在测试启动类添加@SpringBootTest(classes = SpringbootApplication.class【主启动类字节码】)
  • 报错后扫描包的方式

    • 主测试文件位置出发,找注解@SpringBootConfiguration,看谁加载了它,在父包或子包中找不见,则报错

      主启动类中包含@SpringBootConfiguration 注解
  • 注意事项

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

  • junit4

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // junit4,还需要添加适配依赖
    package com.coderitl;

    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = SpringbootJunitApplication.class) // 主启动类字节码文件
    class SpringbootJunitApplicationTests {
    @Autowired
    private UserService service;

    @Test
    void testAdd() {
    service.add();
    }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <dependency>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    <scope>test</scope>
    <exclusions>
    <exclusion>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest-core</artifactId>
    </exclusion>
    </exclusions>
    </dependency>
    说明
  • JUnit5 常用注解

    • @Test: 表示方法是测试方法,但是与Junit4 @Test 不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter 提供额外测试
    • @ParameterizedTest:表示方法是参数化测试
    • @RepeatedTest: 表示方法可重复执行
    • @DisplayName: 为测试类或者测试方法设置展示名称
    • @BeforeEach: 表示为每个单元测试之前执行
    • @AfterEach: 表示为每个单元测试之后执行
    • @BeforeAll: 表示在所有单元测试之前执行
    • @AfterAll:表示在所有单元测试之后执行
    • @Tag(): 表示单元测试类别,类似于JUint4 @Categories
    • @Disabled: 表示测试类或测试方法不执行,类似与JUint4 @Ignore
    • @Timeout(): 表示测试方法运行如果超过了指定时间将会返回错误
    • @ExtendWith(): 为测试类或测试方法提供扩展类引用
  • JUnit5 官网文档

    https://junit.org/junit5/docs/current/user-guide/

  • 实例

    基本使用
  • 概述

    断言是测试方法中的核心部分,用来对测试需要满足条件进行验证。这些断言方法都是org.junit.jupiter.api.Assertions 的静态方法。JUnit5 内置的断言可以分成如下几个类别

  • 简单断言

    方法 说明
    Assertions.assertEquals(); 判断两个对象或两个原始类型是否相等
    Assertions.assertNotEquals(); 判断两个对象或两个原始类型是否不相等
    Assertions.assertSame(); 判断两个对象引用是否指向同一个对象
    Assertions.assertNotSame(); 判断两个对象引用是否指向不同的对象
    Assertions.assertTrue(); 判断给定的布尔值是否为true
    Assertions.assertFalse(); 判断给定的布尔值是否为false
    Assertions.assertNull(); 判断给定的布尔值是否为null
    Assertions.assertNotNull(); 判断给定的布尔值是否不为null

集成MyBatis

  • 通过向导选择依赖时,添加SQL 中的MybatisFrameWork 和 MYSQL Driver
  • 默认MYSQL 依赖版本

    依赖版本查看
    依赖版本查看
  • 更换依赖

    • 查看已安装版本

      已安装版本信息
      已安装版本信息
    • 修改依赖5+

      1
      2
      3
      4
      5
      6
      <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.35</version>
      <scope>runtime</scope>
      </dependency>
    • 配置数据库连接信息

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      # 5.7+版本的MYSQL datas 输入会有提示
      spring:
      datasource:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/bookshop
      username: root
      password: root


      # 8.0+版本的MYSQL
      spring:
      datasource:
      driver-class-name: com.mysql.jdbc.Driver
      # 添加时区 serverTimezone=Asia/Shanghai 也相同
      url: jdbc:mysql://localhost:3306/bookshop?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
      username: root
      password: root
  1. mybatis 起步依赖:完成mybatis 对象自动装配,对象放在容器中

  2. pom.xml 指定吧src/main/java 目录中的xml 包含到classpath

    1
    2
    3
    4
    5
    6
    7
    8
    <resources>
    <resource>
    <directory>src/main/java</directory>
    <includes>
    <include>**/*.xml</include>
    </includes>
    </resource>
    </resources>
  3. 创建实体类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    package com.coderitl.springbootmybatis.entity;

    import org.springframework.stereotype.Component;

    import java.util.Date;

    /**
    * mysql> desc bookinfo;
    * +-----------------+--------------+------+-----+---------+-------+
    * | Field | Type | Null | Key | Default | Extra |
    * +-----------------+--------------+------+-----+---------+-------+
    * | bookId | varchar(100) | NO | PRI | NULL | |
    * | bookName | varchar(100) | NO | | NULL | |
    * | bookCategory | varchar(100) | YES | | NULL | |
    * | bookPrice | double | YES | | NULL | |
    * | bookProductDate | date | YES | | NULL | |
    * | shoppingCart | int(11) | YES | | NULL | |
    * +-----------------+--------------+------+-----+---------+-------+
    */
    public class BookInfo {
    private String bookId;
    private String bookName;
    private String bookCategory;
    private Double bookPrice;
    private Date bookProductDate;
    private Integer shoppingCart;

    ...
    }

  4. 创建dao 接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    package com.coderitl.springbootmybatis.dao;

    import com.coderitl.springbootmybatis.entity.BookInfo;
    import org.apache.ibatis.annotations.Mapper;

    import java.util.List;

    /**
    * @Mapper 告诉 MyBatis 这就是 dao 接口,创建此接口的代理对象 每个接口都需要添加,后续更新为新注解,用以简化
    */
    @Mapper
    public interface BookInfoDao {
    /**
    * 查询所有
    *
    * @return
    */
    List<BookInfo> selectAllBook();
    }

  5. 创建dao 接口对应的Mapper 文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.coderitl.springbootmybatis.dao.BookInfoDao">
    <select id="selectAllBook" resultType="com.coderitl.springbootmybatis.entity.BookInfo">
    select *
    from bookinfo
    </select>
    </mapper>
  6. 创建service 层对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package com.coderitl.springbootmybatis.service.impl;

    import com.coderitl.springbootmybatis.dao.BookInfoDao;
    import com.coderitl.springbootmybatis.entity.BookInfo;
    import com.coderitl.springbootmybatis.service.BookInfoService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;

    import javax.annotation.Resource;
    import java.util.List;

    @Service
    public class BookInfoServiceImpl implements BookInfoService {
    @Resource
    private BookInfoDao dao;

    @Override
    public List<BookInfo> showAllBook() {
    List<BookInfo> bookInfos = dao.selectAllBook();
    return bookInfos;
    }
    }

  7. 创建controller,访问server

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    package com.coderitl.springbootmybatis.controller;

    import com.coderitl.springbootmybatis.entity.BookInfo;
    import com.coderitl.springbootmybatis.service.BookInfoService;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;

    import javax.annotation.Resource;
    import java.util.List;

    // @RestController = @Controller + @ResponseBody
    @RestController
    @RequestMapping("/book")
    public class BookInfoController {
    @Resource
    private BookInfoService service;

    @RequestMapping("/show")
    public List<BookInfo> showAllBookInfo() {
    // 调用业务方法
    List<BookInfo> bookInfos = service.showAllBook();
    return bookInfos;
    }
    }

  8. 配置数据库连接信息

  9. 访问测试

    访问测试
    访问测试
  • @MapperScan 加在主启动类上

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

    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    // MapperScan(basePackages = "com.coderitl.springbootmybatis.dao") 只需要指定 dao 所在位置即可
    @MapperScan(basePackages = "com.coderitl.springbootmybatis.dao")
    @SpringBootApplication
    public class SpringbootMybatisApplication {
    public static void main(String[] args) {
    SpringApplication.run(SpringbootMybatisApplication.class, args);
    }
    }

集成MybatisPlus和集成Druid

  • MP MybatisPlus 的简称

  • 仓库搜索注意

    注意选择带有stater druid
    注意选择带有stater druid
  • 项目依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    <dependencies>
    <!-- springmvc -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- mysql 驱动 -->
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.35</version>
    <scope>runtime</scope>
    </dependency>

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

    <!-- mybatis-plus -->
    <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
    <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.2</version>
    </dependency>

    <!-- druid 连接池 -->
    <!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter -->
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.6</version>
    </dependency>

    </dependencies>

  • 数据源配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    # druid 数据源配置
    spring:
    datasource:
    druid:
    # mysql8: com.mysql.cj.jdbc.Driver
    driver-class-name: com.mysql.jdbc.Driver
    # mysql8: jdbc:mysql://localhost:3306/test_demo?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
    url: jdbc:mysql://localhost:3306/myemployees?autoReconnect=true&useSSL=false&characterEncoding=utf-8
    username: root
    password: root
    initial-size: 1
    min-idle: 1
    max-active: 20

    # mybatis-plus 配置
    mybatis-plus:
    global-config:
    db-config:
    table-underline: false # 设置表明不使用下划线方式
    configuration:
    map-underscore-to-camel-case: false # 设置字段不使用下划线方式

  • 添加所需依赖

  • 实体类

  • dao

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package com.example.springbootmybatisplus.dao;

    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.example.springbootmybatisplus.domain.BookInfo;

    // @Mapper => @MapperScan(basePackage)
    public interface BookInfoDao extends BaseMapper<BookInfo> {
    // 可以自定义也可以不用
    }

    • 不定义xml

      mybatis plus 实现了部分功能,只需要调用对应接口即可
      mybatis plus实现了部分功能
  • Lombok: 一个Java 类库,提供了一组注解,简化POJO 实体类开发

  • 依赖添加

    1
    2
    3
    4
    5

    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    </dependency>
  • 刷新maven 右下角出现一个选项框,选择注解 Enable

    未出现也可以在此处配置
    未出现也可以在此处配置****
  • Lombok 具体使用

    • 实体类

      • 快捷键Ctrl+F12

        • 功能:查看当前类中的所有成员:方法、属性、内部类
        • 亮点:直接显示所有成员的列表,支持搜索功能快速定位到某个成员的位置上。
      • 未添加对应getter | setter 注解

      • 添加注解@Getter

        注解添加(setter同理)
        注解添加后
      • 复合注解

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        package com.example.springbootmybatisplus.domain;

        import lombok.Data;

        import java.util.Date;

        // @Data => @Getter + @Setter
        @Data
        public class BookInfo {
        private String bookId;
        private String bookName;
        private String bookCategory;
        private Double bookPrice;
        private Date bookProductDate;
        private Integer shoppingCart;
        }

        // 对应注解也可以实现 无参 | 全参 构造

        @AllArgsConstructor
        @NoArgsConstructor

        • 注意

          Lombok简化实体类,缺少构造
          Lombok简化实体类
  • BaseMapper 接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by FernFlower decompiler)
    //

    package com.baomidou.mybatisplus.core.mapper;

    import com.baomidou.mybatisplus.core.conditions.Wrapper;
    import com.baomidou.mybatisplus.core.metadata.IPage;
    import java.io.Serializable;
    import java.util.Collection;
    import java.util.List;
    import java.util.Map;
    import org.apache.ibatis.annotations.Param;

    public interface BaseMapper<T> extends Mapper<T> {
    // 添加方法
    int insert(T entity);
    // 根据 Serializable id 删除
    int deleteById(Serializable id);

    int delete(@Param("ew") Wrapper<T> queryWrapper);

    int updateById(@Param("et") T entity);

    int update(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper);

    T selectById(Serializable id);

    List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList);

    List<T> selectByMap(@Param("cm") Map<String, Object> columnMap);

    T selectOne(@Param("ew") Wrapper<T> queryWrapper);

    Integer selectCount(@Param("ew") Wrapper<T> queryWrapper);

    List<T> selectList(@Param("ew") Wrapper<T> queryWrapper);

    List<Map<String, Object>> selectMaps(@Param("ew") Wrapper<T> queryWrapper);

    List<Object> selectObjs(@Param("ew") Wrapper<T> queryWrapper);


    }

  • MP 调试日志

    1
    2
    3
    mybatis-plus:
    configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    • 测试

      测试日志
      测试日志
  • 拦截器

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

    import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
    import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;

    @Configuration
    public class MPConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    // 做分页用的
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
    return interceptor;
    }
    }

  • 实体类

    1
    2
    @Data
    ...
  • 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    package com.example.springbootmybatisplus;

    import com.baomidou.mybatisplus.core.metadata.IPage;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.example.springbootmybatisplus.dao.BookInfoDao;
    import org.junit.jupiter.api.Test;
    import org.springframework.boot.test.context.SpringBootTest;

    import javax.annotation.Resource;

    @SpringBootTest
    class SpringbootMybatisPlusApplicationTests {
    @Resource
    private BookInfoDao dao;

    @Test
    void testGetPage() {
    // 接口 : 实现类
    IPage page = new Page(2, 3);
    dao.selectPage(page, null);
    // 当前页码值
    System.out.println("getCurrent: " + page.getCurrent());
    // 每页数据总量
    System.out.println("getSize: " + page.getSize());
    // 最大页码值
    System.out.println("getTotal: " + page.getTotal());
    System.out.println("getPages: " + page.getPages());
    System.out.println("getRecords: " + page.getRecords());

    }

    }

  • 输出

    分页实现
  • 条件查询like、between..

    1
    2
    3
    4
    5
    String name = "Ja";
    LambdaQueryWrapper<BookInfo> lqw = new LambdaQueryWrapper<>();
    // BookInfo::getBookName 条件 lqw.条件
    lqw.like(name != null, BookInfo::getBookName, name);
    dao.selectList(lqw);
    • 输出

      条件查询
  • Service 层接口定义与数据层定义具有较大区别

    • selectByUserNameAndPassword(String username,String password) => 数据层定义名称
    • login(String username,String password) => 业务层
  • 使用MybatisPlus 快速开发service

    • 主键ID

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      @Data
      public class BookInfo {
      // 需要为自增类型 还需要标识 否则后续使用解析id方法使用会出现 where null=?
      @TableId(value = "bookId", type = IdType.AUTO)
      private Integer bookId;
      private String bookName;
      private String bookCategory;
      private Double bookPrice;
      private Date bookProductDate;
      private Integer shoppingCart;
      }

    • service 接口

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


      import com.baomidou.mybatisplus.core.metadata.IPage;
      import com.baomidou.mybatisplus.extension.service.IService;
      import com.example.springbootmybatisplus.domain.BookInfo;


      public interface BookInfoService extends IService<BookInfo> {
      Boolean modify(BookInfo bookInfo);

      Boolean saveBook(BookInfo bookInfo);

      Boolean delete(Integer bookId);

      // 分页
      IPage<BookInfo> getPage(Integer current, Integer pageSize);
      }

      • 实现类

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        package com.example.springbootmybatisplus.service.impl;

        import com.baomidou.mybatisplus.core.metadata.IPage;
        import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
        import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
        import com.example.springbootmybatisplus.dao.BookInfoDao;
        import com.example.springbootmybatisplus.domain.BookInfo;
        import com.example.springbootmybatisplus.service.BookInfoService;
        import org.springframework.stereotype.Service;

        import javax.annotation.Resource;

        @Service
        public class BookInfoServiceImpl extends ServiceImpl<BookInfoDao, BookInfo> implements BookInfoService {
        @Resource
        private BookInfoDao dao;

        // 可以自行定义
        @Override
        public Boolean modify(BookInfo bookInfo) {
        return dao.updateById(bookInfo) > 0;
        }

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

        @Override
        public Boolean saveBook(BookInfo bookInfo) {
        return dao.insert(bookInfo) > 0;
        }

        @Override
        public IPage<BookInfo> getPage(Integer current, Integer pageSize) {
        IPage page = new Page(current, pageSize);
        return dao.selectPage(page, null);
        }
        }

      • 解析

        解析使用
        解析使用
    • dao

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      package com.example.springbootmybatisplus.dao;

      import com.baomidou.mybatisplus.core.mapper.BaseMapper;
      import com.example.springbootmybatisplus.domain.BookInfo;

      // @Mapper => @MapperScan(basePackage)
      public interface BookInfoDao extends BaseMapper<BookInfo> {
      // 也可以实现自定义内容
      }

    • 测试

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      package com.example.springbootmybatisplus;

      import com.example.springbootmybatisplus.dao.BookInfoDao;
      import com.example.springbootmybatisplus.service.BookInfoService;
      import org.junit.jupiter.api.Test;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.boot.test.context.SpringBootTest;

      import javax.annotation.Resource;

      @SpringBootTest
      class SpringbootMybatisPlusApplicationTests {
      @Resource
      private BookInfoDao dao;

      @Autowired
      private BookInfoService service;

      // 业务层测试
      // 根据 id 查询
      @Test
      void getByIdTest() {
      service.getById(2022041010);
      }

      // 查询所有
      @Test
      void listTest() {
      service.list();
      }

      // 根据 id 删除
      @Test
      void deleteTest() {
      service.removeById(2022041010);
      }


      }

    • 快速开发方案

      • 使用MybatisPlus 提供有业务层通用接口(IService<T>)与业务层通用实现类ServiceImpl<M,T>
      • 在通用类基础功能上做功能重载或功能追加
      • 注意重载时不要覆盖原始操作,避免原始提供的功能丢失

统一返回数据模型

  1. 设计统一的返回值结果类型便于前端开发读取数据
  2. 返回值结果类型可以根据需求自行设定,没有固定格式
  3. 返回值结果模型类用于后端与前端进行数据格式统一,也称前后端数据协议

日志

  • 设置组的日志级别和包的日志级别

    1
    2
    3
    4
    5
    6
    7
    logging:
    group:
    mygroup: com.example.controller
    level:
    root: warn
    # 给组设置日志级别
    mygroup: debug
    输出
  • 输出信息说明

    1
    2022-11-21 16:29:07.774 DEBUG 928 --- [nio-8080-exec-1] c.e.controller.HelloWroldController      : mygroup log test................
    • 输出以下项目
      • 日期和时间: 毫秒精度,易于排序
      • 日志级别: ERROR、WARN、INFO、DEBUG
      • 进程标识
      • ---用于区分实际日志消息开始的分隔符
      • 线程名称: 括在方括号内(可能会被截断以用于控制台输出)
      • 记录器名称: 这通常是源类名称(通常缩写)
      • 日志消息
  • 自定义日志输出

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    logging:
    group:
    mygroup: com.example.controller
    level:
    root: warn
    # 给组设置日志级别
    mygroup: debug
    # 输出到文件
    file:
    name: output.log
    logback:
    rollingpolicy:
    # 文件大小
    max-file-size: 2KB
    # 保存格式
    file-name-pattern: server.%d{yyyy-MM-dd}.%i.log
    日志文件
  • 更换日志实现

    默认的日志
    • 更换日志实现

      1. spring-boot-starter-web 中排除spring-boot-starter-logging 的依赖

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.7.1</version>
        <exclusions>
        <exclusion>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
        </exclusions>
        </dependency>

        <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        </dependency>
      2. 添加新的日志实现依赖

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

高级配置

  • 临时属性配置

    可以打包成 jar,在运行jar 包的时候传递参数

    • 步骤

      1. 使用maven 插件打jar 包,前提时必须引入maven 的打包插件

        1
        2
        3
        4
        5
        6
        7
        8
        9

        <build>
        <plugins>
        <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
        </plugins>
        </build>
      2. 使用maven 中的package 进行打包

        打包 启动jar
      3. 启动时添加临时参数: 在 application.yml 中的配置都可以指定

        修改端口号
      4. 参数传递的原理

        1
        2
        3
        4
        public static void main(String[] args) {
        SpringApplication.run(HelloWorld.class, args);
        }
        // args当传递参数时,替换原先配置

JSP-集成

  • 加入一个处理jsp 的依赖,负责编译jsp 文件

    1
    2
    3
    4
    5
    <!--  编译 jsp -->
    <dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    </dependency>
  • 如果需要使用servlet、jsp、jstl 的功能

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <!--  jstl -->
    <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
    </dependency>
    <!-- servlet -->
    <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>servlet-api</artifactId>
    <version>2.5</version>
    </dependency>
    <!-- jsp -->
    <dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>javax.servlet.jsp-api</artifactId>
    <version>2.3.3</version>
    </dependency>
  • 创建一个存放jsp 的目录,一般叫做webapp

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    pom.xml 配置 jsp 编译后的文件位置

    <resources>
    <resource>
    <!-- jsp 原来的目录 -->
    <directory>src/main/webapp</directory>
    <!-- 指定编译后的存放目录 -->
    <targetPath>META-INF/resource</targetPath>
    <!-- 指定处理的目录和文件 -->
    <includes>
    <include>**/*.*</include>
    </includes>
    </resource>
    </resources>

集成Redis

  • 添加依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <!--  集成 redis: 直接在项目中使用 RedisTemplate(StringRedisTemplate) -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>


    data-redis 使用的 lettuce 客户端库

    在程序中使用 RedisTemplate 类的方法,操作 redis 数据库,实际就是调用 lettuce 客户端中的方法

  • 连接配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 必须是可读可写的主机(主从复制的从机不可连接) 复制配置文件修改如下
    1. 注释掉 # bin 127.0.0.1 (原因:bind 127.0.0.1生效,只能本机访问redis)
    2. protected-mode yes 改为:protected-mode no (原因:把yes改成no,允许外网访问)

    # 关闭防火墙
    systemctl stop firewalld.service
    # 禁止开机启动启动防火墙
    systemctl disable firewalld.service

    daemonize:yes: redis 采用的是单进程多线程的模式。当redis.conf 中选项daemonize 设置成yes 时,代表开启守护进程模式。在该模式下,redis 会在后台运行,并将进程pid 号写入至redis.conf 选项pidfile 设置的文件中,此时redis 将一直运行,除非手动kill该进程。

    daemonize:no: 当daemonize 选项设置成no 时,当前界面将进入redis 的命令行界面,exit 强制退出或者关闭连接工具(putty,xshell 等)都会导致redis 进程退出。

    1
    2
    3
    4
    # 获取静态 ip
    dhclient
    # 查看虚拟机 ip 地址
    ip addr
    1
    2
    3
    4
    5
    6
    spring:
    redis:
    host: 192.168.247.128 # 虚拟机 ip
    port: 6380 # (配置过主从复制)必须使用 master 机器 的端口号
    database: 0 # 选择的数据库实例
    connect-timeout: 10000 # 超时时间
  • 测试请求

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    package com.example.controller;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.core.ValueOperations;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RestController;

    import javax.annotation.Resource;

    @RestController
    public class RedisController {
    /**
    *
    *
    * */
    @Resource private RedisTemplate redisTemplate;

    @PostMapping("/redis/addstring")
    public String addToRedis(String name, String value) {
    // 操作 Redis 中 String 类型数据 先获取 ValueOperations 对象
    ValueOperations valueOperations = redisTemplate.opsForValue();
    valueOperations.set("myname","lisi");
    return "redis add string key and value";
    }
    }

    测试Redis 连接
    测试Redis连接
  • 读取

    1
    2
    3
    4
    5
    6
    7
    8
    // 从 redis 获取数据
    @GetMapping("/redis/getk")
    public String getRedisData(String k) {
    ValueOperations valueOperations = redisTemplate.opsForValue();
    Object v = valueOperations.get(k);

    return "key 是: " + k + " 他的值是: " + v;
    }
    `key·查看
    key查看
  • RestFul 路径传参

    使用restful 风格 数据可读性提高
    使用restful风格 数据可读性提高
  • RedisTemplateStringRedisTemplate 对比

    • StringRedisTemplate: 把k,v 都是作为String 处理,使用的是String 的序列化,可读性好
    • RedisTemplate:把k,v 经过了序列化存到redisk,v 是序列化的内容,不能直接识别

监控

  • 添加依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- 监控 -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
  • 配置

    1
    2
    3
    4
    5
    # 开启健康检查的完整信息
    management:
    endpoint:
    health:
    show-details: always
  • 访问

    http://localhost:8080/actuator

    快速定位第三方问题 连接Redis

Redis 序列化

  • 序列化: 把对象转换为可传输的字节序列化过程成为序列化

  • 反序列化: 把字节序列还原为对象的过程称为反序列化

  • 为什么需要序列化

    序列化最终的目的是为了对象可以跨平台存储和进行网络传输,而我们进行跨平台存储和网络传输的方式就是IO,而我们的IO 支持的数据格式就是字节数组,我们必须把对象转成字节数组的时候就是制定一种规则(序列化),那么我们从IO 流里面读取出数据的时候再以这种规则把对象还原回来(反序列化)

  • 什么情况下需要序列化

    我们已经知道凡是需要进行跨平台存储网络传输的数据,都需要进行序列化

    本质上存储和网络传输,都需要经过把一个对象状态保存成一种跨平台识别的字节风格,然后把其他的平台才可以通过字节信息解析还原成对象信息

  • 序列化的方式

    序列化只是一种拆装组成对象的规则,那么这种规则可定可能有多种多样,比如现在常见的序列化方式有:

    JDK(不支持跨语言)、JSON、XML、Hessian、Kryo(不支持跨语言)

  • RedisTemplate 的序列化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // RedisTemplate 的序列化
    @PostMapping("/redis/setkey")
    public String setRedisDataRestFul(String k, String val) {
    // 设置 key String 序列化
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    // 设置 value String 序列化
    redisTemplate.setValueSerializer(new StringRedisSerializer());
    redisTemplate.opsForValue().set(k, val);
    return "redisTemplate 设置序列化";
    }
    设置序列化
    设置序列化

JSON 序列化

  • IDEA 配置,之后通过 alt + enter 即可生成序列号

    开启序列化 idea 生成序列化版本号
    开启序列化 idea生成序列化版本号
  • 将实体类序列化为JSON 数据格式存储在redis

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @PostMapping("/redis/jsonserialize")
    public String jsonSerialize() {
    Student student = new Student("coder-itl", 18);
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(Student.class));
    // 获取时 redisTemplate.opsForValue().get("studentInfo");
    redisTemplate.opsForValue().set("studentInfo", student);
    return "Student => json Serialize in redis";
    }
    序列化为json 格式存储数据
    序列化为json格式存储数据

Thymeleaf

简介
  • JSP 必须依赖Tomcat 运行,不能直接运行在浏览器
  • HTML 可以直接运行在浏览器中,但是不能接受控制器传递的数据
  • Thymeleaf 是一种既保留了HTML 的后缀能够直接在浏览器运行的能力,又实现了JSP 显示动态数据的功能-静能查看页面效果,动则可以显示数据
使用
  • 添加依赖

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
  • resources/template 下创建HTML 文件

  • 通过控制器访问

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

    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;

    @Controller
    @RequestMapping("/page")
    public class PageController {

    @RequestMapping("/HelloWorld.html")
    public String showPage() {
    return "HelloWorld";
    }
    }

    控制器
    控制器
    控制器访问测试 声明
    控制器访问测试 声明
  • 重要说明

    • static 目录下的资源被定义为静态资源,SpringBoot 应用默认放行,如果将HTML 页面创建在static 目录下是可以直接访问的
    • templates 目录下的文件会被定义为动态网页模板,SpringBoot 应用会拦截temnplates 中定义的资源,如果将HTML 文件定义在templates 目录,则必须通过控制器跳转访问
配置
  • Thymeleaf 配置信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    spring:
    # Thymeleaf 配置:
    thymeleaf:
    # 开发阶段关闭 cache(模板缓存) false: 让修改立即生效
    cache: false
    # 以下内容为默认配置(可以省略)
    encoding: UTF-8
    mode: HTML
    prefix: classpath:/templates/
    suffix: .html
语法掌握
标准变量表达式
  • 标准变量表达式

    • 语法:${key}

    • 作用: 获取key 对应的文本数据,key request 作用域中的key,使用request.setAttribute() | model.addAttribute() 在页面的html 标签中,使用th:text="${key}"

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      package com.example.springbootssm.controller;

      import org.springframework.stereotype.Controller;
      import org.springframework.ui.Model;
      import org.springframework.web.bind.annotation.RequestMapping;

      import javax.servlet.http.HttpServletRequest;

      @Controller
      @RequestMapping("/page")
      public class PageController {

      @RequestMapping("/HelloWorld.html")
      public String showPage(Model model, HttpServletRequest request) {
      // 使用 model 存放对象
      model.addAttribute("data", "2101100123");
      // 使用 request 存放数据
      request.setAttribute("userInfo", "coder-itl");
      // 指定视图(模板引擎使用的页面 html ) => 逻辑名称
      return "HelloWorld";
      }
      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      <!DOCTYPE html>
      <html lang="en" xmlns:th="http://www.thymeleaf.org">
      <head>
      <meta charset="UTF-8">
      <title>Title</title>
      </head>
      <body>
      Hello World Data =>
      <h1 th:text="${data}">数据</h1>
      <h1 th:text="${userInfo}"></h1>
      </body>
      </html>
      • 渲染

        数据获取
        数据获取
选择变量表达式(星号变量表达式)
  • 语法: *{key}

  • 作用: 获取这个key 对应的数据,*{key} 需要和th:object 这个属性一起使用,目的是简单获取对象的属性值

  • 使用

    1
    2
    3
    4
    5
    6
    <div th:object="${userInfo}">
    <p>选择(星号)</p>
    <p th:text="*{id}"></p>
    <p th:text="*{name}"></p>
    <p th:text="*{password}"></p>
    </div>
    • 简化书写

      星号使用
      星号使用
链接表达式
  • 语法: @{url}

  • 作用: 表示链接

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <script src="...">
    <link href="...">
    <a href="...">
    <form action="...">
    <img src="...">

    <!-- 使用 -->
    <div>
    <h1>@{url}</h1>
    <p>绝对路径跳转: <a th:href="@{http://www.baidu.com}">绝对路径跳转</a></p>
    <p>相对路径跳转: <a th:href="@{showData.html}">绝对路径跳转</a></p>
    <p>static/图片文件: <img th:src="@{/linkImg.png}" alt=""></p>
    <p>static/css文件:
    <link rel="stylesheet" th:href="@{/css/index.css}"> 已显示
    </p>
    <p>static/js文件:
    <script th:src="@{/js/index.js}"></script> 已显示
    </p>
    </div>
    • 文件目录层级静态资源存放在 static 目录中,使用@{url} 时省略 static

      文件目录层级
      文件目录层级
    • 渲染

      @{url} 使用
      在这里插入图片描述
循环
  • each 循环,可以循环List,Array,Map

  • 语法: 在一个html 标签中,使用th:each

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @GetMapping("/list.html")
    public String showEachListPage(Model model) {
    List<User> users = new ArrayList<>();
    users.add(new User(1001, "c1", "c1"));
    users.add(new User(1002, "c2", "c1"));
    users.add(new User(1003, "c3", "c1"));
    users.add(new User(1004, "c4", "c1"));
    // 存储数据
    model.addAttribute("userList", users);
    // 视图逻辑名称
    return "list";
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <!-- 语法 -->
    th:each="集合循环成员,循环的状态变量:${key}"
    th:text="${集合循环成员}"

    集合循环成员 | 循环的状态变量: 两个名称都是自定义的
    循环的状态变量: 这个名称可以不自定义,默认是"集合循环的成员 State"

    循环的状态变量: 是循环体的信息,通过该变量可以获取如下信息
    index: 当前迭代对象的 index (从 0 开始)
    count: 当前迭代对象的个数(从 1 开始计算)
    size: 被迭代对象的大小
    current: 当前迭代变量
    even/odd: 布尔值,当前循环的是否是偶数|奇数(从 0 开始计算)
    first: 布尔值,当前循环是否为第一个
    last: 布尔值,当前循环是否为最后一个
    循环的状态变量也可以不定义,则默认采用迭代变量加上 Stat 后缀,即 userStat


    <h1>循环</h1>
    <div th:each="user:${userList}">
    <p th:text="${user}"></p>
    </div>
    • 渲染

      each 默认形式
      each 默认形式
    • map 遍历

      1
      2
      3
      4
      5
      6
      <div th:each="mp:${userList}">

      <p th:text="${mp.key}"></p>

      <p th:text="${mp.value}"></p>
      </div>
if
  • 语法: th:if="boolean 条件",条件为 true 显示内容

  • th:unless th:if 的一个相反操作

  • 使用

    1
    2
    3
    4
    <div>
    <p th:if="1<10"> 1<10=>true(条件为真显示内容) </p>
    <p th:unless="1>10"> 1>10 =>false(条件为假显示内容) </p>
    </div>
switch
  • java 语法一致

  • 使用

    1
    2
    3
    4
    5
    6
    7
    <p th:switch="${user.password}">
    <span th:case="login">显示已登录 </span>
    <span th:case="nologin">显示未登录 </span>
    <span th:case="register"> 显示注册 </span>
    <!-- 默认 -->
    <span th:case="*"> 未知错误 </span>
    </p>
th:inline(内联 )
  • 内联text: 在html 标签外,获取表达式的值

  • 语法:

    1
    2
    3
    4
    5
    <div th:inline="text">
    aa=> [[${userInfo}]] 其他数据
    </div>
    <!-- th:inline="text" 可以省略 -->
    aa=> [[${userInfo}]] 其他数据
  • 内联脚本

    1
    2
    3
    4
    5
     <!-- th:inline="javascript" 不可以省略  -->
    <script th:inline="javascript">
    let data = [[${data}]];
    alert(data)
    </script>
字符串连接
  • 语法: 使用双竖线| |

    1
    <p th:text="|我是 ${data} 123|"></p>
运算符
  • 基本使用

    • 算数运算:+,-,*,/,%

    • 关系比较:>,<,>=,<=(gt,lt,ge,le)

    • 相等判断:==,!=(eq,ne)

    • 使用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <div>
      <p th:text="|'1>2': ${1>2}|"></p>
      <p th:text="|'1<2': ${1<2}|"></p>
      <p th:text="|'1==2': ${1==2}|"></p>
      <p th:text="|'1 eq 2': ${1 eq 2}|"></p>
      <p th:text="|'1 ne 2': ${1 ne 2}|"></p>
      <p th:text="|'1 gt 2': ${1 gt 2}|"></p>
      <p th:text="|'1 lt 2': ${1 lt 2}|"></p>
      <p th:text="|三目运算: ${data=='2101100123' ? 10:110}|"></p>
      </div>
    • 渲染

      运算符
      运算符
    内置对象
    • #request 表示HttpServletRequest

    • #session 表示HttpSession 对象

    • session 对象,表示HttpSession 对象,#session 的简单表示方式

    • 使用

      1
      2
      3
      4
      5
      6
      7
      @GetMapping("/builtin.html") // .html 不可以少
      public String builtInPage(HttpServletRequest request, HttpSession session) {
      request.setAttribute("reqData", "request data...........");
      request.getSession().setAttribute("reqSession", "SESSIONID=coder-itl");
      session.setAttribute("jSession", "jSession data................");
      return "builtin";
      }
      1
      2
      3
      4
      5
      6
      7
      8
      <div>
      <!-- request 作用域 -->
      <p th:text="${#request.getAttribute('reqData')}"> request作用域 </p>
      <!-- request.getSession.setAttribute() -->
      <p th:text="${#session.getAttribute('reqSession')}"></p>
      <!-- 简化 session -->
      <p th:text="${session.jSession}"></p>
      </div>
碎片
  • 碎片的概念

    碎片,就是HTML 片段,我们可以将多个页面中使用相同的HTML 标签部分单独定义,然后通过th:include 可以在HTML 网页中引入定义的碎片

  • 定义碎片

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <!-- builtin.html 中的局部片段 -->
    <div th:fragment="builtin">
    将被引入
    <!-- request 作用域 -->
    <p th:text="${#request.getAttribute('reqData')}"> request作用域 </p>
    <!-- request.getSession.setAttribute() -->
    <p th:text="${#session.getAttribute('reqSession')}"></p>
    <!-- 简化 session -->
    <p th:text="${session.jSession}"></p>
    </div>
  • 使用

    1
    2
    3
    <!-- builtin::builtin => builtin.html 中定义了一个片段名称为 builtin 的碎片  -->
    <h1>引入 builtin </h1>
    <div th:include="builtin::builtin"></div>

文件上传与下载

  • 上传文件开发步骤

    1. 在系统中开发一个可以进行文件上传的页面,包含一个form 表单,在表单中开发一个可以选择本地计算机文件的入口

      1
      2
      3
      4
      5
      6
      7
      8
      <form
      action="http://localhost:8080/file/upload"
      enctype="multipart/form-data"
      method="post"
      >
      <input type="file" name="file" />
      <input type="submit" value="文件上传" />
      </form>
      ApiFox 测试文件上传
      ApiFox
    2. form 表单,提交方式method="post"

      • form method 提交方式必须是post
      • form 的 enctype="application/x-www-form-urlencoded(文本) | multipart/form-data(二进制)"
    3. 开发Controller 在控制器方法中使用MultipartFile 进行文件的接收

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      package com.coderitl.com.coderitl.controller;

      import lombok.extern.slf4j.Slf4j;
      import org.springframework.web.bind.annotation.*;
      import org.springframework.web.multipart.MultipartFile;

      import javax.servlet.http.HttpServletRequest;
      import java.io.File;
      import java.io.IOException;
      import java.util.Objects;

      // 适用于 war 包,通用方式使用注入(配置文件将路径注入)
      @Slf4j
      @CrossOrigin
      @RestController
      @RequestMapping("/file")
      public class FileUpload {
      @PostMapping("/upload")
      public String upload(@RequestPart("file") MultipartFile file, HttpServletRequest request) throws IOException {
      log.info("文件信息: {}", file.toString());
      // 获取文件原始名称
      String originalFilename = file.getOriginalFilename();
      System.out.println("originalFilename = " + originalFilename);
      // 获取文件大小
      long size = file.getSize();
      System.out.println("size = " + size);
      // 获取文件类型
      String contentType = file.getContentType();
      System.out.println("contentType = " + contentType);
      // 文件上传
      // 1. 根据 upload 相对路径获取部署到服务器之后的绝对路径
      // 可以替代过时接口: String realPath = request.getSession().getServletContext().getRealPath("/uploadFile");
      // 也可以进行对象存储上传
      // 2. 将文件上传到 uploadFile 对应路径
      file.transferTo(new File("E:\\springboot-demo\\springboot-junit\\src\\main\\resources\\uploadFile", Objects.requireNonNull(file.getOriginalFilename())));
      return "ok";
      }
      }

    4. springmvc 配置文件中加入文件上传解析器配置

      1
      2
      3
      <!-- CommonsMultipartResolver -->
      <bean id="multipartResolver" class="">
      <!-- 要求: 文件上传解析器必须存在 id 且 id 必须为 multipartResolver -->
      1
      2
      3
      4
      5
      6
      # springboot 文件上传配置
      spring:
      servlet:
      multipart:
      max-file-size: 10MB # 默认 1 MB
      max-request-size: 100MB # 整个请求上传文件大小
    5. 引入文件上传的相关依赖

      1
      2
      3
      <!-- springmvc: commons-fileupload -->

      <!-- springboot 可以不引入依赖 -->
    • 文件下载

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      package org.example.controller;

      import lombok.extern.slf4j.Slf4j;
      import org.springframework.beans.factory.annotation.Value;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;

      import javax.servlet.ServletOutputStream;
      import javax.servlet.http.HttpServletResponse;
      import java.io.File;
      import java.io.FileInputStream;

      @Slf4j
      @RestController
      @RequestMapping("/file")
      public class DownloadController {

      @Value("${download.path}")
      private String downloadPath;


      @GetMapping("/download")
      public void download(String fileName, HttpServletResponse response) throws Exception {
      log.info("fileName: [{}]", fileName);
      log.info("downloadPath: [{}]", downloadPath);
      File file = new File(downloadPath, fileName);
      // 将文件读取为文件输入流
      FileInputStream fileInputStream = new FileInputStream(file);
      // 获取响应流之前一定要设置以附件形式下载()
      response.setContentType("image/jpeg");
      // 获取相应输出流
      ServletOutputStream fileOutputStream = response.getOutputStream();
      // 输入流赋值给输出流
      int len = 0;
      byte[] b = new byte[1024];
      while ((len = fileInputStream.read(b)) != -1) {
      fileOutputStream.write(b, 0, len);
      fileOutputStream.flush();
      }
      // 释放资源
      fileInputStream.close();
      }
      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      //Content-Disposition的作用:告知浏览器以何种方式显示响应返回的文件,用浏览器打开还是以附件的形式下载到本地保存
      //attachment表示以附件方式下载 inline表示在线打开 "Content-Disposition: inline; filename=文件名.mp3"
      // filename表示文件的默认名称,因为网络传输只支持URL编码的相关支付,因此需要将文件名URL编码后进行传输,前端收到后需要反编码才能获取到真正的名称
      response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
      // 告知浏览器文件的大小
      response.addHeader("Content-Length", "" + file.length());
      OutputStream outputStream = new BufferedOutputStream(response.getOutputStream());
      response.setContentType("application/octet-stream");

  • 多文件上传

    • 前端

      1
      2
      3
      4
      <form action="/upload" method="post" enctype="multipart/form-data">
      <input type="file" name="file" multiple>
      <input type="submit" value="上传">
      </form>
    • 后端

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      package com.example.jsondemo.controller;

      import lombok.extern.slf4j.Slf4j;
      import org.springframework.util.StringUtils;
      import org.springframework.web.bind.annotation.PostMapping;
      import org.springframework.web.bind.annotation.RequestParam;
      import org.springframework.web.bind.annotation.RestController;
      import org.springframework.web.multipart.MultipartFile;

      import java.io.File;
      import java.io.IOException;

      @Slf4j
      @RestController
      public class FileUploadController {

      // 设置上传文件的存储路径
      private String UPLOAD_DIR = "D:/mult/";

      // 处理上传文件的接口
      @PostMapping("/upload")
      public String uploadFiles(@RequestParam("files") MultipartFile[] files) {

      // 用于存储上传文件的完整路径
      StringBuilder fileNames = new StringBuilder();

      // 遍历所有上传的文件
      for (MultipartFile file : files) {

      // 获取上传文件的文件名,并清理掉可能存在的非法字符
      String fileName = StringUtils.cleanPath(file.getOriginalFilename());

      // 创建上传文件的存储路径,并写入到磁盘
      File dest = new File(UPLOAD_DIR + fileName);
      try {
      if (!dest.getParentFile().exists()) { // 如果路径不存在则创建目录
      dest.getParentFile().mkdirs();
      }
      file.transferTo(dest);
      fileNames.append(dest.getAbsolutePath()).append(" "); // 将上传文件的完整路径追加到字符串中
      } catch (IOException e) {
      e.printStackTrace();
      }
      }

      // 返回上传文件的完整路径
      return "Files uploaded successfully: " + fileNames.toString();
      }
      }

集成Dubbo

  • 环境问题

    1. 升级到 2.7.x 的注意事项和兼容性问题总结:
      2.7.0 版本在改造的过程中遵循了一个原则,即保持与低版本的兼容性,因此从功能层面来说它是与2.6.x 及更低版本完全兼容的,而接下来将要提到的兼容性问题主要是包重命名带来的。另外,虽然功能用法保持向后兼容,但参考本文能帮助您尽快用到2.7.0 版本的新特性。
    2. 环境要求:需要 Java 8 及以上版本。
  • 添加依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <!-- dubbo 起步依赖 -->
    <dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>2.7.8</version>
    </dependency>
    <!-- zookeeper 注册中心 -->
    <dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-dependencies-zookeeper</artifactId>
    <version>2.7.8</version>
    <type>pom</type>
    </dependency>
  • 创建聚合项目

    • 创建provider(spring-boot)

      • 依赖

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        <dependencies>
        <!-- dubbo 起步依赖 -->
        <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-dependencies-zookeeper</artifactId>
        <type>pom</type>
        <!-- 排除 lof4j(根据添加日志情况选择添加) -->
        <exclusions>
        <exclusion>
        <groupId>slf4j-log4jl2</groupId>
        <artifactId>org.slf4j</artifactId>
        </exclusion>
        </exclusions>
        </dependency>

        <dependency>
        <groupId>com.coderitl.dubbo</groupId>
        <artifactId>api-interface</artifactId>
        <version>1.0.0</version>
        </dependency>
        </dependencies>
      • 配置

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        server:
        port: 8000

        # dubbo 配置
        # 配置服务名称 xml: dubbo:application name="名称"
        spring:
        application:
        name: dubbo-provider

        # 配置扫描包 扫描 @DubboService
        # application.yml

        dubbo:
        registry:
        address: zookeeper://43.142.80.9:2181 # 注册中心
        timeout: 600000 # 超时时间
        application:
        name: dubbo-provider # 服务名称,如果多个启动的服务进程命名一致,自动组成集群
        protocol:
        name: dubbo # 协议
        port: 20880 # 端口
        config-center: # 配置中心超时时间
        timeout: 600000
      • 实现

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        package com.coderitl.dubbo.service.impl;

        import com.coderitl.dubbo.domain.UserInfo;
        import com.coderitl.dubbo.service.UserInfoService;
        import lombok.extern.slf4j.Slf4j;
        import org.apache.dubbo.config.annotation.DubboService;

        // 使用 Dubbo 注解暴露服务
        @Slf4j
        @DubboService(interfaceClass = UserInfoService.class, version = "v1.0", timeout = 50000)
        public class UserServiceImpl implements UserInfoService {
        @Override
        public UserInfo getUserById(Integer userId) {
        log.info("userId: {}", userId);
        UserInfo user = new UserInfo(1001, "coder-itl", "男");
        return user;
        }
        }

        // 主启动
        @EnableDubbo
        @SpringBootApplication
        public class DubboProviderMain {
        public static void main(String[] args) {
        SpringApplication.run(DubboProviderMain.class, args);
        }
        }

    • 创建consomer(springboot)

      • 依赖

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        <dependencies>
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
        <groupId>com.coderitl.dubbo</groupId>
        <artifactId>api-interface</artifactId>
        <version>1.0.0</version>
        </dependency>
        <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-dependencies-zookeeper</artifactId>
        <type>pom</type>
        <!-- 排除 lof4j -->
        <exclusions>
        <exclusion>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        </exclusion>
        <exclusion>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        </exclusion>
        </exclusions>
        </dependency>
        </dependencies>
      • 配置

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        server:
        port: 8001

        dubbo:
        registry:
        address: zookeeper://43.142.80.9:2181
        timeout: 600000
        application:
        name: dubbo-consumer
        protocol: # 可不配置
        name: dubbo
        port: 20880
        config-center:
        timeout: 600000
        scan:
        base-packages: com.coderitl.dubbo.service

      • 实现

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        @Slf4j
        @RestController
        public class UserController {

        // 远程注入
        @DubboReference(interfaceClass = UserInfoService.class, version = "v1.0", check = false)
        private UserInfoService userInfoService;


        @GetMapping("/info")
        public UserInfo getInfo() {
        return userInfoService.getUserById(1001);
        }
        }

        // 主启动
        @EnableDubbo
        @SpringBootApplication
        public class DubboWebMain {
        public static void main(String[] args) {
        SpringApplication.run(DubboWebMain.class, args);
        }
        }

    • 创建公共接口模块(quick)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      // 序列化
      @Data
      @AllArgsConstructor
      public class UserInfo implements Serializable {
      private Integer userId;
      private String userName;
      private String sex;
      }

      // 接口
      public interface UserInfoService {
      UserInfo getUserById(Integer userId);
      }

项目部署

  • 打包方式
    • jar (推荐)
    • war

聚合工程

  • 创建maven-父工程

  • 修改父工程的pom.xml 打包方式为pom

    1
    2
    3
    4
    5
    <!-- 父工程打包方式 -->
    <packaging>pom</packaging>



  • 在父工程下创建子工程

    右键-new-Model
    右键-选择新建-model
  • 依赖继承

    依赖继承
    依赖继承
  • 依赖管理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    <dependencyManagement>
    <dependencies>
    <dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.6.1</version>
    </dependency>
    </dependencies>
    </dependencyManagement>

Swageer

  • 添加swageer 依赖(api)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
    </dependency>
    <dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
    </dependency>
  • Java 配置,api 下创建config 包,并创建配置类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    package com.coderitl.fmmall.config;

    import org.springframework.context.annotation.Configuration;
    import springfox.documentation.builders.ApiInfoBuilder;
    import springfox.documentation.builders.PathSelectors;
    import springfox.documentation.builders.RequestHandlerSelectors;
    import springfox.documentation.service.ApiInfo;
    import springfox.documentation.service.Contact;
    import springfox.documentation.spi.DocumentationType;
    import springfox.documentation.spring.web.plugins.Docket;
    import springfox.documentation.swagger2.annotations.EnableSwagger2;

    @Configuration
    @EnableSwagger2
    public class SwaggerConfig {
    /**
    * Swagger 会帮助我们生成接口文档
    * 1. 配置生成的文档信息
    * 2. 配置生成规则
    */

    /**
    * Docket 封装接口文档信息
    */
    public Docket getDocket() {
    /**
    * 指定文档风格
    */
    ApiInfoBuilder apiInfoBuilder = new ApiInfoBuilder();
    apiInfoBuilder
    // 文档标题
    .title("<>后端接口说明文档")
    // 文档描述
    .description("后端接口说明自动化实现")
    // 文档版本信息 v
    .version("v 2.0.0")
    // 作者信息
    .contact(new Contact("coder-itl", "https://www.lovobin.github.io", "3327511395@qq.com"));
    ApiInfo apiInfo = apiInfoBuilder.build();
    // 指定生成的文档中的封面信息: 文档的标题 版本 作者
    Docket docket = new Docket(DocumentationType.SWAGGER_2)
    .apiInfo(apiInfo)
    .select()
    // 指定包
    .apis(RequestHandlerSelectors.basePackage("com.coderitl.fmmall.controller"))
    // 指定 那些请求
    .paths(PathSelectors.any()).build();
    return docket;
    }
    }

    • 启动报错

      启动报错
      启动报错
      • 解决方案

        1
        2
        3
        4
        5
        # 在 api 的 application.yml 加入如下配置信息
        spring:
        mvc:
        pathmatch:
        matching-strategy: ANT_PATH_MATCHER
      • 访问http://localhost:8080/swagger-ui.html

  • 注解说明

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    package com.coderitl.fmmall.controller.user;

    ...

    @RestController
    @CrossOrigin
    // 类注解,在控制器类添加此注解,可以对控制器类进行功能说明
    @Api(value = "提供用户的登录和注册接口", tags = "用户管理")
    @RequestMapping("/user")
    @ResponseBody
    public class UserController {
    @Autowired
    private UserService service;

    // 方法注解: 说明接口方法的作用
    @ApiOperation("用户登录接口")
    // 方法注解: 说明接口方法的参数
    @ApiImplicitParams({
    @ApiImplicitParam(dataType = "String", name = "id", value = "用户登录账号", required = true),
    @ApiImplicitParam(dataType = "String", name = "password", value = "用户登录密码", required = true,defaultValue = "123456")
    })
    @GetMapping("/login")
    public R<User> loginUser(String id, String password) {
    R<User> login = service.login(id, password);
    if (login != null) {
    return login;
    }
    return R.error("登录失败!");
    }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    package com.coderitl.fmmall.entity;
    ...
    // 当接口参数和返回值为对象类型时,在实体类中添加注解说明
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @ToString
    // 类注解
    @ApiModel(value = "User-对象", description = "用户管理实体信息")
    public class User {
    @ApiModelProperty(dataType = "Integer", required = false)
    private Integer id;
    @ApiModelProperty(dataType = "String", required = true, value = "用户注册用户名")
    private String name;
    @ApiModelProperty(dataType = "String", required = true, value = "用户注册密码")
    private String password;
    }


    // 加在不需要显示的地方 => 接口文档忽略显示
    @ApiIgnore
  • Swagger-UI 插件

    • 添加依赖

      1
      2
      3
      4
      5
      <dependency>
      <groupId>com.github.xiaoymin</groupId>
      <artifactId>swagger-bootstrap-ui</artifactId>
      <version>1.9.6</version>
      </dependency>
    • 启动项目,更新访问路径:http://localhost:8080/doc.html

    • 调式

      接口调试
      接口调试

Jasypt 加密

  • 添加依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.github.ulisesbocchio</groupId>
    <artifactId>jasypt-spring-boot-starter</artifactId>
    <version>3.0.4</version>
    </dependency>
  • 编写配置

    1
    2
    3
    4
    jasypt:
    encryptor:
    algorithm: PBEWITHHMACSHA512ANDAES_256 # 指定加密算法
    password: root # 指定密钥(运行时参数)
  • 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    @SpringBootTest
    public class JasyptTest {
    private StringEncryptor stringEncryptor;

    @Autowired
    public JasyptTest(StringEncryptor stringEncryptor) {
    this.stringEncryptor = stringEncryptor;
    }

    @Test
    public void testSecret() {
    // 加密
    String secrty = stringEncryptor.encrypt("root");
    System.out.println("secrty = " + secrty);
    // 解密
    String decrypt = stringEncryptor.decrypt("6jiQYevMPC0AbOAcrO7+3tHE1o7SYhxV7ynMfKL+mQf94WSA/bq36iCm2KVXtrL9");
    System.out.println("decrypt = " + decrypt);
    }
    }

  • 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    mysql:
    host: ENC(密文)

    # jvm 运行参数 配置密文
    -Djasypt.encryptor.password=xxxx


    # 数据源配置
    spring:
    datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/jdbc?autoReconnect=true&useSSL=false&characterEncoding=utf-8
    username: ENC(Khmy4ImDX1McS9qMJIP/98FBGxaOzpCCKwIWJ01CaPK8XkxvpQBBEJ51PQAS5zlP)
    password: ENC(Khmy4ImDX1McS9qMJIP/98FBGxaOzpCCKwIWJ01CaPK8XkxvpQBBEJ51PQAS5zlP)
    添加配置

集成-ES

  • 添加依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <properties>
    <java.version>1.8</java.version>
    <!-- 覆盖默认的版本【当前客户端安装的是 7。12.1 】 -->
    <elasticsearch.version>7.12.1</elasticsearch.version>
    </properties>

    <dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>${elasticsearch.version}</version>
    </dependency>
  • 初始化RestHeightLevelClient

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    @SpringBootTest
    class HotelIndexTest {

    private RestHighLevelClient client;

    @BeforeEach
    void setUp() {
    client = new RestHighLevelClient(RestClient.builder(
    HttpHost.create("http://192.168.2.3:9200")
    ));
    }

    /****************************************************************************************************/
    @Test
    void testCreateIndex() throws IOException {
    // 1.准备Request PUT /hotel
    CreateIndexRequest request = new CreateIndexRequest("hotel");
    // 2.准备请求参数
    request.source(MAPPING_TEMPLATE, XContentType.JSON);
    // 3.发送请求
    client.indices().create(request, RequestOptions.DEFAULT);
    }

    @Test
    void testExistsIndex() throws IOException {
    // 1.准备Request
    GetIndexRequest request = new GetIndexRequest("hotel");
    // 3.发送请求
    boolean isExists = client.indices().exists(request, RequestOptions.DEFAULT);

    System.out.println(isExists ? "存在" : "不存在");
    }

    @Test
    void testDeleteIndex() throws IOException {
    // 1.准备Request
    DeleteIndexRequest request = new DeleteIndexRequest("hotel");
    // 3.发送请求
    client.indices().delete(request, RequestOptions.DEFAULT);
    }

    /****************************************************************************************************/
    @AfterEach
    void tearDown() throws IOException {
    client.close();
    }

    }

  • RestHighLevelClient 注入Spring

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @SpringBootApplication
    public class HotelDemoApplication {

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

    // 注入 RestHighLevelClient
    @Bean
    public RestHighLevelClient client() {
    return new RestHighLevelClient(RestClient.builder(HttpHost.create("http://192.168.2.3:9200")));
    }
    }

  • 业务使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    @Service
    public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
    @Autowired
    private RestHighLevelClient client;

    @Override
    public PageResult search(RequestParams requestParams) {
    SearchResponse response = null;
    try {
    // 1. 准备 Resuest
    SearchRequest request = new SearchRequest("hotel");
    // 2. 准备 DSL
    /****************************/
    // 2.1 准备 query
    String key = requestParams.getKey();
    if (key == null || "".equals(key)) {
    // 前端未传递 key
    request.source().query(QueryBuilders.matchAllQuery());
    } else {
    request.source().query(QueryBuilders.matchQuery("all", key));
    }
    // 2.2 分页
    int page = requestParams.getPage();
    int size = requestParams.getSize();
    request.source().from((page - 1) * size).size(size);
    /****************************/
    // 3. 发送请求,得到响应
    response = client.search(request, RequestOptions.DEFAULT);
    } catch (IOException e) {
    throw new RuntimeException(e);
    }
    // 4. 解析响应
    return handleResponse(response);
    }

    // 4. 解析响应
    private PageResult handleResponse(SearchResponse response) {
    // 解析响应
    SearchHits searchHits = response.getHits();
    // 获取总条数
    long total = searchHits.getTotalHits().value;
    // 文档数组
    SearchHit[] hits = searchHits.getHits();
    List<HotelDoc> hotels = new ArrayList<>();
    for (SearchHit hit : hits) {
    // 获取文档 source
    String json = hit.getSourceAsString();
    // 反序列化
    HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
    hotels.add(hotelDoc);
    }
    // 封装返回
    return new PageResult(total, hotels);
    }
    }

  • 【BUG】解决方案: 对查询结果进行校验,如果当前页码指大于最大页码值,使用最大页码值作为当前页码值重新查询

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @GetMapping("/{currentPage}/{pageSize}")
    public R getPage(
    @PathVariable int currentPage,
    @PathVariable int pageSize) {
    Ipage<Book> page = bookService.getPage(currentPage,pageSize);
    // 如果当前页码值大于了总页码值,那么重新执行查询操作,使用最大页码值最为当前页码值
    if(currentPage>page.getPages()){
    page = bookService.getPage((int)page.getPages(),pageSize);
    }
    return new R();
    }

底层注解

  • @Configuration

    理解
    • 测试是否成功创建对象

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      @SpringBootApplication
      public class DemoSpringBootApplication {
      public static void main(String[] args) {
      // 1. 返回 IOC 容器
      ConfigurableApplicationContext run = SpringApplication.run(DemoSpringBootApplication.class, args);
      // 2. 查看容器里面的组件
      String[] names = run.getBeanDefinitionNames();
      for (String name : names) {
      System.out.println(name);
      }
      // 3. 从容器中获取组件
      User user = run.getBean("ConfigUser", User.class);
      // User(userName=coder-itl, gender=男)
      System.out.println(user);
      }

    • 说明

      1. 配置类里面使用@Bean 标注再方法尚给容器注册组件, 默认 也是单实例
      2. 配置类本身也是组件
      3. proxyBeanMethods(@Configuration(proxyBeanMethods = true)):代理Bean 的方法(可以解决循环依赖)
    • 最佳实战

      • 配置 类组件之间无依赖关系用Lite(proxyBeanMethods = false)) 模式加速容器启动过程、减少判断
      • 配置类组件之间有依赖关系,方法会用到之前单实例组件,用Full(proxyBeanMethods = true)) 模式
  • @Import({xxx.class,xxx.class,...}),导入配置

  • @Conditional 条件装配: 满足Conditional 指定的条件,则进行组件注入

    • @ConditionalOnBean(name="user") 当存在user 组件时,装配该配置

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      // 可以加在类上该注解
      @Configuration(proxyBeanMethods = true)
      public class MyConfig {
      @Bean
      // 当存在`user`组件时,装配该配置
      @ConditionalOnBean(name="user")
      public User ConfigUser() {
      return new User("coder-itl", "男");
      }
      }

    • @ConditionalOnMissingBean(name="user") 当不存在时,装配该配置

  • @ImportResource("classpath:XmlConfig.xml"),导入xml 的配置文件

配置绑定: @ConfigurationProperties

  • 绑定

    1
    2
    3
    4
    # 配置文件
    my-car:
    brand: BYD
    price: 100000
    1
    2
    3
    4
    5
    6
    7
    @Data
    @Component // 只有在 容器中的组件,才会拥有 springboot 提供的强大功能
    @ConfigurationProperties(prefix = "my-car")
    public class Car {
    private String brand;
    private Integer price;
    }
    通过如下依赖解决
    1
    2
    3
    4
    5
    6
    7
    8
    <!-- 文档: 
    https://docs.spring.io/spring-boot/docs/2.7.4/reference/html/configuration-metadata.html#appendix.configuration-metadata.annotation-processor
    -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
    </dependency>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <!-- 打包时排除该依赖 --> 
    <build>
    <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
    <excludes>
    <exclude>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    </exclude>

    <exclude>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    </exclude>
    </excludes>
    </configuration>
    </plugin>
    </plugins>
    </build>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 测试是否绑定成功

    @RestController
    public class CarController {
    @Resource
    private Car car;

    @GetMapping("/car")
    public Car car(){
    return car;
    }
    }

    测试访问
  • 第三方包绑定方式

    1
    2
    3
    4
    5
    6
    7
    8
    @Data
    // @Component 假如是第三方的,不能指定 @Component
    @ConfigurationProperties(prefix = "my-car")
    public class Car {
    private String brand;
    private Integer price;
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 必须在配置类中
    // 1. 开启 Car 配置绑定功能
    // 2. 把这个 Car 组件自动注册到容器中
    @EnableConfigurationProperties(Car.class)
    @Configuration()
    public class MyConfig {
    ...
    }

    名称规定:kebab-case 方式命名

数据校验

  • 添加依赖

    1
    2
    3
    4
    5
    6
    7
    8
    <dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    </dependency>
    <dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    </dependency>
  • 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    package com.example.vo;

    import lombok.Data;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.validation.annotation.Validated;

    import javax.validation.constraints.Max;
    import javax.validation.constraints.Min;
    import javax.validation.constraints.Size;

    @Data
    @Validated
    @ConfigurationProperties(prefix = "user-info")
    public class UserInfo {
    @Max(value = 8, message = "超过最大值")
    @Min(value = 0, message = "超过最小值")
    private Integer id;
    @Size(max = 100, min = 0, message = "maxmin是字符个数")
    private String info;
    }

    数据校验

@SpringBootApplication

  • @SpringBootConfiguration

    • @Configuration: 代表当前是一个配置类
  • @EnableAutoConfiguration

    • @AutoConfigurationPackage: 自动配置包

      • @Import({AutoConfigurationPackages.Registrar.class})

        1
        2
        3
        4
        5
        6
        7
        // 给容器导入一个组件
        @Import({AutoConfigurationPackages.Registrar.class})
        public @interface AutoConfigurationPackage {}

        // 利用 Registrar 给容器导入一系列组件
        // AutoConfigurationPackage 将指定的一个包下的所有组件导入进来
        // 导入的是启动类所在包下
    • @Import({AutoConfigurationImportSelector.class})

      1
      2
      3
      4
      5
      6
      7
      8
      public String[] selectImports(AnnotationMetadata annotationMetadata) {
      if (!this.isEnabled(annotationMetadata)) {
      return NO_IMPORTS;
      } else {
      AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
      return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
      }
      }
  • @ComponentScan

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
    ), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
    )}
    )
  • 自动装配原理

    • SpringBoot 先加载所有的自动配置类:xxxAutoConfiguration

    • 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值,xxProperties 里面拿,xxxProperties 和配置文件进行绑定

    • 生效的配置类就会给容器中装配很多组件

    • 只要容器中有了这些组件,相当于这些功能都有了

    • 定制化配置

      • 用户直接自己@Bean 替换底层的组件

      • 用户去看这个组件是获取配置文件什么值就去修改

        字符集(根据 prefix 在配置文件中修改) 修改

WEB 场景

  • 静态资源规则与定制化

    • 静态资源目录

      static or public or resources or /META-INF/resources 目录中的静态资源都可以被访问

    • 使用静态资源访问前缀

      1
      2
      3
      4
      5
      # 配置文件
      spring:
      mvc:
      # res 为自定义内容
      static-path-pattern: /res/**
      必须添加访问前缀 原始目录无变化
    • 指定唯一的静态资源目录

      1
      2
      3
      4
      5
      # 配置
      spring:
      web:
      resources:
      static-locations: classpath:/public
      指定静态资源目录
  • 自定义Favicon.ico

    1
    2
    3
    4
    # 此项配置会影响显示 图标,需要关闭
    spring:
    mvc:
    static-path-pattern: /res/** # res 为自定义内容

JavaMail -发送简单邮件

  • 添加依赖

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
    </dependency>
  • 定义service

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    package com.example.javamail.service.impl;

    import com.example.javamail.service.SendMailService;
    import org.springframework.mail.SimpleMailMessage;
    import org.springframework.mail.javamail.JavaMailSender;
    import org.springframework.stereotype.Service;

    import javax.annotation.Resource;

    @Service
    public class SendMailServiceImpl implements SendMailService {
    @Resource
    private JavaMailSender javaMailSender;
    // 发送人
    private String from = "@qq.com";
    // 接收人 @qq.com
    private String to = "@qq.com";
    // 主题
    private String subject = "测试邮件";
    // 正文
    private String context = "邮件正文内容";

    @Override
    public void sendMail() {
    SimpleMailMessage message = new SimpleMailMessage();
    message.setFrom(from+"(小甜甜)");
    message.setTo(to);
    message.setSubject(subject);
    message.setText(context);
    javaMailSender.send(message);
    }
    }

  • 配置

    1
    2
    3
    4
    5
    spring:
    mail:
    host: smtp.qq.com # qq/126/xxx
    username: xxxx@qq.com
    password: xxx
    password 获取
  • 发送多部邮件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    package com.example.javamail.service.impl;

    import com.example.javamail.service.SendMailService;
    import org.springframework.mail.SimpleMailMessage;
    import org.springframework.mail.javamail.JavaMailSender;
    import org.springframework.mail.javamail.MimeMailMessage;
    import org.springframework.mail.javamail.MimeMessageHelper;
    import org.springframework.stereotype.Service;

    import javax.annotation.Resource;
    import javax.mail.MessagingException;
    import javax.mail.internet.MimeMessage;

    @Service
    public class SendMailServiceImpl implements SendMailService {
    @Resource
    private JavaMailSender javaMailSender;
    // 发送人
    private String from = "@qq.com";
    // 接收人 @qq.com
    private String to = "@qq.com";
    // 主题
    private String subject = "测试邮件";
    // 正文
    private String context = "点开有惊喜";

    @Override
    public void sendMail() {
    MimeMessage message = javaMailSender.createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(message);
    try {
    helper.setFrom(from + "(小甜甜)");
    helper.setTo(to);
    helper.setSubject(subject);
    helper.setText(context,true);
    } catch (Exception e) {
    throw new RuntimeException(e);
    }
    javaMailSender.send(message);
    }
    }

  • 添加附件发送

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    package com.example.javamail.service.impl;

    import com.example.javamail.service.SendMailService;
    import org.springframework.mail.SimpleMailMessage;
    import org.springframework.mail.javamail.JavaMailSender;
    import org.springframework.mail.javamail.MimeMailMessage;
    import org.springframework.mail.javamail.MimeMessageHelper;
    import org.springframework.stereotype.Service;

    import javax.annotation.Resource;
    import javax.mail.MessagingException;
    import javax.mail.internet.MimeMessage;
    import java.io.File;

    @Service
    public class SendMailServiceImpl implements SendMailService {
    @Resource
    private JavaMailSender javaMailSender;
    // 发送人
    private String from = "@qq.com";
    // 接收人 @qq.com
    private String to = "@qq.com";
    // 主题
    private String subject = "测试邮件";
    // 正文
    private String context = "点开有惊喜";

    @Override
    public void sendMail() throws MessagingException {
    MimeMessage message = javaMailSender.createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(message, true);
    try {
    helper.setFrom(from + "(小甜甜)");
    helper.setTo(to);
    helper.setSubject(subject);
    helper.setText(context, true);

    // 发送附件
    File f1 = new File("D:\\GithubCode\\Private\\source\\_posts\\2022\\04\\Es6-Es11.md");
    helper.addAttachment("Es6学习笔记.md", f1);
    javaMailSender.send(message);
    } catch (Exception e) {
    throw new RuntimeException(e);
    }
    }
    }