SpringBoot

前言

Spring 的优点

Spring 是 Java 企业版(Java Enterprise Edition,JEE,也称J2EE)的轻量级代替品。无需开发重量级的 Enterprise JavaBean(EJB)。Spring 为企业级 Java 开发提供了一种相对简单的方法,通过依赖注入和面向切面编程,用简单的 Java 对象(Plain Old Java Object,POJO)实现了 EJB 的功能。

Spring 的缺点

虽然 Spring 的组件代码是轻量级的,但它的配置却是重量级的。一开始,Spring 用 XML 配置,而且是很多 XML 配置。Spring 2.5 引入了基于注解的组件扫描,这消除了大量针对应用程序自身组件的显式 XML 配置。Spring 3.0 引入了基于Java 的配置,这是一种类型安全的可重构配置方式,可以代替 XML。

所有这些配置都代表了开发时的损耗。因为在思考 Spring 特性配置和解决业务问题之间需要进行思维切换,所以编写配置挤占了编写应用程序逻辑的时间。和所有框架一样,Spring 实用,但与此同时它要求的回报也不少。

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

使用 SpringBoot 解决上面的问题

SpringBoot 对上述 Spring 的缺点进行的改善和优化,基于约定优于配置的思想,可以让开发人员不必在配置与逻辑业务之间进行思维的切换,全身心的投入到逻辑业务的代码编写中,从而大大提高了开发的效率,一定程度上缩短了项目周期。

uploading.4e448015.gif

转存失败重新上传取消

概述

SpringBoot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,SpringBoot 致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。

SpringBoot 官方网址:Spring Boot

SpringBoot 开发版本推荐

  • Springboot 目前分为两大版本系列,1.x 系列和 2.x 系列
  • 如果是使用 Eclipse,推荐安装 Spring Tool Suite (STS) 插件
  • 如果使用 IDEA 旗舰版,自带了 SpringBoot 插件,使用很方便
  • 必须使用 Maven 3.3+,Maven 目前最新版本为 3.8.5(2022-04-10)
  • 推荐使用 Java 8,Java 11,Java16,SpringBoot 1.x 系列的版本兼容Java 6,SpringBoot 2.x 系列需要至少 Java8

SpringBoot 的特性

  1. 能够快速创建基于 Spring 的应用程序,避免了大量的配置文件
  2. 能够直接使用 java main 方法启动内嵌的 Tomcat 服务器运行 SpringBoot 程序,不需要部署 war 包文件
  3. 提供约定的 starter POM 来简化 Maven 配置,让 Maven 的配置变得简单
  4. 自动化配置,根据项目的 Maven 依赖配置,Springboot 自动配置 Spring、Spring MVC 等
  5. 提供了一些大型项目中常见的非功能性特性,如嵌入式服务器、安全、指标,健康检测、外部配置等
  6. 基本可以完全不使用 XML 配置文件,采用注解配置
  7. SpringBoot 不是对 Spring 功能上的增强,而是提供了一种快速使用 Spring 的方式,简单理解,就是框架的框架

SpringBoot 四大核心

  1. 自动装配【重点】

    针对很多 Spring 应用程序和常见的应用功能,SpringBoot 能自动提供相关配置。可以根据简单的配置甚至0配置来搭建整个项目。

  2. 起步依赖【重点】

    起步依赖本质上是一个 Maven 项目对象模型(Project Object Model,POM),定义了对其他库的传递依赖,这些东西加在一起即支持某项功能。

  3. Actuator【了解】

    是 SpringBoot 的程序监控器,可监控 Spring 应用程序上下文中的 Bean、查看自动配置决策、Controller 映射、线程活动、应用程序健康状况等,能够深入运行中的 Spring Boot 应用程序,一探 Spring boot 程序的内部信息。

  4. 命令行界面【了解】

    这是 SpringBoot 的可选特性,主要针对 Groovy 语言使用;

    Groovy 是一种基于JVM(Java虚拟机)的敏捷开发语言,它结合了 Python、Ruby 和 Smalltalk的许多强大的特性,Groovy 代码能够与 Java 代码很好地结合,也能用于扩展现有代码,由于其运行在 JVM 上的特性,Groovy 可以使用其他 Java 语言编写的库。

SpringBoot 重要策略

SpringBoot 框架中还有两个非常重要的策略:开箱即用和约定优于配置。

  • 开箱即用,Out of box,是指在开发过程中,通过在 Maven 项目的 pom 文件中添加相关依赖包,然后使用对应注解来代替繁琐的 XML 配置文件以管理对象的生命周期。这个特点使得开发人员摆脱了复杂的配置工作以及依赖的管理工作,更加专注于业务逻辑。
  • 约定优于配置,Convention over configuration,是一种由 SpringBoot 本身来配置目标结构,由开发者在结构中添加信息的软件设计范式。这一特点虽降低了部分灵活性,增加了BUG定位的复杂性,但减少了开发人员需要做出决定的数量,同时减少了大量的XML配置,并且可以将代码编译、测试和打包等工作自动化。

SpringBoot 应用系统开发模板的基本架构设计从前端到后台进行说明:前端常使用模板引擎,主要有 FreeMarker 和 Thymeleaf,它们都是用 Java 语言编写的,渲染模板并输出相应文本,使得界面的设计与应用的逻辑分离,同时前端开发还会使用到 Bootstrap、AngularJS、JQuery 等;在浏览器的数据传输格式上采用 Json,非 xml,同时提供 RESTful API;SpringMVC 框架用于数据到达服务器后处理请求;到数据访问层主要有Hibernate、MyBatis、JPA 等持久层框架;数据库常用 MySQL;开发工具推荐 IntelliJIDEA。

SpringBoot 入门

网站创建地址:https://start.spring.io/

自定义网站创建地址:https://start.aliyun.comhttps://start.springboot.io

项目创建

1、点击创建项目

uploading.4e448015.gif

转存失败重新上传取消

2、选择 Spring Initializr快捷创建 SpringBoot 项目(需要联网),点击下一步

uploading.4e448015.gif

转存失败重新上传取消

3、输入对应的公司名和骨架名,选择对应的JDK版本以及编辑包名,点击下一步

uploading.4e448015.gif

转存失败重新上传取消

4、添加所需的依赖,这里选择 Spring Web,点击下一步

uploading.4e448015.gif

转存失败重新上传取消

5、选择项目名和项目本地存储的位置

uploading.4e448015.gif

转存失败重新上传取消

6、项目创建完成

项目结构

uploading.4e448015.gif

转存失败重新上传取消

结构说明

名称描述
javajava 代码
com.fc自定义包名,所有的 Java 代码都需要放在此包下
SpringbootProjectApplication【重点】SpringBoot 启动类,程序的入口
resources【重点】资源
static【重点】存放静态资源,如图片、 存放静态资源,如图片、 存放静态资源,如图片、 存放静态资源,如图片、CSS、JavaScript 等,可以直接访问
templates【重点】存放 Web 页面的模板文件,只能通过 Controller 进行访问,类似 WEB-INF
application.properties【重点】等同于 application.yml 文件,用于存放程序的各种依赖模块配置信息,比如服务端口,数据库连接配置等
pom.xml【重点】maven 配置文件
.mvn|mvnw|mvnw.cmd【了解】使用脚本操作执行 maven 相关命令,国内使用较少可直接删除
.gitignore使用版本控制工具 ,设置一些忽略提交内容

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!--SpringBoot核心起步依赖-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.2</version>
        <!--相对路径,可以用配置一个路径用来加载parent模块,只要加上这个标签后不会走本地路径,直接走仓库,后面的注释也写了,从仓库进行查找-->
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.fc</groupId>
    <artifactId>09-SpringBoot-01-Starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>09-SpringBoot-01-Starter</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!--SpringBoot使用SpringMVC作为控制层,需要引入的web依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--SpringBoot单元测试-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!--SpringBoot插件-->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

项目启动

uploading.4e448015.gif

转存失败重新上传取消

控制台无报错说明启动成功

案例代码

Controller

@RestController
@RequestMapping("user")
public class UserController {
    @RequestMapping("findAll")
    public String findAll() {
        return "findAll";
    }
}

【注意】新创建的类一定要位于 Application 启动类同级目录或者子目录下,否则 SpringBoot 加载不到。

启动类

// 此注解用于声明一个SpringBoot的启动类,该注解具备多种功能
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        // 运行启动类,参数为启动类的字节码对象和相关的参数
        SpringApplication.run(Application.class, args);
    }
}

Spring Boot 核心配置文件

两个核心配置格式【重点】

SpringBoot 是基于约定的,所以很多配置都有默认值,但如果想使用自己的配置替换默认配置的话,就可以使用 application.properties 或者 application.yml(application.yaml)进行配置,SpringBoot 默认会从 resources 目录下加载 application.properties 或application.yml(application.yaml)文件。

properties 文件格式

application.properties 文件是键值对类型的文件(默认)

application.properties 配置文件

# 配置Tomcat的端口号
server.port=8081
# 配置Web访问的路径前缀
server.servlet.context-path=/properties

yml 文件格式

概述

