Spring Boot

SpringBoot

1、SpringBoot入门

1.1、Spring与SpringBoot

Spring的生态:https://spring.io/projects/spring-boot

为什么用SpringBoot:能快速创建出生产级别的Spring应用

  • SpringBoot的优点:

    • 创建独立Spring应用

    • 内嵌web服务器

    • 自动starter依赖,简化构建配置

    • 自动配置Spring以及第三方功能

    • 提供生产级别的监控、健康检查及外部化配置

    • 无代码生成、无需编写XML

SpringBoot是整合Spring技术栈的一站式框架

SpringBoot是简化Spring技术栈的快速开发脚手架

  • SpringBoot的缺点:

    • 迭代快,需要时刻关注变化

    • 封装太深,内部原理复杂,不容易精通

1.2、时代背景

  • 微服务:

    • 微服务是一种架构风格

    • 一个应用拆分为一组小型服务

    • 每个服务运行在自己的进程内,也就是可独立部署和升级

    • 服务之间使用轻量级HTTP交互

    • 服务围绕业务功能拆分

    • 可以由全自动部署机制独立部署

    • 去中心化,服务自治。服务可以使用不同的语言、不同的存储技术

  • 分布式:

    • 分布式的困难

      • 远程调用

      • 服务发现

      • 负载均衡

      • 服务容错

      • 配置管理

      • 服务监控

      • 链路追踪

      • 日志管理

      • 任务调度

    • 分布式的解决:SpringBoot + SpringCloud

  • 云原生:

    • 上云的困难

      • 服务自愈

      • 弹性伸缩

      • 服务隔离

      • 自动化部署

      • 灰度发布

      • 流量治理

      • ......

    • 上云的解决

      • 截屏2022-05-27 13.24.06

1.3、官方文档

官方文档:https://spring.io/projects/spring-boot#learn

SpringBoot2.7.0官方文档:https://docs.spring.io/spring-boot/docs/2.7.0/reference/htmlsingle/

查看新特性:https://github.com/spring-projects/spring-boot/wiki#release-notes

1.4、Helloworld

1.4.1、设置maven配置文件

 <!--setting.xml-->
 <mirrors>
       <mirror>
         <id>nexus-aliyun</id>
         <mirrorOf>central</mirrorOf>
         <name>Nexus aliyun</name>
         <url>http://maven.aliyun.com/nexus/content/groups/public</url>
       </mirror>
 </mirrors>
 
   <profiles>
          <profile>
               <id>jdk-1.8</id>
               <activation>
                 <activeByDefault>true</activeByDefault>
                 <jdk>1.8</jdk>
               </activation>
               <properties>
                 <maven.compiler.source>1.8</maven.compiler.source>
                 <maven.compiler.target>1.8</maven.compiler.target>
                 <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
               </properties>
          </profile>
   </profiles>

 

1.4.2、创建maven工程,设置pom.xml父项目、导入依赖

 <!--pom.xml-->
  <parent>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-parent</artifactId>
         <version>2.7.0</version>
     </parent>
 
     <dependencies>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
         </dependency>
     </dependencies>

 

1.4.3、创建SpringBoot主程序

 //====src/main/java/com/colin/boot/MainApplication.java====
 /**
  * 主程序类
  * @SpringBootApplication 声明这是一个SpringBoot应用,这个类是springBoot的主配置类,应运行该类的主方法启动SpringBoot应用
  */
 @SpringBootApplication
 public class MainApplication {
 
     public static void main(String[] args) {
         SpringApplication.run(MainApplication.class, args);
    }
 
 }

 

1.4.4、编写业务

 //=====src/main/java/com/colin/boot/controller/HelloController.java=====
 @RestController
 public class HelloController {
 
     @RequestMapping("/")
     public String handler01(){
         return "Hello SpringBoot2";
    }
 
 }

 

1.4.5、测试

直接运行主程序中的main方法,访问localhost:8080即可,不需要配置tomcat

 

1.4.6、修改配置

通过资源目录下的application.properties文件即可修改整个应用的配置(注意文件名不能改变)

具体可配置的内容可参照官方文档的Application Properties

 #=========src/main/resources/application.properties==========
 # 设置服务器端口号
 server.port = 8888

 

1.4.7、简化部署

直接将项目打包成一个可执行的jar包(fat jars),在目标服务器执行即可完成部署

  1. 设置打包方式为jar

  2. 在pom.xml的<dependencies>标签后添加<build>标签导入插件

 <!--pom.xml-->
  <build>
         <plugins>
             <plugin>
                 <groupId>org.springframework.boot</groupId>
                 <artifactId>spring-boot-maven-plugin</artifactId>
             </plugin>
         </plugins>
     </build>
  1. 在idea右侧Maven窗口中,执行Lifecycle中的clean和package,即可在target目录中生成jar包

  2. 在命令行中输入java -jar+jar包路径即可运行

1.5、SpringBoot的特点1:依赖管理

  • 通过父项目spring-boot-starter-parent进行依赖管理:

    • 通过pom.xml中parent标签内设置的父项目可以实现依赖管理和插件管理:spring-boot-starter-parent-->spring-boot-dependencies

    • 父项目中几乎声明了所有开发中常用的依赖的版本号(自动版本仲裁),<dependencies>导入依赖时无需注明依赖的版本号(引入非版本仲裁的jar,要写版本号)

    • 通过pom.xml的<properties>标签可以自定义想要使用的依赖版本号

           <properties>
               <mysql.version>5.1.43</mysql.version>
           </properties>

       

  • starter场景启动器:

    • Spring Boot 将日常企业应用研发中的各种场景都抽取出来,做成一个个的 starter(启动器),starter 中整合了该场景下各种可能用到的依赖,用户只需要在 Maven 中引入 starter 依赖,SpringBoot 就能自动扫描到要加载的信息并启动相应的默认配置。

    • 命名方式:spring-boot-starter-*,*代表某种场景

    • 只要引入starter,这个场景的所有常规需要的依赖都会自动引入

    • 文档6.1.5中可以查看springboot所有支持的场景

    • 可以自定义starter,非官方的starter有特殊的命名要求,例如*-spring-boot-starter:第三方为我们提供的简化开发的场景启动器

    • 所有场景启动器最底层都依赖于spring-boot-starter

 

1.6、SpringBoot的特点2:自动配置

  • 自动配置Tomcat

    • 引入Tomcat依赖:spring-boot-starter-web中包含的spring-boot-starter-tomcat

    • 配置Tomcat

  • 自动配置SpringMVC

    • 引入SpringMVC全套组件

    • 自动配好SpringMVC常用组件(如字符编码、视图解析、文件上传等)

  • 默认的包结构

    • 主程序所在的包和下面的子包中的组件都会被默认扫描,无需配置开启扫描

    • 如果要改变扫描路径,可以在主程序中配置@SpringBootApplication(scanBasePackages = "com.colin")

       @SpringBootApplication(scanBasePackages = "com.colin")
       public class MainApplication {
       
           public static void main(String[] args) {
               SpringApplication.run(MainApplication.class, args);
          }
       
       }
       /**
        * @SpringBootApplication
        * =
        * @SpringBootConfiguration
        * @EnableAutoConfiguration
        * @ComponentScan("com.colin.boot")
        */

       

实际上,@SpringBootApplication注解= @SpringBootConfiguration+ @EnableAutoConfiguration+ @ComponentScan("com.colin.boot")

  • 各种配置都有默认值

    • 默认配置最终都映射到某个类上,如MultipartProperties

    • 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象

  • 自动配置会按需加载

    • 引入了哪些场景,这个场景的自动配置才会开启

    • SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面

    • SpringBoot所有的自动配置都依赖@EnableAutoConfiguration

 

2、SpringBoot自动配置原理

2.1、容器功能

2.1.1、组件添加

@Configuration

 

 

2.1.2、原生配置文件引入

默认情况下,Spring Boot 中是不包含任何的 Spring 配置文件的,即使我们手动添加 Spring 配置文件到项目中,也不会被识别。

  • Spring Boot 为了我们提供了以下 2 种方式来导入 Spring 配置:

    • 使用 @ImportResource 注解加载 Spring 配置文件

    • 使用全注解方式加载 Spring 配置

@ImportResource

在主启动类上使用 @ImportResource 注解可以导入一个或多个 Spring 配置文件,并使其中的内容生效。

 //将 resources/beans.xml 加载到项目中
 @ImportResource(locations = {"classpath:/beans.xml"})
全注解方式

Spring Boot 推荐使用全注解的方式加载 Spring 配置,其实现方式如下:

  1. 使用 @Configuration 注解定义配置类,替换 Spring 的配置文件;

  2. 配置类内部可以包含有一个或多个被 @Bean 注解的方法,这些方法会被 AnnotationConfigApplicationContext 或 AnnotationConfigWebApplicationContext 类扫描,构建 bean 定义(相当于 Spring 配置文件中的<bean></bean>标签),方法的返回值会以组件的形式添加到容器中,组件的 id 就是方法名。

 /**
 * @Configuration 注解用于定义一个配置类,相当于 Spring 的配置文件
 * 配置类中包含一个或多个被 @Bean 注解的方法,该方法相当于 Spring 配置文件中的 <bean> 标签定义的组件。
 */
 @Configuration
 public class MyAppConfig {
     /**
      * 与 <bean id="personService" class="PersonServiceImpl"></bean> 等价
      * 该方法返回值以组件的形式添加到容器中
      * 方法名是组件 id(相当于 <bean> 标签的属性 id)
      */
     @Bean
     public PersonService personService() {
         System.out.println("在容器中添加了一个组件:peronService");
         return new PersonServiceImpl();
    }
 }

 

2.1.3、配置绑定

所谓“配置绑定”就是把配置文件中的值与 JavaBean 中对应的属性进行绑定。通常,我们会把一些配置信息(例如,数据库配置)放在配置文件中,然后通过 Java 代码去读取该配置文件,并且把配置文件中指定的配置封装到 JavaBean(实体类) 中。

  • SpringBoot 提供了以下 2 种方式进行配置绑定:

    • 使用 @ConfigurationProperties 注解

    • 使用 @Value 注解

@ConfigurationProperties
  1. 在全局配置文件 application.yml 中添加以下自定义属性

     person:
      lastName: 张三
      age: 18
      boss: false
      birth: 1990/12/12
      maps: { k1: v1,k2: 12 }
      lists:
        ‐ lisi
        ‐ zhaoliu
      dog:
        name: 迪迪
        age: 5
  2. 创建一个名为 Person 的实体类,并将配置文件中的属性映射到这个实体类上

     /**
     * 将配置文件中配置的每一个属性的值,映射到这个组件中
     *
     * @Component: 只有这个组件是容器中的组件,才能使用容器提供的@ConfigurationProperties功能;
     *
     * @ConfigurationProperties:告诉 SpringBoot 将本类中的所有属性和配置文件中相关的配置进行绑定;
     * prefix = "person":配置文件中哪个下面的所有属性进行一一映射
     */
     @Component
     @ConfigurationProperties(prefix = "person")
     public class Person {
         private String lastName;
         private Integer age;
         private Boolean boss;
         private Date birth;
         private Map<String, Object> maps;
         private List<Object> lists;
         private Dog dog;
         // get set toString...
     }

     

@ConfigurationProperties:支持松散绑定(松散语法),例如实体类 Person 中有一个属性为 firstName,那么配置文件中的属性名支持以下写法: person.firstName person.first-name person.first_name PERSON_FIRST_NAME

@Value

当我们只需要读取配置文件中的某一个配置时,可以通过 @Value 注解获取。

@Value只支持基本数据类型的封装,例如字符串、布尔值、整数等类型。

 @Component
 public class Person {
     @Value("${person.lastName}")
     private String lastName;
     @Value("${person.age}")
     private Integer age;
     @Value("${person.boss}")
     private Boolean boss;
     @Value("${person.birth}")
     private Date birth;
     private Map<String, Object> maps;
     private List<Object> lists;
     private Dog dog;
 }

 

若只是获取配置文件中的某项值,则推荐使用 @Value 注解; 若专门编写了一个 JavaBean 来和配置文件进行映射,则建议使用 @ConfigurationProperties 注解。

@PropertySource

如果将所有的配置都集中到 application.properties 或 application.yml 中,那么这个配置文件会十分的臃肿且难以维护,因此我们通常会将与 Spring Boot 无关的配置(例如自定义配置)提取出来,写在一个单独的配置文件中,并在对应的 JavaBean 上使用 @PropertySource 注解指向该配置文件。

 @PropertySource(value = "classpath:person.properties")//指向对应的配置文件 src/main/resources/person.properties

 

 

2.2、自动配置原理入门

2.2.1、Spring Factories 机制

Spring Boot 的自动配置是基于 Spring Factories 机制实现的。

Spring Factories 机制是 Spring Boot 中的一种服务发现机制,这种扩展机制与 Java SPI 机制十分相似。Spring Boot 会自动扫描所有 Jar 包类路径下 META-INF/spring.factories 文件,并读取其中的内容,进行实例化,这种机制也是 Spring Boot Starter 的基础。

Spring Factories 实现原理

spring-core 包里定义了 SpringFactoriesLoader 类,这个类会扫描所有 Jar 包类路径下的 META-INF/spring.factories 文件,并获取指定接口的配置。在 SpringFactoriesLoader 类中定义了两个对外的方法loadFactoriesloadFactoryNames从指定的 ClassLoader 中获取 spring.factories 文件,并解析得到实现类的类名列表

