准备

Spring

  • Spring 是一个开源框架,2003 年兴起的一个轻量级的 Java 开发框架,作者:Rod Johnson 。

  • Spring 是为了解决企业级应用开发的复杂性而创建的,简化开发。

  • 为了降低 Java 开发的复杂性,Spring 采用了以下 4 种关键策略:

    1. 基于 POJO 的轻量级和最小侵入性编程,所有东西都是 bean;
    2. 通过 IOC,依赖注入(DI)和面向接口实现松耦合;
    3. 基于切面(AOP)和惯例进行声明式编程;
    4. 通过切面和模版减少样式代码,RedisTemplate,xxxTemplate;

SpringBoot

  • 学过 javaweb 的同学就知道,开发一个 web 应用,从最初开始接触 Servlet 结合 Tomcat, 跑出一个 Hello Wolrld 程序,是要经历特别多的步骤;后来就用了框架 Struts,再后来是 SpringMVC,到了现在的 SpringBoot,过一两年又会有其他 web 框架出现;框架不断的演进,然后自己开发项目所有的技术也在不断的变化.

  • SpringBoot 就是一个 javaweb 的开发框架,和 SpringMVC 类似,对比其他 javaweb 框架的好处,官方说是简化开发,约定大于配置, you can “just run”,能迅速的开发 web 应用,几行代码开发一个 http 接口。

  • 所有的技术框架的发展似乎都遵循了一条主线规律:从一个复杂应用场景衍生一种规范框架,人们只需要进行各种配置而不需要自己去实现它,这时候强大的配置功能成了优点;发展到一定程度之后,人们根据实际生产应用情况,选取其中实用功能和设计精华,重构出一些轻量级的框架;之后为了提高开发效率,嫌弃原先的各类配置过于麻烦,于是开始提倡“约定大于配置”,进而衍生出一些一站式的解决方案。这就是 Java 企业级应用-> J2EE-> spring-> springboot 的过程。

  • 随着 Spring 不断的发展,涉及的领域越来越多,项目整合开发需要配合各种各样的文件,慢慢变得不那么易用简单,违背了最初的理念,甚至人称配置地狱。

  • Spring Boot 正是在这样的一个背景下被抽象出来的开发框架,目的为了让大家更容易的使用 Spring . 更容易的集成各种常用的中间件或开源软件;

  • Spring Boot 基于 Spring 开发,Spirng Boot 本身并不提供 Spring 框架的核心特性以及扩展功能,只是用于快速. 敏捷地开发新一代基于 Spring 框架的应用程序。也就是说,它并不是用来替代 Spring 的解决方案,而是和 Spring 框架紧密结合用于提升 Spring 开发者体验的工具。

  • Spring Boot 以 约定大于配置的核心思想,默认帮我们进行了很多设置,多数 Spring Boot 应用只需要很少的 Spring 配置。同时它集成了大量常用的第三方库配置(例如 Redis. MongoDB. Jpa. RabbitMQ. Quartz 等等),Spring Boot 应用中这些第三方库几乎可以零配置的开箱即用。简单来说就是 SpringBoot 其实不是什么新的框架,它默认配置了很多框架的使用方式,就像 maven 整合了所有的 jar 包,spring boot 整合了所有的框架 。

Spring Boot 优点

  • 为所有 Spring 开发者更快的入门
  • 开箱即用,提供各种默认配置来简化项目配置
  • 内嵌式容器简化 Web 项目
  • 没有冗余代码生成和 XML 配置的要求

HelloWorld

  • 下面将快速的创建一个 Spring Boot 应用,并且实现一个简单的 Http 请求处理。

  • 通过这个例子对 Spring Boot 有一个初步的了解,并体验其结构简单. 开发快速的特性。

  • 环境准备:

    • java version “1.8.0_181”
    • Maven-3.6.1
    • SpringBoot 2.x
    • 开发工具:IDEA

创建基础项目

方式一

  • 使用 Spring Initializr 的 Web 页面创建项目

    1. 打开 https://start.spring.io/

    2. 填写项目信息

    3. 点击”Generate Project“按钮生成项目;下载此项目

    4. 解压项目包,并用 IDEA 以 Maven 项目导入,一路下一步即可,直到项目导入完毕。

    5. 如果是第一次使用,可能速度会比较慢,包比较多. 需要耐心等待一切就绪。

方式二

  1. 创建一个 IDEA 新项目 Maven 项目

  2. 专业版可以选择 spring initalizr , 可以看到默认就是去官网的快速构建工具那里实现

  3. 填写项目信息

  4. 选择初始化的组件(初学勾选 Web 即可)

  5. 填写项目路径

  6. 等待项目构建成功

  • 通过上面步骤完成了基础项目的创建。就会自动生成以下文件。

    1. 程序的主启动类

    2. 一个 application.properties 配置文件

    3. 一个 测试类

    4. 一个 pom.xml

pom.xml 分析

  • 打开 pom.xml,看看 Spring Boot 项目的依赖:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    <!-- 父依赖 -->
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.5.RELEASE</version>
    <relativePath/>
    </parent>

    <dependencies>
    <!-- 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>
    <!-- 剔除依赖 -->
    <exclusions>
    <exclusion>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    </exclusion>
    </exclusions>
    </dependency>
    </dependencies>

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