SpringBoot 还可以使用 yml 文件进行配置,YML 文件格式是 YAML (YAML Ain't Markup Language)编写的文件格式,YAML 是一种直观的能够被电脑识别的的数据序列化格式,并且容易被人类阅读,容易和脚本语言交互的,可以被支持 YAML 库的不同的编程语言程序导入,比如: C/C++,Ruby,Python,Java,Perl,C#,PHP等。YML 文件是以数据为核心的,比传统的 xml 方式更加简洁,YML 文件的扩展名可以使用 .yml 或者 .yaml。

官方文档:The Official YAML Web Site

语法要求

  • **key: value; **
  • 大小写敏感
  • 使用缩进表示层级关系
  • 缩进不允许使用 tab,只允许空格
  • 缩进的空格数不重要,只要相同层级的元素左对齐即可
  • '#'表示注释
  • 单引号与双引号表示字符串内容会被转义/不转义

【注意】kv之间有空格

案例代码

application.yml 配置文件

server:
#  配置Tomcat端口号
  port: 8082
  servlet:
#    配置Web访问路径前缀
    context-path: /yml

数据类型

student:
  name: "易烊千玺"                    # 字符串
  age: 21                             # 数值
  gender: true                        # 布尔值
  birthday: 2000/1/1 12:30:20         # 日期
  hobby: ["唱", "跳", "rap"]          # 数组
  food:                               # 集合
    - 烤羊排
    - 烤韭菜
    - 烤鱿鱼
  score: {"Java": 100, "MySQL": 100}  # Map
  car:                                # 对象
    name: 比亚迪

【注意】如果是字符串类型的数据,单引号下转义字符无效,双引号下转义字符才能生效,Map 中的键不能用中文

properties 和 yaml 的加载顺序

如果 application.properties 文件与 application.yml 配置文件同时存在,当application.properties  和 application.yaml 进行优先级加载时,它们都会加载,并不是application.properties 加载了 application.yaml 就不加载了,而是它们的内容根据优先级的高低进行合并(merge)并以 application.properties 为主。

自定义配置

在 SpringBoot 的核心配置文件中,除了使用内置的配置项之外,我们还可以在自定义配置,然后采用对应的注解去读取配置的属性值

@Value

Value 注解用于获取配置文件中属性键对应的值并赋值给声明的变量

使用格式

@Value("${属性名}")

1、编写 application.yml 配置文件

server:
  port: 8080

# 自定义配置
user:
  username: 易烊千玺
  password: 123456

2、声明 Controller 并通过 @Value 注解使用自定义配置

@RestController
@RequestMapping("user")
public class UserController {
    // 声明成员变量并使用配置文件中的自定义配置
    @Value("${user.username}")
    private String username;

    @Value("${password}")
    private String password;

    @RequestMapping("customConfiguration")
    public String customConfiguration() {
        return "用户名:" + username + " 密码:" + password;
    }
}

@ConfigurationProperties

ConfigurationProperties 注解用于将整个文件映射成一个对象,用于自定义配置项比较多的情况

格式

@ConfigurationProperties(prefix = "属性名")

1、编写 application.yml 配置文件

# 自定义对象进行参数绑定
user:
  name: "易烊千玺"                    # 字符串
  age: 21                             # 数值
  gender: true                        # 布尔值
  birthday: 2000/1/1 12:30:20         # 日期
  hobby: ["唱", "跳", "rap"]          # 数组
  food:                               # 集合
    - 烤羊排
    - 烤韭菜
    - 烤鱿鱼
  score: {"Java": 100, "MySQL": 100}  # Map
  car:                                # 对象
    name: 比亚迪

2、声明配置类

@Data
public class Car {
    private String name;
}

// 注意必须将当前类的对象注册到Spring容器中才能使用ConfigurationProperties
@Component
@ConfigurationProperties(prefix = "user")
@Data
public class User {
    // 注意成员变量与配置文件中的属性要相同
    private String name;
    private Integer age;
    private Boolean gender;
    private Date birthday;
    private String[] hobby;
    private List<String> food;
    private Map<String, Object> score;
    private Car car;
}

【注意】

1、必须将类注册到容器中,否则会报错!可以使用 @Component (类上)或者 @EnableConfigurationProperties (配置类上)

1、@ConfigurationProperties 注解必须声明 prefix 属性

2、成员变量必须要和配置文件中的属性相同

3、一定要在配置的对象中加上 set、get 方法

3、声明 Controller

@RestController
@RequestMapping("user")
public class StudentController {
    // 注入自定义配置类
    @Autowired
    private User user;

    @RequestMapping("getUser")
    public User getUser() {
        return user;
    }
}

【补充】配置处理器

解决 ConfigurationProperties 注解报错以及在配置文件中使用提示

在 ConfigInfo类中使用了 类中使用了 ConfigurationProperties 注解后, IDEA 会出现一个警告, 不影响程序的执行

Spring Boot Configuration Annotation Processor not found in classpath

点击 open documentnation 跳转到网页,在网页中提示需要加一个依赖,将这个依赖拷贝,粘贴到 pom.xml 文件中即可解决,此时 application.yml 配置文件中的黄色警告线也会消失,并且配置文件中也能使用提示了

<!-- 解决使用 @ConfigurationProperties 注解出现警告问题 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

另外,如果您在项目中使用 AspectJ,则需要确保注释处理器只运行一次。有几种方法可以做到这一点。使用 Maven,您可以maven-apt-plugin显式配置并将依赖项添加到注释处理器。您还可以让 AspectJ 插件运行所有处理并在maven-compiler-plugin配置中禁用注释处理。

<!--确保注解处理器只运行一次-->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <proc>none</proc>
    </configuration>
</plugin>

常用配置

官方描述:Common Application Properties (spring.io)

配置默认值描述
spring.aop.autotrue添加@EnableAspectJAutoProxy。
spring.aop.proxy-target-classtrue与标准的基于Java接口的代理(false)相反,是否要创建基于子类的代理(CGLIB)(true)。
spring.application.name应用名称。
spring.autoconfigure.exclude要排除的自动配置类。
spring.config.import导入其他配置数据。
spring.config.location替换默认设置的配置文件位置。
spring.config.nameapplication配置文件名。
spring.jackson.date-format日期格式字符串或标准日期格式类名称。例如,yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone格式化日期时使用的时区。例如,“ America / Los_Angeles”或“ GMT + 10”。

服务器相关

server.port8080服务器HTTP端口。
server.servlet.context-path
server.servlet.path
server.address服务器应绑定到的网络地址。
server.server-header用于服务器响应标头的值(如果为空,则不发送标头)。
server.servlet.application-display-nameapplication显示应用程序的名称。
server.servlet.context-parameters.*Servlet上下文初始化参数。
server.servlet.context-path应用程序的上下文路径。
server.servlet.encoding.charset编码集

SpringMVC 相关

配置默认值描述
spring.mvc.view.prefixSpring MVC视图前缀。
spring.mvc.view.suffixSpring MVC视图后缀。
spring.mvc.format.date要使用的日期格式,例如dd / MM / yyyy
spring.mvc.format.time使用的时间格式,例如“ HH:mm:ss”。
spring.mvc.format.date-time要使用的日期时间格式,例如“ yyyy-MM-dd HH:mm:ss”。
spring.mvc.static-path-pattern/**用于静态资源的访问路径加上前缀,默认无前缀。
spring.mvc.servlet.load-on-startup-1加载调度程序Servlet的启动优先级。
spring.mvc.servlet.path/调度程序Servlet的路径。为此属性设置自定义值与PathPatternParser匹配策略不兼容。
**spring.web.resources.static-locations: **classpath:/**用于静态资源的本地路径加上前缀,默认无前缀。

Datasource 相关

配置默认值描述
spring.datasource.driver-class-name数据库的连接
spring.datasource.password数据库的登录密码。
spring.datasource.username数据库的登录用户名。
spring.datasource.url数据库的JDBC URL。
spring.datasource.typeHikariDataSource要使用的连接池实现的完全限定名称。默认情况下,它是从类路径中自动检测到的,使用的是HikariCP,可以修改为Druid。

MyBatis 相关

属性配置默认值描述
mybatis.config-locationmybatis-config.xml 配置文件的本地路径
mybatis.mapper-locationsmapper.xml 映射文件的本地路径
mybatis.type-aliases-package扫描指定包下的类作为别名
mybatis.configuration.cache-enabledtrue二级缓存
mybatis.configuration.log-impl日志输出
mybatis.configuration.map-underscore-to-camel-casefalse开启驼峰命名自动映射
mybatis.configuration.lazy-loading-enabledfalse开启延迟加载的全局开关
mybatis.configuration.aggressive-lazy-loadingfalse开启时,任一方法的调用都会加载该对象的所有延迟加载属性。否则,每个延迟加载属性会按需加载
mybatis.configuration.use-generated-keysfalse允许 JDBC 支持自动生成主键,需要数据库驱动支持。如果设置为true,将强制使用自动生成主键。
mybatis.configuration.default-statement-timeout超时时间,它决定数据库驱动等待数据库响应的秒数

Quartz 定时任务相关

配置默认值描述
spring.quartz.auto-startuptrue初始化后是否自动启动调度器。
spring.quartz.scheduler-namequartzScheduler调度程序的名称。
spring.quartz.startup-delay0s初始化完成后启动调度程序的延迟。如果在整个应用程序启动之前不应运行任何作业,则设置此属性是有意义的。
spring.quartz.wait-for-jobs-to-complete-on-shutdownfalse是否等待正在运行的作业在关机时完成。

Elasticsearch 相关

配置默认值描述
spring.elasticsearch.password使用 Elasticsearch 进行身份验证的密码。
spring.elasticsearch.connection-timeout0s与 Elasticsearch 通信时使用的连接超时。
spring.elasticsearch.path.prefix添加到发送到 Elasticsearch 的每个请求的路径的前缀。
spring.elasticsearch.urishttp://localhost:9200要使用的 Elasticsearch 实例的逗号分隔列表。
spring.elasticsearch.username使用 Elasticsearch 进行身份验证的用户名。

PageHelper 相关

配置默认值描述
pagehelper.dialect自动选择对应的数据库方言
pagehelper.reasonablefalse分页合理化
pagehelper.support-methods-argumentsfalse支持通过 Mapper 接口参数来传递分页参数

【补充】多环境配置

1、创建 application-dev.yml 配置文件

# 设置开发环境配置
server:
  # 设置Tomcat内嵌端口号
  port: 8080
  # 设置上下文根
  servlet: 
    context-path: /dev

2、创建 application-product.yml 配置文件

# 设置生产环境配置
server:
  # 设置Tomcat内嵌端口号
  port: 8081
  # 设置上下文根
  servlet:
    context-path: /product

3、创建 application-test.yml 配置文件

# 设置测试环境配置
server:
  # 设置Tomcat内嵌端口号
  port: 8082
  # 设置上下文根
  servlet:
    context-path: /test

4、application.yml 配置文件

# 使用开发环境
spring:
  profiles:
    active: product

5、控制器

@RestController
public class StudentController {
@RequestMapping("hello")
    public String hello() {
       return "say hello";
    }
}

6、通过命令启动并设置初始化参数

java -jar 09-springboot-03-profile-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev

@Profile 条件装配

@Profile 指示当一个或多个指定的配置文件处于活动状态时,该组件才有资格注册。此注解可用于类或者方法上,配合 @Component 或者 @Bean 创建对象到容器中。

1、application-dev.yml 配置文件

student:
  name: 易烊千玺
  age: 21

2、application-test.yml 配置文件

student:
  name: 迪丽热巴
  age: 22

3、application-product.yml 配置文件

student:
  name: 马尔扎哈
  age: 200

4、声明一个接口以及三个实现类

public interface Student {
}

@Profile("product")
@ConfigurationProperties("student")
@Data
@Component
public class GrandPa implements Student {
    private String name;
    private Integer age;
}

@Profile("dev")
@ConfigurationProperties("student")
@Data
@Component
public class Father implements Student {
    private String name;
    private Integer age;
}

@Profile("test")
@ConfigurationProperties("student")
@Data
@Component
public class Son implements Student {
    private String name;
    private Integer age;
}

5、声明控制层中的方法

@RestController
public class StudentController {
    // 注入一个接口
    @Autowired
    private Student student;

    @RequestMapping("getStudent")
    public Student getStudent() {
        System.out.println(student);
        return student;
    }
}

6、通过 application.yml 文件指定运行的环境

# 使用开发环境
spring:
  profiles:
    active: product

【补充】加载其他配置文件

创建 jdbc.properties 配置文件

# jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/FC2022?useSSL=false&useUnicode=true&characterEncoding=UTF8
jdbc.username=root
jdbc.password=root

控制器

@RestController
public class StudentController {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @RequestMapping("getJdbc")
    public String getJdbc() {
        return driver + url + username + password;
    }
}

SpringBoot启动类上加载配置文件即可!

@SpringBootApplication
// 加载自定义的配置文件
@PropertySource("classpath:jdbc.properties")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

【补充】自定义环境处理类

public class MyEnvironmentPostProcessor implements EnvironmentPostProcessor {
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        // 自定义配置文件
        String[] profiles = {
                "jdbc.properties"
        };

        //循环添加
        for (String profile : profiles) {
            // 从classpath路径下面查找文件
            Resource resource = new ClassPathResource(profile);
            // 加载成PropertySource对象,并添加到Environment环境中
            environment.getPropertySources().addLast(loadProfiles(resource));
        }
    }

    //加载单个配置文件
    private PropertySource<?> loadProfiles(Resource resource) {
        if (!resource.exists()) {
            throw new IllegalArgumentException("资源" + resource + "不存在");
        }
        if (resource.getFilename().contains(".yml")) {
            return loadYaml(resource);
        } else {
            return loadProperty(resource);
        }
    }

    /**
     * 加载properties格式的配置文件
     */
    private PropertySource<?> loadProperty(Resource resource) {
        try {
            // 从输入流中加载一个Properties对象
            Properties properties = new Properties();
            properties.load(resource.getInputStream());
            return new PropertiesPropertySource(resource.getFilename(), properties);
        } catch (Exception ex) {
            throw new IllegalStateException("加载配置文件失败" + resource, ex);
        }
    }

    /**
     * 加载yml格式的配置文件
     */
    private PropertySource<?> loadYaml(Resource resource) {
        try {
            YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
            factory.setResources(resource);
            //从输入流中加载一个Properties对象
            Properties properties = factory.getObject();

            return new PropertiesPropertySource(resource.getFilename(), properties);
        } catch (Exception ex) {
            throw new IllegalStateException("加载配置文件失败" + resource, ex);
        }
    }
}

resources资源目录下,我们还需要创建一个文件META-INF/spring.factories

org.springframework.boot.env.EnvironmentPostProcessor=com.fc.config.MyEnvironmentPostProcessor

【补充】加载 yml 文件

1、person.yml 配置文件

person:
  name: 古力娜扎
  age: 20

2、创建一个配置类