(spring.factories 文件本质上与 properties 文件相似,其中包含一组或多组键值对(key=vlaue),其中,key 的取值为接口的完全限定名;value 的取值为接口实现类的完全限定名,一个接口可以设置多个实现类,不同实现类之间使用“,”隔开

  • 源码调用过程如下:

    1. 首先调用 SpringFactoriesLoader 类中的 loadFactories() 方法,最终目的是获取指定接口的实现类对象

    2. loadFactories() 方法中调用了 loadFactoryNames() 方法,目的是根据接口获取其实现类类名的集合

    3. loadFactoryNames() 方法调用了loadSpringFactories() 方法读取该项目中所有 Jar 包类路径下 META-INF/spring.factories 文件的配置内容,并以 Map 集合的形式返回

2.2.2、自动配置的加载

http://c.biancheng.net/spring_boot/auto-config.html

Spring Boot 自动化配置(自动配置这个行为)也是基于 Spring Factories 机制实现的,在 spring-boot-autoconfigure-xxx.jar 类路径下的 META-INF/spring.factories 中设置了 Spring Boot 自动配置的内容

@SpringBootApplication-->@EnableAutoConfiguration-->AutoConfigurationImportSelector类

@SpringBootApplication

@SpringBootApplication 注解是 Spring Boot 中最重要的注解之一 ,也是 Spring Boot 实现自动化配置的关键。

@SpringBootApplication 是一个组合元注解,其主要包含两个注解:@SpringBootConfiguration 和 @EnableAutoConfiguration,其中 @EnableAutoConfiguration 注解是 SpringBoot 自动化配置的核心所在。

@EnableAutoConfiguration

@EnableAutoConfiguration 注解用于开启 Spring Boot 的自动配置功能, 它使用 Spring 框架提供的 @Import 注解通过 AutoConfigurationImportSelector类(选择器)给容器中导入自动配置组件。

AutoConfigurationImportSelector类

AutoConfigurationImportSelector 类实现了 DeferredImportSelector 接口,AutoConfigurationImportSelector 中还包含一个静态内部类 AutoConfigurationGroup,它实现了 DeferredImportSelector 接口的内部接口 Group(Spring 5 新增)。

  • AutoConfigurationImportSelector 类中包含 3 个方法:

    1. getImportGroup()

      获取实现了 DeferredImportSelector.Group 接口的类

    2. process()

      静态内部类 AutoConfigurationGroup 中的核心方法,通过调用 getAutoConfigurationEntry() 方法读取 spring.factories 文件中的内容,获得自动配置类的集合

      getAutoConfigurationEntry() 方法通过调用 getCandidateConfigurations() 方法来获取自动配置类的完全限定名,并在经过排除、过滤等处理后,将其缓存到成员变量中

      在 getCandidateConfigurations() 方法中,根据 Spring Factories 机制调用 SpringFactoriesLoader 的 loadFactoryNames() 方法,根据 EnableAutoConfiguration.class (自动配置接口)获取其实现类(自动配置类)的类名的集合

    3. selectImports()

      内部类 AutoConfigurationImportSelector.AutoConfigurationGroup 的方法 selectImports() 会将 process() 方法处理后得到的自动配置类,进行过滤、排除,最后将所有自动配置类添加到容器中

2.2.3、自动配置的生效和修改

spring.factories 文件中的所有自动配置类(xxxAutoConfiguration),都是必须在一定的条件下才会作为组件添加到容器中,配置的内容才会生效。这些限制条件在 Spring Boot 中以 @Conditional 派生注解的形式体现,如下表。

注解生效条件
@ConditionalOnJava 应用使用指定的 Java 版本时生效
@ConditionalOnBean 容器中存在指定的 Bean 时生效
@ConditionalOnMissingBean 容器中不存在指定的 Bean 时生效
@ConditionalOnExpression 满足指定的 SpEL 表达式时生效
@ConditionalOnClass 存在指定的类时生效
@ConditionalOnMissingClass 不存在指定的类时生效
@ConditionalOnSingleCandidate 容器中只存在一个指定的 Bean 或这个 Bean 为首选 Bean 时生效
@ConditionalOnProperty 系统中指定属性存在指定的值时生效
@ConditionalOnResource 类路径下存在指定的资源文件时生效
@ConditionalOnWebApplication 当前应用是 web 应用时生效
@ConditionalOnNotWebApplication 当前应用不是 web 应用生效
ServletWebServerFactoryAutoConfiguration

以 ServletWebServerFactoryAutoConfiguration 为例,介绍 Spring Boot 自动配置是如何生效的

  • @Conditional

    • 该类使用了 5 个 @Conditional 衍生注解:

    • @ConditionalOnClass({ServletRequest.class}):判断当前项目是否存在 ServletRequest 这个类,若存在,则该配置类生效。

    • @ConditionalOnWebApplication(type = Type.SERVLET):判断当前应用是否是 Web 应用,如果是的话,当前配置类生效。

    • @ConditionalOnClass(name = {"org.apache.catalina.startup.Tomcat"}):判断是否存在 Tomcat 类,若存在则该方法生效。

    • @ConditionalOnMissingFilterBean({ForwardedHeaderFilter.class}):判断容器中是否有 ForwardedHeaderFilter 这个过滤器,若不存在则该方法生效。

    • @ConditionalOnProperty(value = {"server.forward-headers-strategy"},havingValue = "framework"):判断配置文件中是否存在 server.forward-headers-strategy = framework,若不存在则该方法生效。

  • @EnableConfigurationProperties

    • 另外 ServletWebServerFactoryAutoConfiguration 使用了一个 @EnableConfigurationProperties 注解,而 ServerProperties 类上则使用了一个 @ConfigurationProperties 注解。这其实是 Spring Boot 自动配置机制中的通用用法。

    • Spring Boot 中为我们提供了大量的自动配置类 XxxAutoConfiguration 以及 XxxProperties,每个自动配置类 XxxAutoConfiguration 都使用了 @EnableConfigurationProperties 注解,而每个 XxxProperties 上都使用 @ConfigurationProperties 注解。

    • @ConfigurationProperties 注解的作用,是将这个类的所有属性与配置文件中相关的配置进行绑定,以便于获取或修改配置,但是 @ConfigurationProperties 功能是由容器提供的,被它注解的类必须是容器中的一个组件,否则该功能就无法使用。而 @EnableConfigurationProperties 注解的作用正是将指定的类以组件的形式注入到 IOC 容器中,并开启其 @ConfigurationProperties 功能。因此,@ConfigurationProperties + @EnableConfigurationProperties 组合使用,便可以为 XxxProperties 类实现配置绑定功能。

    • 自动配置类 XxxAutoConfiguration 负责使用 XxxProperties 中属性进行自动配置,而 XxxProperties 则负责将自动配置属性与配置文件的相关配置进行绑定,以便于用户通过配置文件修改默认的自动配置。也就是说,真正“限制”我们可以在配置文件中配置哪些属性的类就是这些 XxxxProperties 类,它与配置文件中定义的 prefix 关键字开头的一组属性是唯一对应的。

    • 注意:XxxAutoConfiguration 与 XxxProperties 并不是一一对应的,大多数情况都是多对多的关系,即一个 XxxAutoConfiguration 可以同时使用多个 XxxProperties 中的属性,一个 XxxProperties 类中属性也可以被多个 XxxAutoConfiguration 使用。

2.2.4、自定义starter

命名规范

SpringBoot 提供的 starter 以 spring-boot-starter-xxx 的形式命名。为了与 SpringBoot 生态提供的 starter 进行区分,官方建议第三方开发者或技术(例如 Druid、Mybatis 等等)厂商自定义的 starter 使用 xxx-spring-boot-starter 的形式命名,例如 mybatis-spring-boot-starter、druid-spring-boot-starter 等等。

模块规范

Spring Boot 官方建议我们在自定义 starter 时,创建两个 Module :autoConfigure Module 和 starter Module,其中 starter Module 依赖于 autoConfigure Module。当然,这只是 Spring Boot 官方的建议,并不是硬性规定,若不需要自动配置代码和依赖项目分离,我们也可以将它们组合到同一个 Module 里。

自定义starter

自定义 starter 可以分为以下 7 步:

  1. 创建工程

  2. 添加 POM 依赖

  3. 定义 propertie 类

  4. 定义 Service 类

  5. 定义配置类

  6. 创建 spring.factories文件

  7. 构建 starter

 

 

2.3、开发技巧

Lombok

dev-tools

 

 

3、SpringBoot核心功能

3.1、配置文件

SpringBoot 默认使用以下 2 种全局的配置文件,其文件名是固定的。

  • application.properties

  • application.yml

3.1.1、YAML简介

YAML 全称 YAML Ain't Markup Language,它是一种以数据为中心的标记语言,比 XML 和 JSON 更适合作为配置文件。

想要使用 YAML 作为属性配置文件(以 .yml 或 .yaml 结尾),需要将 SnakeYAML 库添加到 classpath 下,Spring Boot 中的 spring-boot-starter-web 或 spring-boot-starter 都对 SnakeYAML 库做了集成, 只要项目中引用了这两个 Starter 中的任何一个,Spring Boot 会自动添加 SnakeYAML 库到 classpath 下。

3.1.2、YAML语法

  • YAML 的语法如下:

    • 使用缩进表示层级关系。

    • 缩进时不允许使用 Tab 键,只允许使用空格。

    • 缩进的空格数不重要,但同级元素必须左侧对齐。

    • 大小写敏感。

3.1.3、YAML常用写法

  • YAML 支持以下三种数据结构:

    • 字面量:单个的、不可拆分的值,如:数字、字符串、布尔值、日期等

      • 在 YAML 中,使用“key:[空格]value”的形式表示一对键值对(空格不能省略),如 url: www.biancheng.net

      • 字面量直接写在键值对的“value”中即可,默认情况下字符串不需要使用单引号或双引号

      • 若字符串使用单引号,则会转义特殊字符。若字符串使用双引号,则不会转义特殊字符

    • 对象:键值对的集合,因为对象可能包含多个属性,每一个属性都是一对键值对

      • 普通写法:使用缩进表示对象与属性的层级关系

         website: 
          name: baidu
          url: www.baidu.com
      • 行内写法:

         website: {name: baidu,url: www.baidu.com} 
    • 数组:一组按次序排列的值

      • YAML 使用“-”表示数组中的元素

      • 普通写法:

         pets:
          -dog
          -cat
          -pig
      • 行内写法:

         pets: [dog,cat,pig]

以上三种数据结构可以任意组合使用

3.1.4、YAML组织结构

一个 YAML 文件可以由一个或多个配置文档组成,配置文档之间使用---作为分隔符,且各个文档相互独立,互不干扰。 如果 YAML 文件只包含一个文档,则---分隔符可以省略。

3.1.5、Profile多环境配置

在实际的项目开发中,一个项目通常会存在多个环境,Profile 为在不同环境下使用不同的配置提供了支持,我们可以通过激活、指定参数等方式快速切换环境。

多Profile文件方式

SpringBoot配置文件可以通过文件名的命名形式区分出不同的环境的配置,文件命名格式为:application-{profile}.properties/yml,其中,{profile} 一般为各个环境的名称或简称,例如 dev(开发环境配置)、test(测试环境配置) 和 prod(生产环境配置) 等等。

多Profile文件块方式

在 YAML 配置文件中,可以使用“---”把配置文件分割成了多个文档块,并在第一文档块内指定激活测试环境的 Profile

 #默认配置
 server:
  port: 8080
 #切换配置,激活测试环境test
 spring:
  profiles:
    active: test
 ---
 #开发环境
 server:
  port: 8081
 spring:
  config:
    activate:
      on-profile: dev
 ---
 #测试环境
 server:
  port: 8082
 spring:
  config:
    activate:
      on-profile: test
 ---
 #生产环境
 server:
  port: 8083
 spring:
  config:
    activate:
      on-profile: prod

 

激活Profile

除了可以在配置文件中激活指定 Profile,Spring Boot 还为我们提供了另外 2 种激活 Profile 的方式:

  • 命令行激活

java -jar helloworld-0.0.1-SNAPSHOT.jar  --spring.profiles.active=dev
  • 虚拟机参数激活

java -Dspring.profiles.active=prod -jar helloworld-0.0.1-SNAPSHOT.jar

 

3.1.6、默认配置文件

Spring Boot 项目中可以存在多个 application.properties 或 apllication.yml。

Spring Boot 启动时会依次扫描以下 5 个位置的 application.properties 或 apllication.yml 文件,并将它们作为 Spring boot 的默认配置文件。(application.properties 的优先级高于 application.yml)

file:./config/*/
file:./config/
file:./
classpath:/config/
classpath:/
# file: 指当前项目根目录;classpath: 指当前项目的类路径,即 resources 目录。

存在相同的配置内容时,高优先级的内容会覆盖低优先级的内容;

存在不同的配置内容时,高优先级和低优先级的配置内容取并集。

3.1.7、外部配置文件

除了默认配置文件,Spring Boot 还可以加载一些位于项目外部的配置文件。我们可以通过如下 2 个参数,指定外部配置文件的路径:

spring.config.location

先将 Spring Boot 项目打包成 JAR 文件,然后在命令行启动命令中,使用命令行参数 --spring.config.location,指定外部配置文件的路径。

java -jar {JAR}  --spring.config.location={外部配置文件全路径}

需要注意的是,使用该参数指定配置文件后,会使项目默认配置文件(application.properties 或 application.yml )失效,Spring Boot 将只加载指定的外部配置文件。

将 Spring Boot 项目打包后,然后在命令行启动命令中添加 spring.config.additional-location 参数指定外部配置文件,会导致项目根目录下的配置文件无法被加载,我们可以通过以下 3 种方式解决这个问题。

在 IDEA 的运行配置(Run/Debug Configuration)中,添加虚拟机参数 -Dspring.config.additional-location=D:\myConfig\my-application.yml,指定外部配置文件; 在 IDEA 的运行配置(Run/Debug Configuration)中,添加程序运行参数 --spring.config.additional-location=D:\myConfig\my-application.yml,指定外部配置文件; 在主启动类中调用 System.setProperty()方法添加系统属性 spring.config.additional-location,指定外部配置文件。

spring.config.additional-location

可以在 Spring Boot 启动时,使用命令行参数 --spring.config.additional-location 来加载外部配置文件。

 java -jar {JAR}  --spring.config.additional-location={外部配置文件全路径}

--spring.config.additional-location 不会使项目默认的配置文件失效,使用该命令行参数添加的外部配置文件会与项目默认的配置文件共同生效,形成互补配置,且其优先级是最高的,比所有默认配置文件的优先级都高。

3.1.8、配置优先级

  • 以下是常用的 Spring Boot 配置形式及其加载顺序(优先级由高到低):

    1. 命令行参数

    2. 来自 java:comp/env 的 JNDI 属性

    3. Java 系统属性(System.getProperties())

    4. 操作系统环境变量

    5. RandomValuePropertySource 配置的 random.* 属性值

    6. 配置文件(YAML 文件、Properties 文件)

    7. @Configuration 注解类上的 @PropertySource 指定的配置文件

    8. 通过 SpringApplication.setDefaultProperties 指定的默认属性

Spring Boot 配置文件加载位置及优先级:

Spring Boot 配置文件加载顺序

3.2、日志框架

3.2.1、日志框架的选择

市面上常见的日志框架有很多,它们可以被分为两类:日志门面(日志抽象层)和日志实现,如下表。

日志分类描述举例
日志门面(日志抽象层) 为 Java 日志访问提供一套标准和规范的 API 框架,其主要意义在于提供接口。 JCL(Jakarta Commons Logging)、SLF4j(Simple Logging Facade for Java)、jboss-logging
日志实现 日志门面的具体的实现 Log4j、JUL(java.util.logging)、Log4j2、Logback

通常情况下,日志由一个日志门面与一个日志实现组合搭建而成,Spring Boot 选用 SLF4J + Logback 的组合来搭建日志系统。

SLF4J 是目前市面上最流行的日志门面,使用 Slf4j 可以很灵活的使用占位符进行参数占位,简化代码,拥有更好的可读性。

Logback 是 Slf4j 的原生实现框架,它与 Log4j 出自一个人之手,但拥有比 log4j 更多的优点、特性和更做强的性能,现在基本都用来代替 log4j 成为主流。

3.2.2、SLF4J的使用

在项目开发中,记录日志时不应该直接调用日志实现层的方法,而应该调用日志门面(日志抽象层)的方法。

在使用 SLF4J 记录日志时,我们需要在应用中导入 SLF4J 及日志实现,并在记录日志时调用 SLF4J 的方法

 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 public class HelloWorld {
     public static void main(String[] args) {
         Logger logger = LoggerFactory.getLogger(HelloWorld.class);
        //调用 sl4j 的 info() 方法,而非调用 logback 的方法
         logger.info("Hello World");
    }
 }

 

SLF4J 作为一款优秀的日志门面或者日志抽象层,它可以与各种日志实现框架组合使用,以达到记录日志的目的

从 SLF4J 官方给出的方案可以看出:

  • Logback 作为 Slf4j 的原生实现框架,当应用使用 SLF4J+Logback 的组合记录日志时,只需要引入 SLF4J 和 Logback 的 Jar 包即可;

  • Log4j 虽然与 Logback 出自同一个人之手,但是 Log4j 出现要早于 SLF4J,因而 Log4j 没有直接实现 SLF4J,当应用使用 SLF4J+Log4j 的组合记录日志时,不但需要引入 SLF4J 和 Log4j 的 Jar 包,还必须引入它们之间的适配层(Adaptation layer)slf4j-log4j12.jar,该适配层可谓“上有老下有小”,它既要实现 SLF4J 的方法,还有调用 Log4j 的方法,以达到承上启下的作用;

  • 当应用使用 SLF4J+JUL 记录日志时,与 SLF4J+Log4j 一样,不但需要引入 SLF4J 和 JUL 的对应的 Jar 包,还要引入适配层 slf4j-jdk14.jar。

SLF4J 的使用方案:

SLF4J 与与日志实现配合使用方案

3.2.3、统一日志框架

通常一个完整的应用下会依赖于多种不同的框架,而且它们记录日志使用的日志框架也不尽相同,例如,Spring Boot(slf4j+logback),Spring(commons-logging)、Hibernate(jboss-logging)等等。那么如何统一日志框架的使用呢?

对此,SLF4J 官方也给出了相应的解决方案,如下图。

同一日志框架的使用

从上图中可以看出,统一日志框架一共需要以下 3 步 :

  1. 排除应用中的原来的日志框架;

  2. 引入替换包替换被排除的日志框架;

  3. 导入 SLF4J 实现。

SLF4J 官方给出的统一日志框架的方案是“狸猫换太子”,即使用一个替换包来替换原来的日志框架,例如 log4j-over-slf4j 替换 Log4j(Commons Logging API)、jul-to-slf4j.jar 替换 JUL(java.util.logging API)等等。

替换包内包含被替换的日志框架中的所有类,这样就可以保证应用不会报错,但替换包内部实际使用的是 SLF4J API,以达到统一日志主框架的目的。

3.2.4、在SpringBoot中统一日志框架

在使用 Spring Boot 时,同样可能用到其他的框架,例如 Mybatis、Spring MVC、 Hibernate 等等,这些框架的底层都有自己的日志框架,此时我们也需要对日志框架进行统一。

我们知道,统一日志框架的使用一共分为 3 步,Soring Boot 作为一款优秀的开箱即用的框架,已经为用户完成了其中 2 步:引入替换包和导入 SLF4J 实现。

Spring Boot 的核心启动器 spring-boot-starter 引入了 spring-boot-starter-logging ,查看依赖关系可知,spring-boot-starter-logging 的 Maven 依赖不但引入了 logback-classic (包含了日志框架 SLF4J 的实现),还引入了 log4j-to-slf4j(log4j 的替换包),jul-to-slf4j (JUL 的替换包),即 Spring Boot 已经为我们完成了统一日志框架的 3 个步骤中的 2 步。

截屏2022-05-31 21.30.49

SpringBoot 底层使用 slf4j+logback 的方式记录日志,当我们引入了依赖了其他日志框架的第三方框架(例如 Hibernate)时,只需要把这个框架所依赖的日志框架排除,即可实现日志框架的统一,示例代码如下:

 <dependency>
     <groupId>org.apache.activemq</groupId>
     <artifactId>activemq-console</artifactId>
     <version>${activemq.version}</version>
     <exclusions>
         <exclusion>
             <groupId>commons-logging</groupId>
             <artifactId>commons-logging</artifactId>
         </exclusion>
     </exclusions>
 </dependency>

怎么查看这个框架所依赖的日志框架?

3.2.5、日志配置及输出

http://c.biancheng.net/spring_boot/log-config.html

Spring Boot 日志默认级别为 info,日志输出内容默认包含以下元素: 时间日期 日志级别 进程 ID 分隔符:--- 线程名:方括号括起来(可能会截断控制台输出) Logger 名称 日志内容

修改默认日志配置

我们可以根据自身的需求,通过全局配置文件(application.properties/yml)修改 Spring Boot 日志级别和显示格式等默认配置。

 #=======application.properties=======
 #日志级别
 logging.level.net.biancheng.www=trace
 #使用相对路径的方式设置日志输出的位置(项目根目录目录\my-log\mylog\spring.log)
 #logging.file.path=my-log/myLog
 #绝对路径方式将日志文件输出到 【项目所在磁盘根目录\springboot\logging\my\spring.log】
 logging.file.path=/spring-boot/logging
 #控制台日志输出格式
 logging.pattern.console=%d{yyyy-MM-dd hh:mm:ss} [%thread] %-5level %logger{50} - %msg%n
 #日志文件输出格式
 logging.pattern.file=%d{yyyy-MM-dd} === [%thread] === %-5level === %logger{50} === - %msg%n

 

自定义日志配置

在 Spring Boot 的配置文件 application.porperties/yml 中,可以对日志的一些默认配置进行修改,但这种方式只能修改个别的日志配置,想要修改更多的配置或者使用更高级的功能,则需要通过日志实现框架自己的配置文件进行配置。

Spring 官方提供了各个日志实现框架所需的配置文件,用户只要将指定的配置文件放置到项目的类路径下即可。

日志框架配置文件
Logback logback-spring.xml、logback-spring.groovy、logback.xml、logback.groovy
Log4j2 log4j2-spring.xml、log4j2.xml
JUL (Java Util Logging) logging.properties

从上表可以看出,日志框架的配置文件基本上被分为 2 类:

  • 普通日志配置文件,即不带 srping 标识的配置文件,例如 logback.xml;

  • 带有 spring 表示的日志配置文件,例如 logback-spring.xml。

这两种日志配置文件在使用时大不相同:

  • logback.xml、log4j2.xml 等不带 spring 标识的普通日志配置文件,放在项目的类路径下后,这些配置文件会跳过 Spring Boot,直接被日志框架加载。通过这些配置文件,我们就可以达到自定义日志配置的目的。

  • Spring Boot 推荐用户使用 logback-spring.xml、log4j2-spring.xml 等这种带有 spring 标识的配置文件。这种配置文件被放在项目类路径后,不会直接被日志框架加载,而是由 Spring Boot 对它们进行解析,这样就可以使用 Spring Boot 的高级功能 Profile,实现在不同的环境中使用不同的日志配置。

    例如:通过 <springProfile name="dev"> 配置信息 </springProfile> 设置dev开发环境下的日志配置

自己试一次

3.3、web开发

3.3.1、web快速开发

Spring Boot 为 Spring MVC 提供了自动配置,并在 Spring MVC 默认功能的基础上添加了以下特性:

  • 引入了 ContentNegotiatingViewResolver 和 BeanNameViewResolver(视图解析器)

  • 对包括 WebJars 在内的静态资源的支持

  • 自动注册 Converter、GenericConverter 和 Formatter (转换器和格式化器)

  • 对 HttpMessageConverters 的支持(Spring MVC 中用于转换 HTTP 请求和响应的消息转换器)

  • 自动注册 MessageCodesResolver(用于定义错误代码生成规则)

  • 支持对静态首页(index.html)的访问

  • 自动使用 ConfigurableWebBindingInitializer

只要我们在 Spring Boot 项目中的 pom.xml 中引入了 spring-boot-starter-web ,即使不进行任何配置,也可以直接使用 Spring MVC 进行 Web 开发。

3.3.2、静态资源访问

Spring MVC 导入静态资源文件时,需要配置静态资源的映射;但在 SpringBoot 中则不再需要进行此项配置,因为 SpringBoot 已经默认完成了这一工作。

Spring Boot 默认为我们提供了 3 种静态资源映射规则:

  • WebJars 映射

  • 默认资源映射

  • 静态首页(欢迎页)映射

WebJars映射

Web 应用中通常会使用大量的 JS 和 CSS,例如 jQuery,Backbone.js 和 Bootstrap 等等。通常我们会将这些 Web 前端资源拷贝到 Java Web 项目的 webapp 相应目录下进行管理。但是 Spring Boot 项目是以 JAR 包的形式进行部署的,不存在 webapp 目录,WebJars 可以完美的解决上面的问题,它可以以 Jar 形式为 Web 项目提供资源文件。

WebJars 可以将 Web 前端资源(JS,CSS 等)打成一个个的 Jar 包,然后将这些 Jar 包部署到 Maven 中央仓库中进行统一管理,当 Spring Boot 项目中需要引入 Web 前端资源时,只需要访问 WebJars 官网,找到所需资源的 pom 依赖,将其导入到项目中即可。

所有通过 WebJars 引入的前端资源都存放在当前项目类路径(classpath)下的/META-INF/resources/webjars/ 目录中。

Spring Boot 通过 MVC 的自动配置类 WebMvcAutoConfiguration 为这些 WebJars 前端资源提供了默认映射规则,通过源码可知,WebJars 的映射路径为/webjars/,即所有访问/webjars/的请求,都会去classpath:/META-INF/resources/webjars/查找 WebJars 前端资源。

默认资源映射

当访问项目中的任意资源(即/**)时,Spring Boot 会默认从以下路径中查找资源文件(优先级依次降低):

  1. classpath:/META-INF/resources/

  2. classpath:/resources/

  3. classpath:/static/

  4. classpath:/public/

当我们请求某个静态资源(即以“.html”结尾的请求)时,Spring Boot 会先查找优先级高的文件夹,再查找优先级低的文件夹,直到找到指定的静态资源为止。

静态首页映射

静态资源文件夹下的所有 index.html 被称为静态首页或者欢迎页,它们会被被 /** 映射,换句话说就是,当我们访问“/”或者“/index.html”时,都会跳转到静态首页(欢迎页)。

访问静态首页或欢迎页时,其查找顺序也遵循默认静态资源的查找顺序,即先查找优先级高的目录,在查找优先级低的目录,直到找到 index.html 为止。

3.3.3、Thymeleaf

Thymeleaf教程(10分钟入门)

Thymeleaf 是一款用于渲染 XML/XHTML/HTML5 内容的模板引擎。Thymeleaf 最大的特点是,即使不启动 Web 应用,也可以直接在浏览器中打开并正确显示模板页面。

 

Spring Boot 整合 Thymeleaf 模板引擎,需要以下步骤:

  1. 引入 Starter 依赖(项目中一但导入了 Thymeleaf 的依赖,相对应的自动配置 ThymeleafAutoConfiguration 就会自动生效)

     <!--Thymeleaf 启动器-->
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-thymeleaf</artifactId>
     </dependency>
  2. 创建模板文件,并放在在指定目录下

    Spring Boot 通过 ThymeleafAutoConfiguration 自动配置类对 Thymeleaf 提供了一整套的自动化配置方案

    ThymeleafAutoConfiguration 使用 @EnableConfigurationProperties 注解导入了 ThymeleafProperties 类,该类包含了与 Thymeleaf 相关的自动配置属性

    ThymeleafProperties 通过 @ConfigurationProperties 注解将配置文件(application.properties/yml) 中前缀为 spring.thymeleaf 的配置和这个类中的属性绑定。我们可以在 application.properties/yml 中修改以 spring.thymeleaf 开始的属性,以实现修改 Spring Boot 对 Thymeleaf 的自动配置的目的。

    根据默认配置属性可知,Thymeleaf 模板的默认位置在 resources/templates 目录下,默认的后缀是 html,即只要将 HTML 页面放在“classpath:/templates/”下,Thymeleaf 就能自动进行渲染。

3.3.4、SpringBoot定制SpringMVC

Spring Boot 对 Spring MVC 的自动配置可以满足我们的大部分需求,但是我们也可以通过自定义配置类(标注 @Configuration 的类)并实现 WebMvcConfigurer 接口来定制 Spring MVC 配置,例如拦截器、格式化程序、视图控制器等等。

WebMvcConfigurer 是一个基于 Java 8 的接口,该接口定义了许多与 Spring MVC 相关的方法,其中大部分方法都是 default 类型的,且都是空实现。因此我们只需要定义一个配置类实现 WebMvcConfigurer 接口,并重写相应的方法便可以定制 Spring MVC 的配置。

方法说明
default void configurePathMatch(PathMatchConfigurer configurer) {} HandlerMappings 路径的匹配规则。
default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {} 内容协商策略(一个请求路径返回多种数据格式)。
default void configureAsyncSupport(AsyncSupportConfigurer configurer) {} 处理异步请求。
default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {} 这个接口可以实现静态文件可以像 Servlet 一样被访问。
default void addFormatters(FormatterRegistry registry) {} 添加格式化器或者转化器。
default void addInterceptors(InterceptorRegistry registry) {} 添加 Spring MVC 生命周期拦截器,对请求进行拦截处理。
default void addResourceHandlers(ResourceHandlerRegistry registry) {} 添加或修改静态资源(例如图片,js,css 等)映射; Spring Boot 默认设置的静态资源文件夹就是通过重写该方法设置的。
default void addCorsMappings(CorsRegistry registry) {} 处理跨域请求。
default void addViewControllers(ViewControllerRegistry registry) {} 主要用于实现无业务逻辑跳转,例如主页跳转,简单的请求重定向,错误页跳转等
default void configureViewResolvers(ViewResolverRegistry registry) {} 配置视图解析器,将 Controller 返回的字符串(视图名称),转换为具体的视图进行渲染。
default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {} 添加解析器以支持自定义控制器方法参数类型,实现该方法不会覆盖用于解析处理程序方法参数的内置支持; 要自定义内置的参数解析支持, 同样可以通过 RequestMappingHandlerAdapter 直接配置 RequestMappingHandlerAdapter 。
default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {} 添加处理程序来支持自定义控制器方法返回值类型。使用此选项不会覆盖处理返回值的内置支持; 要自定义处理返回值的内置支持,请直接配置 RequestMappingHandlerAdapter。
default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {} 用于配置默认的消息转换器(转换 HTTP 请求和响应)。
default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {} 直接添加消息转换器,会关闭默认的消息转换器列表; 实现该方法即可在不关闭默认转换器的起提下,新增一个自定义转换器。
default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {} 配置异常解析器。
default void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {} 扩展或修改默认的异常解析器列表。
  • 在 Spring Boot 项目中,我们可以通过以下 2 种形式定制 Spring MVC:

    • 扩展 Spring MVC

    • 全面接管 Spring MVC

扩展 Spring MVC

如果 Spring Boot 对 Spring MVC 的自动配置不能满足我们的需要,我们还可以通过自定义一个 WebMvcConfigurer 类型(实现 WebMvcConfigurer 接口)的配置类(标注 @Configuration,但不标注 @EnableWebMvc 注解的类,@EnableWebMvc表示使用配置类全面接管SpringMVC),来扩展 Spring MVC。

例如:(需要提前配置thymeleaf和静态资源)

//============MyMvcConfig============
//实现 WebMvcConfigurer 接口可以来扩展 SpringMVC 的功能
//@EnableWebMvc 不添加这个注释表示此配置类不要全面接管SpringMVC,只扩展功能
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//当访问 “/” 或 “/index.html” 时,都直接跳转到登陆页面
registry.addViewController("/").setViewName("login");
registry.addViewController("/index.html").setViewName("login");
}
}
//========LoginController=========
@Controller
public class IndexController {

@GetMapping(value = {"/login"})
public String loginPage() {
return "login";
}
}
//通过以上配置,“http://localhost:8080/login”、“http://localhost:8080/”“http://localhost:8080/index.html”3 个 URL 都能跳转到登陆页 login.html

 

全面接管 Spring MVC

在配置类上添加@EnableWebMvc注解,完全接管 Spring MVC,此时 Spring Boot 对 Spring MVC 的自动配置将全部失效。

我们知道,Spring Boot 能够访问位于静态资源文件夹中的静态文件,这是在 Spring Boot 对 Spring MVC 的默认自动配置中定义的,当我们全面接管 Spring MVC 后,Spring Boot 对 Spring MVC 的默认配置都会失效,此时再访问静态资源文件夹中的静态资源就会报 404 错误。所以需要重新配置 addResourceHandlers 。

3.3.5、国际化

国际化(Internationalization 简称 I18n,其中“I”和“n”分别为首末字符,18 则为中间的字符数)是指软件开发时应该具备支持多种语言和地区的功能。换句话说就是,开发的软件需要能同时应对不同国家和地区的用户访问,并根据用户地区和语言习惯,提供相应的、符合用具阅读习惯的页面和数据。

 

在 Spring 项目中实现国际化,通常需要以下 3 步:

  1. 编写国际化资源(配置)文件;

  2. 使用 ResourceBundleMessageSource 管理国际化资源文件;

  3. 在页面获取国际化内容。

编写国际化资源文件

在 Spring Boot 的类路径下创建国际化资源文件,文件名格式为:基本名语言代码国家或地区代码,例如 login_en_US.properties、login_zh_CN.properties。

在 src/main/resources 下创建一个 i18n 的目录,并在该目录中按照国际化资源文件命名格式分别创建以下三个文件,

  • login.properties:无语言设置时生效

  • login_en_US.properties :英语时生效

  • login_zh_CN.properties:中文时生效

打开任意一个国际化资源文件,并切换为 Resource Bundle 模式,然后点击“+”号,即可创建所需的国际化属性

使用 ResourceBundleMessageSource 管理国际化资源文件

Spring Boot 通过 MessageSourceAutoConfiguration 对 ResourceBundleMessageSource 提供了默认配置:

  • Spring Boot 将 MessageSourceProperties 以组件的形式添加到容器中;

  • MessageSourceProperties 的属性与配置文件中以“spring.messages”开头的配置进行了绑定;

  • Spring Boot 从容器中获取 MessageSourceProperties 组件,并从中读取国际化资源文件的 basename(文件基本名)、encoding(编码)等信息,将它们封装到 ResourceBundleMessageSource 中;

  • Spring Boot 将 ResourceBundleMessageSource 以组件的形式添加到容器中,进而实现对国际化资源文件的管理。

查看 MessageSourceProperties 类可以知道:

  • MessageSourceProperties 为 basename、encoding 等属性提供了默认值;

  • basename 表示国际化资源文件的基本名,其默认取值为“message”,即 Spring Boot 默认会获取类路径下的 message.properties 以及 message_XXX.properties 作为国际化资源文件;

  • 在 application.porperties/yml 等配置文件中,使用配置参数“spring.messages.basename”即可重新指定国际化资源文件的基本名。

通过以上源码分析可知,Spring Boot 已经对国际化资源文件的管理提供了默认自动配置,我们这里只需要在 Spring Boot 全局配置文件中,使用配置参数“spring.messages.basename”指定我们自定义的国际资源文件的基本名即可,代码如下(当指定多个资源文件时,用逗号分隔):spring.messages.basename=i18n.login

获取国际化内容

由于页面使用的是 Tymeleaf 模板引擎,因此我们可以通过表达式 #{...} 获取国际化内容。

区域信息解析器自动配置

Spring MVC 进行国际化时有 2 个十分重要的对象:

  • Locale:区域信息对象

  • LocaleResolver:区域信息解析器,容器中的组件,负责获取区域信息对象

我们可以通过以上两个对象对区域信息的切换,以达到切换语言的目的。

Spring Boot 在 WebMvcAutoConfiguration 中为区域信息解析器(LocaleResolver)进行了自动配置:

  • 该方法默认向容器中添加了一个区域信息解析器(LocaleResolver)组件,它会根据请求头中携带的“Accept-Language”参数,获取相应区域信息(Locale)对象。

  • 该方法上使用了 @ConditionalOnMissingBean 注解,其参数 name 的取值为 localeResolver(与该方法注入到容器中的组件名称一致),该注解的含义为:当容器中不存在名称为 localResolver 组件时,该方法才会生效。换句话说,当我们手动向容器中添加一个名为“localeResolver”的组件时,Spring Boot 自动配置的区域信息解析器会失效,而我们定义的区域信息解析器则会生效。

手动切换语言
  1. 修改 login.html 切换语言链接,在请求中携带国际化区域信息

     <!--thymeleaf 模板引擎的参数用()代替 ?-->
     <a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文</a>|
     <a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a>
  2. 创建一个 component 包,并在该包中创建一个区域信息解析器 MyLocalResolver

     //自定义区域信息解析器
     public class MyLocalResolver implements LocaleResolver {
         @Override
         public Locale resolveLocale(HttpServletRequest request) {
             //获取请求中参数
             String l = request.getParameter("l");
             //获取默认的区域信息解析器
             Locale locale = Locale.getDefault();
             //根据请求中的参数重新构造区域信息对象
             if (StringUtils.hasText(l)) {
                 String[] s = l.split("_");
                 locale = new Locale(s[0], s[1]);
            }
             return locale;
        }
         @Override
         public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
        }
     }
  3. 在 MyMvcConfig 中添加以下方法,将自定义的区域信息解析器以组件的形式添加到容器中

     //将自定义的区域信息解析器以组件的形式添加到容器中
     @Bean
     public LocaleResolver localeResolver(){
         return new MyLocalResolver();
     }

     

3.3.6、拦截器

拦截器可以根据 URL 对请求进行拦截,主要应用于登陆校验、权限验证、乱码解决、性能监控和异常处理等功能上。

在 Spring Boot 项目中,使用拦截器功能通常需要以下 3 步:

  1. 定义拦截器;

  2. 注册拦截器;

  3. 指定拦截规则(如果是拦截所有,静态资源也会被拦截)

定义拦截器

在 Spring Boot 中定义拦截器十分的简单,只需要创建一个拦截器类,并实现 HandlerInterceptor 接口即可。

HandlerInterceptor 接口中定义以下 3 个方法:

返回值类型方法声明描述
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 该方法在控制器处理请求方法前执行,其返回值表示是否中断后续操作,返回 true 表示继续向下执行,返回 false 表示中断后续操作。
void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) 该方法在控制器处理请求方法调用之后、解析视图之前执行,可以通过此方法对请求域中的模型和视图做进一步修改。
void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 该方法在视图渲染结束后执行,可以通过此方法实现资源清理、记录日志信息等工作。
 @Slf4j
 public class LoginInterceptor implements HandlerInterceptor {
     /**
      * 目标方法执行前
      *
      * @param request
      * @param response
      * @param handler
      * @return
      * @throws Exception
      */
     @Override
     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
         Object loginUser = request.getSession().getAttribute("loginUser");
         if (loginUser == null) {
             //未登录,返回登陆页
             request.setAttribute("msg", "您没有权限进行此操作,请先登陆!");
             request.getRequestDispatcher("/index.html").forward(request, response);
             return false;
        } else {
             //放行
             return true;
        }
    }
     /**
      * 目标方法执行后
      *
      * @param request
      * @param response
      * @param handler
      * @param modelAndView
      * @throws Exception
      */
     @Override
     public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
         log.info("postHandle执行{}", modelAndView);
    }
     /**
      * 页面渲染后
      *
      * @param request
      * @param response
      * @param handler
      * @param ex
      * @throws Exception
      */
     @Override
     public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
         log.info("afterCompletion执行异常{}", ex);
    }
 }

 