编写一个 http 接口

  1. 在主程序的同级目录下,新建一个 controller 包,一定要在同级目录下,默认扫描启动类下的子包。

  2. 在包中新建一个 HelloController 类

    1
    2
    3
    4
    5
    6
    @RestControllerpublic class HelloController {
    @RequestMapping("/hello")
    public String hello() {
    return "Hello World";
    }
    }
  3. 编写完毕后,从主程序启动项目,浏览器发起请求,看页面返回;控制台输出了 Tomcat 访问的端口号!

将项目打成 jar 包

  • 点击 maven 的 package

  • 发现 IDEA 打包时,会跑测试用例,跑不过,就会打包失败报错。如果遇到以上错误,可以配置打包时跳过项目运行测试用例,也可也配置 mvn package -Dmaven.test.skip=true

    • -DskipTests,不执行测试用例,但编译测试用例类生成相应的 class 文件至 target/test-classes 下

    • -Dmaven.test.skip=true,不执行测试用例,也不编译测试用例类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      <!--
      在工作中,很多情况下我们打包是不想执行测试用例的
      可能是测试用例不完事,或是测试用例会影响数据库数据
      跳过测试用例执
      -->
      <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-surefire-plugin</artifactId>
      <configuration>
      <!--跳过项目运行测试用例-->
      <skipTests>true</skipTests>
      </configuration>
      </plugin>
  • 如果打包成功,则会在 target 目录下生成一个 jar 包, jar 包后,就可以在任何地方运行了

  • 官方文档

  • 更改启动时显示的 banner 图案: 到项目下的 resources 目录下新建一个 banner.txt 即可。

  • 图案生成网站,生产后拷贝到文件中即可

    • https://www.bootschool.net/ascii

    • http://patorjk.com/software/taag

    • http://www.network-science.de/ascii/

    • http://www.degraeve.com/img2txt.php

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      # banner.txt的内容:


      ////////////////////////////////////////////////////////////////////
      // _ooOoo_ //
      // o8888888o //
      // 88" . "88 //
      // (| ^_^ |) //
      // O\ = /O //
      // ____/`---'\____ //
      // .' \\| |// `. //
      // / \\||| : |||// \ //
      // / _||||| -:- |||||- \ //
      // | | \\\ - /// | | //
      // | \_| ''\---/'' | | //
      // \ .-\__ `-` ___/-. / //
      // ___`. .' /--.--\ `. . ___ //
      // ."" '< `.___\_<|>_/___.' >'"". //
      // | | : `- \`.;`\ _ /`;.`/ - ` : | | //
      // \ \ `-. \_ __\ /__ _/ .-` / / //
      // ========`-.____`-.___\_____/___.-`____.-'======== //
      // `=---=' //
      // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //
      // 佛祖保佑 永不宕机 永无BUG   //
      ////////////////////////////////////////////////////////////////////

关闭 banner

  • 设置 setBannerMode

    1
    2
    3
    4
    5
    public static void main(String[] args) {
    SpringApplication app = new SpringApplication(MySpringConfiguration.class);
    app.setBannerMode(Banner.Mode.OFF);
    app.run(args);
    }

配置文件

  • 官方配置文件说明

  • 配置文件的作用 : 修改 SpringBoot 自动配置的默认值 (SpringBoot 在底层已经配置好的属性)。

  • SpringBoot 使用一个全局的配置文件 , 配置文件名称是固定的.

    • application.properties: key=value
    • application.yml: key:空格 value
  • 注意:如果 yml 和 properties 同时都配置,并且没有激活其他环境 , 默认会使用 properties 配置文件

  • 比如我们可以在配置文件中修改 Tomcat 默认启动的端口号!

    1
    server.port=8081
  • 在代码里指定配置文件

    1
    2
    3
    4
    5
    6
    7
    @SpringBootApplication
    @PropertySource(value={"file:config.properties"})
    public class SpringbootrestdemoApplication {
    public static void main(String[] args) {
    SpringApplication.run(SpringbootrestdemoApplication.class, args);
    }
    }

yaml 概述

  • YAML 是 “YAML Ain’t a Markup Language” (YAML 不是一种标记语言)的递归缩写。

  • 在开发的这种语言时,YAML 的意思其实是:”Yet Another Markup Language”(仍是一种标记语言), 这种语言以数据为中心,而不是以标记语言为重点

  • 以前的配置文件,大多数都是使用 xml 来配置;比如一个简单的端口配置,使用 yaml 标记数据更轻便

    1
    2
    3
    4
    5
    6
    7
    8
    # 传统xml配置:
    <server>
    <port>8081<port>
    </server>

    # yaml配置:
    server:
    port: 8081

yaml 基础语法

  • YAML 语法要求严格!
  1. 空格不能省略
  2. 以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。
  3. 属性和值的 大小写十分敏感 的。