@Configuration
public class LoadYamlConfig {

    /**
     * 加载YML格式自定义配置文件
     */
    @Bean
    public static PropertySourcesPlaceholderConfigurer properties() {
        PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
        YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
        yaml.setResources(new ClassPathResource("person.yml"));
        configurer.setProperties(yaml.getObject());
        return configurer;
    }
}

3、控制器读取配置

@RestController
public class StudentController {
    @Value("${person.name}")
    private String personName;
    @Value("${person.age}")
    private String personAge;

    @RequestMapping("getPerson")
    public String getPerson() {
        return personName + personAge;
    }
}

SpringBoot 常用注解

@Configuration【重点】

被 @Configuration 修饰的类作为一个配置类,相当于 xml 配置文件,会在启动时被加载到 Spring 容器中。通常和 @Bean 标签配合使用,相当于 xml 配置文件中的 Bean 标签。

概述

  1. 指示一个类声明了一个或多个 @Bean 方法,并且可以由 Spring 容器进行处理,以在运行时为这些 bean 生成 bean 定义和服务请求。

  2. 元注解为 @Component,可以使用 @Autowired 进行依赖注入。

  3. @Configuration 类不仅可以使用组件扫描来引导,还可以自己使用 @ComponentScan 注解来配置组件扫描。

  4. @Configuration 默认会生成当前类的代理对象,可以通过内部的 proxyBeanMethods 属性设置为 false 来生成真实对象。用来达到跳过 Spring 容器并强制执行 @Bean 注解生成对象的生命周期的目的

案例代码

1、TestConfig

/**
 * 声明此注解的类相当于xml配置文件,SpringBoot启动时加载
 */
@Configuration
public class TestConfig {
    public TestConfig() {
        System.out.println("相当于Spring中的xml配置文件");
    }

    public void testComponent() {
        System.out.println("测试@Component");
    }
}

2、控制层

@Controller
@RequestMapping("test")
public class ConfigController {
    @Autowired
    public TestConfig testConfig;

    @RequestMapping("component")
    public void testComponent() {
        testConfig.testComponent();
    }
}

@Bean【重点】

概述

指示方法产生一个由 Spring 容器管理的 bean。该注解的 name 属性的名称和语义有意类似于 Spring XML 配置文件中的 bean 标签,并且默认是单例的,如果想要修改为多例,可以使用 @Scope 注解进行声明。

bean 的 name 属性默认使用对象的首字母小写的形式,也可以自定义,支持一个字符串数组,允许给一个 bean 对象起多个别名。

案例代码

1、User 实体类

public class User {
    private int id;
    private String name;
    private int age;
    private String info;

    // Constructor、Setters、Getters、toString
}

2、TestConfig

@Configuration
public class TestConfig {
 // 声明通过此方法获取一个对象放入到Spring容器中进行管理
    @Bean
    public User getUser() {
        return new User(1, "易烊千玺", 20, "送你一朵小红花");
    }
}

3、控制层

@Controller
@RequestMapping("test")
public class ConfigController {
    @Autowired
    public User user;

    @RequestMapping("bean")
    public void testBean() {
        System.out.println(user);
    }
}

@PostConstruct【重点】

常用此注解来完成一些初始化的操作

概述

被 @PostConstruct 修饰的方法会在服务器加载的时候运行,并且只会被服务器执行一次(构造方法以及 @Autowired 之后,Servlet 的 init() 执行之前执行)。

案例代码

@Component
public class TestInit {
    @PostConstruct
    public void init() {
        System.out.println("@PostConstruct注解,启动时加载");
    }
}

@Import【了解】

概述

表明给容器导入一个组件(在 Spring 容器中创建对象),Spring 4.2 之前只能是配置类 ,Spring 4.2 之后也可以是普通的类,相当于交给 Spring 容器中创建一个当前类的对象,对象名为完整的全限定名。

案例代码

1、实体类

@Data
public class Cat {
    private String name;
}

2、配置类

// 将指定的类注入到Spring的容器中
@Import({Cat.class})
@Configuration()
 public class TestConfig {
}

3、启动测试类

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        // 获取Spring容器
        ConfigurableApplicationContext run = SpringApplication.run(Application.class, args);
        // 使用完整的全限定名获取容器中的对象
        Cat cat1 = run.getBean("com.fc.bean.Cat", Cat.class);
        
        // 使用指定的类型获取容器中的对象
        Cat cat2 = run.getBean(Cat.class);

        System.out.println(cat1);
        System.out.println(cat2);
    }
}

@Conditional【重点】

概述

条件装配:指示仅当所有指定条件都匹配时,组件才有资格注册(创建对象)。

uploading.4e448015.gif

转存失败重新上传取消

案例代码

实体类

@Data
public class Son {
}

@Data
public class Wife {
}

@Data
public class GirlFriend {
}

配置类

@Configuration
public class BeanConfig {
    // 注意条件类的位置要在被引用条件之前
    // @Bean("wife")
    public Wife getWife() {
        return new Wife();
    }

    // 当spring容器中包含Wife类的对象时才创建对象
    @ConditionalOnBean(Wife.class)
    @Bean(name = "son")
    public Son getSon() {
        return new Son();
    }

    // 当spring容器中不包含Wife类的对象时才创建对象
    @ConditionalOnMissingBean(Wife.class)
    @Bean("girlFriend")
    public GirlFriend getGirlFriend() {
        return new GirlFriend();
    }
}

【注意】被作为条件的类一定要在被引用之前注册到 Spring 容器中,否则根据条件注册会失败

启动类

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        // 获取容器
        ConfigurableApplicationContext run = SpringApplication.run(Application.class, args);

        // 判断是否包含指定组件
        boolean hasSon = run.containsBean("son");

        // 如果包含指定的组件
        if (hasSon) {
            // 从容器中获取对象
            Son son = run.getBean("son", Son.class);

            System.out.println("有儿子:" + son);
        } else {
            System.out.println("没有儿子");
        }

        // 判断容器中是否包含指定组件
        boolean hasGirlFriend = run.containsBean("girlFriend");

        if (hasGirlFriend) {
            GirlFriend girlFriend = run.getBean(GirlFriend.class);

            System.out.println("有女朋友:" + girlFriend);
        } else {
            System.out.println("没有女朋友");
        }
    }
}

其他常用注解【了解】

注解描述
@SpringBootApplication放置在Springboot启动类上,表明该类是开启Springboot容器的入口。2.0中的注解@SpringBootApplication是注解@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan的组合
@SpringBootConfiguration它表示的是该类会作为Springboot的一个配置类
@EnableAutoConfiguration它表示开启自动配置功能。包含@AutoConfigurationPackage以及@Import两个注解
@AutoConfigurationPackage表示自动注入包
@ImportResource用来加载 xml 配置文件,常用于导入 xml 中装配好的组件(classpath:xxx.xml)
@ComponentScan用来将指定包(如果未指定就是将当前类所在包及其子孙包)加入SpringIOC的包扫描,本质上等于context:component-scan配置
@Inject等价于默认的@Autowired,只是没有required属性
@Qualifier当有多个同一类型的Bean时,可以用@Qualifier(“name”)来指定。与@Autowired配合使用。@Qualifier限定描述符除了能根据名字进行注入,但能进行更细粒度的控制如何选择候选者
@Resource(name=”name”,type=”type”)没有括号内内容的话,默认byName。与@Autowired干类似的事,相当于@Autowired加上@Qualifier。此注解不是Spring的注解,而是Java Web中的注解

SpringBoot 静态资源访问

概述

SpingBoot 会默认访问/static (or /public or /resources or /META-INF/resources下所有的静态资源,访问方式是当前项目名加资源名即可。

访问方式: 当前项目根路径/静态资源名

可以给访问路径以及本地路径添加指定前缀,用来实现 SpringMvc.mxl 配置文件中 resources 标签的映射功能

配置访问路径前缀

# 访问指定img路径下的静态资源
spring
  mvc
    static-path-pattern: /img/**

访问路径为:当前项目根路径/img/静态资源名

配置本地路径前缀

# 访问static下的img下的静态资源,除此路径外的静态资源都无法进行访问
spring
  web
    resources
      static-locations: classpath:/static/img/

访问路径为:当前项目根路径 / + 静态资源名

使用 WebJars 下的静态资源【了解】

pom.xml 文件

<!--jquery-->
<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.6.0</version>
</dependency>

static 下的 html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<script src="webjars/jquery/3.6.0/jquery.min.js"></script>
<body>
</body>
</html>

能够正常访问到静态资源

欢迎页面【了解】

静态资源路径下的 index.html 为欢迎页面,启动项目会默认访问此页面

图标【了解】

将 favicon.ico 文件放在静态资源路径下即可,如果访问不到图标,可以重新编译项目并重启浏览器

SpringBoot 整合 JSP【了解】

1、pom.xml 文件添加依赖并指定JSP文件的编译目录

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

    <!--引入 Spring Boot 内嵌的 Tomcat 对 JSP 的解析包,不加解析不了 jsp 页面-->
    <!--如果只是使用 JSP 页面,可以只添加该依赖-->
    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-jasper</artifactId>
    </dependency>

    <!--jstl 标签依赖的 jar 包-->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</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>

    <!-- SpringBoot要求jsp文件必须编译到指定的META-INF/resources目录下才能访问,否则访问不到。 -->
    <resources>
        <resource>
            <!--源文件位置-->
            <directory>src/main/webapp</directory>
            <!--指定编译到META-INF/resources,固定格式-->
            <targetPath>META-INF/resources</targetPath>
            <!--指定要把哪些文件编译进去,**表示webapp目录及子目录,*.*表示所有文件-->
            <includes>
                <include>**/*.*</include>
            </includes>
        </resource>
    </resources>
</build>

2、application.yml 配置文件中配置视图解析

server:
  port: 8080

# 配置SpringMVC视图解析器
spring:
  mvc:
    view:
      # 配置前缀和后缀
      prefix: /
      suffix: .jsp

3、声明 Controller

@Controller
@RequestMapping("test")
public class JspController {
    @RequestMapping("jsp")
    public String getJsp(Model model) {
        // 添加model属性值
        model.addAttribute("username", "易烊千玺");
        
        // 跳转页面
        return "index";
    }
}

4、创建 index.jsp

【注意】必须在 src/main/webapp 目录下

<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
    <title>测试JSP</title>
</head>
<body>
    ${username}
</body>
</html>

SpringBoot 整合 JdbcTemplate【了解】

pom.xml 配置文件

<properties>
    <java.version>1.8</java.version>
    <!--指定mysql的版本-->
    <mysql.version>5.1.49</mysql.version>
</properties>
<dependencies>
    <!--SpringBoot整合Jdbc核心起步依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jdbc</artifactId>
    </dependency>
    <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>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

yml 配置文件

spring:
  datasource:
#    默认数据源使用的是Hikari,性能非常好
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/FC2022?useSSL=false&useUnicode=true&characterEncoding=UTF8&serverTimeZone=GTM+8
    username: root
    password: root

实体类

@Data
public class User {
    private Integer id;
    private String username;
    private String password;
}

持久层接口以及实现类

public interface UserDao {
    // 查询全部
    List<User> findAll();
    
    // 根据id查询
    User findById(Integer id);

    // 查询总记录数
    Integer findCount();

    // 插入数据
    int insert(User user);

    // 更新
    Integer update(User user);

    // 删除
    Integer delete(Integer id);
}

@Repository
public class UserDaoImpl implements UserDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public List<User> findAll() {
        return jdbcTemplate.query("select * from user", new BeanPropertyRowMapper<>(User.class));
    }

    @Override
    public User findById(Integer id) {
        return jdbcTemplate.queryForObject("select * from user where id = ?", new BeanPropertyRowMapper<>(User.class), id);
    }

    @Override
    public Integer findCount() {
        // 此方法中的requiredType只能使用基本类型包装类或者String,否则会报错
        return jdbcTemplate.queryForObject("select count(*) from user", Integer.class);
    }

    @Override
    public int insert(User user) {

        return jdbcTemplate.update("insert into user(username, password) values(?, ?)", user.getUsername(), user.getPassword());
    }

    @Override
    public Integer update(User user) {
        return jdbcTemplate.update("update user set username = ?, password = ? where id = ?", user.getUsername(), user.getPassword(), user.getId());
    }

    @Override
    public Integer delete(Integer id) {
        return jdbcTemplate.update("delete from user where id = ?", id);
    }
}

