六、Spring Boot - 自动配置原理(2)

一、yaml 语法

创建一个项目 springboot-02-config

在这里插入图片描述

1.1 配置文件

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

  • application.properties
    – 语法结构:key=value
  • application.yml
    – 语法结构:key:空格 value

配置文件的作用:修改SpringBoot自动配置的默认子,因为SpringBoot在底层都给我们自动配置好了;
比如我们可以在配置文件中修改Tomcat默认启动的端口号!测试一下!

server.port=8081

1.2 yml概述

YAML是“YAML Ain`t a Markup Language”(YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)

这种语言以数据作为中心,而不是以标记语言作为重点!

以前的配置文件,大多数都是使用xml来配置的,比如:一个简单的端口配置,我么来对比下yml和xml:

传统的xml配置:

<server>
    <port>8081<port>
</server>

yaml配置:

server:
  prot: 8080

1.3 yml 基础语法

说明:语法要求严格!

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

字面量:普通的值【数字、布尔值、字符串】

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

k:v

注意:

  • “”双引号,不会转义字符串里面的特殊字符,特殊字符会作为本身想表示的意思;
  • 比如:name:“daniel \n wang” 输出:daniel 换行 wang
  • " 单引号,会转义特殊字符,特殊字符最终会变成普通字符一样输出
    比如:name:‘daniel \n wang’ 输出 daniel \n wang

对象、Map(键值对)

#对象、Map格式
k: 
    v1:
    v2:

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

#对象
student:
  name: Daniel
  age: 11

行内写法

#行内写法
sttudent: {name: Daniel,age: 11}

数组( List、set )

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

pets:
 - cat
 - dog
 - pig

行内写法

pets: [cat,dog,pig]

修改SpringBoot的默认端口号

配置文件中添加,端口号的参数,就可以切换端口;

server:
  port: 8082

二、注入配置文件

2.1 yml 注入配置文件

在这里插入图片描述

  1. 在springboot项目中的resources目录下新建一个文件 application.yml
  2. 编写一个实体类 Dog;
  3. 思考,我们原来是如何给bean注入属性值的!@Value,给狗狗类测试一下:

(1)编写一个实体Dog

Dog.java

package com.zql.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.imageio.ImageTranscoder;

/**
 * @Author:Daniel
 * @Version 1.0
 */
@Component  //注入到springboot组件中 注册bean
public class Dog {

    @Value("旺旺")
    private String name;
    @Value("22")
    private Integer age;

    public Dog() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  1. 在SpringBoot的测试类下注入狗狗输出一下;测试类中如下:
package com.zql;

import com.zql.pojo.Dog;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class Springboot02ConfigApplicationTests {

    @Autowired
    private  Dog dog;

    @Test
    void contextLoads() {

        System.out.println(dog);
    }

}

在这里插入图片描述

运行得:结果成功输出,@Value注入成功,这是我们原来的办法对吧。

在这里插入图片描述

  1. 我们在编写一个复杂一点的实体类:Person 类
  2. 我们刚才已经把person这个对象的所有值都写好了,我们现在来注入到我们的类中!

Person.java

package com.zql.pojo;

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

import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * @Author:Daniel
 * @Version 1.0
 */
 
/*
@ConfigurationProperties作用:
将配置文件中配置的每一个属性的值,映射到这个组件中;
告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定
参数 prefix = “person” : 将配置文件中的person下面的所有属性一一对应
*/
@Component //注册bean到容器中
@ConfigurationProperties(prefix = "person")
public class Person {

    private String name;
    private Integer age;
    private String habby;
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;

    private Dog dog;

    public Person() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getHabby() {
        return habby;
    }

    public void setHabby(String habby) {
        this.habby = habby;
    }

    public Date getBirth() {
        return birth;
    }

    public void setBirth(Date birth) {
        this.birth = birth;
    }

    public Map<String, Object> getMaps() {
        return maps;
    }

    public void setMaps(Map<String, Object> maps) {
        this.maps = maps;
    }

    public List getLists() {
        return lists;
    }

    public void setLists(List lists) {
        this.lists = lists;
    }

    public Dog getDog() {
        return dog;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", habby='" + habby + '\'' +
                ", birth=" + birth +
                ", maps=" + maps +
                ", lists=" + lists +
                ", dog=" + dog +
                '}';
    }
}
  1. 我们来使用yaml配置的方式进行注入,大家写的时候注意区别和优势,我们编写一个yaml配置!编写 application.yml
person:
  name: Daniel
  age: 22
  habby: piano
  birth: 2022/09/14
  maps: {v1:k1,v1:k2}
  lists:
    -dog
    -cat
    -pig
  dog:
    name: 旺旺王
    age: 11
  1. IDEA 提示,springboot配置注解处理器没有找到,让我们看文档,我们可以查看文档,找到一个依赖!

问题:

在这里插入图片描述

解决:点击报错处:则会跳转 浏览器 👇🏾👇🏾

在这里插入图片描述

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

import com.zql.pojo.Dog;
import com.zql.pojo.Person;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class Springboot02ConfigApplicationTests {

    @Autowired
    private  Dog dog;
    @Autowired
    private Person person;

    @Test
    void contextLoads() {

        System.out.println(dog);

        System.out.println(person);
    }

}

yml配置注入到实体类完全OK!

2.2 加载指定的配置文件

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

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

  1. 我们去在resources目录下新建一个person.properties文件
name=Daniel

在这里插入图片描述

  1. 然后在我们的代码中指定加载person.properties文件
package com.zql.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * @Author:Daniel
 * @Version 1.0
 */
@Component
//@ConfigurationProperties(prefix = "person")
@PropertySource(value= "classpath:person.properties")
public class Person {

    @Value("Daniel")
    private String name;
    @Value("44")
    private Integer age;
    @Value("tool")
    private String habby;
    @Value("2022/9/14")
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;

    private Dog dog;

    public Person() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getHabby() {
        return habby;
    }

    public void setHabby(String habby) {
        this.habby = habby;
    }

    public Date getBirth() {
        return birth;
    }

    public void setBirth(Date birth) {
        this.birth = birth;
    }

    public Map<String, Object> getMaps() {
        return maps;
    }

    public void setMaps(Map<String, Object> maps) {
        this.maps = maps;
    }

    public List getLists() {
        return lists;
    }

    public void setLists(List lists) {
        this.lists = lists;
    }

    public Dog getDog() {
        return dog;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", habby='" + habby + '\'' +
                ", birth=" + birth +
                ", maps=" + maps +
                ", lists=" + lists +
                ", dog=" + dog +
                '}';
    }
}

在这里插入图片描述

  1. 再次输出测试一下:指定配置文件绑定成功!

在这里插入图片描述

2.3 配置文件占位符(拓展)

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

person:
  name: Daniel${random.uuid} # 随机uuid
  age: ${random.int}  # 随机int
  habby: pinpagn
  birth: 2022/10/01
  maps: {k1: v1,k2: v2}
  lists:
  - code
  - girl
  - music
  dog:
    name: ${person.hello:other}_旺财
    age: 1

2.4 回顾properties配置

我们上面采用的yaml方法都是最简单的方式,开发中最常用的;也是springboot所推荐的!那我们来唠唠其他的实现方式,道理都是相同的;写还是那样写;配置文件除了yml还有我们之前常用的properties , 我们没有讲,我们来唠唠!

【注意】properties配置文件在写中文的时候,会有乱码 , 我们需要去IDEA中设置编码格式为UTF-8;

settings–>FileEncodings 中配置;

在这里插入图片描述

测试步骤

1、新建一个实体类User

package com.zql.pojo;

import org.springframework.stereotype.Component;

/**
 * @Author:Daniel
 * @Version 1.0
 */
@Component
public class User {


    private String name;
    private int age;
    private String sex;
}

2、编辑配置文件 user.properties

user1.name=Daniel
user1.age=18
user1.sex=男

3、我们在User类上使用@Value来进行注入!

package com.zql.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

/**
 * @Author:Daniel
 * @Version 1.0
 */
@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 测试

@SpringBootTest
class Springboot02ConfigApplicationTests{

    @Autowired
    User user;

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

}

结果正常输出:

2.5 对比小结

@Value 这个使用起来并不友好!我们需要为每个属性单独注解赋值,比较麻烦;现在来看看这个功能对比

项目@ConfigurationProperties@Value
功能批量注入文件中的属性一个个指定
松散绑定(送撒语法)支持不支持
SpEl不支持支持
JRE303数据校验支持不支持
复杂类型封装支持不支持
  1. @ConfigurationProperties只需要写一次即可,@Value则需要每个字段添加
  2. 松散绑定:这个什么意思?比我我写的yml中写的last-name,这个和lastName是一样的,后面跟着的字母默认是大写的,这就是松散绑定,可以测试一下。
  3. JSR303数据校验,这个就是我们可以在字段是增加一层过滤器验证,可以保证数据的合法性;
  4. 复杂类型封装,yml中可以封装对象,但用value就不支持。

结论

  • 配置yml和配置 properties 都可以获取到值,强烈推荐yml;
  • 如果我们再某个业务中,只需要获取配置文件中得某个值,可以使用一下@Value;
  • 如果说,我们专门编写了一个JavaBean来和配置文件进行一一映射,就直接@ConfigurationProperties,不要犹豫!

三、JSR303数据校验

3.1 如何使用

Springboot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理,我们这里来写个注解让我们得 name 只能支持 Email 格式;

@Email报红解决

@Component //注册bean
@ConfigurationProperties(prefix = "person")
@Validated  //数据校验
public class Person {

    @Email(message="邮箱格式错误") //name必须是邮箱格式
    private String name;
}

运行结果 :default message [不是一个合法的电子邮件地址];

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

validated jar包

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
    <version>2.3.5.RELEASE</version>
</dependency>

使用数据校验,可以保证数据的正确性;

3.2 常见参数

@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.
    
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       验证 DateCalendar 对象是否在当前时间之前  
@Future     验证 DateCalendar 对象是否在当前时间之后  
@Pattern    验证 String 对象是否符合正则表达式的规则

.......等等
除此以外,我们还可以自定义一些数据校验规则

源码看注解:

在这里插入图片描述

上述案例源码

四、多环境切换

profile是Spring对不同环境提供不同配置的功能支持,可以通过激活不同的环境版本,实现快速切换环境。

4.1 多配置文件

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

例如:

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

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

  • 我们需要通过一个配置来选择需要激活的环境:
#比如在配置文件中指定使用dev环境,我们可以通过设置不同的端口号进行测试;
#我们启动SpringBoot,就可以看到已经切换到dev下的配置了;
spring.profiles.active=dev

4.2 yml的多文档块

和properties配置文件中一样,但是使用yml去实现不需要创建多个配置文件,更加方便了!

server:
  port: 8081
  
#选择要激活那个环境块
spring:
  profiles:
    active: prod

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

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

注意:如果yml和properties同时都配置了端口,并且没有激活其它环境,默认会使用properties配置文件的!

4.3 配置文件加载位置

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

官方外部配置文件说明参考文档

在这里插入图片描述
springboot 启动会扫描一下位置的 application.properties 或者 application.yml文件作为Spring boot的默认配置文件:

  • 优先级 1:项目路径下的config文件夹配置文件
  • 优先级2: 项目路径下配置文件
  • 优先级3:资源路径下的config文件夹配置文件
  • 优先级4:资源路径下配置文件

优先级由高到低(①②③④ 👇🏾👇🏾),高优先级的配置会覆盖低优先级的配置;

在这里插入图片描述

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

我们在最低级的配置文件中设置一个项目访问路径的配置来测试互补的问题。

#配置项目的访问路径

server.servlet.context-path=/Daniel

4.4 拓展(运维小技巧)

  • 指定位置加载配置文件
  • 我可以通过spring. config.location 来改变默认的配置文件位置
  • 项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置,这种情况,一般是后期运维做的多,相同的配置,外部指定的配置文件优先级最高。
java -jar spring-boot-config.jar --spring.config.location=F:/application.properties

五、自动配置原理

配置文件到底写什么?怎么写?

SpringBoot 官方文档 中有大量的配置,我们无法全部记住

在这里插入图片描述

5.1 分析自动配置原理

我们以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理;

//表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件;
@Configuration 

//启动指定类的 ConfigurationProperties功能;
//进入这个 HttpProperties查看,将配置文件中对应的值和 HttpProperties绑定起来;
//并把 HttpProperties加入到ioc容器中
@EnableConfigurationProperties({HttpProperties.class}) 

//Spring底层@Conditional注解
//根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;
//这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(
    type = Type.SERVLET
)

//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass({CharacterEncodingFilter.class})

//判断配置文件中是否存在某个配置:spring.http.encoding.enabled;
  //如果不存在,判断也是成立的
  //即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
@ConditionalOnProperty(
    prefix = "spring.http.encoding",
    value = {"enabled"},
    matchIfMissing = true
)

public class HttpEncodingAutoConfiguration {
    //他已经和SpringBoot的配置文件映射了
    private final Encoding properties;
    //只有一个有参构造器的情况下,参数的值就会从容器中拿
    public HttpEncodingAutoConfiguration(HttpProperties properties) {
        this.properties = properties.getEncoding();
    }
    
    //给容器中添加一个组件,这个组件的某些值需要从properties中获取
    @Bean
    @ConditionalOnMissingBean //判断容器没有这个组件?
    public CharacterEncodingFilter characterEncodingFilter() {
        CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
        filter.setEncoding(this.properties.getCharset().name());
        filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
        filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
        return filter;
    }
    //。。。。。。。
}
  • 一句话总结:根据当前不同的条件判断,决定这个配置类是否生效!
  • 一旦这个类生效,这个配置类就会给容器中添加各种组件;
  • 这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
  • 所有在配置文件中能撇脂的属性都是在xxxxproperties类中封装着;
  • 配置文件能配置什么就可以参照某个功能对应的这个属性类
//从配置文件中获取指定的值和bean的属性进行绑定
@ConfigurationProperties(prefix = "spring.http") 
public class HttpProperties {
    // .....
}

我们去配置文件里面试试前缀,看提示!

这就是自动装配的原理!

5.2 精髓

  1. SpringBoot启动会加载大量的自动配置类
  2. 我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;
  3. 我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在其中,我们九不需要再手动配置了)
  4. 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可。

xxxxAutoConfiguration:自动配置类;给容器中添加组件
xxxxProperties:封装配置文件中相关属性;

5.3 @Conditional(了解)

  • 了解完自动装配的原理后,我们来关注下一个细节问题,自动配置类必须在一定的条件下才能生效。
  • @Conditional派生注解(Spring注解版原生的@Conditional 作用)

作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才能生效。

那么多的自动配置,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。

我们怎么知道哪些自动配置类生效?

我们可以通过启动debug=true属性,来控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;

#开启springboot的调试类
dubug=true

Positive matches:(自动配置类启用的:正匹配)
Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)
Unconditional classes:(没有条件类)

【演示:查看输出的日志】

掌握吸收理解原理,既可以不变应万变!

六、自定义Starter

我们分析完毕了源码以及自动装配的过程,我们可以尝试自定义一个启动器来玩玩!

6.1 说明

启动器模块是一个 空 jar 文件,仅提供辅助性依赖管理,这些依赖可能用于自动装配或者其他类库;

命名归约:

官方命名:

  • 前缀:spring-boot-starter-xxx
  • 比如:spring-boot-starter-web…

自定义命名:

  • xxx-spring-boot-starter
  • 比如:mybatis-spring-boot-starter

6.2 编写启动器

  1. 在IDEA中新建一个空项目 spring-boot-starter-diy
  2. 新建一个普通Maven模块:daniel-spring-boot-starter
  1. 新建一个Springboot模块:daniel-spring-boot-starter-autoconfigure
  1. 点击 apply即可,基本结构
  1. 在我们的start中导入 autoconfigure的依赖!
  1. 将autoconfigure 项目下多余的文件都删掉,Pom中只留下一个starrter,这是所有启动器基本配置!
  1. 我们编写一个自己的服务
  1. 编写HelloProperties配置类
  1. 编写我们的自动配置类并注入bean,测试!
  1. 在resources编写一个自己的META-INR \ spring.factories
  1. 编写完成后,可以安装到 maven 仓库中!

6.3 新建项目测试我们自己写的启动器

  1. 新建一个springboot项目
  2. 导入我们自己写的启动器
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Daniel521-Spark

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值