字面量:

  • 普通的值: 数字,布尔值,字符串

  • 字面量直接写在后面就可以 , 字符串默认不用加上双引号或者单引号

  • 注意:

    1. “ ” 双引号,不会转义字符串里面的特殊字符 , 特殊字符会作为本身想表示的意思;
      • 比如 :name: "张 \n 三" ,结果会输出二行
    2. '' 单引号,会转义特殊字符 , 特殊字符最终会变成和普通字符一样输出
      • 比如 :name: "张 \n 三’ 输出 :张 \n 三

对象、Map

  • 在下一行来写对象的属性和值得关系,注意缩进;比如:

    1
    2
    3
    student:
    name: qinjiang
    age: 3
  • 行内写法

    1
    student: {name: qinjiang,age: 3}

数组

  • - 值表示数组中的一个元素, 比如:

    1
    2
    3
    4
    pets:
    - cat
    - dog
    - pig
  • 行内写法

    1
    pets: [cat,dog,pig]
  • properties 配置文件写法

    1
    2
    com.code.services[0]=service1
    com.code.services[1]=service2

@value 指定值

  1. 使用 @Value 给 bean 注入属性值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package top.fulsun.helloworld.pojo;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    @Component//注册 bean 到容器中
    public class Dog {
    @Value("旺旺")
    private String name;
    @Value("2")
    private Integer age;
    //有参无参构造、get、set 方法、toString()方法
    }
  2. 在 SpringBoot 的测试类下注入狗狗输出一下;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @SpringBootTest
    class HelloworldApplicationTests {
    @Autowired
    // 多个 Dog 示例时候 @Qualifier("name")注解来指明注入的实例
    //将狗狗自动注入进来
    private Dog dog;

    @Test
    void contextLoads() {
    //打印输出
    System.out.println(dog);
    }

    }
    • 结果成功输出,@Value 注入成功,这是我们原来的办法对吧。

注入 yaml 的值

  • yaml 文件更强大的地方在于,他可以给我们的 实体类直接注入匹配值,类的字段必须有公共 setter 方法
  1. 在 springboot 项目中的 resources 目录下新建一个文件 application.yml

  2. 编写一个实体类 Dog;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package top.fulsun.helloworld.pojo;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;

    @Component//注册 bean 到容器中
    public class Dog {
    private String name;
    private Integer age;
    //有参无参构造、get、set 方法、toString()方法
    }
  3. 我们在编写一个复杂一点的实体类:Person 类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Component //注册 bean 到容器中
    public class Person {
    private String name;
    private Integer age;
    private Boolean isHappy;
    private Date birth;
    private Map<String,Object> map;
    private List<Object> lists;
    private Dog dog;
    //有参无参构造、get、set 方法、toString()方法
    }
  4. 我们来使用 yaml 配置的方式进行注入,编写一个 yaml 配置!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    person:
    name: fulsun
    age: 3
    happy: false
    birth: 2020/02/02
    lists:
    - eat
    - sleep
    - code
    dog:
    name: 旺旺
    age: 2
    map: { k1: v1, k2: v2 }
  5. 我们刚才已经把 person 这个对象的所有值都写好了,我们现在来注入到我们的类中!

    • @ConfigurationProperties 作用:将配置文件中配置的每一个属性的值,映射到这个组件中;
    • prefix = “person” : 将配置文件中的 person 下面的所有属性一一对应
    • 配置文件的 key 值 和 属性的值设置为不一样,则结果会输出为 null,注入失败
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Component //注册 bean
    @ConfigurationProperties(prefix = "person")
    public class Person {
    private String name;
    private Integer age;
    private Boolean isHappy;
    private Date birth;
    private Map<String,Object> map;
    private List<Object> lists;
    private Dog dog;
    }
  6. 更具 IDEA 提示,springboot 配置注解处理器没有找到,可以查看文档,找到一个依赖!

    • 文档地址

    • 导入配置文件处理器

      1
      2
      3
      4
      5
      6
      <!-- 导入配置文件处理器,配置文件进行绑定就会有提示,需要重启 -->
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-configuration-processor</artifactId>
      <optional>true</optional>
      </dependency>
  7. 确认以上配置都 OK 之后,我们去测试类中测试一下:

    • 新建测试类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      @SpringBootTest
      class HelloworldApplicationTests {
      @Autowired
      Person person; //将 person 自动注入进来
      @Test
      public void contextLoads() {
      System.out.println(person); //打印 person 信息
      }

      }
    • 结果:所有值全部注入成功!