业务层接口以及实现类

public interface UserService {
    List<User> findAll();

    User findById(Integer id);

    Integer findCount();

    Integer insert(User user);

    Integer update(User user);

    Integer delete(Integer id);
}

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;

    @Override
    public List<User> findAll() {

        return userDao.findAll();
    }

    @Override
    public User findById(Integer id) {
        return userDao.findById(id);
    }

    @Override
    public Integer findCount() {
        return userDao.findCount();
    }

    @Override
    public Integer insert(User user) {
        return userDao.insert(user);
    }

    @Override
    public Integer update(User user) {
        return userDao.update(user);
    }

    @Override
    public Integer delete(Integer id) {
        return userDao.delete(id);
    }
}

控制层

@RestController
@RequestMapping("user")
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping("findCount")
    public Integer findCount() {
        return userService.findCount();
    }

    @RequestMapping("findAll")
    public List<User> findAll() {
        return userService.findAll();
    }

    @RequestMapping("findById")
    public User findById(@RequestParam(value = "id", required = true, defaultValue = "1") Integer id) {
        return userService.findById(id);
    }

    @RequestMapping("insert")
    public Integer insert(User user) {
        return userService.insert(user);
    }

    @RequestMapping("update")
    public Integer update(User user) {
        return userService.update(user);
    }

    @RequestMapping("delete")
    public Integer delete(Integer id) {
        return userService.delete(id);
    }
}

SpringBoot 整合 MyBatis【重点】

使用 Druid 连接池访问数据库

1、pom.xml 配置文件引入依赖

<properties>
    <java.version>1.8</java.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- MyBatis依赖,必须声明版本号 -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.2</version>
    </dependency>

    <!-- 引入mysql相关依赖,默认引入的8.0以上版本
 可以指定为其他版本 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.48</version>
    </dependency>

    <!--druid数据库连接池-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.21</version>
    </dependency>
    
    <!-- Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</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>

2、application.yml 配置文件配置数据源以及 MyBatis 相关配置

server:
  port: 8080

# 配置数据源
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/FC2022?useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    username: root
    password: root

# mybatis相关配置
mybatis:
  # 配置别名需要扫描的包
  type-aliases-package: com.fc.entity
  # mapper映射文件
  mapper-locations: classpath:mapper/*.xml
  configuration:
    # 配置日志在控制台显示sql语句
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    # 开启二级缓存
    cache-enabled: true

3、创建实体类

@Component
@Data
public class Student implements Serializable {
    private Integer id;
    private String name;
    private Integer age;
    private String gender;
    private Date birthday;
    private String info;
}

【注意】因为使用了二级缓存,所以需要实现序列化

3、创建 Dao 层接口

@Repository
public interface StudentDao {
    List<Student> findAll();
}

这里也可以使用 @Mapper 注解,相当于启动类中的 @MapperScan

4、创建 Mapper 映射文件

<?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.fc.dao.StudentDao">
    <!--二级缓存-->
    <cache/>
    <!--查询全部-->
    <select id="findAll" resultType="com.fc.bean.Student">
        select * from student
    </select>
</mapper>

【注意】映射文件需要放在 src\main\resources\mapper 路径下

5、创建 Service 层接口

public interface StudentService {
    List<Student> findAll();
}

6、创建 Service 层接口实现类

@Service("studentService")
public class StudentServiceImpl implements StudentService {
    @Autowired
    private StudentDao studentDao;

    @Override
    public List<Student> findAll() {
        return studentDao.findAll();
    }
}

7、创建 Controller

@RestController
@RequestMapping("student")
public class StudentController {
    @Autowired
    private StudentService studentService;

    @RequestMapping("findAll")
    public List<Student> findAll() {
        return studentService.findAll();
    }
}

8、修改启动类,添加扫描接口的注解

// 此注解用于扫描Mapper接口,指定的包下面的所有接口在编译之后都会生成相应的实现类
@MapperScan("com.fc.dao")
@SpringBootApplication
public class Springboot07MybatisApplication {
    public static void main(String[] args) {
        SpringApplication.run(Springboot07MybatisApplication.class, args);
    }
}

使用分页插件

1、pom.xml 文件中添加依赖

<!--分页插件PageHelper-->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.2.13</version>
</dependency>

2、Controller 中添加方法

@RestController
@RequestMapping("student")
public class StudentController {
    @Autowired
    private StudentService studentService;
 
    /**
     * 分页查询
     * @param pageNum 当前页
     * @param pageSize 每页显示多少条数据
     * @return 每页显示的数据
     */
    @RequestMapping("findAllByPage")
    public PageInfo<Student> findAllByPage(Integer pageNum, Integer pageSize) {
        PageHelper.startPage(pageNum, pageSize);
        List<Student> list = studentService.findAll();

        return new PageInfo<>(list);
    }
}

开启事务支持

持久层代码

@Mapper
public interface AccountDao {
    void increase(@Param("id") int id, @Param("money") int money);

    void decrease(@Param("id") int id, @Param("money") int money);
}

mapper.xml 配置文件

<?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.fc.dao.AccountDao">
    <update id="increase" parameterType="java.lang.Integer">
        update account set money = money + #{money} where id = #{id}
    </update>

    <update id="decrease" parameterType="java.lang.Integer">
        update account set money = money - #{money} where id = #{id}
    </update>
</mapper>

业务层实现类中添加事务注解

public interface AccountService {
    String transfer();
}

@Service("accountService")
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;

    // 开启事务并指定传播行为,隔离级别,是否只读
    @Transactional(readOnly = false, isolation = Isolation.REPEATABLE_READ, propagation = Propagation.REQUIRED)
    @Override
    public String transfer() {
        accountDao.increase(1, 100);

        int num = 1 / 0;

        accountDao.decrease(2, 100);

        return "转账成功";
    }
}

控制层

@RestController
@RequestMapping("account")
public class AccountController {

    @Autowired
    private AccountService accountService;
    
    @RequestMapping("transfer")
    public String transfer() {
        return accountService.transfer();
    }
}

修改启动类,添加开启事务支持的注解,但是没必要,会自动开启

// 开启事务支持
@EnableTransactionManagement
@MapperScan("com.fc.dao")
@SpringBootApplication
public class Springboot07MybatisApplication {
    public static void main(String[] args) {
        SpringApplication.run(Springboot07MybatisApplication.class, args);
    }
}

SpringBoot 实现 RESTful

概述

REST(英文:Representational State Transfer,简称 REST)

一种互联网软件架构设计的风格,但它并不是标准,它只是提出了一组客户端和服务器交互时的架构理念和设计原则,基于这种理念和原则设计的接口可以更简洁,更有层次,REST 这个词,是 Roy Thomas Fielding 在他 2000 年的博士论文中提出的。任何的技术都可以实现这种理念,如果一个架构符合 REST 原则,就称它为 RESTFul 架构

比如我们要访问一个 http 接口:http://localhost:8080/boot/order?id=1021&status=1

采用 RESTful 风格则访问的地址为:http://localhost:8080/boot/order/1021/1

我们通常使用 @PathVariable 注解获取 url 中的数据。

案例代码

1、pom.xml 配置文件引入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--mybatis-->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.2</version>
    </dependency>
    <!--mysql数据库驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.48</version>
    </dependency>
    <!--Druid连接池-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.21</version>
    </dependency>
    <!--lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

2、application.yml 配置文件配置数据源以及 MyBatis 相关配置

server:
  port: 8080

# 数据库源
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/FC2021?useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    username: root
    password: root

# Mybatis相关配置
mybatis:
  type-aliases-package: com.fc.entity
  configuration:
    cache-enabled: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:mapper/*.xml

3、创建实体类

@Component
@Data
public class Student implements Serializable {
    private Integer id;
    private String name;
    private Integer age;
    private String gender;
    private Date birthday;
    private String info;
}

3、创建 Dao 层接口

@Repository
public interface StudentDao {
    List<Student> findAll();

    int add(Student student);

    int update(Student student);

    int delete(Integer id);
}

4、创建 Mapper 映射文件

<?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.fc.dao.StudentDao">
    <!--二级缓存-->
    <cache/>
    <!--查询全部-->
    <select id="findAll" resultType="com.fc.entity.Student">
        select * from student
    </select>

    <!--添加-->
    <insert id="add" parameterType="com.fc.entity.Student">
        insert into student(name, age, gender, birthday, info) value (#{name}, #{age}, #{gender}, #{birthday}, #{info})
    </insert>

    <!--修改-->
    <update id="update" parameterType="com.fc.entity.Student">
        update student set name = #{name}, gender = #{gender}, age = #{age}, info = #{info} where id = #{id}
    </update>

    <!--删除-->
    <delete id="delete" parameterType="Integer">
        delete from student where id = #{id}
    </delete>
</mapper>

5、创建 Service 层接口

public interface StudentService {
    List<Student> findAll();

    int add(Student student);

    int update(Student student);

    int delete(Integer id);
}

6、创建 Service 层接口实现类

@Service("studentService")
public class StudentServiceImpl implements StudentService {
    @Autowired
    private StudentDao studentDao;

    // 开启事务并指定传播行为,隔离级别,是否只读
    @Transactional(isolation = Isolation.READ_COMMITTED,
            readOnly = true,
            propagation = Propagation.REQUIRED)
    @Override
    public List<Student> findAll() {
        return studentDao.findAll();
    }

    @Transactional(readOnly = false)
    @Override
    public int add(Student student) {
        return studentDao.add(student);
    }

    @Transactional(readOnly = false)
    @Override
    public int update(Student student) {
        return studentDao.update(student);
    }

    @Transactional(readOnly = false)
    @Override
    public int delete(Integer id) {
        return studentDao.delete(id);
    }
}

7、创建 Controller

@RestController
@RequestMapping("student")
public class StudentController {
    @Autowired
    private StudentService studentService;

    /**
     * 使用Get请求查询所有
     *
     * 参考URL:
     * http://localhost:8080/student
     */
    @GetMapping()
    public List<Student> findAll() {
        return studentService.findAll();
    }

    /**
     * 使用POST请求添加学生
     * @param name 姓名
     * @param age 年龄
     * @param gender 性别
     * @param birthday 生日
     * @param info 信息
     * @return 返回结果集
     *
     * 参考URL:
     * http://localhost:8080/student/易烊千玺/20/男/2021-02-09 10:21:13/真帅
     */
    @PostMapping("{name}/{age}/{gender}/{birthday}/{info}")
    public Map<String, Object> add(@PathVariable("name") String name,
                                   @PathVariable("age") Integer age,
                                   @PathVariable("gender") String gender,
                                   @PathVariable("birthday") String birthday,
                                   @PathVariable("info") String info) {

        Map<String, Object> map = new HashMap<>();

        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        Date date = null;

        try {
            date = simpleDateFormat.parse(birthday);

            Student student = new Student();
            student.setName(name);
            student.setAge(age);
            student.setGender(gender);
            student.setBirthday(date);
            student.setInfo(info);

            int affectedRows = studentService.add(student);

            if (affectedRows > 0) {
                map.put("code", 200);
                map.put("message", "添加成功");
            } else {
                map.put("code", 500);
                map.put("message", "添加失败");
            }
        } catch (ParseException e) {
            e.printStackTrace();
        }

        return map;
    }

    /**
     * 使用PUT请求修改学生
     * @param id ID
     * @param name 姓名
     * @param age 年龄
     * @param gender 性别
     * @param birthday 生日
     * @param info 信息
     * @return 返回结果集
     *
     * 参考URL:
     * http://localhost:8080/student/13/迪丽热巴/20/女/2021-02-09 18:49:13/年轻
     */
    @PutMapping("{id}/{name}/{age}/{gender}/{birthday}/{info}")
    public Map<String, Object> update(@PathVariable("id") Integer id,
                                      @PathVariable("name") String name,
                                      @PathVariable("age") Integer age,
                                      @PathVariable("gender") String gender,
                                      @PathVariable("birthday") String birthday,
                                      @PathVariable("info") String info) {
        Map<String, Object> map = new HashMap<>();

        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        Date date = null;

        try {
            date = simpleDateFormat.parse(birthday);

            Student student = new Student();
            student.setId(id);
            student.setName(name);
            student.setAge(age);
            student.setGender(gender);
            student.setBirthday(date);
            student.setInfo(info);

            int affectedRows = studentService.update(student);

            if (affectedRows > 0) {
                map.put("code", 200);
                map.put("message", "修改成功");
            } else {
                map.put("code", 500);
                map.put("message", "修改失败");
            }
        } catch (ParseException e) {
            e.printStackTrace();
        }

        return map;
    }

    /**
     * 使用DELETE请求删除学生
     * @param id ID
     * @return 返回结果集
     *
     * 参考URL:
     * http://localhost:8080/student/13
     */
    @DeleteMapping("{id}")
    public Map<String, Object> delete(@PathVariable("id") Integer id) {
        Map<String, Object> map = new HashMap<>();

        int affectedRows = studentService.delete(id);

        if (affectedRows > 0) {
            map.put("code", 200);
            map.put("message", "删除成功");
        } else {
            map.put("code", 500);
            map.put("message", "删除失败");
        }

        return map;
    }
}