注册拦截器

创建一个实现了 WebMvcConfigurer 接口的配置类(使用了 @Configuration 注解的类),重写 addInterceptors() 方法,并在该方法中调用 registry.addInterceptor() 方法将自定义的拦截器注册到容器中。

在配置类 MyMvcConfig 中,添加以下方法注册拦截器:

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
......
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor());
}
}

 

指定拦截规则

修改 MyMvcConfig 配置类中 addInterceptors() 方法的代码,继续指定拦截器的拦截规则:

@Slf4j
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
......
@Override
public void addInterceptors(InterceptorRegistry registry) {
log.info("注册拦截器");
registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**") //拦截所有请求,包括静态资源文件
.excludePathPatterns("/", "/login", "/index.html", "/user/login", "/css/**", "/images/**", "/js/**", "/fonts/**"); //放行登录页,登陆操作,静态资源
}
}

 

在指定拦截器拦截规则时,调用了两个方法,这两个方法的说明如下:

  • addPathPatterns:该方法用于指定拦截路径,例如拦截路径为“/**”,表示拦截所有请求,包括对静态资源的请求。

  • excludePathPatterns:该方法用于排除拦截路径,即指定不需要被拦截器拦截的请求。

 

3.3.7、默认异常处理

http://c.biancheng.net/spring_boot/default-exception.html

Spring Boot 提供了一套默认的异常处理机制,一旦程序中出现了异常,Spring Boot 会自动识别客户端的类型(浏览器客户端或机器客户端),并根据客户端的不同,以不同的形式展示异常信息。

异常处理自动配置原理

Spring Boot 通过配置类 ErrorMvcAutoConfiguration 对异常处理提供了自动配置,该配置类向容器中注入了以下 4 个组件。

  • ErrorPageCustomizer:该组件会在在系统发生异常后,默认将请求转发到“/error”上。

  • BasicErrorController:处理默认的“/error”请求。

  • DefaultErrorViewResolver:默认的错误视图解析器,将异常信息解析到相应的错误视图上。

  • DefaultErrorAttributes:用于页面上共享异常信息。

ErrorPageCustomizer

ErrorMvcAutoConfiguration 向容器中注入了一个名为 ErrorPageCustomizer 的组件,它主要用于定制错误页面的响应规则。

ErrorPageCustomizer 通过 registerErrorPages() 方法来注册错误页面的响应规则。当系统中发生异常后,ErrorPageCustomizer 组件会自动生效,并将请求转发到 “/error”上,交给 BasicErrorController 进行处理。

BasicErrorController

ErrorMvcAutoConfiguration 还向容器中注入了一个错误控制器组件 BasicErrorController

Spring Boot 通过 BasicErrorController 进行统一的错误处理(例如默认的“/error”请求)。Spring Boot 会自动识别发出请求的客户端的类型(浏览器客户端或机器客户端),并根据客户端类型,将请求分别交给 errorHtml() 和 error() 方法进行处理。

返回值类型方法声明客户端类型错误信息返类型
ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) 浏览器客户端 text/html(错误页面)
ResponseEntity<Map<String, Object>> error(HttpServletRequest request) 机器客户端(例如安卓、IOS、Postman 等等) JSON

在 errorHtml() 方法中会调用父类(AbstractErrorController)的 resolveErrorView() 方法

从上述源码可以看出,在响应页面的时候,会在父类的 resolveErrorView 方法中获取容器中所有的 ErrorViewResolver 对象(错误视图解析器,包括 DefaultErrorViewResolver 在内),一起来解析异常信息。

DefaultErrorViewResolver

ErrorMvcAutoConfiguration 还向容器中注入了一个默认的错误视图解析器组件 DefaultErrorViewResolver

当发出请求的客户端为浏览器时,Spring Boot 会获取容器中所有的 ErrorViewResolver 对象(错误视图解析器),并分别调用它们的 resolveErrorView() 方法对异常信息进行解析,其中自然也包括 DefaultErrorViewResolver(默认错误信息解析器)。

DefaultErrorViewResolver 解析异常信息的步骤如下:

  1. 根据错误状态码(例如 404、500、400 等),生成一个错误视图 error/status,例如 error/404、error/500、error/400。

  2. 尝试使用模板引擎解析 error/status 视图,即尝试从 classpath 类路径下的 templates 目录下,查找 error/status.html,例如 error/404.html、error/500.html、error/400.html。

  3. 若模板引擎能够解析到 error/status 视图,则将视图和数据封装成 ModelAndView 返回并结束整个解析流程,否则跳转到第 4 步。

  4. 依次从各个静态资源文件夹中查找 error/status.html,若在静态文件夹中找到了该错误页面,则返回并结束整个解析流程,否则跳转到第 5 步。

  5. 将错误状态码(例如 404、500、400 等)转换为 4xx 或 5xx,然后重复前 4 个步骤,若解析成功则返回并结束整个解析流程,否则跳转第 6 步。

  6. 处理默认的 “/error ”请求,使用 Spring Boot 默认的错误页面(Whitelabel Error Page)。

DefaultErrorAttributes

ErrorMvcAutoConfiguration 还向容器中注入了一个组件默认错误属性处理工具 DefaultErrorAttributes

DefaultErrorAttributes 是 Spring Boot 的默认错误属性处理工具,它可以从请求中获取异常或错误信息,并将其封装为一个 Map 对象返回

在 Spring Boot 默认的 Error 控制器(BasicErrorController)处理错误时,会调用 DefaultErrorAttributes 的 getErrorAttributes() 方法获取错误或异常信息,并封装成 model 数据(Map 对象),返回到页面或 JSON 数据中。该 model 数据主要包含以下属性:

  • timestamp:时间戳;

  • status:错误状态码

  • error:错误的提示

  • exception:导致请求处理失败的异常对象

  • message:错误/异常消息

  • trace: 错误/异常栈信息

  • path:错误/异常抛出时所请求的URL路径

所有通过 DefaultErrorAttributes 封装到 model 数据中的属性,都可以直接在页面或 JSON 中获取。

3.3.8、全局异常处理

Spring Boot 提供的默认异常处理机制却并不一定适合我们实际的业务场景,因此,我们通常会根据自身的需要对 Spring Boot 全局异常进行统一定制,例如定制错误页面,定制错误数据等。

定制错误页面

我们可以通过以下 3 种方式定制 Spring Boot 错误页面:

  • 自定义 error.html

  • 自定义动态错误页面

  • 自定义静态错误页面

自定义 error.html

我们可以直接在模板引擎文件夹(/resources/templates)下创建 error.html ,覆盖 Spring Boot 默认的错误视图页面(Whitelabel Error Page)。

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>自定义 error.html</title>
</head>
<body>
<h1>自定义 error.html</h1>
<p>status:<span th:text="${status}"></span></p>
<p>error:<span th:text="${error}"></span></p>
<p>timestamp:<span th:text="${timestamp}"></span></p>
<p>message:<span th:text="${message}"></span></p>
<p>path:<span th:text="${path}"></span></p>
</body>
</html>
自定义动态错误页面

如果 Sprng Boot 项目使用了模板引擎,当程序发生异常时,Spring Boot 的默认错误视图解析器(DefaultErrorViewResolver)就会解析模板引擎文件夹(resources/templates/)下 error 目录中的错误视图页面。

我们可以根据错误状态码(例如 404、500、400 等等)的不同,分别创建不同的动态错误页面(例如 404.html、500.html、400.html 等等),并将它们存放在模板引擎文件夹下的 error 目录中。当发生异常时,Spring Boot 会根据其错误状态码精确匹配到对应的错误页面上。

我们还可以使用 4xx.html 和 5xx.html 作为动态错误页面的文件名,并将它们存放在模板引擎文件夹下的 error 目录中,来模糊匹配对应类型的所有错误,例如 404、400 等错误状态码以“4”开头的所有异常,都会解析到动态错误页面 4xx.html 上。

自定义静态错误页面

若 Sprng Boot 项目没有使用模板引擎,当程序发生异常时,Spring Boot 的默认错误视图解析器(DefaultErrorViewResolver)则会解析静态资源文件夹下 error 目录中的静态错误页面。

我们可以根据错误状态码(例如 404、500、400 等等)的不同,分别创建不同的静态错误页面(例如 404.html、500.html、400.html 等等),并将它们存放在静态资源文件夹下的 error 目录中。当发生异常时,Spring Boot 会根据错误状态码精确匹配到对应的错误页面上。

我们还可以使用 4xx.html 和 5xx.html 作为静态错误页面的文件名,并将它们存放在静态资源文件夹下的 error 目录中,来模糊匹配对应类型的所有错误,例如 404、400 等错误状态码以“4”开头的所有错误,都会解析到静态错误页面 4xx.html 上。

静态页面无法识别 Thymeleaf 表达式,因此无法展示与错误相关的错误信息。

错误页面优先级

以上 5 种方式均可以定制 Spring Boot 错误页面,且它们的优先级顺序为: 自定义动态错误页面(精确匹配)>自定义静态错误页面(精确匹配)>自定义动态错误页面(模糊匹配)>自定义静态错误页面(模糊匹配)>自定义 error.html

当遇到错误时,Spring Boot 会按照优先级由高到低,依次查找解析错误页,一旦找到可用的错误页面,则直接返回客户端展示。

定制错误数据

我们知道,Spring Boot 提供了一套默认的异常处理机制,其主要流程如下:

  1. 发生异常时,将请求转发到“/error”,交由 BasicErrorController(Spring Boot 默认的 Error 控制器) 进行处理;

  2. BasicErrorController 根据客户端的不同,自动适配返回的响应形式,浏览器客户端返回错误页面,机器客户端返回 JSON 数据。

  3. BasicErrorController 处理异常时,会调用 DefaultErrorAttributes(默认的错误属性处理工具) 的 getErrorAttributes() 方法获取错误数据。

我们还可以定制 Spring Boot 的错误数据,具体步骤如下。

  1. 自定义异常处理类,将请求转发到 “/error”,交由 Spring Boot 底层(BasicErrorController)进行处理,自动适配浏览器客户端和机器客户端。

  2. 通过继承 DefaultErrorAttributes 来定义一个错误属性处理工具,并在原来的基础上添加自定义的错误数据。

自定义异常处理类

被 @ControllerAdvice 注解的类可以用来实现全局异常处理,这是 Spring MVC 中提供的功能,在 Spring Boot 中可以直接使用。

 //======exception/UserNotExistException.java=====
 /**
 * 自定义异常
 */
 public class UserNotExistException extends RuntimeException {
     public UserNotExistException() {
         super("用户不存在!");
    }
 }
 //=====IndexController=====
 @Controller
 public class IndexController {
    ......
     @GetMapping(value = {"/testException"})
     public String testException(String user) {
         if ("user".equals(user)) {
             // 触发UserNotExistException异常
             throw new UserNotExistException();
        }
         //跳转到登录页 login.html
         return "login";
    }
 }
 //======controller/MyExceptionHandler=======
 @ControllerAdvice
 public class MyExceptionHandler {
     @ExceptionHandler(UserNotExistException.class)
     public String handleException(Exception e, HttpServletRequest request) {
         Map<String, Object> map = new HashMap<>();
         //向 request 对象传入错误状态码
         request.setAttribute("javax.servlet.error.status_code",500);
         //根据当前处理的异常,自定义的错误数据
         map.put("code", "user.notexist");
         map.put("message", e.getMessage());
         //将自定的错误数据传入 request 域中
         request.setAttribute("ext",map);
         return "forward:/error";
    }
 }
 
 //访问/testException触发UserNotExistException异常,由MyExceptionHandler代替BasicErrorController的一部分处理异常功能

 