配置文件处理器

  • Spring Boot Configuration Processor 会完成自动补全

  • 当我们在 application.properties 和 application.yml 中写配置的时候会有自动提醒, 重新 build 项目之后,configuration processor 会为我们在 META-INF 下创建一个 JSON 文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    {
    "groups": [
    {
    "name": "com.code",
    "type": "top.fulsun.test.FooProperties",
    "sourceType": "top.fulsun.test.FooProperties"
    }
    ],
    "properties": [
    {
    "name": "com.code.age",
    "type": "java.lang.Integer",
    "sourceType": "top.fulsun.test.FooProperties"
    },
    {
    "name": "com.code.foo",
    "type": "java.lang.String",
    "sourceType": "top.fulsun.test.FooProperties"
    },
    {
    "name": "com.code.name",
    "type": "java.lang.String",
    "sourceType": "top.fulsun.test.FooProperties"
    }
    ],
    "hints": []
    }
  • configuration processor 允许我们标记某一个属性为 deprecated, 我们可以通过添加 @DeprecatedConfigurationProperty 注解到字段的 getter 方法上,来标示该字段为 deprecated,重新 build 项目后,当我们再编写配置文件时,已经给出了明确 deprecated 提示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    {
    "name": "com.code.name",
    "type": "java.lang.String",
    "sourceType": "top.fulsun.test.FooProperties",
    "deprecated": true,
    "deprecation": {
    "reason": "change name ",
    "replacement": "none"
    }
    }

激活 @ConfigurationProperties

  • 需要让 Spring 知道我们的 @ConfigurationProperties 类存在,以便将其加载到应用程序上下文中

  • 我们可以通过下面几种方式将其添加到应用上下文中

    1. 可以通过添加 @Component 注解让 Component Scan 扫描到,只有当类所在的包被 Spring @ComponentScan 注解扫描到才会生效,默认情况下,该注解会扫描在主应用类下的所有包结构

    2. 我们也可以通过 Spring 的 Java Configuration 特性实现同样的效果:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      @Configuration
      public class PropertiesConfig {
      @Bean
      public FooProperties fooProperties(){
      return new FooProperties();
      }

      }

    3. 可以使用 @EnableConfigurationProperties 注解让我们的类被 Spring Boot 所知道,在该注解中其实是用了 @Import(EnableConfigurationPropertiesImportSelector.class) 实现

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      @EnableConfigurationProperties(Person.class) // 这里一定要开启配置生效
      @SpringBootApplication
      public class DemoApplication {
      @Autowired // 需要从容器中获取并注入此类
      private Person person;

      public static void main(String[] args) {
      SpringApplication.run(DemoApplication.class, args);
      }
      }
  • 激活一个 @ConfigurationProperties 类的最佳方式是什么?

    1. 所有上述方法都同样有效。建议模块化你的应用程序, 每个模块提供自己的 @ConfigurationProperties 类,只提供它需要的属性。这使得在不影响其他模块的情况下重构一个模块中的属性变得容易。
    2. 不建议在应用程序类本身上使用 @EnableConfigurationProperties,是在特定于模块的 @Configuration 类上使用 @EnableConfigurationProperties,该类也可以利用包私有的可见性对应用程序的其余部分隐藏属性。

注意

  • 属性配置错误的值时, 如定义 boolean,注入时配置文件填写为 string 类型,不希望 Spring Boot 应用启动失败,我们可以设置 ignoreInvalidFields 属性为 true (默认为 false)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @ConfigurationProperties(prefix = "com.code",ignoreInvalidFields = true)
    public class FooProperties {
    private String foo;
    private String name;
    private Integer age;
    // 出现问题 Spring Boot 将会设置 enabled 字段为我们在 Java 代码里设定好的默认值。
    // 如果我们没有设置默认值,enabled 将为 null,
    private Boolean enable = Boolean.TRUE;
    }
  • application.properties 文件提供了多余属性

    • 默认情况下,Spring Boot 会忽略那些不能绑定到 @ConfigurationProperties 类字段的属性

    • 然而,当配置文件中有一个属性实际上没有绑定到 @ConfigurationProperties 类时,我们可能希望启动失败。

    • 为了实现上述情况,我们仅需要将 ignoreUnknownFields 属性设置为 false (默认是 true)

      1
      @ConfigurationProperties(prefix = "com.code",ignoreInvalidFields = true,ignoreUnknownFields = true)
    • ignoreUnknownFields 未来 Spring Boot 的版本中会被标记为 deprecated.因为我们可能有两个带有 @ConfigurationProperties 的类,同时绑定到了同一个命名空间 (namespace) 上,其中一个类可能知道某个属性,另一个类却不知道某个属性,这样就会导致启动失败.

文件占位符

  • 配置文件还可以编写占位符生成随机数

随机数

  1. ${random.value}
  2. ${random.int}
  3. ${random.long}
  4. ${random.int(10)}
  5. ${random.int[1024,65536]}

获取之前配置的值

  • 如果没有可以指定默认值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    person:
    name: fulsun${random.uuid}
    age: ${random.int}
    happy: false
    birth: 2020/02/02
    lists:
    - eat
    - sleep
    - code
    dog:
    name: ${person.hello:旺旺}_dog # 没有person.hello就使用旺旺
    age: 2
    map: { k1: v1, k2: v2 }