8、修改启动类,添加扫描接口的注解

@MapperScan("com.fc.dao")
@SpringBootApplication
public class Springboot08RestfulApplication {
    public static void main(String[] args) {
        SpringApplication.run(Springboot08RestfulApplication.class, args);
    }
}

9、使用 Postman 访问各接口

# POST
http://localhost:8080/student/易烊千玺/20/男/2021-02-09 10:21:13/真帅

# DELETE
http://localhost:8080/student/13

# PUT
http://localhost:8080/student/13/迪丽热巴/20/女/2021-02-09 18:49:13/年轻

# GET
http://localhost:8080/student

RESTful 风格的特点

  1. 传递参数变简单了
  2. 服务提供者对外只了一个接口,而不是传统的 CRUD四个接口
  3. 一定要注意两个请求路径会发生请求路径冲突问题。如果出现了冲突,要么修改请求路径,要么修改请求方式

RESTful 原则【了解】

1、增使用 post 请求、删使用 delete 请求、改使用 put 请求、查使用 get 请求

2、请求路径不要出现动词

​ 例如:查询订单接口

/boot/order/1021/1(推荐)
/boot/queryOrder/1021/1(不推荐)

3、分页、排序等操作,不需要使用斜杠传参数分页、排序等操作,不需要使用 RESTful 风格传参

​ 例如:订单列表接口

/boot/orders?page=1&sort=desc(推荐)

一般传的参数不是数据库表字段,可以不采用 RESTful 风格传参

【补充】使用表单提交实现 RESTFul

前端页面

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>测试表单提交RESTFul</title>
</head>
<body>
    <form method="post" action="/test/1">
        <input name="_method" value="DELETE" hidden>
        <input type="submit" value="delete提交">
    </form>

    <form method="post" action="/test/1">
        <input name="_method" value="PUT" hidden>
        <input type="submit" value="put提交">
    </form>

    <form method="get" action="/test/1">
     <input type="submit" value="get提交">
    </form>
    
    <form method="post" action="/test/1">
        <input type="submit" value="post提交">
    </form>
</body>
</html>

控制层

@RestController
@RequestMapping("test")
public class TestRestFulController {

    @GetMapping("{id}")
    public String get(@PathVariable("id") Integer id) {
        return "Get:" + id;
    }

    @DeleteMapping("{id}")
    public String delete(@PathVariable("id") Integer id) {
        return "Delete:" + id;
    }

    @PutMapping("{id}")
    public String put(@PathVariable("id") Integer id) {
        return "Put:" + id;
    }

    @PostMapping("{id}")
    public String post(@PathVariable("id") Integer id) {
        return "Post:" + id;
    }
}

yaml 配置文件

spring:
#  开启隐藏方法支持的表单提交
  mvc:
    hiddenmethod:
      filter:
        enabled: true

SpringBoot 异常处理

概述

SpringBoot 默认的已经提供了一套处理异常的机制。 一旦程序中出现了异常 SpringBoot 会向 /error 的 url 发送请求。在 SpringBoot 中提供了一个 叫 BasicErrorController 来处理 /error 请求,然后跳转到默认显示异常的页面来展示异常信息。

uploading.4e448015.gif

转存失败重新上传取消

相关注解

注解描述核心属性及作用
@ControllerAdvice对所有异常集中处理,对Controller层进行拦截value,用于指定异常拦截的包名
@RestControllerAdvice相当于@ControllerAdvice加上@ResponseBody注解value,用于指定异常拦截的包名
@ExceptionHandler对指定异常进行处理value,异常的Class对象
@ResponseStatus当调用处理程序方法并覆盖通过其他方式设置的状态信息时,状态代码将应用于 HTTP 响应value,响应状态码

案例代码

1、创建自定义异常

/**
 * 自定义异常
 */
public class MyException extends Exception {
    public MyException() {
        super();
    }

    public MyException(String message) {
        super(message);
    }
}

2、创建异常处理器

@RestControllerAdvice
public class CustomExceptionHandler {
    // 此注解用于处理对应类型的异常
    @ExceptionHandler(value = Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Map<String, Object> handlerSystemException(Exception e, HttpServletRequest request) {
        Map<String, Object> map = new HashMap<>();

        map.put("msg", e.getMessage());
        map.put("url", request.getRequestURL());

        return map;
    }

    @ExceptionHandler(value = MyException.class)
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public Map<String, Object> handlerMyException(MyException e, HttpServletRequest request) {
        Map<String, Object> map = new HashMap<>();

        map.put("msg", e.getMessage());
        map.put("url", request.getRequestURL());

        return map;
    }
}

3、创建 Controller

@RestController
@RequestMapping("exception")
public class ExceptionController {
    // 测试系统异常
    @RequestMapping("system")
    public void testSystemException() {
        int i = 1 / 0;
    }

    // 测试自定义异常
    @RequestMapping("custom")
    public void testCustomException() throws MyException {
        throw new MyException("自定义异常");
    }
}

SpringBoot 使用过滤器

创建 Controller

@RestController
@RequestMapping("filter")
public class FilterController {

    @RequestMapping("test")
    public String testFilter() {
        return "filter";
    }
}

实现方式一

1、创建自定义过滤器

@WebFilter("/*")
public class MyFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("自定义过滤器被执行");
        filterChain.doFilter(servletRequest, servletResponse);
    }
}

2、修改启动类,添加 @ServletComponentScan 注解,此注解用于@WebServlet、@WebFilter、@WebListener注解自动注册

@SpringBootApplication
// 声明此注解后,Servlet、Filter、Listener可以直接通过@WebServlet、@WebFilter、@WebListener注解自动注册
@ServletComponentScan
public class Springboot10FilterApplication {
    public static void main(String[] args) {
        SpringApplication.run(Springboot10FilterApplication.class, args);
    }
}

实现方式二

@Component
@Order(-1)
public class SexFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("先选性别为女");
        filterChain.doFilter(servletRequest, servletResponse);
    }
}

@Component
@Order(1)
public class AgeFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("再选年龄18-24岁");
        filterChain.doFilter(servletRequest, servletResponse);
    }
}

实现方式三

@Configuration
public class FilterConfig {
    @Bean
    FilterRegistrationBean<Filter> sexFilterRegistrationBean() {
        FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();
        bean.setFilter(new SexFilter());
        // 设置优先级
        bean.setOrder(-1);
        // 字符串转为集合
        bean.setUrlPatterns(Collections.singletonList("/*"));
        return bean;
    }

    @Bean
    FilterRegistrationBean<Filter> ageFilterRegistrationBean2() {
        FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();
        bean.setFilter(new AgeFilter());
        bean.setOrder(1);
        bean.setUrlPatterns(Collections.singletonList("/filter/hello"));
        return bean;
    }
}

SpringBoot 使用拦截器

1、创建自定义拦截器