自定义错误属性处理工具

创建一个错误属性处理工具类 MyErrorAttributes(继承 DefaultErrorAttributes ),通过该类我们可以添加自定义的错误数据(因为 BasicErrorController 会调用 getErrorAttributes 方法获取错误数据)

 //向容器中添加自定义的储物属性处理工具
 @Component
 public class MyErrorAttributes extends DefaultErrorAttributes {
     @Override
     public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
         Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, options);
         //添加自定义的错误数据
         errorAttributes.put("company", "www.biancheng.net");
         //获取 MyExceptionHandler 传入 request 域中的错误数据
         Map ext = (Map) webRequest.getAttribute("ext", 0);
         errorAttributes.put("ext", ext);
         return errorAttributes;
    }
 }
 

 

3.3.9、注册Web原生组件

由于 Spring Boot 默认以 Jar 包方式部署的,默认没有 web.xml,因此无法再像以前一样通过 web.xml 配置来使用 Servlet 、Filter、Listener,但 Spring Boot 提供了 2 种方式来注册这些 Web 原生组件。

  • 通过组件扫描注册

  • 使用 RegistrationBean 注册

通过组件扫描注册

Servlet 3.0 提供了以下 3 个注解:

  • @WebServlet:用于声明一个 Servlet;

  • @WebFilter:用于声明一个 Filter;

  • @WebListener:用于声明一个 Listener。