properties 文件注入

  • 我们上面采用的 yaml 方法都是最简单的方式,开发中最常用的;也是 springboot 所推荐的!

  • 配置文件除了 yml 还有我们之前常用的 properties

  • 注意: properties 配置文件在写中文的时候,会有乱码 , 我们需要去 IDEA 中 settings–> FileEncodings 中配置编码格式为 UTF-8

  1. 新建一个实体类 User

    1
    2
    3
    4
    5
    6
    7
    8
    @Component //注册 bean
    public class User {
    private String name;
    private int age;
    private String sex;

    //......
    }
  2. 编辑配置文件 user.properties

    1
    2
    3
    user1.name=zhangsan
    user1.age=18
    user1.sex=
  3. 我们在 User 类上使用@Value 来进行注入!

    • @PropertySource:加载指定的配置文件;

    • @configurationProperties:默认从全局配置文件 application.properties 中获取值;

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      @Component //注册 bean
      @PropertySource(value = "classpath:user.properties")
      public class User {
      //直接使用@value
      @Value("${user.name}") //从配置文件中取值
      private String name;
      @Value("#{9*2}") // #{SPEL} Spring 表达式
      private int age;
      @Value("男") // 字面量
      private String sex;
      }
  4. Springboot 测试

    • 测试类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      @SpringBootTest
      class DemoApplicationTests {

      @Autowired
      User user;

      @Test
      public void contextLoads() {
      System.out.println(user);
      }

      }
    • 结果正常输出:

@PropertySource

  • Spring 框架提供了 PropertySource 注解,目的是加载指定的属性文件, 可以加载 properties 配置文件的,不能加载 YAML 配置文件;

  • value:加载 classpath 路径下的 test.properties 配置文件;

  • ignoreResourceNotFound:当指定的配置文件不存在是否报错,默认是 false ; 比如上文中指定的加载属性文件是 test.properties。如果该文件不存在,则 ignoreResourceNotFound 为 true 的时候,程序不会报错,如果 ignoreResourceNotFound 为 false 的时候,程序直接报错。实际项目开发中,最好设置 ignoreResourceNotFound 为 false。该参数默认值为 false;

  • encoding:用于指定读取属性文件所使用的编码,我们通常使用的是 UTF-8;

  • name:这个值在 Springboot 的环境中必须是唯一的,如果不设置,则值为:“class path resource [test.properties]”;此值也可以不用设置;若想深入研究,可查资料继续研究;

注入方式对比

@ConfigurationProperties @Value
功能 批量注入配置文件中的属性 一个个指定
松散绑定(松散语法) 支持 不支持
SpEL 不支持 支持
JSR303 数据校验 支持 不支持
复杂类型封装 支持 不支持
  1. @ConfigurationProperties 只需要写一次即可 , @Value 则需要每个字段都添加
  2. 松散绑定:比如我的 yml 中写的 last-name(- 后面跟着的字母默认是大写),Bean 中有 lastName 也可以注入成功。
  3. JSR303 数据校验 , 这个就是我们可以在字段是增加一层过滤器验证 , 可以保证数据的合法性
  4. 复杂类型封装,yml 中可以封装对象 , 使用 value 就不支持
  • 配置 yml 和配置 properties 都可以获取到值 , 强烈推荐 yml;
  • 如果我们在某个业务中,只需要获取配置文件中的某个值,可以使用一下 @value
  • 如果我们专门编写了一个 JavaBean 来和配置文件进行一一映射,就直接@configurationProperties

yaml 松散绑定

  • Bean 的属性名和 yaml 中的表示方式支持使用:

    • 驼峰式、下划线(_)、短横线(-) 后第一个表示大写字母
  • 如 Bean 中属性的写法为 person.firstName

    • 方式一: person.first-name
    • 方式二:person.first_name
  • 如 Bean 中属性的写法为 person.first_name

    • 方式一: person.first-name
    • 方式二:person.firstName

JSR303 数据校验

  • 依赖

    1
    2
    3
    4
    <dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    </dependency>
  • Springboot 中可以用 @validated 来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。我们这里来写个注解让我们的 name 只能支持 Email 格式;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import javax.validation.constraints.Email;

    @Component //注册 bean
    @ConfigurationProperties(prefix = "person")
    @Validated //数据校验
    public class Person {
    @Email(message="邮箱格式错误") //name 必须是邮箱格式
    private String email;
    }
  • 配置文件

    1
    2
    person:
    email: ABC123

常见参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;

空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.(String ,Collection,Map的isEmpty()方法)

数值类型检查
@Min(value) 检查约束元素的值必须大于等于指定的最小值
@Max(value)检查约束元素的值必须小于等于指定的最大值
@DecimalMin(value)检查约束元素的值必须大于等于指定的最小值
@DecunakMax(value)检查约束元素的值必须小于等于指定的最大值

Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false

长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) string is between min and max included.

日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则

.......等等
除此以外,我们还可以自定义一些数据校验规则
我们可以实现一个方法,并用 @PostConstruct 标记,如果验证失败,方法抛出异常即可,当bean创建完成的时候,会后置执行@PostConstruct修饰的方法
  • jar 包下查看

Duration

  • Spring Boot 内置支持从配置参数中解析 durations (持续时间),官网文档 给出了明确的说明

  • 我们既可以配置毫秒数数值,也可配置带有单位的文本, 配置 duration 不写单位,默认按照毫秒来指定,我们也可已通过 @DurationUnit 来指定单位:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @DurationUnit(ChronoUnit.SECONDS)
    private Duration duration;

    ns for nanoseconds (纳秒)
    us for microseconds (微秒)
    ms for milliseconds (毫秒)
    s for seconds (秒)
    m for minutes (分)
    h for hours (时)
    d for days (天)