public class MyInterceptor implements HandlerInterceptor {
    /*
     * 进入controller方法之前调用
     *
     * true表示放行,false表示不放行
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle");
        return true;
    }

    // 调用完controller之后,视图渲染之前
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle");
    }

    // 页面跳转之后,整个流程执行之后,一般用于资源清理操作
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion");
    }
}

2、创建拦截器配置类,这里的操作就相当于 SpringMVC 的注册拦截器 ,@Configuration 就相当于一个 springMvc.xml

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 设置拦截器并指定拦截路径
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/interceptor/*");
        // 拦截所有
        // registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
        // 指定不拦截
        // registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").excludePathPatterns("/test");
    }
}

3、在 static 目录下创建 index.html 页面

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>测试主页</title>
</head>
<body>
 主页
</body>
</html>

4、创建 Controller

@Controller
@RequestMapping("interceptor")
public class InterceptorController {
    @RequestMapping("test")
    public String test() {
        return "/index.html";
    }
}

SpringBoot 文件上传

pom.xml

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

文件上传工具类

// 文件上传工具类
public class FileUploadUtil {
    public static String upload(MultipartFile file) {
        String path = "D:/server/apache-tomcat-8.5.37/webapps/upload/springboot/";

        File pathFile = new File(path);

        if (!pathFile.exists()) {
            pathFile.mkdirs();
        }

        // 获取文件名
        String filename = file.getOriginalFilename();

        // 获取文件名的后缀名
        filename = filename.substring(filename.indexOf('.'));

        // 声明时间格式
        DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmssSSS");

        // 获取当前时间的格式
        String format = dateFormat.format(new Date());

        // 拼接到文件名上
        filename = format + filename;

        try {
            // 文件上传
            file.transferTo(new File(pathFile, filename));
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 返回可以访问的路径
        return "http://localhost:8081/upload/" + filename;
    }
}

yml 配置文件

spring:
  servlet:
    multipart:
      # 开启文件上传
      enabled: true
      # 单个文件大小
      max-file-size: 10MB
      # 所有文件大小
      max-request-size: 100MB

上传成功页

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>上传成功页</title>
</head>
<body>
 上传成功页
</body>
</html>

表单提交

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>测试表单文件上传</title>
</head>
<body>
    <form action="/fileUpload/form" method="post" enctype="multipart/form-data">
        <input type="file" name="header" id="header">
        <input type="file" name="photos" multiple>
        <input type="submit" value="上传">
    </form>
</body>
</html>

Controller

这个时候我们可以用一个注解:RequestPart

它的作用跟@RequestParam差不多,只不过这个是专用于文件上传中的文件进行绑定的。@RequestParam也能实现相同的功能。

主要区别在于,当方法参数不是 String 或原始 MultipartFile Part 时,@RequestParam 依赖于通过注册的 Converter 或 PropertyEditor 进行类型转换,而 RequestPart 依赖于 HttpMessageConverters 考虑到请求部分的“Content-Type”标头。 RequestParam 可能与名称-值表单字段一起使用,而 RequestPart 可能与包含更复杂内容的部分一起使用,例如JSON、XML)。

@Controller
@RequestMapping("fileUpload")
public class FileController {
    @PostMapping("form")
    public String formUpload(@RequestPart("header") MultipartFile header,
                             @RequestPart("photos") MultipartFile[] photos) {

        if (!header.isEmpty()) {
            FileUploadUtil.upload(header);
        }

        if (photos != null && photos.length > 0) {
            for (MultipartFile photo : photos) {
                FileUploadUtil.upload(photo);
            }
        }

        return "redirect:/success.html";
    }
}

Ajax

pom.xml

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.6.0</version>
</dependency>

index.html

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>测试Ajax文件上传</title>
    <script src="webjars/jquery/3.6.0/jquery.min.js"></script>
</head>
<body>
    <form id="form">
 头像:<input type="file" name="header" id="header">
 图片:<input type="file" name="photos" multiple>
        <button type="button" onclick="upload()">上传</button>
    </form>
</body>
<script>
    function upload() {
        var formData = new FormData(document.getElementById("form"));

        $.ajax({
            url: "/fileUpload/json",
            type: "post",
            data: formData,
            contentType: false,
            processData: false,
            dataType: "json",
            success: function (resp) {
                if (resp.code === 200) {
                    alert('文件上传成功!!!')
                }
            }
        })
    }
</script>
</html>

controller

@Controller
@RequestMapping("fileUpload")
public class FileController {
    @PostMapping("json")
    @ResponseBody
    public Map<String, Object> jsonUpload(@RequestPart("header") MultipartFile file,
                                          @RequestPart("photos") MultipartFile[] photos) {
        String headerImgUrl = null;
        if (!file.isEmpty()) {
            headerImgUrl = FileUploadUtil.upload(file);
        }

        List<String> photoImgUrls = null;

        if (photos != null && photos.length > 0) {
            photoImgUrls = new ArrayList<>();
            for (MultipartFile photo : photos) {
                String path = FileUploadUtil.upload(photo);

                photoImgUrls.add(path);
            }
        }

        Map<String, Object> map = new HashMap<>();

        map.put("code", 200);
        map.put("message", "文件上传成功");
        map.put("headerImgUrl", headerImgUrl);
        map.put("photoImgUrls", photoImgUrls);

        return map;
    }
}

SpringBoot 跨域

概述

跨源资源共享 (CORS) (Cross-Origin Resource Sharing,或通俗地译为跨域资源共享)是一种基于HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其它 origin(域,协议和端口),这样浏览器可以访问加载这些资源。跨源资源共享还通过一种机制来检查服务器是否会允许要发送的真实请求,该机制通过浏览器发起一个到服务器托管的跨源资源的"预检"请求。在预检中,浏览器发送的头中标示有HTTP方法和真实请求中会用到的头。

出于安全性,浏览器限制脚本内发起的跨源HTTP请求。 例如,XMLHttpRequest 和 Fetch API 遵循同源策略。 这意味着使用这些 API 的 Web 应用程序只能从加载应用程序的同一个域请求 HTTP 资源,除非响应报文包含了正确 CORS 响应头。

注解

添加了 @CrossOrigin 注解的接口方法会为响应 header 设置 Access-Control-Allow-Origin: * 信息,将允许任意的 Origin(请求 header 中有 Origin 信息)访问资源。

默认情况下 @CrossOrigin 允许的信息:

  • All origins,所有的来源。
  • All headers,所有的 Header 信息。
  • All HTTP,任意的 HTTP 请求方式。

allowCredentials:该信息默认是不开启的。

@CrossOrigin 注解也可以应用在类上,这样类中所有的方法都支持跨源资源共享了。

相关参数

参数描述
origins允许跨域请求的源列表,默认情况下,所有来源都是允许的
originPatterns替代支持更灵活的 origins 模式的模板方式,和 origins 只能存在一个
allowedHeaders实际请求中允许的请求标头列表,默认情况下,允许所有请求的标头。
methods支持的 HTTP 请求方法列表。默认情况下,支持的方法与控制器方法映射到的方法相同。
maxAge预检响应的缓存持续时间的最长期限(以秒为单位)。默认情况下,这设置为1800秒(30 分钟)。

案例代码

@RestController
@RequestMapping("cors")
// 声明此注解用来对该类下面的所有方法进行跨域资源放行
@CrossOrigin
public class CorsController {

    @RequestMapping("test")
    public void test() {
        System.out.println("跨域请求");
    }
}

配置类

全局设置跨源资源共享仅仅声明一个跨域配置类即可,注意必须实现 WebMvcConfigurer 接口并重写其中关于跨域的方法

@Configuration
public class WebConfig implements WebMvcConfigurer {
    // 添加跨域资源访问映射
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // 跨域的访问路径
        registry.addMapping("/**")
                // 允许全部的源
                .allowedOrigins("*")
                // 允许指定的源
                // .allowedOrigins("http://127.0.0.1:8848")
                // 允许指定默认的源
                // .allowedOriginPatterns("http://127.0.0.1:*")
                // .allowedOriginPatterns("http://*.baidu.com")
                // 允许的请求头
                .allowedHeaders("*")
                // 允许的请求类型
                .allowedMethods("GET", "POST", "DELETE", "PUT")
                // 缓存时间,单位秒
                .maxAge(3600 * 24);
    }
}

内置跨域过滤器

// 跨域配置类
@Configuration
public class CorsConfig {
    // 配置跨域过滤器
    @Bean
    public CorsFilter corsFilter() {
        // 创建跨域配置对象
        CorsConfiguration config = new CorsConfiguration();

        // 添加允许访问的内容
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("POST") ;
        config.addAllowedMethod("GET") ;
        config.addAllowedMethod("DELETE") ;
        config.addAllowedMethod("PUT") ;

        // 访问路径跨域配置类
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);

        return new CorsFilter(source);
    }
}

SpringBoot 国际化

概述

国际化(Internationalization)指的是同一个网站可以支持多种不同的语言,以方便不同国家,不同语种的用户访问。

i18n(其来源是英文单词 internationalization的首末字符i和n,18为中间的字符数)是“国际化”的简称。在资讯领域,国际化(i18n)指让产品(出版物,软件,硬件等)无需做大的改变就能够适应不同的语言和地区的需要。对程序来说,在不修改内部代码的情况下,能根据不同语言及地区显示相应的界面。 在全球化的时代,国际化尤为重要,因为产品的潜在用户可能来自世界的各个角落。通常与i18n相关的还有L10n(“本地化”的简称)。

项目搭建

在 static/i18n 路径下创建两个用于存储翻译内容的文件

美式英语:messages_en_US.properties

title=User Login
welcome=Welcome
username=Username
password=Password
login=Sign In
rememberMe=Remember Me

中文简体:messages_zh_CN.properties

title=用户登陆
welcome=欢迎
username=登陆用户
password=登陆密码
login=登陆
rememberMe=记住我

application.yml 配置

spring:
  messages:
    basename: static/i18n/messages
    encoding: UTF-8

用于获取单个国际化翻译值的工具类

/**
 * 国际化工具类
 */
@Component
public class MessageUtils {

    private static MessageSource messageSource;

    public MessageUtils(MessageSource messageSource) {
        MessageUtils.messageSource = messageSource;
    }

    /**
     * 获取单个国际化翻译值
     */
    public static String get(String msgKey) {
        try {
            return messageSource.getMessage(msgKey, null, LocaleContextHolder.getLocale());
        } catch (Exception e) {
            return msgKey;
        }
    }
}

用来传递给前端的 VO

@Data
public class LocaleVO {
    private String title;
    private String welcome;
    private String username;
    private String password;
    private String login;
    private String rememberMe;
}

Controller

@RestController
@RequestMapping("i18n")
public class I18nController {
    @GetMapping("get")
    public LocaleVO get() {
        LocaleVO locale = new LocaleVO();
        locale.setLogin(MessageUtils.get("login"));
        locale.setPassword(MessageUtils.get("password"));
        locale.setUsername(MessageUtils.get("username"));
        locale.setWelcome(MessageUtils.get("welcome"));
        locale.setTitle(MessageUtils.get("title"));
        locale.setRememberMe(MessageUtils.get("rememberMe"));

        return locale;
    }
}

前端页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title id="title">测试SpringBoot国际化</title>
    <script src="webjars/jquery/3.6.0/jquery.min.js"></script>
</head>
<body>
<form>
    <table align="center">
        <caption><h1 style="color:red" id="welcome">测试国际化</h1></caption>
        <tr>
            <td id="username"></td>
            <td><input type="text" name="name"/></td>
        </tr>
        <tr>
            <td id="password"></td>
            <td><input type="password" name="password"/></td>
        </tr>
        <tr>
            <td colspan="2" style="height: 50px" align="center">
                <label>
                    <span id="rememberMe"></span>
                    <input type="checkbox">
                </label>
                <button type="button" id="login"></button>
            </td>
        </tr>
        <tr>
            <td colspan="2" align="center">
                <button id="changeLang" type="button" onclick="i18n()">中文</button>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
            </td>
        </tr>
    </table>
</form>
<script>
    var lang = 'en_US';

    $(function () {
        i18n(lang);
    })

    function i18n() {
        if (lang === 'en_US') {
            lang = 'zh_CN';
            $("#changeLang").text('中文');
        } else {
            lang = 'en_US';
            $("#changeLang").text('English');
        }

        $.ajax({
            url: "/i18n/get",
            type: "get",
            data: {"lang": lang},
            success: function (resp) {
                $("#username").text(resp.username)
                $("#password").text(resp.password)
                $("#login").text(resp.login)
                $("#welcome").text(resp.welcome)
                $("#title").text(resp.title)
                $("#rememberMe").text(resp.rememberMe)
            }
        })
    }
</script>
</body>
</html>

自定义国际化解析器

// 自定义国际化解析器
public class MyLocaleResolver implements LocaleResolver {
    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        // 从请求参数中获取语言
        String lang = request.getParameter("lang");

        // 从请求对象中获取区域对象
        Locale locale;

        // 判断是否为空
        if (lang != null && !lang.equals("")) {
            // 获取语言和地区
            String[] arr = lang.split("_");

            // 给区域对象赋值
            locale = new Locale(arr[0], arr[1]);
        } else {
            // 给区域对象赋值
            locale = new Locale("en", "US");
        }

        return locale;
    }

    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {

    }
}

注入到容器中

/**
 * 配置国际化语言
 */
@Configuration
public class LocaleConfig {
    @Bean
    public LocaleResolver localeResolver() {
        return new MyLocaleResolver();
    }
}

国际化拦截器

/**
 * 配置国际化语言
 */
@Configuration
public class LocaleConfig {
    /**
     * 默认解析器 其中locale表示默认语言
     */
    @Bean
    public LocaleResolver localeResolver() {
        SessionLocaleResolver localeResolver = new SessionLocaleResolver();
        localeResolver.setDefaultLocale(Locale.US);
        return localeResolver;
    }

    /**
     * 默认拦截器 其中lang表示切换语言的参数名
     */
    @Bean
    public WebMvcConfigurer localeInterceptor() {
        return new WebMvcConfigurer() {
            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                LocaleChangeInterceptor localeInterceptor = new LocaleChangeInterceptor();
                localeInterceptor.setParamName("lang");
                registry.addInterceptor(localeInterceptor);
            }
        };
    }
}

SpringBoot 整合 JUnit4

概述

Spring Test 与 JUnit 等其他测试框架结合起来,提供了便捷高效的测试手段。而 Spring Boot Test 是在 Spring Test 之上的再次封装,增加了切片测试,增强了mock 能力。

SpringBootTest 注解

@SpringBootTest 替代了 Spring-Test 中的 @ContextConfiguration 注解,目的是加载 ApplicationContext,启动 Spring 容器。

使用 @SpringBootTest 时并没有像 @ContextConfiguration 一样显示指定 locations 或 classes 属性,原因在于 @SpringBootTest 注解会自动检索程序的配置文件,检索顺序是从当前包开始,逐级向上查找被 @SpringBootApplication 或 @SpringBootConfiguration 注解所修饰的类。

案例代码

1、pom.xml 导入对应的依赖

<!--SpringBoot核心起步依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>

<!--web起步依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!--junit-->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <scope>test</scope>
</dependency>

<!--Spring Test起步依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

2、声明 Controller

@Controller
public class UserController {
    public void test() {
        System.out.println("测试");
    }
}

3、编写测试类

// 告知junit加载Spring运行环境,SpringRunner是SpringJUnit4ClassRunner的子类
@RunWith(SpringRunner.class)
@SpringBootTest
class ApplicationTests {
    // 依赖注入
    @Autowired
    private UserController userController;

    @Test
    void contextLoads() {
        userController.test();
    }
}