这些注解可直接标注在对应组件上,它们与在 web.xml 中的配置意义相同。每个注解都具有与 web.xml 对应的属性,可直接配置,省去了配置 web.xml 的繁琐。

 

想要在 SpringBoot 中注册这些原生 Web 组件,可以使用 @ServletComponentScan 注解实现,该注解可以扫描标记 @WebServlet、@WebFilter 和 @WebListener 三个注解的组件类,并将它们注册到容器中。

注意:@ServletComponentScan 注解只能标记在启动类或配置类上。

 //=====================
 //使用 @WebServlet 注解声明一个 Servlet
 @WebServlet(name = "myServlet", urlPatterns = "/myServlet")
 public class MyServlet extends HttpServlet {
     @Override
     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
         doPost(req, resp);
    }
     @Override
     protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
         resp.setContentType("text/html;charset=UTF-8");
         PrintWriter writer = resp.getWriter();
         writer.write("Spring Boot Servlet");
         writer.close();
    }
 }
 //=====================
 //使用 @WebFilter注解声明一个自定义的 Filter
 @WebFilter(urlPatterns = ("/myServlet"))
 public class MyFiler implements Filter {
     @Override
     public void init(FilterConfig filterConfig) throws ServletException {
         System.out.println("MyFiler 初始化");
    }
     @Override
     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
         System.out.println("MyFiler doFilter");
         chain.doFilter(request, response);
    }
     @Override
     public void destroy() {
         System.out.println("MyFiler 销毁");
    }
 }
 //=====================
 //使用 @WebListener 注解声明一个自定义的 Listener
 @WebListener
 public class MyListener implements ServletContextListener {
     @Override
     public void contextInitialized(ServletContextEvent sce) {
         System.out.println("MyListener 监听到 ServletContext 初始化");
    }
     @Override
     public void contextDestroyed(ServletContextEvent sce) {
         System.out.println("MyListener 监听到 ServletContext 销毁");
    }
 }
 //=====================
 //扫描以上刚刚声明的 Servlet、Filter 和 Listener,并将它们注册到容器中使用
 @ServletComponentScan
 @SpringBootApplication
 public class SpringBootServletApplication {
     public static void main(String[] args) {
         SpringApplication.run(SpringBootServletApplication.class, args);
    }
 }

 

使用 RegistrationBean 注册

我们还可以在配置类中使用 RegistrationBean 来注册原生 Web 组件,不过这种方式相较于注解方式要繁琐一些。使用这种方式注册的原生 Web 组件,不再需要使用 @WebServlet 、@WebListener 和 @WebListener 等注解。

RegistrationBean 是个抽象类,负责将组件注册到 Servlet 容器中,Spring 提供了三个它的实现类,分别用来注册 Servlet、Filter 和 Listener。

  • ServletRegistrationBean:Servlet 的注册类

  • FilterRegistrationBean:Filter 的注册类

  • ServletListenerRegistrationBean:Listener 的注册类