DataSize

  • 与 Duration 的用法一毛一样,默认单位是 byte (字节),可以通过 @DataSizeUnit 单位指定:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @DataSizeUnit(DataUnit.MEGABYTES)
    private DataSize maxAttachmentSize = DataSize.ofMegabytes(2);

    常见单位如下:

    B for bytes
    KB for kilobytes
    MB for megabytes
    GB for gigabytes
    TB for terabytes

自定义类型

  • 有些情况,我们想解析配置参数到我们自定义的对象类型上,假设,我们我们设置最大包裹重量:

    1
    private Weight maxAttachementWeight;
  • 可以模仿 DataSize 和 Duration 创造自己的 converter (转换器)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import org.springframework.core.convert.converter.Converter;

    public class WeightConverter implements Converter<String,Weight> {

    @Override
    public Weight convert(String s) {
    return null;
    }
    }
  • 将其注册到 Spring Boot 上下文中, @ConfigurationPropertiesBinding 注解是让 Spring Boot 知道使用该转换器做数据绑定

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Configuration
    public class PropertiesConfig {
    @Bean
    @ConfigurationPropertiesBinding
    public WeightConverter weightConverter(){
    return new WeightConverter();
    }
    }

多环境切换

  • 实际开发中存在多种环境,profile 是 Spring 对不同环境提供不同配置功能的支持,可以通过激活不同的环境版本,实现快速切换环境;
    • dev 环境:application-dev.properties
    • test 环境:application-test.properties
    • prod 环境:application-prod.properties

多配置文件

  • 主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml , 用来指定多个环境版本;

properties 配置

  • application-test.properties 代表测试环境配置

  • application-dev.properties 代表开发环境配置

  • 但是 Springboot 并不会直接启动这些配置文件,它 默认使用 application.properties 主配置文件

  • 我们需要通过一个配置来选择需要激活的环境:

    1
    2
    3
    #比如在配置文件中指定使用dev环境,我们可以通过设置不同的端口号进行测试;
    #我们启动SpringBoot,就可以看到已经切换到dev下的配置了;
    spring.profiles.active=dev

yml 文档块方式

  • 和 properties 配置文件中一样,但是使用 yml 使用三条短线 --- 可以作为分隔文档块,不需要创建多个配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    server:
    port: 8080
    servlet:
    context-path: demo #配置项目的访问路径
    #选择要激活那个环境块
    spring:
    profiles:
    active: prod #激活prod生产环境,因此端口为8084

    ---
    server:
    port: 8083
    spring:
    profiles: dev #配置环境的名称

    ---
    server:
    port: 8084
    spring:
    profiles: prod #配置环境的名称

激活指定 profile

  1. 在 properties 配置文件中指定 spring.profiles.active=dev

  2. 命令行:在最后加上 --spring.profiles.active=dev

    1
    java -jar spring-boot-02-config-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev;
  3. 虚拟机参数 vmoption:-Dspring.profiles.active=dev

配置文件加载位置

  • 外部加载配置文件的方式十分多,我们选择最常用的即可,在开发的资源文件中进行配置!

  • springboot 启动会扫描一下位置的 application.propertiesapplication.yml 文件作为默认配置文件, file 指的是项目根目录。

    优先级 位置 描述 优先级
    1 file:./config/ 工程项目/config
    2 file:./ 工程项目 较高
    3 classpath:/config/ resources/config 较低
    4 classpath:/ resources
  • 优先级由高到低,高优先级的配置会覆盖低优先级的配置;

  • SpringBoot 会从这四个位置全部加载主配置文件; 互补配置

外部配置加载顺序

  • SpringBoot 也可以从以下位置加载配置; 优先级从高到低;高优先级的配置覆盖低优先级的配置,所有的配置会形成互补配置

  • 常用的 11 个位置 (优先级从高到低)

    1. 命令行参数:所有的配置都可以在命令行上进行指定, 多个配置用空格分开; --配置项=值

      1
      2
      3
      java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar --server.port=8087 --server.context-path=/abc
      # 可以在代码中通过 setAddCommandLineProperties 来禁用
      SpringApplication.setAddCommandLineProperties(false).
    2. 来自 java: comp/env 的 JNDI 属性

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

    4. 操作系统环境变量

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

    6. Jar 包寻找: 顺序由 jar 包外向 jar 包内进行寻找, 优先加载带 profile, 再来加载不带 profile

      1. jar 包外部的 application-{profile}.properties 或 application.yml(带 spring.profile)配置文件
      2. jar 包内部的 application-{profile}.properties 或 application.yml(带 spring.profile)配置文件
      3. jar 包外部的 application.properties 或 application.yml(不带 spring.profile)配置文件
      4. jar 包内部的 application.properties 或 application.yml(不带 spring.profile)配置文件
  1. @Configuration 注解类上的@PropertySource

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