SpringBoot 整合 JUnit5

概述

Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库

作为最新版本的 JUnit 框架,JUnit5 与之前版本的 Junit 框架有很大的不同。由三个不同子项目的几个不同模块组成。

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

JUnit Platform: Junit Platform 是在 JVM 上启动测试框架的基础,不仅支持 Junit 自制的测试引擎,其他测试引擎也都可以接入。

JUnit Jupiter: JUnit Jupiter 提供了 JUnit5 的新的编程模型,是 JUnit5 新特性的核心。内部包含了一个测试引擎,用于在 Junit Platform 上运行。

JUnit Vintage: 由于 JUint 已经发展多年,为了照顾老的项目,JUnit Vintage 提供了兼容 JUnit4.x,Junit3.x 的测试引擎。

注意:

1、SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容 junit4 需要自行引入(不能使用junit4的功能 @Test)

2、Junit 类具有 Spring 的功能,比如 @Autowired 能够自动注入容器中的对象、比如 @Transactional 声明的测试方法,测试完成后能够自动回滚

常用注解

注解描述
@Test表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
@ParameterizedTest表示方法是参数化测试
@RepeatedTest表示方法是重复测试的测试模板
@DisplayName声明测试类或测试方法的自定义显示名称
@BeforeEach表示在每个单元测试之前执行,类似于JUnit 4的@Before
@AfterEach表示在每个单元测试之后执行,类似于 JUnit 4的@After
@BeforeAll表示在所有单元测试之前执行,类似于JUnit 4的@BeforeClass
@AfterAll表示在所有单元测试之后执行,类似于JUnit 4的@AfterClass
@Tag表示单元测试类别,类似于JUnit4中的@Categories
@Disabled表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
@Timeout表示测试方法运行如果超过了指定时间将会返回错误
@RepeatedTest表示测试方法会被重复测试
@ExtendWith为测试类或测试方法提供扩展类引用,类似于JUnit4中的@RunWith
// 测试Junit5中的注解
// @SpringBootTest内置@ExtendWith注解,相当于@Runwith注解,用来指明Spring的运行环境
@SpringBootTest
@DisplayName("测试DisplayName注解加到类上")
class ApplicationTests {
    @Autowired
    private User user;

    @Test
    void testDI() {
        System.out.println("测试从容器里获取的对象:" + user);
    }

    @Test
    void contextLoads() {
        System.out.println("测试Junit5中的@Test方法");
    }

    // @DisplayName用来标记显示的名称
    @DisplayName("测试DisplayName注解加到方法上")
    @Test
    void testDisplayName() {
        System.out.println("测试DisplayName注解");
    }

    // @Disabled标记当前测试方法不可用
    @Disabled
    @Test
    @Tag("测试注解")
    void testDisabled() {
        System.out.println("测试Disabled注解:被此注解声明的测试方法将不会生效");
    }

    // @Timeout指定测试方法的过期时间,unit为时间单位
    @Timeout(value = 3, unit = TimeUnit.SECONDS)
    @Test
    void testTimeout() {
        try {
            Thread.sleep(5000);
            System.out.println("测试Timeout注解,超时报错");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // @RepeatedTest用于指明重复执行的次数
    @RepeatedTest(3)
    void testRepeatedTest() {
        System.out.println("测试RepeatedTest注解:重复测试,可以手动指定测试的次数");
    }

    // @BeforeEach用来指定每个测试方法前执行的方法
    @BeforeEach()
    void testBeforeEach() {
        System.out.println("测试BeforeEach注解:任意测试方法执行前执行此方法");
    }

    // @AfterEach用来指定每个测试方法后执行的方法
    @AfterEach
    void testAfterEach() {
        System.out.println("测试AfterEach注解:任意测试方法执行后执行此方法");
    }

    // @BeforeAll用来指定所有测试方法前执行的方法
    @BeforeAll
    static void testBeforeAll() {
        System.out.println("测试BeforeAll注解:所有测试方法执行前执行此方法(一次)");
    }

    // @AfterAll用来指定所有测试方法后执行的方法
    @AfterAll
    static void testAfterAll() {
        System.out.println("测试AfterAll注解:所有测试方法执行后执行此方法(一次)");
    }
}

断言

断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是 Assertions 类中的静态方法。

作用

  • 检查业务逻辑返回的数据是否合理。
  • 所有的测试运行结束以后,会有一个详细的测试报告;

常用方法

方法描述
assertEquals判断两个对象或两个原始类型是否相等
assertNotEquals判断两个对象或两个原始类型是否不相等
assertSame判断两个对象引用是否指向同一个对象
assertNotSame判断两个对象引用是否指向不同的对象
assertTrue判断给定的布尔值是否为 true
assertFalse判断给定的布尔值是否为 false
assertNull判断给定的对象引用是否为 null
assertNotNull判断给定的对象引用是否不为 null
assertArrayEquals判断两个对象或原始类型的数组是否相等
assertAll判断多个条件是否同时满足
assertThrows判断是否能够抛出指定的异常
assertTimeout判断是否能够在指定的时间内运行成功断言
fail快速失败

分类

JUnit 5 内置的断言可以分成如下几个类别:

简单断言

用来对单个值进行简单的验证。

int count(int a, int b) {
    return a + b;
}

@Test
void testSimpleAssert() {
    int count = count(2, 3);
    Assertions.assertEquals(5, count, "逻辑计算错误");
    Assertions.assertSame(new Object(), new Object(), "不是同一个对象");
}

数组断言

判断两个对象或原始类型的数组是否相等。

@Test
@DisplayName("array assertion")
public void array() {
    Assertions.assertArrayEquals(new int[]{1, 2}, new int[]{1, 2}, "数组内容不匹配");
}

组合断言

对多个内容作为实例进行断言。

@Test
@DisplayName("assert all")
public void all() {
    Assertions.assertAll("Math",
            () -> Assertions.assertEquals(2, 1 + 1, "不相同"),
            () -> Assertions.assertTrue(1 + 1 > 1, "不为真"));
}

异常断言

对指定的异常进行断言。

@Test
@DisplayName("异常测试")
public void exceptionTest() {
    ArithmeticException exception = Assertions.assertThrows(
            ArithmeticException.class, () -> System.out.println(1 % 0), "居然可以正常运行?");
}

超时断言

测试方法可以设置超时时间。

@Test
@DisplayName("超时测试")
public void timeoutTest() {
    //如果测试方法时间超过1s将会异常
    Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
}

快速失败

通过 fail 方法直接使得测试失败。

@Test
@DisplayName("fail")
public void shouldFail() {
    Assertions.fail("This should fail");
}

前置条件

JUnit 5 中的前置条件(assumptions【假设】)类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。

@DisplayName("前置条件")
public class AssumptionTest {

    private final String environment = "dev";

    @Test
    @DisplayName("simple")
    public void simpleAssume() {
        Assumptions.assumeTrue(this.environment.equals("dev"));
        Assumptions.assumeFalse(() -> this.environment.equals("product"));
    }

    @Test
    @DisplayName("assume then do")
    public void assumeThenDo() {
        Assumptions.assumingThat(
                this.environment.equals("dev"),
                () -> System.out.println("In dev")
        );
    }
}

嵌套测试

JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用 @BeforeEach 和 @AfterEach 注解,而且嵌套的层次没有限制。

// 官方文档案例,一个栈的测试类
@DisplayName("A stack")
class TestingAStackDemo {
    // 声明一个栈
    Stack<Object> stack;

    @Test
    @DisplayName("is instantiated with new Stack()")
    void isInstantiatedWithNew() {
        new Stack<>();
    }

    @Nested
    @DisplayName("when new")
    class WhenNew {
        @BeforeEach
        void createNewStack() {
            stack = new Stack<>();
        }

        @Test
        @DisplayName("is empty")
        void isEmpty() {
            Assertions.assertTrue(stack.isEmpty());
        }

        @Test
        @DisplayName("throws EmptyStackException when popped")
        void throwsExceptionWhenPopped() {
            Assertions.assertThrows(EmptyStackException.class, () -> {
                stack.pop();
            });
        }

        @Test
        @DisplayName("throws EmptyStackException when peeked")
        void throwsExceptionWhenPeeked() {
            Assertions.assertThrows(EmptyStackException.class, () -> {
                stack.peek();
            });
        }

        @Nested
        @DisplayName("after pushing an element")
        class AfterPushing {
            String anElement = "an element";

            @BeforeEach
            void pushAnElement() {
                stack.push(anElement);
            }

            @Test
            @DisplayName("it is no longer empty")
            void isNotEmpty() {
                Assertions.assertFalse(stack.isEmpty());
            }

            @Test
            @DisplayName("returns the element when popped and is empty")
            void returnElementWhenPopped() {
                Assertions.assertEquals(anElement, stack.pop());
                Assertions.assertTrue(stack.isEmpty());
            }

            @Test
            @DisplayName("returns the element when peeked but remains not empty")
            void returnElementWhenPeeked() {
                Assertions.assertEquals(anElement, stack.peek());

                Assertions.assertFalse(stack.isEmpty());
            }
        }
    }
}

参数化测试

参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。

我们可以利用 @ValueSource 等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。

注解描述
@ValueSource为参数化测试指定入参来源,支持八大基础类以及String类型、Class类型
@NullSource表示为参数化测试提供一个null的入参
@EnumSource表示为参数化测试提供一个枚举入参
@CsvFileSource表示读取指定CSV文件内容作为参数化测试入参
@MethodSource表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流,或者能转换成流的对象,比如数组、集合、迭代器等)
public class ParameterizedTestDemo {

    @ParameterizedTest
    @ValueSource(strings = {"one", "two", "three"})
    @DisplayName("参数化测试1")
    public void parameterizedTest1(String string) {
        System.out.println(string);
        Assertions.assertTrue(!string.isEmpty());
    }

    @ParameterizedTest
    @MethodSource("method") //指定方法名
    @DisplayName("方法来源参数")
    public void testWithExplicitLocalMethodSource(String name) {
        System.out.println(name);
        Assertions.assertNotNull(name);
    }

    static List<String> method() {
        List<String> list = new ArrayList<String>();
        list.add("易烊千玺");
        list.add("迪丽热巴");
        list.add("古力娜扎");
        list.add("欧阳娜娜");

        return list;
    }
}

SpringBoot 邮件发送

pom.xml

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

邮件服务器地址和端口

服务器名称服务器地址(腾讯)服务器地址(网易)非SSL端口号SSL端口号
SMTPsmtp.qq.comsmtp.163.com25465(SMTP SSL)
994(网易)
587(腾讯)
IMAPimap.qq.comimap.163.com143993
POP3pop.qq.compop.163.com110995

application.yml 配置文件

spring:
  mail:
    # 腾讯的和网易的和其他的不太一样
    # 腾讯邮件服务器
    host: smtp.qq.com
    # 邮件发送者的邮箱
    username: XXXXXXXXXX@qq.com
    # 从邮箱获取到的认证授权码,【注意】不是QQ密码
    password: nnwfbgcacqumddbd
    default-encoding: UTF-8
    # 端口
    port: 587
    properties:
      mail:
        smtp:
          socketFactoryClass: javax.net.ssl.SSLSocketFactory
        #表示开启 DEBUG 模式,这样,邮件发送过程的日志会在控制台打印出来,方便排查错误
        debug: true
  servlet:
    multipart:
      # 开启文件上传功能
      enabled: true
      # 文件的最大容量【单个】
      max-file-size: 10MB
      # 全部文件的最大容量
      max-request-size: 100MB

简单邮件

@SpringBootTest
class ApplicationTests {
    // 邮件发送器
    @Autowired
    private JavaMailSender javaMailSender;