我们可以在配置类中,使用 @Bean 注解将 ServletRegistrationBean、FilterRegistrationBean 和 ServletListenerRegistrationBean 添加 Spring 容器中,并通过它们将我们自定义的 Servlet、Filter 和 Listener 组件注册到容器中使用。

 @Configuration
 public class MyConfig {
     /**
      * 注册 servlet
      * @return
      */
     @Bean
     public ServletRegistrationBean servletRegistrationBean() {
         MyServlet myServlet = new MyServlet();
         return new ServletRegistrationBean(myServlet, "/myServlet");
    }
     /**
      * 注册过滤器
      * @return
      */
     @Bean
     public FilterRegistrationBean filterRegistrationBean() {
         MyFiler myFiler = new MyFiler();
         FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFiler);
         //注册该过滤器需要过滤的 url
         filterRegistrationBean.setUrlPatterns(Arrays.asList("/myServlet"));
         return filterRegistrationBean;
    }
     /**
      * 注册监听器
      * @return
      */
     @Bean
     public ServletListenerRegistrationBean servletListenerRegistrationBean() {
         MyListener myListener = new MyListener();
         return new ServletListenerRegistrationBean(myListener);
    }
 }

 

3.4、数据访问

3.4.1、Spring Boot使用JDBC访问数据库

对于数据访问层,无论是 SQL(关系型数据库) 还是 NOSQL(非关系型数据库),Spring Boot 都默认采用整合 Spring Data 的方式进行统一处理,通过大量自动配置,来简化我们对数据访问层的操作,我们只需要进行简单的设置即可实现对书层的访问。

在Spring Boot中使用JDBC进行数据访问需要经过以下步骤:

  1. 导入JDBC场景启动器

  2. 导入数据库驱动

  3. 配置数据源

  4. 测试

导入JDBC场景启动器

在 pom.xml 中导入 JDBC 场景启动器:spring-boot-starter-data-jdbc

<!--导入JDBC的场景启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>

查看 spring-boot-starter-data-jdbc 的依赖树,可以看到,该场景启动器默认引入了一个数据源:HikariCP,Spring Boot 默认使用 HikariCP 作为其数据源,对数据库的访问。

导入数据库驱动

JDBC 的场景启动器中并没有导入数据库驱动,我们需要根据自身的需求引入所需的数据库驱动。例如,访问 MySQL 数据库时,需要导入 MySQL 的数据库驱动:mysql-connector-java

<!--导入数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>

Spring Boot 默认为数据库驱动程序做了版本仲裁,所以我们在导入数据库驱动时,可以不再声明版本。需要注意的是,数据库驱动的版本必须与数据库的版本相对应。

配置数据源

在配置文件(application.properties/yml)中配置数据源

#数据源连接信息
spring:
datasource:
username: root
password: root
url: jdbc:mysql://127.0.0.1:3306/bianchengbang_jdbc
driver-class-name: com.mysql.cj.jdbc.Driver
测试

Spring Boot 提供了一个名为 JdbcTemplate 的轻量级数据访问工具,它是对 JDBC 的封装。Spring Boot 对 JdbcTemplate 提供了默认自动配置,我们可以直接使用 @Autowired 或构造函数将它注入到 bean 中使用。

@SpringBootTest
class SpringBootJdbcApplicationTests {
//数据源组件
@Autowired
DataSource dataSource;
//用于访问数据库的组件
@Autowired
JdbcTemplate jdbcTemplate;
@Test
void contextLoads() throws SQLException {
System.out.println("默认数据源为:" + dataSource.getClass());
System.out.println("数据库连接实例:" + dataSource.getConnection());
//访问数据库
Integer i = jdbcTemplate.queryForObject("SELECT count(*) from `user`", Integer.class);
System.out.println("user 表中共有" + i + "条数据。");
}
}

 

3.4.2、数据源配置原理

在数据库访问过程中,“数据源”无疑是最重要的概念之一,它不仅可以对与数据库访问相关的各种参数进行封装和统一管理,还可以管理数据库连接池,提高数据库连接性能。在 Spring Boot 2.x 中,采用目前性能最佳的 HikariCP 作为其默认数据源。

DataSourceAutoConfiguration

Spring Boot 中几乎所有的默认配置都是通过配置类 XxxAutoConfiguration 进行配置的,Spring Boot 数据源也不例外,它的自动配置类是:DataSourceAutoConfiguration。

DataSourceAutoConfiguration 中共包括以下 5 个内部静态类:

  • EmbeddedDatabaseCondition

  • PooledDataSourceAvailableCondition

  • PooledDataSourceCondition

  • PooledDataSourceConfiguration(池化数据源自动配置类,使用了 @Configuration 注解)

  • EmbeddedDatabaseConfiguration(内嵌数据源自动配置类,使用了 @Configuration 注解)

EmbeddedDatabaseConfiguration

EmbeddedDatabaseConfiguration 是内嵌数据源的自动配置类,该类中并没有任何的方法实现,它的主要功能都是通过 @Import 注解引入 EmbeddedDataSourceConfiguration 类来实现的。

EmbeddedDataSourceConfiguration 向容器中添加了一个 Spring Boot 内嵌的数据源,该数据源支持 HSQL,H2 和 DERBY 三种数据库

自动配置类 EmbeddedDatabaseConfiguration 的作用是向容器中添加一个内嵌的数据源(DataSource),但这是有条件限制的。在 EmbeddedDatabaseConfiguration 类上还使用一个 @Conditional 注解,该注解使用了 DataSourceAutoConfiguration 的内部限制条件类 EmbeddedDatabaseCondition 来进行条件判断。

EmbeddedDatabaseCondition 主要用来检测容器中是否已经存在池化数据源(PooledDataSource)。若容器中存在池化数据源时,则 EmbeddedDatabaseConfiguration 不能被实例化。只有当容器中不存在池化数据源时,EmbeddedDatabaseConfiguration 才能被实例化,才能向容器中添加内嵌数据源(EmbeddedDataSource)。

PooledDataSourceConfiguration

PooledDataSourceConfiguration 是池化数据源的自动配置类,该类上使用了一个 @Conditional 注解,该注解使用了 DataSourceAutoConfiguration 的内部限制条件类 PooledDataSourceCondition 来进行条件判断。

PooledDataSourceCondition 与 EmbeddedDatabaseCondition 一样,也是用来检测容器中是否已经存在池化数据源的,但不同的是,PooledDataSourceConfiguration 是只有当容器中存在池化数据源时, 才可以被实例化,才可以向容器中添加池化数据源。

与 EmbeddedDatabaseConfiguration 一样,PooledDataSourceConfiguration 类中也没有任何的方法实现,它的所有功能都是通过 @Import 注解引入其他的类实现的。

PooledDataSourceConfiguration 通过 @Import 注解引入了 Hikari、Tomcat、Dbcp2、OracleUcp 和 Generic 五个数据源配置类,它们都是 DataSourceConfiguration 的内部类,且它们的功能类似,都是向容器中添加指定的数据源。

 

以 Hikari 为例,在 Hikari 类中,主要使用以下注解:

  • @Configuration:表示当前类是一个配置类;

  • @ConditionalOnMissingBean({DataSource.class}):表示容器中没有用户自定义的数据源时,该配置类才会被实例化;

  • @ConditionalOnClass({HikariDataSource.class}) :表示必须在类路径中存在 HikariDataSource 类时,Hikari 才会实例化。而 HikariDataSource 类是由 spring- boot-starter-jdbc 默认将其引入的,因此只要我们在 pom.xml 中引入了该 starter, Hikari 就会被实例化(这也是 Spring Boot 2.x 默认使用 HikariCP 作为其数据源的原因)。;

  • @ConditionalOnProperty( name = {"spring.datasource.type"},havingValue = "com.zaxxer.hikari.HikariDataSource",matchIfMissing = true): 表示当 Spring Boot 配置文件中,配置了 spring.datasource.type = com.zaxxer.hikari.HikariDataSource(明确指定使用 Hikari 数据源)或者不配置 spring.datasource.type(即默认情况)时,Hikari 才会被实例化。

Hikari 类通过 @Bean 注解向容器中添加了 HikariDataSource 组件,该组件的实例对象是通过调用 DataSourceConfiguration 的 createDataSource() 方法得到的

在 createDataSource() 方法中,调用 DataSourceProperties 的 initializeDataSourceBuilder() 来初始化 DataSourceBuilder

initializeDataSourceBuilder() 方法通过调用 DataSourceBuilder 的 create() 方法创建 DataSourceBuilder 对象,并根据 Spring Boot 的配置文件(application.properties/yml)中的配置,依次设置数据源类型、驱动类名、连接 url、 用户名和密码等信息。

上面提到 spring.datasource.type 默认是可以不用配置的,因此在 createDataSource() 方法在获取到回传回来的 DataSourceBuilder 对象后,还需要将其 type 属性再次设置为 HikariDataSource,并调用 DataSourceBuilder 的 build() 方法,完成 HikariDataSource 的初始化。

dataSource() 方法获得数据源对象,并设置了连接池的名字(name),注入到容器中。

总结

通过对 Spring Boot 数据源自动配置原理的分析可知:

  • 在用户没有配置数据源的情况,若容器中存在 HikariDataSource 类,则 Spring Boot 就会自动实例化 Hikari,并将其作为其数据源。

  • Spring Boot 的 JDBC 场景启动器(spring-boot-starter-data-jdbc)通过 spring- boot-starter-jdbc 默认引入了 HikariCP 数据源(包含 HikariDataSource 类),因此 Spring Boot 默认使用 HikariCP 作为其数据源。

3.4.3、整合Druid数据源

Druid 官方文档

Spring Boot 2.x 默认使用 HikariCP 作为数据源,我们只要在项目中导入了 Spring Boot 的 JDBC 场景启动器,便可以使用 HikariCP 数据源获取数据库连接,对数据库进行增删改查等操作。

HikariCP 是目前市面上性能最好的数据源产品,但在实际的开发过程中,企业往往更青睐于另一款数据源产品:Druid,它是目前国内使用范围最广的数据源产品。

Druid 不是 Spring Boot 内部提供的技术,它属于第三方技术,我们可以通过以下两种方式进行整合:

  • 自定义整合 Druid

  • 通过 starter 整合 Druid

自定义整合Druid

通过手动创建 Druid 数据源的方式,将 Druid 整合到 Spring Boot 中。

引入Druid依赖
 <!--导入 JDBC 场景启动器-->
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-jdbc</artifactId>
 </dependency>
 <!--导入数据库驱动-->
 <dependency>
     <groupId>mysql</groupId>
     <artifactId>mysql-connector-java</artifactId>
     <scope>runtime</scope>
 </dependency>
 <!--采用自定义方式整合 druid 数据源-->
 <!--自定义整合需要编写一个与之相关的配置类-->
 <dependency>
     <groupId>com.alibaba</groupId>
     <artifactId>druid</artifactId>
     <version>1.2.6</version>
 </dependency>
创建数据源

向容器中添加 Druid 数据源类(DruidDataSource,继承自 DataSource)的对象,告诉 Spring Boot 使用 Druid 作为其数据源,而不再使用 HikariCP。

 @Configuration
 public class MyDataSourceConfig implements WebMvcConfigurer {
     /**
      * 当向容器中添加了 Druid 数据源
      * 使用 @ConfigurationProperties 将配置文件中 spring.datasource 开头的配置与数据源中的属性进行绑定
      * @return
      */
     @ConfigurationProperties("spring.datasource")
     @Bean
     public DataSource dataSource() throws SQLException {
         DruidDataSource druidDataSource = new DruidDataSource();
         //我们一般不建议将数据源属性硬编码到代码中,而应该在配置文件中进行配置(@ConfigurationProperties 绑定)
 //       druidDataSource.setUrl("jdbc:mysql://127.0.0.1:3306/bianchengbang_jdbc");
 //       druidDataSource.setUsername("root");
 //       druidDataSource.setPassword("root");
 //       druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
         return druidDataSource;
    }
 }
 #数据源连接信息
 spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://127.0.0.1:3306/bianchengbang_jdbc
    driver-class-name: com.mysql.cj.jdbc.Driver

此时数据源已经成功地切换到了 Druid 数据源,且通过它也可以正常的获取数据库连接,访问数据库。

开启 Druid 内置监控页面

Druid 内置提供了一个名为 StatViewServlet 的 Servlet,这个 Servlet 可以开启 Druid 的内置监控页面功能, 展示 Druid 的统计信息,它的主要用途如下:

  • 提供监控信息展示的 html 页面

  • 提供监控信息的 JSON API

注意:使用 StatViewServlet,建议使用 Druid 0.2.6 以上版本。

根据 Druid 官方文档-配置_StatViewServlet 配置,StatViewServlet 是一个标准的 javax.servlet.http.HttpServlet,想要开启 Druid 的内置监控页面,需要将该 Servlet 配置在 Web 应用中的 WEB-INF/web.xml 中:

 <servlet>
     <servlet-name>DruidStatView</servlet-name>
     <servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
 </servlet>
 <servlet-mapping>
     <servlet-name>DruidStatView</servlet-name>
     <url-pattern>/druid/*</url-pattern>
 </servlet-mapping>

Spring Boot 项目中是没有 WEB-INF/web.xml 的,因此我们可以在配置类中,通过 ServletRegistrationBean 将 StatViewServlet 注册到容器中,来开启 Druid 的内置监控页面。(参考3.3.9注册Web原生组件)

 /**
 * 开启 Druid 数据源内置监控页面
 * @return
 */
 @Bean
 public ServletRegistrationBean statViewServlet() {
     StatViewServlet statViewServlet = new StatViewServlet();
     //向容器中注入 StatViewServlet,并将其路径映射设置为 /druid/*
     ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(statViewServlet, "/druid/*");
     //配置监控页面访问的账号和密码(选配)
     servletRegistrationBean.addInitParameter("loginUsername", "admin");
     servletRegistrationBean.addInitParameter("loginPassword", "123456");
     return servletRegistrationBean;
 }
开启 SQL 监控

Druid 内置提供了一个 StatFilter,通过它可以开启 Druid 的 SQL 监控功能,对 SQL 进行监控。

StatFilter 的别名是 stat,这个别名的映射配置信息保存在 druid-xxx.jar!/META-INF/druid-filter.properties 中。只要在 dataSource 的 Bean 中添加一个取值为“stat”的“filters”属性,就能开启 Druid SQL 监控,因此我们只要将该配置转换为在配置类中进行即可,代码如下。

 @ConfigurationProperties("spring.datasource")
 @Bean
 public DataSource dataSource() throws SQLException {
     DruidDataSource druidDataSource = new DruidDataSource();
     //设置 filters 属性值为 stat,开启 SQL 监控
     druidDataSource.setFilters("stat");
     return druidDataSource;
 }