运维小技巧

  • 可以指定位置加载配置文件:通过 spring.config.location 来改变默认的配置文件位置

  • 项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;这种情况,一般是后期运维做的多,相同配置,外部指定的配置文件优先级最高

    1
    java -jar spring-boot-config.jar --spring.config.location=F:/application.propert

bootstrap 与 application

  1. 加载顺序: 这里主要是说明 application 和 bootstrap 的加载顺序。

    • bootstrap.yml(bootstrap.properties)先加载
    • application.yml(application.properties)后加载
    • bootstrap.yml 用于应用程序上下文的引导阶段。
    • bootstrap.yml 由父 Spring ApplicationContext 加载。
    • 父 ApplicationContext 被加载在 application.yml 的之前。
  2. 配置区别

    • bootstrap.yml 和 application.yml 都可以用来配置参数。

    • bootstrap.yml 可以理解成系统级别的一些参数配置,这些参数一般是不会变动的。

    • application.yml 可以用来定义应用级别的,如果搭配 spring-cloud-config 使用 application.yml 里面定义的文件可以实现动态替换。

    • 使用 Spring Cloud Config Server 时,应在 bootstrap.yml 中指定:

      1
      2
      spring.application.name
      spring.cloud.config.server.git.uri
  3. 一些加密/解密信息

    • 实例 bootstrap.yml :

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      spring:
      application:
      name: service-a
      cloud:
      config:
      uri: http://127.0.0.1:8888
      fail-fast: true
      username: user
      password: ${CONFIG_SERVER_PASSWORD:password}
      retry:
      initial-interval: 2000
      max-interval: 10000
      multiplier: 2
      max-attempts: 10
    • 当使用 Spring Cloud 时,通常从服务器加载“real”配置数据。为了获取 URL(和其他连接配置,如密码等),您需要一个较早的或“bootstrap”配置。因此,您将配置服务器属性放在 bootstrap.yml 中,该属性用于加载实际配置数据(通常覆盖 application.yml [如果存在] 中的内容)。

    • 当然,在一些情况上不用那么区分这两个文件,你只需要使用 application 文件即可,把全部选项都写在这里,效果基本是一致的,在不考虑上面的加载顺序覆盖的问题上。

总结

  • bootstrap.yml 文件里面放系统级别的配置,基本不会发生改变
  • 并且在使用 spring cloud 的时候,一定要把 config server 的属性配置到 bootstrap.yml 文件里面,因为 bootstrap.yml 加载顺序优先于 application.yml 文件,这样的话,就可以在 application.yml 加载之前加载远程的配置,然后去覆盖 application.yml (如果有)中的配置;然后 application.yml 配置文件中的配置就是应用的自动化配置了;
  • 所以,如果项目中的配置比较多的时候建议 bootstrap.yml 和 application.yml 这两个配置文件一起使用最好;
  • 如果项目中的配置比较少,就建议直接使用 application.yml 一个配置文件就够了;

YAML 编写规范

  • yaml 官网

  • 它的基本语法规则如下:

    • 大小写敏感
    • 使用缩进表示层级关系
    • 缩进时不允许使用 Tab 键,只允许使用空格
    • 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可

规范一:编码规范

  • 文档使用 Unicode 编码作为字符标准编码,例如 UTF-8

规范二:注释书写

  • 使用“#”来表示注释内容

  • 功能注释标在开头

  • 语句注释标在 行后

    1
    2
    3
    4
    5
    6
    7
    # 客户订单
    date: 2015-02-01
    customer:
    name: Jai
    items:
    no: 1234 # 订单号
    descript: cpu

规范三:缩进书写(2 个空格)

  • 使用空格作为嵌套缩进工具。通常建议使用两个空格缩进,不建议使用 tab (甚至不支持)

规范四:序列表示

  • 使用“-”(横线) + 单个空格表示单个列表项

    1
    2
    3
    --- # 文档开始
    - 第一章 简介
    - 第二章 设计目录
  • 使用 “[]” 表示一组数据

    1
    2
    --- # 文档开始
    [blue, red, green]
  • 组合表示。每个结构都可以嵌套组成复杂的表示结构。

    1
    2
    3
    4
    --- # 文档开始
    - [blue, red, green] # 列表项本身也是一个列表
    - [Age, Bag]
    - site: { osc:www.oschina.net, baidu: www.baidu.com } # 这里是同 键值表 组合表示