    // 测试简单消息邮件
    @Test
    void contextLoads() {
        // 简单消息邮件
        SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
        // 设置发送人
        simpleMailMessage.setFrom("2937753364@qq.com");

        // 设置接收人
        simpleMailMessage.setTo("2XXXXXXXX2@qq.com", "2XXXXXXXX4@qq.com", "2XXXXXXXX6@qq.com", "2XXXXXXXX3@qq.com");

        // 设置主题
        simpleMailMessage.setSubject("重要消息,收到请回复");

        // 设置邮件的内容
        simpleMailMessage.setText("你好,我是秦始皇,其实我并没有死!我在西安有 100 亿吨黄金,我现在只需 100 元人民币来解冻我在西安的黄金!你微信、支付宝转给我都可以,账号就是我的手机号!转过来我直接带兵打过去,让你统领三军!");

        // 设置抄送
        simpleMailMessage.setCc("2XXXXXXXX4@qq.com");

        // 设置发送的日期
        simpleMailMessage.setSentDate(new Date());

        // 设置私密抄送人
        simpleMailMessage.setBcc("2XXXXXXXX6@qq.com");

        try {
            // 通过发送者,需要一个消息对象,直接发送邮件即可
            javaMailSender.send(simpleMailMessage);
        } catch (Exception e) {
            System.out.println("发送失败:" + e);
        }
    }
}

HTML 邮件

@SpringBootTest
class ApplicationTests {
    // 邮件发送器
    @Autowired
    private JavaMailSender javaMailSender;

 // 测试HTML邮件
    @Test
    void testHtmlMail() {
        // 声明一个发送文件的内容
        String context = "<img src='https://t10.baidu.com/it/u=1447337390,142630286&fm=30&app=106&fc=JPEG?w=312&h=208&s=978590414A313A157481CC150300E0C2' alt='图片'/><font align='center' color='red'>欧阳xx,28岁嫁夫港商,因夫无法生育,为继承家业,想寻健康男士与我共孕,通话谈好,飞你处见面首付定金50万,电话1383838382</font>";

        // 创建一个复杂邮件消息对象
        MimeMessage mimeMessage = javaMailSender.createMimeMessage();

        // 创建一个帮助器对象
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage);

        try {
            // 设置一个发送者
            helper.setFrom("2XXXXXXXX4@qq.com");

            // 设置接受者
            helper.setTo(new String[]{"1XXXXXXXX7@qq.com", "1XXXXXXXX5@qq.com"});

            // 设置抄送
            helper.setCc("1XXXXXXXX3@qq.com");

            // 设置主题
            helper.setSubject("重金求子");

            // 设置内容
            helper.setText(context, true);
        } catch (MessagingException e) {
            e.printStackTrace();
        }

        // 发送邮件
        javaMailSender.send(mimeMessage);
    }
}

复杂邮件(携带附件)

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>测试发送邮件</title>
    <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
    <!-- 可选的 Bootstrap 主题文件(一般不用引入) -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap-theme.min.css" integrity="sha384-6pzBo3FDv/PJ8r2KRkGHifhEocL+1X2rVCTTkUfGk7/0pbek5mMa1upzvWbrUbOZ" crossorigin="anonymous">
    <!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>
    <script src="webjars/jquery/3.6.0/jquery.min.js"></script>
</head>
<body>

</body>
<div class="container">
    <form id="form" class="form-horizontal">
        <div class="form-group">
            <label class="col-sm-2 control-label" for="inputFrom">发件人</label>
            <div class="col-sm-10">
                <input type="text" name="from" class="form-control" id="inputFrom" placeholder="请输入发件人">
            </div>
        </div>
        <div class="form-group">
            <label class="col-sm-2 control-label" for="inputTo">收件人</label>
            <div class="col-sm-10">
                <input type="text" name="to" class="form-control" id="inputTo" placeholder="请输入收件人">
            </div>
        </div>
        <div class="form-group">
            <label class="col-sm-2 control-label" for="inputCc">抄送人</label>
            <div class="col-sm-10">
                <input type="text" name="cc" class="form-control" id="inputCc" placeholder="请输入抄送人">
            </div>
        </div>
        <div class="form-group">
            <label class="col-sm-2 control-label" for="inputBcc">秘密抄送</label>
            <div class="col-sm-10">
                <input type="text" name="bcc" class="form-control" id="inputBcc" placeholder="请输入秘密抄送人">
            </div>
        </div>
        <div class="form-group">
            <label class="col-sm-2 control-label" for="inputSubject">邮件主题</label>
            <div class="col-sm-10">
                <input type="text" name="subject" class="form-control" id="inputSubject" placeholder="请输入邮件主题">
            </div>
        </div>
        <div class="form-group">
            <label class="col-sm-2 control-label">邮件内容</label>
            <div class="col-sm-10">
                <textarea name="content" class="form-control" placeholder="请输入邮件的内容" rows="3"></textarea>
            </div>
        </div>
        <div class="form-group">
            <label class="col-sm-2 control-label" for="inputFile">上传附件</label>
            <div class="col-sm-10">
                <input type="file" name="file" id="inputFile" multiple>
                <p class="help-block">请选择您要上传的附件</p>
            </div>
        </div>
        <div class="form-group">
            <button type="button" onclick="send()" class="btn btn-default">发送邮件</button>
        </div>
    </form>
</div>
<script>
    function send() {
        var formData = new FormData(document.getElementById("form"));

        formData.set("to", formData.get('to').split(";"));
        formData.set("cc", formData.get('cc').split(";"));
        formData.set("bcc", formData.get('bcc').split(";"));

        $.ajax({
            url: "/mail/send",
            type: "post",
            data: formData,
            processData: false,
            contentType: false,
            success: function (resp) {
                alert(resp);
            }
        })
    }
</script>
</html>

controller

@RestController
@RequestMapping("mail")
public class MailController {
    @Autowired
    private JavaMailSender sender;

    @PostMapping("send")
    public String send(MailVO vo, MultipartFile file) {
        // 获取消息对象
        MimeMessage mimeMessage = sender.createMimeMessage();

        // 创建一个消息帮助对象
        MimeMessageHelper helper;
        try {
            helper = new MimeMessageHelper(mimeMessage, true);

            // 设置发送人
            helper.setFrom(vo.getFrom());

            // 设置收件人
            helper.setTo(vo.getTo());

            // 设置主题
            helper.setSubject(vo.getSubject());

            // 设置内容
            helper.setText(vo.getContent());

            // 设置发送时间
            if (vo.getSentDate() != null) {
                helper.setSentDate(vo.getSentDate());
            } else {
                helper.setSentDate(new Date());
            }

            // 设置上传附件
            if (!file.isEmpty() && file.getOriginalFilename() != null) {
                helper.addAttachment(file.getOriginalFilename(), file);
            }

            // 发送邮件
            sender.send(mimeMessage);
        } catch (MessagingException e) {
            e.printStackTrace();
        }

        return "success";
    }
}

SpringBoot 数据脱敏

概述

数据脱敏也叫数据的去隐私化,在我们给定脱敏规则和策略的情况下,对敏感数据比如 手机号银行卡号 等信息,进行转换或者修改的一种技术手段,防止敏感数据直接在不可靠的环境下使用。

数据脱敏又分为静态数据脱敏(SDM)和 动态数据脱敏(DDM)。

静态数据脱敏

静态数据脱敏(SDM):适用于将数据抽取出生产环境脱敏后分发至测试、开发、培训、数据分析等场景。

有时我们可能需要将生产环境的数据 copy 到测试、开发库中,以此来排查问题或进行数据分析,但出于安全考虑又不能将敏感数据存储于非生产环境,此时就要把敏感数据从生产环境脱敏完毕之后再在非生产环境使用。

这样脱敏后的数据与生产环境隔离,满足业务需要的同时又保障了生产数据的安全。

动态数据脱敏

动态数据脱敏(DDM):一般用在生产环境,访问敏感数据时实时进行脱敏,因为有时在不同情况下对于同一敏感数据的读取,需要做不同级别的脱敏处理,例如:不同角色、不同权限所执行的脱敏方案会不同。

注意:在抹去数据中的敏感内容同时,也需要保持原有的数据特征、业务规则和数据关联性,保证我们在开发、测试以及数据分析类业务不会受到脱敏的影响,使脱敏前后的数据一致性和有效性。

Jasypt

Jasypt (Java Simplified Encryption) 是一个 java 库,它允许开发人员以最小的努力将基本的加密功能添加到他/她的项目中,而无需深入了解密码学的工作原理。

配置

pom.xml

<dependency>
    <groupId>com.github.ulisesbocchio</groupId>
    <artifactId>jasypt-spring-boot-starter</artifactId>
    <version>3.0.4</version>
</dependency>

JasyptUtil

// 加密工具类
public class JasyptUtil {
    public static void main(String[] args) {
        String username = "root";
        String password = "root";
        BasicTextEncryptor encryptor = new BasicTextEncryptor();
        //秘钥
        //encryptor.setPassword(System.getProperty("jasypt.encryptor.password"));
        encryptor.setPassword("qwertyui");
        //密码进行加密
        String newUsername = encryptor.encrypt(username);
        String newPassword = encryptor.encrypt(password);
        System.out.println("加密后账号:" + newUsername);
        System.out.println("加密后密码:" + newPassword);
    }
}

application.yml

# 配置数据源
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/FC2022?useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    # 账号密码要使用ENC(加密后的账号密码)
    username: ENC(mtCqMGiFJmc0cAtwOkin0Q==)
    password: ENC(62TKWTGKGrYRYCoIGgZwiQ==)

jasypt:
  encryptor:
    # 加密密码
    password: 请在此输入加密密码
    # 加解密算法
    algorithm: PBEWithMD5AndDES
    # 指定最新版本的加密器,防止启动失败
    iv-generator-classname: org.jasypt.iv.NoIvGenerator

程序启动时携带对应真实的加密密码参数即可:

--jasypt.encryptor.password=qwertyui

【补充】SpringBoot 字符编码集

如果出现了字符编码集的问题,在 application.yml 文件中添加以下内容即可

server:
  servlet:
    encoding:
      enabled: true
      force: true
      charset: UTF-8

【补充】SpringBoot 热部署

概述

为了方便开发,可以在创建项目时手动勾选热部署工具 spring-boot-devtools,或导入该依赖,修改完代码后可快速自动重启应用,就不需要每次重启启动类。

实现原理主要是因为它使用了两种不同的类加载器。基础类加载器用于加载不会改变的类(比如第三方库中的类),重启类加载器用于加载你应用程序中的类。当应用程序启动时,重启类加载器中的类将会被替换掉,这就意味着重启将比冷启动更快!

使用

1、pom.xml 配置文件添加依赖

<!--热部署配置-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
</dependency>

2、点击 Settings

uploading.4e448015.gif

转存失败重新上传取消

3、勾选自动构建项目

uploading.4e448015.gif

转存失败重新上传取消

4、Ctrl + Alt + Shift + / 打开保持页面,并选择 Registry

uploading.4e448015.gif

转存失败重新上传取消

5、在注册页面找到 complier.automake.allow.when.app.running 并勾选 Close

uploading.4e448015.gif

转存失败重新上传取消

6、配置更新类时触发热部署

uploading.4e448015.gif

转存失败重新上传取消

7、开启热部署配置

spring:
  devtools:
    restart:
      # 轮询间隔,多久查询一次是否做了修改。默认1s
      poll-interval: 2s
      # 在重新启动之前,在没有任何类路径更改的情况下需要的静默时间。默认400ms
      quiet-period: 1s
      # 是否开启
      enabled: true

修改启动类

@SpringBootApplication
public class SpringbootApplication {

    public static void main(String[] args) {

        SpringApplication springApplication = new SpringApplication(SpringbootApplication.class);
        // 关闭启动logo
        springApplication.setBannerMode(Banner.Mode.OFF);
        springApplication.run(args);
    }
}

在 src/main/resources 目录下添加 banner.txt 文件,此文件中的内容就是 Logo。可以利用网站生成图标: Spring Boot banner在线生成工具,制作下载banner.txt,修改替换banner.txt文字实现自定义,个性化启动banner-bootschool.net 或者 http://patorjk.com/software/taag/,将生成好的图标文字粘贴到 banner.txt 文件中即可。

或者直接使用图片,在配置文件中进行对应的配置也可以

spring.banner.image.location=classpath:
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值