开启防火墙

Druid 内置提供了一个 WallFilter,使用它可以开启防火墙功能,防御 SQL 注入攻击。

WallFilter 的别名是 wall,这个别名映射配置信息保存在 druid-xxx.jar!/META-INF/druid-filter.properties 中。只要在 dataSource 的 Bean 中添加一个取值为“wall”的“filters”属性,就能开启 Druid 的防火墙功能,因此我们只需要在配置类中为 dataSource 的 filters 属性再添加一个“wall”即可(多个属性值之间使用逗号“,”隔开)

 @ConfigurationProperties("spring.datasource")
 @Bean
 public DataSource dataSource() throws SQLException {
     DruidDataSource druidDataSource = new DruidDataSource();
     //同时开启 sql 监控(stat) 和防火墙(wall),中间用逗号隔开。
     //开启防火墙能够防御 SQL 注入攻击
     druidDataSource.setFilters("stat,wall");
     return druidDataSource;
 }
开启 Web-JDBC 关联监控

Druid 还内置提供了一个名为 WebStatFilter 的过滤器,它可以用来监控与采集 web-jdbc 关联监控的数据。

根据 Druid 官方文档-配置_配置WebStatFilter,想要开启 Druid 的 Web-JDBC 关联监控,只需要将 WebStatFilter 配置在 Web 应用中的 WEB-INF/web.xml 中即可,web.xml 配置如下。

 <filter>
     <filter-name>DruidWebStatFilter</filter-name>
     <filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class>
     <init-param>
         <param-name>exclusions</param-name>
         <param-value>*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*</param-value>
     </init-param>
 </filter>
 <filter-mapping>
     <filter-name>DruidWebStatFilter</filter-name>
     <url-pattern>/*</url-pattern>
 </filter-mapping>

Spring Boot 项目中是没有 WEB-INF/web.xml 的,但是我们可以在配置类中,通过 FilterRegistrationBean 将 WebStatFilter 注入到容器中,来开启 Druid 的 Web-JDBC 关联监控(包括 Web 监控、 URI 监控和 Session 监控等等)。

 /**
 * 向容器中添加 WebStatFilter
 * 开启内置监控中的 Web-jdbc 关联监控的数据
 * @return
 */
 @Bean
 public FilterRegistrationBean druidWebStatFilter() {
     WebStatFilter webStatFilter = new WebStatFilter();
     FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(webStatFilter);
     // 监控所有的访问
     filterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));
     // 监控访问不包括以下路径
     filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
     return filterRegistrationBean;
 }
通过starter整合Druid

目前为止 Spring Boot 官方只对 Hikari、Tomcat、Dbcp2 和 OracleUcp 等 4 种数据源产品提供了自动配置支持,对于其他的数据源连接池产品(包括 Druid),则并没有提供自动配置支持。

为了解决这一问题,于是阿里官方提供了 Druid Spring Boot Starter,它可以帮助我们在 Spring Boot 项目中,轻松地整合 Druid 的数据库连接池和监控功能。

引入 Druid Spring Boot Starter 依赖