规范五:键值表

  • 使用 “:”(冒号) + 空格表示单个键值对

    1
    2
    3
    4
    5
    6
    7
    8
    # 客户订单
    date: 2015-02-01
    customer:
    - name: Jai
    items:
    - no: 1234 # 订单号
    - descript: cpu
    - price: ¥800.00
  • 使用 “{}” 表示一个键值表

    1
    2
    3
    4
    5
    # 客户订单
    date: 2015-02-01
    customer:
    - name: Jai
    items: { no: 1234, descript: cpu, price: ¥800.00 }
  • “? “ 问号+空格表示复杂的键。当键是一个列表或键值表时,就需要使用本符号来标记。

    1
    2
    3
    4
    5
    6
    7
    8
    # 使用一个列表作为键
    ? [blue, reg, green]: Color

    # 等价于
    ? - blue
    - reg
    - gree
    : Color
  • 组合表示。每个结构都可以嵌套组成复杂的表示结构。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    Color:
    - blue
    - red
    - green

    # 相当于 (也是 JSON 的表示)
    {Color: [blue, red, green]}
    div:
    - border: {color: red, width: 2px}
    - background: {color: green}
    - padding: [0, 10px, 0, 10px]

    # 使用缩进表示的键值表与列表项
    items:
    - item: cpu
    model: i3
    price: ¥800.00
    - item: HD
    model: WD
    price: ¥450.00
    # 上面使用 “-” 前导与缩进来表示多个列表项,相当于下面的JSON表示
    items: [{item:cpu, model:i3, price:¥800.00}, {item:HD, model:WD, price: ¥450.00}]

规范六:文本块

yaml: | 保留回车换行

  • 使用 “|” 和文本内容缩进表示的块:保留块中已有的回车换行。相当于段落块

    1
    2
    yaml: | # 注意 ":" 与 "|" 之间的空格
    JSON的语法其实是YAML的子集,大部分的JSON文件都可以被YAML的解释器解释。

yaml: > 回车变空格

  • 使用 “>” 和文本内容缩进表示的块:将块中回车替换为空格,最终连接成一行。

    1
    2
    3
    yaml: > # 注意 ":" 与 ">" 之间的空格,另外可以使用空行来分段落
    JSON的语法其实是YAML的子集,
    大部分的JSON文件都可以被YAML的解释器解释。

多行变一行

  • 使用定#界符“”(双引号)、‘’(单引号)或回车表示的块:最终表示成一行。

    1
    2
    3
    4
    5
    6
    yaml:     # 使用回车的多行,最终连接成一行。
    JSON的语法其实是YAML的子集,
    大部分的JSON文件都可以被YAML的解释器解释。
    yaml: # 使用了双引号,双引号的好处是可以转义,即在里面可以使用特殊符号
    "JSON的语法其实是YAML的子集,
    大部分的JSON文件都可以被YAML的解释器解释。"

规范七:数据类型的约定

常用基本类型

  • 对一些常用数据类型的表示格式进行了约定,包括:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    integer: 12345 # 整数标准形式
    octal: 0o34 # 八进制表示,第二个是字母 o
    hex: 0xFF # 十六进制表示
    float: 1.23e+3 # 浮点数
    fixed: 13.67 # 固定小数
    minmin: -.inf # 表示负无穷
    notNumber: .NaN # 无效数字
    null: # 空值
    boolean: [true, false] # 布尔值
    string: "12345" # 字符串
    date: 2015-08-23 # 日期
    datetime: 2015-08-23T02:02:00.1z # 日期时间
    iso8601: 2015-08-23t21:59:43.10-05:00 # iso8601 日期格式
    spaced: 2015-08-23 21:59:43.10 -5 # ?

自定义数据类型

  • “!”(叹号)显式指示类型,或自定义类型标识。单叹号通常是自定义类型,双叹号是内置类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    isString: !!str 2015-08-23     # 强调是字符串不是日期数据
    picture: !!binary | # Base64 图片
    R0lGODlhDAAMAIQAAP//9/X
    17unp5WZmZgAAAOfn515eXv
    Pz7Y6OjuDg4J+fn5OTk6enp
    56enmleECcgggoBADs=
    #下面是内置类型
    !!int # 整数类型
    !!float # 浮点类型
    !!bool # 布尔类型
    !!str # 字符串类型
    !!binary # 也是字符串类型
    !!timestamp # 日期时间类型
    !!null # 空值
    !!set # 集合
    !!omap, !!pairs # 键值列表或对象列表
    !!seq # 序列,也是列表
    !!map # 键值表
    #下面是一些例子:
    --- !!omap
    - Mark: 65
    - Sammy: 63
    - Key: 58
    --- !!set # 注意,“?”表示键为列表,在这里列表为 null
    ? Mark
    ? Sammy
    ? Key
    # 下面是自定义的类型或标识
    %TAG ! tag:clarkevans.com,2002: # % 是指令符号
    --- !shape
    # Use the ! handle for presenting
    # tag:clarkevans.com,2002:circle
    - !circle
    center: &ORIGIN {x: 73, y: 129}
    radius: 7
    - !line
    start: *ORIGIN
    finish: { x: 89, y: 102 }
    - !label
    start: *ORIGIN
    color: 0xFFEEBB
    text: Pretty vector drawing.

规范八:锚点与引用,定义数据的复用

  • 第一步:使用 “&” 定义数据锚点(即要复制的数据)

  • 第二步:使用 “*” 引用上述锚点数据(即数据的复制目的地)

    1
    2
    3
    4
    5
    6
    7
    8
    ---
    hr:
    - Mark McGwire
    # Following node labeled SS
    - &SS Sammy Sosa # 定义要复制的数据
    rbi:
    - *SS # Subsequent occurrence 这里是数据复制目标
    - Ken Griffey

说明