在 Spring Boot 项目的 pom.xml 中添加以下依赖,引入 Druid Spring Boot Starter(点击查询最新版本

 <!--添加 druid 的 starter-->
 <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.17</version>
 </dependency>
配置属性

Druid Spring Boot Starter 已经将 Druid 数据源中的所有模块都进行默认配置,我们也可以通过 Spring Boot 配置文件(application.properties/yml)来修改 Druid 各个模块的配置,否则将使用默认配置。

在 Spring Boot 配置文件中配置以下内容:

  • JDBC 通用配置

  • Druid 数据源连接池配置

  • Druid 监控配置

  • Druid 内置 Filter 配置

JDBC 通用配置
   ################################################## JDBC 通用配置  ##########################################
 spring:
  datasource:
    username: root                                                                   #数据库登陆用户名
    password: root                                                                   #数据库登陆密码
    url: jdbc:mysql://127.0.0.1:3306/bianchengbang_jdbc                              #数据库url
    driver-class-name: com.mysql.cj.jdbc.Driver    
Druid 数据源连接池配置
   ################################################## Druid连接池的配置 ##########################################
 spring:
  datasource:
    druid:
      initial-size: 5                                                                 #初始化连接大小
      min-idle: 5                                                                     #最小连接池数量
      max-active: 20                                                                  #最大连接池数量
      max-wait: 60000                                                                 #获取连接时最大等待时间,单位毫秒
      time-between-eviction-runs-millis: 60000                                        #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      min-evictable-idle-time-millis: 300000                                          #配置一个连接在池中最小生存的时间,单位是毫秒
      validation-query: SELECT 1 FROM DUAL                                            #测试连接
      test-while-idle: true                                                           #申请连接的时候检测,建议配置为true,不影响性能,并且保证安全性
      test-on-borrow: false                                                           #获取连接时执行检测,建议关闭,影响性能
      test-on-return: false                                                           #归还连接时执行检测,建议关闭,影响性能
      pool-prepared-statements: false                                                 #是否开启PSCache,PSCache对支持游标的数据库性能提升巨大,oracle建议开启,mysql下建议关闭
      max-pool-prepared-statement-per-connection-size: 20                             #开启poolPreparedStatements后生效
      filters: stat,wall                                                              #配置扩展插件,常用的插件有=>stat:监控统计 wall:防御sql注入
      connection-properties: 'druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000' #通过connectProperties属性来打开mergeSql功能;慢SQL记录
Druid 监控配置
   ###################################################### Druid 监控配置信息  ##########################################
 spring:
  datasource:
    druid:
       # StatViewServlet配置,说明请参考Druid Wiki,配置_StatViewServlet配置
      stat-view-servlet:
        enabled: true                                                                 #是否开启内置监控页面,默认值为 false
        url-pattern: '/druid/*'                                                       #StatViewServlet 的映射路径,即内置监控页面的访问地址
        reset-enable: true                                                            #是否启用重置按钮
        login-username: admin                                                         #内置监控页面的登录页用户名 username
        login-password: admin                                                         #内置监控页面的登录页密码 password
 
       # WebStatFilter配置,说明请参考Druid Wiki,配置_配置WebStatFilter
      web-stat-filter:
        enabled: true                                                                 #是否开启内置监控中的 Web-jdbc 关联监控的数据
        url-pattern: '/*'                                                             #匹配路径
        exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'                     #排除路径
        session-stat-enable: true                                                     #是否监控session
 
       # Spring监控配置,说明请参考Druid Github Wiki,配置_Druid和Spring关联监控配置
      aop-patterns: net.biancheng.www.*                                               #Spring监控AOP切入点,如x.y.z.abc.*,配置多个英文逗号分隔
Druid 内置 Filter 配置

Druid Spring Boot Starter 对以下 Druid 内置 Filter,都提供了默认配置:

  • StatFilter

  • WallFilter

  • ConfigFilter

  • EncodingConvertFilter

  • Slf4jLogFilter

  • Log4jFilter

  • Log4j2Filter

  • CommonsLogFilter

我们可以通过 spring.datasource.druid.filters=stat,wall ... 的方式来启用相应的内置 Filter,不过这些 Filter 使用的都是默认配置。如果默认配置不能满足我们的需求,我们还可以在配置文件使用 spring.datasource.druid.filter.* 对这些 Filter 进行配置

 #  ####################################################### Druid 监控配置信息  ##########################################
 spring:
  datasource:
    druid:
      # 对配置已开启的 filters 即 stat(sql 监控) wall(防火墙)
      filter:
         #配置StatFilter (SQL监控配置)
        stat:
          enabled: true                                                               #开启 SQL 监控
          slow-sql-millis: 1000                                                       #慢查询
          log-slow-sql: true                                                          #记录慢查询 SQL
 
         #配置WallFilter (防火墙配置)
        wall:
          enabled: true                                                               #开启防火墙
          config:
            update-allow: true                                                        #允许更新操作
            drop-table-allow: false                                                   #禁止删表操作
            insert-allow:  true                                                       #允许插入操作
            delete-allow: true                                                        #删除数据操作

在配置 Druid 内置 Filter 时,需要先将对应 Filter 的 enabled 设置为 true,否则内置 Filter 的配置不会生效。

Druid Spring Boot Starter 并不是只支持以上属性,它支持 DruidDataSource 内所有具有 setter 方法的属性。

总结

无论是自定义整合还是通过 Druid Spring Boot Starter 整合,都能实现 Spring Boot 整合 Druid 数据源的目的,它们都各有利弊。

  • 根据官方文档,自定义整合 Druid 数据源能够更加清晰地了解 Druid 的各种功能及其实现方式,但整合过程繁琐。

  • 通过 Druid Spring Boot Starter 整合 Druid 数据源,则更加方便快捷,大大简化了整合过程,但无法清晰地了解 Druid 的功能内部的实现方式和原理。

这里,我们更加推荐使用 Druid Spring Boot Starter 进行整合,毕竟这种整合方式大大简化了整个整合的过程。

3.4.4、整合MyBatis

MyBatis 是一个半自动化的 ORM 框架,所谓半自动化是指 MyBatis 只支持将数据库查出的数据映射到 POJO 实体类上,而实体到数据库的映射则需要我们自己编写 SQL 语句实现,相较于Hibernate 这种完全自动化的框架,Mybatis 更加灵活,我们可以根据自身的需求编写 sql 语句来实现复杂的数据库操作。

(1)引入依赖
 <!--引入 mybatis-spring-boot-starter 的依赖-->
 <dependency>
     <groupId>org.mybatis.spring.boot</groupId>
     <artifactId>mybatis-spring-boot-starter</artifactId>
     <version>2.2.0</version>
 </dependency>
(2)配置 MyBatis

在 Spring Boot 的配置文件(application.properties/yml)中对 MyBatis 进行配置,例如指定 mapper.xml 的位置、实体类的位置、是否开启驼峰命名法等等

 ###################################### MyBatis 配置######################################
 mybatis:
  mapper-locations: classpath:mybatis/mapper/*.xml  #指定 mapper.xml 的位置,classpath=/resource
  type-aliases-package: net.biancheng.www.bean      #扫描实体类的位置,在此处指明扫描实体类的包,在 mapper.xml 中就可以不写实体类的全路径名
  configuration:
    map-underscore-to-camel-case: true              #默认开启驼峰命名法,可以不用设置该属性

注意:使用 MyBatis 时,必须配置数据源信息,例如数据库 URL、数据库用户型、数据库密码和数据库驱动等。

(3)创建实体类
(4)创建 Mapper 接口

当 mapper 接口较多时,我们可以在 Spring Boot 主启动类上使用 @MapperScan 注解扫描指定包下的 mapper 接口,而不再需要在每个 mapper 接口上都标注 @Mapper 注解。

 //##############Springboot启动类#################
 @SpringBootApplication
 // 扫描指定包下的mapper接口
 @MapperScans(@MapperScan({"com.turing.onebox.admin.mapper", "com.turing.onebox.home.mapper"}))
 public class OneboxApplication {
 
     public static void main(String[] args) {
         SpringApplication.run(OneboxApplication.class, args);
    }
 
 }
(5)创建 Mapper 映射文件

使用 Mapper 进行开发时,需要遵循以下规则:

  • mapper 映射文件中 namespace 必须与对应的 mapper 接口的完全限定名一致。

  • mapper 映射文件中 statement 的 id 必须与 mapper 接口中的方法的方法名一致

  • mapper 映射文件中 statement 的 parameterType 指定的类型必须与 mapper 接口中方法的参数类型一致。

  • mapper 映射文件中 statement 的 resultType 指定的类型必须与 mapper 接口中方法的返回值类型一致。

 <!--mapper映射文件 UserMapper.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接口的存放位置-->
 <mapper namespace="com.turing.onebox.admin.mapper.UserMapper">
 
     <select id="selectUserByUsernameAndPwd" resultType="com.turing.onebox.common.model.dto.User">
        select * from config_user where username = #{username} and password = #{password}
     </select>
   
 </mapper>
 //########## mapper接口 src/main/java/com/turing/onebox/admin/mapper/UserMapper.java #############
 @Repository
 // 在启动类中设置了mapperscan,这里可以不需要@Mapper注解
 public interface UserMapper{
     User selectUserByUsernameAndPwd(@Param("username") String username, @Param("password") String password);
 }
(5)注解方式

MyBatis 针对实际实际业务中使用最多的“增删改查”操作,分别提供了以下注解来替换 mapper 映射文件,简化配置:

  • @Select

  • @Insert

  • @Update

  • @Delete

 @Mapper
 public interface UserMapper {
     @Select("select * from user where user_name = #{userName,jdbcType=VARCHAR} and password = #{password,jdbcType=VARCHAR}")
     List<User> getByUserNameAndPassword(User user);
 
     @Delete("delete from user where id = #{id,jdbcType=INTEGER}")
     int deleteByPrimaryKey(Integer id);
 
     @Insert("insert into user ( user_id, user_name, password, email)" +
             "values ( #{userId,jdbcType=VARCHAR}, #{userName,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR}, #{email,jdbcType=VARCHAR})")
     int insert(User record);
 
 
     @Update(" update user" +
             "   set user_id = #{userId,jdbcType=VARCHAR},\n" +
             "     user_name = #{userName,jdbcType=VARCHAR},\n" +
             "     password = #{password,jdbcType=VARCHAR},\n" +
             "     email = #{email,jdbcType=VARCHAR}\n" +
             "   where id = #{id,jdbcType=INTEGER}")
     int updateByPrimaryKey(User record);
 }

mapper 接口中的任何一个方法,都只能使用一种配置方式,即注解和 mapper 映射文件二选一,但不同方法之间,这两种方式则可以混合使用,例如方法 1 使用注解方式,方法 2 使用 mapper 映射文件方式。

 

我们可以根据 SQL 的复杂程度,选择不同的方式来提高开发效率。

  • 如果没有复杂的连接查询,我们可以使用注解的方式来简化配置;

  • 如果涉及的 sql 较为复杂时,则使用 XML (mapper 映射文件)的方式更好一些。

3.4.5、整合MyBatis-Plus

详见MyBatis-Plus笔记中1.4节配置

3.4.6、整合NoSQL

 

 

3.4.7、添加内存数据库H2

什么是H2内存数据库

H2是一个用Java开发的嵌入式数据库,它本身只是一个类库,可以直接嵌入到应用项目中。

H2内存数据库官方网站

 

H2有哪些用途?

  • H2最大的用途在于可以同应用程序打包在一起发布,这样可以非常方便地存储少量结构化数据。

  • 它的另一个用途是用于单元测试。启动速度快,而且可以关闭持久化功能,每一个用例执行完随即还原到初始状态。

  • H2的第三个用处是作为缓存,作为NoSQL的一个补充。当某些场景下数据模型必须为关系型,可以拿它当Memcached使,作为后端MySQL/Oracle的一个缓冲层,缓存一些不经常变化但需要频繁访问的数据,比如字典表、权限表。不过这样系统架构就会比较复杂了。

 

H2的产品优势?

  • 纯Java编写,不受平台的限制;

  • 只有一个jar文件,适合作为嵌入式数据库使用;

  • h2提供了一个十分方便的web控制台用于操作和管理数据库内容;

  • 功能完整,支持标准SQL和JDBC。麻雀虽小五脏俱全;

  • 支持内嵌模式、服务器模式和集群。

JDBC, ORM, JPA

JDBC(JavaDataBase Connectivity)就是Java数据库连接,说白了就是用Java语言来操作数据库。原来我们操作数据库是在控制台使用SQL语句来操作数据库,JDBC是用Java语言向数据库发送SQL语句。

ORM(Object Relational Mapping)是对象关系映射,简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将java程序中的对象自动持久化到关系数据库中。本质上就是将数据从一种形式转换到另外一种形式。

  • 具体映射:

    1. 数据库的表(table) --> 类(class)

    2. 记录(record,行数据)--> 对象(object)

    3. 字段(field)--> 对象的属性(attribute)

JPA是Spring提供的一种ORM,Spring Data基于Spring runtime体系,JPA 属于Spring Data,JPA和JDBC的关系如下:

mongo-x-usage-spring-5

 

H2和JPA的使用案例

使用H2存放用户表,并通过JPA操作用户数据。

  1. 添加依赖

 <dependency>
     <groupId>com.h2database</groupId>
     <artifactId>h2</artifactId>
     <scope>runtime</scope>
 </dependency>
 
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-jpa</artifactId>
 </dependency>
  1. 配置H2和JPA注入参数

 spring:
  datasource:
    data: classpath:db/data.sql
    driverClassName: org.h2.Driver
    password: sa
    platform: h2
    url: jdbc:h2:mem:dbtest
    username: sa
  sql:
    init:
      data-locations: classpath:db/data-h2.sql
      schema-locations: classpath:db/schema-h2.sql
  h2:
    console:
      enabled: true
      path: /h2
      settings:
        web-allow-others: true
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
  1. 配置数据库的表结构resources/db/schema.sql和数据文件resources/db/data.sql

 create table if not exists tb_user (
 USER_ID int not null primary key auto_increment,
 USER_NAME varchar(100)
 );
 INSERT INTO tb_user (USER_ID,USER_NAME) VALUES(1,'赵一');
  1. 实体关联表

给实体类添加@Entity注解(声明实体类)和@Table注解(指定对应的表格)

 @Entity
 @Table(name = "tb_user")
 public class User {
 
     @Id
     private int userId;
     private String userName;
 
     public int getUserId() {
         return userId;
    }
 
     public void setUserId(int userId) {
         this.userId = userId;
    }
 
     public String getUserName() {
         return userName;
    }
 
     public void setUserName(String userName) {
         this.userName = userName;
    }
 }
  1. Dao继承JpaRepository(国内常用MyBatis-Plus)

 @Repository
 public interface UserRepository extends JpaRepository<User, Integer> {
 
 }

JpaRepository继承了接口PagingAndSortingRepository和QueryByExampleExecutor。而PagingAndSortingRepository又继承CrudRepository。因此,JpaRepository接口同时拥有了基本CRUD功能以及分页功能。

  • CrudRepository<T, ID>提供的方法:

功能方法
保存一个实体 <S extends T> S save(S entity);
保存提供的所有实体 <S extends T> Iterable<S> saveAll(Iterable<S> entities);
根据id查询对应的实体 Optional<T> findById(ID id);
根据id查询对应的实体是否存在 boolean existsById(ID id);
查询所有的实体 Iterable<T> findAll();
根据给定的id集合查询所有对应的实体,返回实体集合 Iterable<T> findAllById(Iterable<ID> ids);
统计现存实体的个数 long count();
根据id删除对应的实体 void deleteById(ID id);
删除给定的实体 void delete(T entity);
删除给定的实体集合 void deleteAll(Iterable<? extends T> entities);
删除所有的实体 void deleteAll();
  • PagingAndSortingRepository<T, ID>提供的方法:

功能方法
返回所有的实体,根据Sort参数提供的规则排序 Iterable<T> findAll(Sort sort);
返回一页实体,根据Pageable参数提供的规则进行过滤 Page<T> findAll(Pageable pageable);
  • JpaRepository<T, ID>提供的方法

功能方法
将所有未决的更改刷新到数据库 void flush();
保存一个实体并立即将更改刷新到数据库 <S extends T> S saveAndFlush(S entity);
在一个批次中删除给定的实体集合,这意味着将产生一条单独的Query void deleteInBatch(Iterable<T> entities);
在一个批次中删除所有的实体 void deleteAllInBatch();
根据给定的id标识符,返回对应实体的引用 T getOne(ID id);
  • QueryByExampleExecutor<T>:按“实例”查询,详见源码

 

H2数据库的使用方式
  • 嵌入式模式

在嵌入式模式下,应用程序使用JDBC从同一JVM中打开数据库。这是最快也是最容易的连接方式。缺点是数据库可能只在任何时候在一个虚拟机(和类加载器)中打开。与所有模式一样,支持持久性和内存数据库。对并发打开数据库的数量或打开连接的数量没有限制。

springboot-hello-h2-2

  • 服务器模式

当使用服务器模式(有时称为远程模式或客户机/服务器模式)时,应用程序使用 JDBC 或 ODBC API 远程打开数据库。服务器需要在同一台或另一台虚拟机上启动,或者在另一台计算机上启动。许多应用程序可以通过连接到这个服务器同时连接到同一个数据库。在内部,服务器进程在嵌入式模式下打开数据库。

服务器模式比嵌入式模式慢,因为所有数据都通过TCP/IP传输。与所有模式一样,支持持久性和内存数据库。对每个服务器并发打开的数据库数量或打开连接的数量没有限制。

springboot-hello-h2-3

  • 混合模式

混合模式是嵌入式和服务器模式的结合。连接到数据库的第一个应用程序在嵌入式模式下运行,但也启动服务器,以便其他应用程序(在不同进程或虚拟机中运行)可以同时访问相同的数据。本地连接的速度与数据库在嵌入式模式中的使用速度一样快,而远程连接速度稍慢。

服务器可以从应用程序内(使用服务器API)启动或停止,或自动(自动混合模式)。当使用自动混合模式时,所有想要连接到数据库的客户端(无论是本地连接还是远程连接)都可以使用完全相同的数据库URL来实现。

springboot-hello-h2-4

H2数据库用于单元测试

https://pdai.tech/md/develop/ut/dev-ut-springboot2.html

 

3.5、单元测试

3.5.1、JUnit5

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

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的测试引擎。

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

 <!--自行引入vintage-->
 <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只需要引入spring-boot-starter-test:

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

SpringBoot整合Junit以后:

  • 编写测试方法:@Test标注(注意需要使用junit5版本的注解)

  • Junit类具有Spring的功能,@Autowired、比如 @Transactional 标注测试方法,测试完成后自动回滚

注意:测试类要和SpringBoot主启动类放在同样的来自源根的路径下,否则会报错 Unable to find a @SpringBootConfiguration

3.5.2、JUnit5常用注解

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

例子:

 import org.junit.jupiter.api.Test; //注意这里使用的是jupiter的Test注解!!
 
 
 public class TestDemo {
 
   @Test
   @DisplayName("第一次测试")
   public void firstTest() {
       System.out.println("hello world");
  }

 

3.5.3、断言

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

简单断言

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

方法说明
assertEquals 判断两个对象或两个原始类型是否相等
assertNotEquals 判断两个对象或两个原始类型是否不相等
assertSame 判断两个对象引用是否指向同一个对象
assertNotSame 判断两个对象引用是否指向不同的对象
assertTrue 判断给定的布尔值是否为 true
assertFalse 判断给定的布尔值是否为 false
assertNull 判断给定的对象引用是否为 null
assertNotNull 判断给定的对象引用是否不为 null
 @Test
 @DisplayName("simple assertion")
 public void simple() {
      assertEquals(3, 1 + 2, "simple math");
      assertNotEquals(3, 1 + 1);
 
      assertNotSame(new Object(), new Object());
      Object obj = new Object();
      assertSame(obj, obj);
 
      assertFalse(1 > 2);
      assertTrue(1 < 2);
 
      assertNull(null);
      assertNotNull(new Object());
 }
 // 以上所有断言都正确
数组断言

通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等

 @Test
 @DisplayName("array assertion")
 public void array() {
  assertArrayEquals(new int[]{1, 2}, new int[] {1, 2});
 }
组合断言

assertAll 方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言

 @Test
 @DisplayName("assert all")
 public void all() {
     assertAll("Math",
        () -> assertEquals(2, 1 + 1),
        () -> assertTrue(1 > 0)
    );
 }
异常断言

在JUnit4时期,想要测试方法的异常情况时,需要用@Rule注解的ExpectedException变量还是比较麻烦的。而JUnit5提供了一种新的断言方式Assertions.assertThrows() ,配合函数式编程就可以进行使用。

 @Test
 @DisplayName("异常测试")
 public void exceptionTest() {
     ArithmeticException exception = Assertions.assertThrows(
            //扔出断言异常
             ArithmeticException.class, () -> System.out.println(1 % 0));
 
 }
超时断言

Junit5还提供了Assertions.assertTimeout() 为测试方法设置了超时时间

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

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

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

3.5.4、前置条件

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

 @DisplayName("前置条件")
 public class AssumptionsTest {
  private final String environment = "DEV";
 
  @Test
  @DisplayName("simple")
  public void simpleAssume() {
     assumeTrue(Objects.equals(this.environment, "DEV"));
     assumeFalse(() -> Objects.equals(this.environment, "PROD"));
  }
 
  @Test
  @DisplayName("assume then do")
  public void assumeThenDo() {
     assumingThat(
        Objects.equals(this.environment, "DEV"),
        () -> System.out.println("In DEV")
    );
  }
 }

 

assumeTrue 和 assumFalse 确保给定的条件为 true 或 false,不满足条件会使得测试执行终止。

assumingThat 的参数是表示条件的布尔值和对应的 Executable 接口的实现对象。只有条件满足时,Executable 对象才会被执行;当条件不满足时,测试执行并不会终止。

 

3.5.5、嵌套测试

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() {
             assertTrue(stack.isEmpty());
        }
 
         @Test
         @DisplayName("throws EmptyStackException when popped")
         void throwsExceptionWhenPopped() {
             assertThrows(EmptyStackException.class, stack::pop);
        }
 
         @Test
         @DisplayName("throws EmptyStackException when peeked")
         void throwsExceptionWhenPeeked() {
             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() {
                 assertFalse(stack.isEmpty());
            }
 
             @Test
             @DisplayName("returns the element when popped and is empty")
             void returnElementWhenPopped() {
                 assertEquals(anElement, stack.pop());
                 assertTrue(stack.isEmpty());
            }
 
             @Test
             @DisplayName("returns the element when peeked but remains not empty")
             void returnElementWhenPeeked() {
                 assertEquals(anElement, stack.peek());
                 assertFalse(stack.isEmpty());
            }
        }
    }
 }

 

3.5.6、参数化测试

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

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

参数化测试注解功能
@ValueSource 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
@NullSource 表示为参数化测试提供一个null的入参
@EnumSource 表示为参数化测试提供一个枚举入参
@CsvFileSource 表示读取指定CSV文件内容作为参数化测试入参
@MethodSource 表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)

参数化测试的强大在于:只需要去实现ArgumentsProvider接口,任何外部文件(如:CSV,YML,JSON 文件甚至方法的返回值)都可以作为它的入参。

 @ParameterizedTest
 @ValueSource(strings = {"one", "two", "three"})
 @DisplayName("参数化测试1")
 public void parameterizedTest1(String string) {
     System.out.println(string);
     Assertions.assertTrue(StringUtils.isNotBlank(string));
 }
 
 
 @ParameterizedTest
 @MethodSource("method")    //指定方法名
 @DisplayName("方法来源参数")
 public void testWithExplicitLocalMethodSource(String name) {
     System.out.println(name);
     Assertions.assertNotNull(name);
 }
 
 static Stream<String> method() {
     return Stream.of("apple", "banana");
 }

 

3.5.7、迁移指南

在进行迁移的时候需要注意如下的变化:

  • 注解在 org.junit.jupiter.api 包中,断言在 org.junit.jupiter.api.Assertions 类中,前置条件在 org.junit.jupiter.api.Assumptions 类中。

  • 把@Before 和@After 替换成@BeforeEach 和@AfterEach。

  • 把@BeforeClass 和@AfterClass 替换成@BeforeAll 和@AfterAll。

  • 把@Ignore 替换成@Disabled。

  • 把@Category 替换成@Tag。

  • 把@RunWith、@Rule 和@ClassRule 替换成@ExtendWith。

 

3.6、指标监控

 

3.7、原理解析

 
posted @ 2022-08-31 01:55  Colin13  阅读(89)  评论(0编辑  收藏  举报