Spring Boot 01-入门

一 Spring Boot 入门

1.1 Spring Boot 简介

Spring Boot来简化Spring应用开发,约定大于配置,去繁从简,just run就能创建一个独立的,产品级别的应用。
即:

  1. 简化Spring应用开发的一个框架;
  2. 整个Spring技术栈的一个大整合;
  3. J2EE开发的一站式解决方案;

1.2 背景

J2EE笨重的开发、繁多的配置、低下的开发效率、复杂的部署流程、第三方技术集成难度大。

1.3. 解决

“Spring全家桶”时代;
Spring Boot ------>J2EE一站式解决方案;
Spring Cloud ------->分布式整体解决方案;

优点:
– 快速创建独立运行的Spring项目以及与主流框架集成。
– 使用嵌入式的Servlet容器,应用无需打成WAR包。
– starters自动依赖与版本控制。
– 大量的自动配置,简化开发,也可修改默认值。
– 无需配置XML,无代码生成,开箱即用。
– 准生产环境的运行时应用监控。
– 与云计算的天然集成。
在这里插入图片描述

二 微服务

2.1 简介

微服务,最早是有Martin Fowler与James Lewis于2014年共同提出的一个概念。是当前比较主流的一种软件设计架构。
简而言之,就是将一个大型的单机应用系统拆分成若干个小的服务,这些小的服务独立部署,服务与服务之间采用rpc/http轻量协议传输数据,服务间不具有强耦合性,从而实现了单个服务的高内聚,服务与服务之间低耦合的效果。这些一个一个细分的小服务就称之为微服务。
在这里插入图片描述

2.2 优点

优点:

  1. 便于开发,降低复杂度:各个团队中分别维护各自服务,不会出现等待的无用功的间隙便于开发,降低复杂度;各个团队中分别维护各自服务,不会出现等待的无用功的间隙。并且更新迭代的时候,不必再学习整个项目的业务代码,仅关注所在的服务模块即可。
  2. 业务解耦:各个服务间通过协议互相通讯,代码没有强耦合性,互不干扰。
  3. 独立部署:每个模块都可以单独部署,所以在上线新功能的时候只需发布相应的模块即可,既降低测试的工作量也降低了服务发布的风险(单服务情况下,新增需求可能就得把整个的流程测试再回归一下,并且上线失败的话整个项目都要回滚,而微服务则只需要回滚相应的模块即可)。
  4. 稳定性强;单个服务出现问题,其他服务仍可继续工作,这在技术潮流中是非常重要的一个进步,结合集群部署,我们完美的实现不停机更新。
  5. 扩展性强;可以根据不同服务的流量和压力,进行自主扩展即商品服务流量高,而订单服务流量低,则可以部署两个商品服务,一个订单服务,更加灵活。

详细参考微服务文档:https://martinfowler.com/articles/microservices.html#MicroservicesAndSoa

三 环境准备

3.1 环境约束

  • jdk1.8:Spring Boot 推荐jdk1.7及以上;java version “1.8.0_112”
  • maven3.x:maven 3.3以上版本;Apache Maven 3.8.4
  • IntelliJIDEA2020:IntelliJ IDEA 2020.1 x64、STS
  • SpringBoot 1.5.9.RELEASE:1.5.9;
    统一环境;

3.2 MAVEN设置

给maven 的settings.xml配置文件的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>

3.3 IDEA设置

整合maven进来
文件–>设置–>构建,执行,部署–>Maven
在这里插入图片描述
为了使项目所需依赖导入更快,我们可以让Maven导入阿里云,将原本的mirrors标签中的mirror标签内容注释掉,添加以下脚本。

<mirror>
	<id>alimaven</id>
	<name>aliyun maven</name>
	<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
	<mirrorOf>central</mirrorOf>
	</mirror>
	<mirror>
	<id>alimaven</id>
	<mirrorOf>central</mirrorOf>
	<name>aliyun maven</name>
	<url>http://maven.aliyun.com/nexus/content/repositories/central/</url>
	</mirror>

四 Spring Boot HelloWorld

需求:浏览器发送hello请求,服务器接受请求并处理,响应Hello World字符串;

4.1 创建一个maven工程(jar)

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

4.2 导入spring boot相关的依赖

    <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.1.1.RELEASE</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

4.3 编写一个主程序并启动Spring Boot应用

package com.tedu.java;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * Create with IntelliJ IDEA
 *
 * @author : zyy
 * @date : 2022/6/28 11:39
 * @Description :@SpringBootApplication 来标注一个主程序类,说明这是一个Spring Boot应用
 */
@SpringBootApplication
public class HelloWorldApplication {
    public static void main(String[] args) {
    	// Spring应用启动起来
        SpringApplication.run(HelloWorldApplication.class,args);
    }
}

4.4 编写相关的Controller

package com.tedu.java.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * Create with IntelliJ IDEA
 *
 * @author : zyy
 * @date : 2022/6/28 11:40
 * @Description :
 */
@RestController
public class HelloController {
    @RequestMapping("/hello")
    public String hello(){
        return "hello";
    }
}

4.5 运行主程序测试

启动成功:
在这里插入图片描述
在这里插入图片描述

4.6 简化部署

在pom.xml添加依赖

<!--这个插件,可以将应用打包成一个可执行的jar包;-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.1.1.RELEASE</version>
            </plugin>
        </plugins>
    </build>

打包:
在这里插入图片描述
在这里插入图片描述在控制台进入到target目录下,使用java -jar命令运行该jar包:
在这里插入图片描述
运行成功:
在这里插入图片描述在这里插入图片描述

五 Hello World探究

5.1 POM文件

5.1.1 父项目

<parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.1.1.RELEASE</version>
    </parent>

它的父项目是:

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>2.1.1.RELEASE</version>
        <relativePath>../../spring-boot-dependencies</relativePath>
    </parent>

它来真正管理Spring Boot应用里面的所有依赖版本;
Spring Boot的版本仲裁中心;以后我们导入依赖默认是不需要写版本;(没有在dependencies里面管理的依赖自然需要声明版本号)。
在这里插入图片描述

5.1.2 启动器

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

spring-boot-starter-web:springboot中web模块的启动器。
spring-boot-starter:spring-boot场景启动器;帮我们导入了web模块正常运行所依赖的组件;
Spring Boot将所有的功能场景都抽取出来,做成一个个的starters(启动器),只需要在项目里面引入这些starter相关场景的所有依赖都会导入进来。要用什么功能就导入什么场景的启动器。
即:

  1. Spring Boot为我们提供了简化企业级开发绝大多数场景的starter pom(启动器),只要引入了相应场景的starter pom,相关技术的绝大部分配置将会消除(自动配置),从而简化我们开发。业务中我们就会使用到Spring Boot为我们自动配置的bean。
  2. 参考https://docs.spring.io/spring-boot/docs/1.5.9.RELEASE/reference/htmlsingle/#using-boot-starter
  3. 这些starters几乎涵盖了javaee所有常用场景,Spring Boot对这些场景依赖的jar也做了严格的测试与版本控制。我们不必担心jar版本合适度问题。
  4. spring-boot-dependencies里面定义了jar包的版本。

5.2 主程序类,主入口类

package com.tedu.java;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * Create with IntelliJ IDEA
 *
 * @author : zyy
 * @date : 2022/6/28 11:39
 * @Description :@SpringBootApplication 来标注一个主程序类,说明这是一个Spring Boot应用
 */
@SpringBootApplication
public class HelloWorldApplication {
    public static void main(String[] args) {
        SpringApplication.run(HelloWorldApplication.class,args);
    }
}

@SpringBootApplication: Spring Boot应用标注在某个类上说明这个类是SpringBoot的主配置类,SpringBoot就应该运行这个类的main方法来启动SpringBoot应用;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
}

@Configuration
public @interface SpringBootConfiguration {
}

@SpringBootConfiguration:Spring Boot的配置类,标注在某个类上,表示这是一个Spring Boot的配置类;
@Configuration:配置类上来标注这个注解;即配置类 ----- 配置文件;配置类也是容器中的一个组件(@Component);
@EnableAutoConfiguration:开启自动配置功能;以前我们需要配置的东西,Spring Boot帮我们自动配置,@EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样自动配置才能生效;

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}


@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

@AutoConfigurationPackage:自动配置包;
@Import(AutoConfigurationPackages.Registrar.class):Spring的底层注解@Import,给容器中导入一个组件;导入的组件由AutoConfigurationPackages.Registrar.class将主配置类(@SpringBootApplication标注的类)的所在包及下面所有子包里面的所有组件扫描到Spring容器;
@Import(EnableAutoConfigurationImportSelector.class):给容器中导入组件;
EnableAutoConfigurationImportSelector:导入哪些组件的选择器,将所有需要导入的组件以全类名的方式返回,这些组件就会被添加到容器中,会给容器中导入非常多的自动配置类(xxxAutoConfiguration),就是给容器中导入这个场景需要的所有组件,并配置好这些组件;
重点看@AutoConfigurationPackage注解和@Import(AutoConfigurationImportSelector.class)注解:
@AutoConfigurationPackage注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}

看其@Import进来的类AutoConfigurationPackages.Registrar类:

	static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata,
				BeanDefinitionRegistry registry) {
			register(registry, new PackageImport(metadata).getPackageName());
		}

		@Override
		public Set<Object> determineImports(AnnotationMetadata metadata) {
			return Collections.singleton(new PackageImport(metadata));
		}
	}

我们看registerBeanDefinitions方法中的register方法,在其方法上打断点:

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
		if (registry.containsBeanDefinition(BEAN)) {
			BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
			ConstructorArgumentValues constructorArguments = beanDefinition
					.getConstructorArgumentValues();
			constructorArguments.addIndexedArgumentValue(0,
					addBasePackages(constructorArguments, packageNames));
		}
		else {
			GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
			beanDefinition.setBeanClass(BasePackages.class);
			beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,
					packageNames);
			beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
			registry.registerBeanDefinition(BEAN, beanDefinition);
		}
	}

在这里插入图片描述

可见是在启动类中配置的scanBasePackages属性的包。
方法进入到else代码块:

			GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
			beanDefinition.setBeanClass(BasePackages.class);
			beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,
					packageNames);
			beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
			registry.registerBeanDefinition(BEAN, beanDefinition);

该步骤就是创建BeanDefinition对象,然后将其注册到BeanDefinition注册器中。我们看其注册的BeanDefinition对像的详情:
在这里插入图片描述
可以看到,这个BeanDefinition封装的是一个Package对象。所以,BeanDefinition不只能封装类的属性,还可以封装整个包的属性。
这样,就把包下的类,全部注册到了Spring容器中。
@Import(AutoConfigurationImportSelector.class):
看导入的AutoConfigurationImportSelector类:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这个方法中,将jar包中spring.factories文件信息加载到了cache缓存中。 这些配置的类,都是加入Spring容器中的。所以,配置的这些类,自动就在Spring容器中有了,无需我们再进行配置,这就是自动配置的原理。
有了自动配置类,免去了我们手动编写配置注入功能组件等的工作;
Spring Boot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值,将这些值作为自动配置类导入到容器中,自动配置类就生效,帮我们进行自动配置工作;以前我们需要自己配置的东西,自动配置类都帮我们;
J2EE的整体整合解决方案和自动配置都在spring-boot-autoconfigure-1.5.9.RELEASE.jar;

六 使用Spring Initializer快速创建Spring Boot项目

6.1 IDEA:使用 Spring Initializer快速创建项目

  1. 文件–>new–>Module
    在这里插入图片描述
  2. Spring Initializr -->选择Jdk–> 选择阿里云仓库(https://start.aliyun.com/)–>下一步在这里插入图片描述
  3. 填写组id等信息–>下一步
    在这里插入图片描述
  4. 先默认添加spring web依赖,点击下一步
    在这里插入图片描述
  5. 设置模块名称和路径
    在这里插入图片描述
  6. 项目创建成功
    在这里插入图片描述

IDE都支持使用Spring的项目创建向导快速创建一个Spring Boot项目;
选择我们需要的模块;向导会联网创建Spring Boot项目;
默认生成的Spring Boot项目;

  • 主程序已经生成好了,我们只需要我们自己的逻辑。
  • resources文件夹中目录结构:
    a) static:保存所有的静态资源; js css images。
    b) templates:保存所有的模板页面;(Spring Boot默认jar包使用嵌入式的Tomcat,默认不支持JSP页面);可以使用模板引擎(freemarker、thymeleaf)。
    c) application.properties:Spring Boot应用的配置文件;可以修改一些默认设置。

6.2 配置文件

6.2.1 配置文件

Spring Boot使用一个全局的配置文件,配置文件名是固定的:

  1. application.properties
  2. application.yml
    配置文件的作用:修改SpringBoot自动配置的默认值;SpringBoot在底层都给我们自动配置好;
    以前的配置文件;大多都使用的是 xxxx.xml文件,YAML:以数据为中心,比json、xml等更适合做配置文件。
    YAML:配置例子:
server:
	port: 8081

XML:配置例子:

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

6.2.2 YAML语法

  1. 基本语法
    k:(空格)v:表示一对键值对(空格必须有)。
    以空格的缩进来控制层级关系;只要是左对齐的一列数据,都是同一个层级的。
server:
	port: 8081
	path: /hello

属性和值也是大小写敏感。

  1. 值的写法
    字面量:普通的值(数字,字符串,布尔)。
    字符串默认不用加上单引号或者双引号。
    “”:双引号;不会转义字符串里面的特殊字符;特殊字符会作为本身想表示的意思。
name: "zhangsan \n lisi":输出;zhangsan 换行 lisi

‘’:单引号;会转义特殊字符,特殊字符最终只是一个普通的字符串数据。

name: ‘zhangsan \n lisi’:输出;zhangsan \n lisi

对象、Map(属性和值)(键值对):
k: v:在下一行来写对象的属性和值的关系;注意缩进。
对象还是k: v的方式。

friends:
		lastName: zhangsan
		age: 20

行内写法:

friends: {lastName: zhangsan,age: 18}

数组(List、Set):
用- 值表示数组中的一个元素

pets:
‐ cat
‐ dog
‐ pig

行内写法:

pets: [cat,dog,pig]

6.2.3 配置文件值注入

配置文件:application.yml

# 应用服务 WEB 访问端口
person:
  lastName : 张三
  age : 19
  boss : false
  birth : 2022-06-28
  maps :
    k1 : v1
    k2 : v2
  lists :
    - lisi
    - zhaoliu
  dog :
    name : 小狗
    age : 8
server:
  port: 8080

Person类:

package com.tedu.java.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;

/**
 * Create with IntelliJ IDEA
 *
 * @author : zyy
 * @date : 2022/6/28 16:13
 * @Description :将配置文件中配置的每一个属性的值,映射到这个组件中
 * @ConfigurationProperties:告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定;
 * prefix = "person":配置文件中哪个下面的所有属性进行一一映射
 * 只有这个组件是容器中的组件,才能容器提供的@ConfigurationProperties功能;
 */
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
    private String lastName;
    private Integer age;
    private Boolean boss;
    private String birth;
    private Map<String,Object> maps;
    private List lists;
    private Dog dog;

    public Person() {
    }

    public Person(String lastName, Integer age, Boolean boss, String birth, Map<String, Object> maps, List lists, Dog dog) {
        this.lastName = lastName;
        this.age = age;
        this.boss = boss;
        this.birth = birth;
        this.maps = maps;
        this.lists = lists;
        this.dog = dog;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public Integer getAge() {
        return age;
    }

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

    public Boolean getBoss() {
        return boss;
    }

    public void setBoss(Boolean boss) {
        this.boss = boss;
    }

    public String getBirth() {
        return birth;
    }

    public void setBirth(String 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{" +
                "lastName='" + lastName + '\'' +
                ", age=" + age +
                ", boss=" + boss +
                ", birth=" + birth +
                ", maps=" + maps +
                ", lists=" + lists +
                ", dog=" + dog +
                '}';
    }
}

Dog类:

package com.tedu.java.pojo;

import org.springframework.stereotype.Component;

/**
 * Create with IntelliJ IDEA
 *
 * @author : zyy
 * @date : 2022/6/28 16:15
 * @Description :
 */
@Component
public class Dog {
    private String name;
    private Integer age;

    public Dog() {
    }

    public Dog(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    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 +
                '}';
    }
}

若有提示:
在这里插入图片描述
其实这个并不影响代码的编译,但是如果你想去掉它,可以在pom.xml中加入spring-boot-configuration-processor依赖即可!

	<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-configuration-processor</artifactId>
	</dependency>

测试:

package com.tedu.java;

import com.tedu.java.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 Person person;

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

在这里插入图片描述
注:properties配置文件在idea中默认utf-8可能会乱码
在这里插入图片描述

6.2.4 @Value获取值和@ConfigurationProperties获取值比较

-@ConfigurationProperties@Value
功能批量注入配置文件中的属性一个个指定
松散绑定(松散语法)支持不支持
SpEL不支持支持
松散绑定(松散语法)支持不支持
复杂类型封装支持不支持
配置文件yml还是properties他们都能获取到值;
总结:
  1. 如果说,我们只是在某个业务逻辑中需要获取一下配置文件中的某项值,使用@Value;
  2. 如果说,我们专门编写了一个javaBean来和配置文件进行映射,我们就直接使用@ConfigurationProperties;

6.2.5 @PropertySource&@ImportResource&@Bean

  1. @PropertySource:加载指定的配置文件;
package com.tedu.java.pojo;

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

/**
 * Create with IntelliJ IDEA
 *
 * @author : zyy
 * @date : 2022/7/4 16:09
 * @Description :
 */
@Component
@PropertySource("classpath:cat.properties")
public class Cat {
    @Value("${cat.lastName}")
    private String lastName;
    @Value("${cat.age}")
    private Integer age;

    public Cat() {
    }

    public Cat(String lastName, Integer age) {
        this.lastName = lastName;
        this.age = age;
    }

    public String getLastName() {
        return lastName;
    }

    @Override
    public String toString() {
        return "Cat{" +
                "lastName='" + lastName + '\'' +
                ", age=" + age +
                '}';
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public Integer getAge() {
        return age;
    }

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

在resource目录下创建cat.properties文件

cat.lastName=keNi
cat.age=12

测试:

 @Autowired
 private Cat cat;
@Test
    public void test01(){
        System.out.println(cat);
    }

在这里插入图片描述

  1. @ConfigurationProperties
    @ConfigurationProperties:告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定;
    prefix = “person”:配置文件中哪个下面的所有属性进行一一映射
    只有这个组件是容器中的组件,才能容器提供的@ConfigurationProperties功能;
    @ConfigurationProperties(prefix = “person”)默认从全局配置文件中获取值;
  2. @ImportResource
    @ImportResource:导入Spring的配置文件,让配置文件里面的内容生效;
    Spring Boot里面没有Spring的配置文件,我们自己编写的配置文件,也不能自动识别;
    想让Spring的配置文件生效,加载进来;@ImportResource标注在一个配置类上
package com.tedu.java.pojo;

import org.springframework.context.annotation.ImportResource;
import org.springframework.stereotype.Component;

/**
 * Create with IntelliJ IDEA
 *
 * @author : zyy
 * @date : 2022/7/4 16:20
 * @Description :
 */
public class Pig {
    private String pName;
    private Integer pAge;

    public Pig() {
    }

    public Pig(String pName, Integer pAge) {
        this.pName = pName;
        this.pAge = pAge;
    }

    public String getpName() {
        return pName;
    }

    public void setpName(String pName) {
        this.pName = pName;
    }

    public Integer getpAge() {
        return pAge;
    }

    public void setpAge(Integer pAge) {
        this.pAge = pAge;
    }

    @Override
    public String toString() {
        return "Pig{" +
                "pName='" + pName + '\'' +
                ", pAge=" + pAge +
                '}';
    }
}

在启动类上加上@ImportResource(locations = “classpath:pig.xml”),使其配置文件生效

package com.tedu.java;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportResource;

@SpringBootApplication
@ImportResource(locations = "classpath:pig.xml")/*导入spring的配置文件让其生效*/
public class SpringBoot02ConfigApplication {

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

}

在resource目录下创建pig.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="pig" class="com.tedu.java.pojo.Pig">
        <property name="pName" value="SmallPig"></property>
        <property name="pAge" value="22"></property>
    </bean>
</beans>

测试:

@Autowired
private ApplicationContext ac;

@Test
    public void test02(){
        System.out.println(ac.getBean("pig"));
    }

在这里插入图片描述

  1. SpringBoot推荐给容器中添加组件的方式;推荐使用全注解的方式
  • 配置类@Configuration------>Spring配置文件。
  • 使用@Bean给容器中添加组件。
package com.tedu.java.pojo;

/**
 * Create with IntelliJ IDEA
 *
 * @author : zyy
 * @date : 2022/7/4 17:13
 * @Description :
 */
public class HelloService {
}

package com.tedu.java.config;

import com.tedu.java.pojo.HelloService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Create with IntelliJ IDEA
 *
 * @author : zyy
 * @date : 2022/7/4 17:14
 * @Description :@Configuration:指明当前类是一个配置类;就是来替代之前的Spring配置文件
 * 在配置文件中用<bean><bean/>标签添加组件
 */
@Configuration
public class MyConfig {
    //将方法的返回值添加到容器中;容器中这个组件默认的id就是方法名
    @Bean
    public HelloService helloService(){
        System.out.println("配置类@Bean给容器中添加组件了...");
        return new HelloService();
    }
}

@Autowired
private HelloService helloService;

@Test
    public void test03(){
        System.out.println(helloService);
    }

在这里插入图片描述

6.3 配置文件占位符

6.3.1 随机数

${random.value}、${random.int}、${random.long}
${random.int(10)}、${random.int[1024,65536]}

6.3.2 占位符获取之前配置的值,如果没有可以是用:指定默认值

person.last‐name=张三${random.uuid}
person.age=${random.int}
person.birth=2017/12/15
person.boss=false
person.maps.k1=v1
person.maps.k2=14
person.lists=a,b,c
person.dog.name=${person.hello:hello}_dog
person.dog.age=15

6.4 Profile

6.4.1 多Profile文件

我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml;
默认使用application.properties的配置;

6.4.2 yml支持多文档块方式

server:
  port: 8081
spring:
  profiles: prod

---
server:
  port: 8083
spring:
  profiles: dev

---
server:
  port: 8084
spring:
  profiles: test

6.4.3 激活指定profile

  1. 在配置文件中指定 spring.profiles.active=dev
    在这里插入图片描述
  2. 命令行:java -jar xxxx.jar --spring.profiles.active=dev;
  3. 虚拟机参数;-Dspring.profiles.active=dev
    在这里插入图片描述

6.5 配置文件加载位置

Springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件:

  1. –file:./config/
  2. –file:./
  3. –classpath:/config/
  4. –classpath:/
    优先级由高到底,高优先级的配置会覆盖低优先级的配置;
    SpringBoot会从这四个位置全部加载主配置文件,互补配置;
    我们还可以通过spring.config.location来改变默认的配置文件位置;
    项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;指定配置文件和默认加载的这些配置文件共同起作用形成互补配置;
java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar --spring.config.location=D:/application.properties

6.5.1. 外部配置加载顺序

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

  1. 命令行参数
    所有的配置都可以在命令行上进行指定:
java -jar xxx.jar --server.port=8087 --server.context-path=/abc
#多个配置用空格分开; --配置项=值
  1. 来自java:comp/env的JNDI属性。
  2. Java系统属性(System.getProperties())。
  3. 操作系统环境变量。
  4. RandomValuePropertySource配置的random.*属性值。
    由jar包外向jar包内进行寻找;优先加载带profile
  5. jar包外部的application-{profile}.properties或application.yml(带spring.profile)配置文件。
  6. jar包内部的application-{profile}.properties或application.yml(带spring.profile)配置文件。
    再来加载不带profile
  7. jar包外部的application.properties或application.yml(不带spring.profile)配置文件。
  8. jar包内部的application.properties或application.yml(不带spring.profile)配置文件。
  9. @Configuration注解类上的@PropertySource。
  10. 通过SpringApplication.setDefaultProperties指定的默认属性。
    所有支持的配置加载来源访问官方文档:https://docs.spring.io/spring-boot/docs/1.5.9.RELEASE/reference/htmlsingle/#boot-features-external-config

6.5.1 测试

分别在 –file:./config/, –file:./, –classpath:/config/,–classpath:/创建application.properties文件,端口号分别为 8084,8083,8082,8081
在这里插入图片描述resources下的application.properties加入server.context-path = /hello

server.port = 8081
server.context-path = /hello

controller下的HelloServer类

@RestController
public class HelloServer {
 
    @RequestMapping("/hello")
    public String hello(){
 
        return "配置文件具有互补性";
    }
}

访问:locahost:8084/hello 和 locahost:8084/hello/hello
在这里插入图片描述

6.5.2 源码解析

SpringBoot对于配置文件的加载是利用ConfigFileApplicationListener监听器来完成的。
对于每一个SpringBoot项目,都会有一个项目主程序启动类,在该类的main方法中调用SpringApplication.run();方法来启动SpringBoot程序,在启动过程中,便会有SpringBoot的一系列内部加载和初始化过程,因此该类对于我们的分析尤为重要。
点击进入到SpringApplication的主类中,观察其构造函数:
在这里插入图片描述
可以看到在构造器中对监听器进行了设置。其中getSpringFactoriesInstances方法用于从spring.factories文件中加载ApplicationListener实现类。该方法的加载原理如下所示,一直深入调用过程找到该方法,可以看到该方法主要就是去META-INF/spring.factories路径下加载spring.factories文件,并对文件进行解析。对该文件下的每一个接口及其实现类,以接口名为Key,包含的实现类名为value进行保存,形成一个Map>的数据结构进行返回。
在这里插入图片描述然后对调用过程回推,在对spring.factories文件加载完,并保存了文件下每个接口及其实现类的全限定类名后,getSpringFactoriesInstances方法会根据传递的参数Class type来从上面得到的Map结构中拿出该接口名对应的所有实现类的集合。并对集合中的所有实现类,利用反射生成对应的实例进行保存:
在这里插入图片描述

在这里插入图片描述
到这里我们可以理清构造函数中setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));方法的过程和作用,即在SpringBoot启动初始化时,通过读取META-INF/spring.factories文件,并对其进行解析,生成示例化对象,然后从中取出ApplicationListener接口对应的所有实现类的实例对象,注入监听器中。
经过上面的分析我们可以看到,SpringBoot在启动时会初始化一系列监听器,而这些监听器都是:ApplicationListener接口下的,因此我们取到META-INF/spring.factories看一下有哪些实现类:
在这里插入图片描述
这里最关键的实现类就是ConfigFileApplicationListener,该监听器会监听ApplicationEnvironmentPreparedEvent事件,当监听到该事件后,会调用load方法,去上面说的四个默认路径检索配置文件,如果检索到了,则进行加载封装供上层方法调用。
这是它的一个大致的整体流程,接下来我们深入源码中,按步骤对其进行分析

6.5.3 发送事件与监听器的触发

6.5.3.1 ApplicationStartingEvent事件

首先进入到run方法中:
在这里插入图片描述
该方法中首先调用getRunListeners方法,同样是从spring.factories文件中加载SpringApplicationRunListeners接口下的实现类org.springframework.boot.context.event.EventPublishingRunListener,接下来调用该监听器的starting()方法
在这里插入图片描述
该starting()方法内,会创建一个ApplicationStartingEvent的事件,并利用multicastEvent方法进行广播该事件给应用中包含的所有监听器,这里的应用就是参数this.application,也就是现在的SpringApplication,它所包含的监听器也就是上文中最初加载的ApplicationListener下的11个监听器。可以看到,每个event对象下都包含一个source源,这个源表示了事件最初在其上发生的对象,这里的source源就是SpringApplication。
在这里插入图片描述
接下来,会进入到SimpleApplicationEventMulticaster类下的multicastEvent方法,这里比较重要的一个方法就是getApplicationListeners方法,该方法内部会根据该事件的类型,以及事件所包含的源里的监听器,筛选出对该事件感兴趣的监听器集合 。
在这里插入图片描述
选出来getApplicationListeners方法内的重要方法: retrieveApplicationListeners方法,该方法就是实际检索给定事件和源类型的应用程序侦听器,返回的listeners对象即包含了监听该事件的应用程序监听器集合。
在这里插入图片描述
这里的listeners即包含了最初ApplicationListeners接口下的11个监听器。
在这里插入图片描述
方法中的supportsEvent方法即判断给定监听器是否支持给定事件(或者说是否监听该事件)。由于目前这里的eventType表示的是ApplicationStartingEvent,该事件触发的监听器包括11个中的:

  • BackgroundPreinitializer
  • org.springframework.boot.context.logging.LoggingApplicationListener
  • org.springframework.boot.context.config.DelegatingApplicationListener
  • org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
    在这里插入图片描述
    最终当getApplicationListeners方法拿到监听器对象集合后,遍历得到每个监听器,然后调用invokeListener(listener, event);方法,再利用listener.onApplicationEvent(event)方法,通过调用相应监听器的onApplicationEvent(event)方法来唤醒监听器对象,执行相应的触发操作。
    在这里插入图片描述
6.5.3.2 ApplicationEnvironmentPreparedEvent事件

执行完相应监听器的操作后,会继续回到run方法中执行prepareEnvironment方法,该方法同样是利用监听器和事件的机制,来触发监听完成环境准备的工作。
在这里插入图片描述
这里的listeners仍然是EventPublishingRunListener,因此这里的prepareEnvironment相当于是调用了该监听器的不同方法,来产生不同的事件类型,可以看到,这一次创建的事件类型为ApplicationEnvironmentPreparedEvent,也就是我们最开始说的加载配置文件的监听器所监听的事件类型,因此到这里我们就离探究配置文件加载原理又近了一步。创建该事件类型后,同样是利用multicastEvent将该事件广播给该应用程序下的所有监听器,其实它的流程就跟上面是一样的了,只是产生的事件不同。
在这里插入图片描述
因此,这里不在赘述该事件的触发流程,同样的是在retrieveApplicationListeners方法里的supportEvent方法中,筛选出支持ApplicationEnvironmentPreparedEvent事件的监听器集合并返回,而这次触发的监听器就包括了org.springframework.boot.context.config.ConfigFileApplicationListener监听器。由于监听器的真正执行是通过调用listener.onApplicationEvent(event)方法来执行的,因此我们从该方法开始分析:
在这里插入图片描述

这里loadPostProcessors方法就是从spring.factories中加载EnvironmentPostProcessor接口对应的实现类,并把当前对象也添加进去(因为ConfigFileApplicationListener也实现了EnvironmentPostProcessor接口,所以可以添加)。因此在下方遍历时,会访问该类下的postProcessEnvironment方法。
在这里插入图片描述接下来进入到postProcessEnvironment方法
在这里插入图片描述

在这里插入图片描述接下来就是要分析最重要的Loader方法 :
在这里插入图片描述
该方法中,首先SpringFactoriesLoader.loadFactories从spring.factories中加载PropertySourceLoader接口对应的实现类,也就是
在这里插入图片描述这两个实现类分别用于加载文件名后缀为properties和yaml的文件。
接下来最核心的方法是load方法,这里会最终加载我们的配置文件,因此我们进行深入探究:

private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
			getSearchLocations().forEach((location) -> {
				boolean isDirectory = location.endsWith("/");
				Set<String> names = isDirectory ? getSearchNames() : NO_SEARCH_NAMES;
				names.forEach((name) -> load(location, name, profile, filterFactory, consumer));
			});
		}

首先调用了getSearchLocations方法

//获得加载配置文件的路径
//可以通过spring.config.location配置设置路径,如果没有配置,则使用默认
//默认路径由DEFAULT_SEARCH_LOCATIONS指定:

// CONFIG_ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location"
// CONFIG_LOCATION_PROPERTY = "spring.config.location";
// DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/"
private Set<String> getSearchLocations() {
            Set<String> locations = getSearchLocations(CONFIG_ADDITIONAL_LOCATION_PROPERTY);
            if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
                locations.addAll(getSearchLocations(CONFIG_LOCATION_PROPERTY));
            }
            else {
                locations.addAll(
                        asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS));
            }
            return locations;
        }

该方法用于获取配置文件的路径,如果利用spring.config.location指定了配置文件路径,则根据该路径进行加载。否则则根据默认路径加载,而默认路径就是我们最初提到的那四个路径。接下来,再深入asResolvedSet方法内部分析一下:

private Set<String> asResolvedSet(String value, String fallback) {
   List<String> list = Arrays.asList(StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(
         (value != null) ? this.environment.resolvePlaceholders(value) : fallback)));
   Collections.reverse(list);
   return new LinkedHashSet<>(list);
}

这里的value表示ConfigFileApplicationListener初始化时设置的搜索路径,而fallback就是DEFAULT_SEARCH_LOCATIONS默认搜索路径。StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray())方法就是以逗号作为分隔符对"classpath:/,classpath:/config/,file:./,file:./config/"进行切割,并返回一个字符数组。而这里的Collections.reverse(list);之后,就是体现优先级的时候了,先被扫描到的配置文件会优先生效。
这里我们拿到搜索路径之后,load方法里对每个搜索路径进行遍历,首先调用了getSearchNames()方法:

// 返回所有要检索的配置文件前缀
// CONFIG_NAME_PROPERTY = "spring.config.name"
// DEFAULT_NAMES = "application"
private Set<String> getSearchNames() {
            if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
                String property = this.environment.getProperty(CONFIG_NAME_PROPERTY);
                return asResolvedSet(property, null);
            }
            return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
        }

该方法中如果我们通过spring.config.name设置了要检索的配置文件前缀,会按设置进行加载,否则加载默认的配置文件前缀即application。
拿到所有需要加载的配置文件前缀后,则遍历每个需要加载的配置文件,进行搜索加载,加载过程如下:

private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
                DocumentConsumer consumer) {
            //下面的if分支默认是不走的,除非我们设置spring.config.name为空或者null
            //或者是spring.config.location指定了配置文件的完整路径,也就是入参location的值
            if (!StringUtils.hasText(name)) {
                for (PropertySourceLoader loader : this.propertySourceLoaders) {
                    //检查配置文件名的后缀是否符合要求,
                    //文件名后缀要求是properties、xml、yml或者yaml
                    if (canLoadFileExtension(loader, location)) {
                        load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
                        return;
                    }
                }
                throw new IllegalStateException("File extension of config file location '" + location
                        + "' is not known to any PropertySourceLoader. If the location is meant to reference "
                        + "a directory, it must end in '/'");
            }
            Set<String> processed = new HashSet<>();
            //propertySourceLoaders属性是在Load类的构造方法中设置的,可以加载文件后缀为properties、xml、yml或者yaml的文件
            for (PropertySourceLoader loader : this.propertySourceLoaders) {
                for (String fileExtension : loader.getFileExtensions()) {
                    if (processed.add(fileExtension)) {
                        loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory,
                                consumer);
                    }
                }
            }
        }

关注下面的两个for循环,this.propertySourceLoaders既包含了上面提到的两个PropertiesPropertySourceLoader和YamlPropertySourceLoader,PropertiesPropertySourceLoader可以加载文件扩展名为properties和xml的文件,YamlPropertySourceLoader可以加载文件扩展名为yml和yaml的文件。获取到搜索路径、文件名和扩展名后,就可以到对应的路径下去检索配置文件并加载了。

private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension,
      Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
   DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null);
   DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);
   if (profile != null) {
      // Try profile-specific file & profile section in profile file (gh-340)
       //在文件名上加上profile值,之后调用load方法加载配置文件,入参带有过滤器,可以防止重复加载
      String profileSpecificFile = prefix + "-" + profile + fileExtension;
      load(loader, profileSpecificFile, profile, defaultFilter, consumer);
      load(loader, profileSpecificFile, profile, profileFilter, consumer);
      // Try profile specific sections in files we've already processed
      for (Profile processedProfile : this.processedProfiles) {
         if (processedProfile != null) {
            String previouslyLoaded = prefix + "-" + processedProfile + fileExtension;
            load(loader, previouslyLoaded, profile, profileFilter, consumer);
         }
      }
   }
   // Also try the profile-specific section (if any) of the normal file
    //加载不带profile的配置文件
   load(loader, prefix + fileExtension, profile, profileFilter, consumer);
}
// 加载配置文件
private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter,
                DocumentConsumer consumer) {
            try {
                //调用Resource类到指定路径加载配置文件
                // location比如file:./config/application.properties
                Resource resource = this.resourceLoader.getResource(location);
                if (resource == null || !resource.exists()) {
                    if (this.logger.isTraceEnabled()) {
                        StringBuilder description = getDescription("Skipped missing config ", location, resource,
                                profile);
                        this.logger.trace(description);
                    }
                    return;
                }
                if (!StringUtils.hasText(StringUtils.getFilenameExtension(resource.getFilename()))) {
                    if (this.logger.isTraceEnabled()) {
                        StringBuilder description = getDescription("Skipped empty config extension ", location,
                                resource, profile);
                        this.logger.trace(description);
                    }
                    return;
                }
                String name = "applicationConfig: [" + location + "]";
                //读取配置文件内容,将其封装到Document类中,解析文件内容主要是找到
                //配置spring.profiles.active和spring.profiles.include的值
                List<Document> documents = loadDocuments(loader, name, resource);
                //如果文件没有配置数据,则跳过
                if (CollectionUtils.isEmpty(documents)) {
                    if (this.logger.isTraceEnabled()) {
                        StringBuilder description = getDescription("Skipped unloaded config ", location, resource,
                                profile);
                        this.logger.trace(description);
                    }
                    return;
                }
                List<Document> loaded = new ArrayList<>();
                //遍历配置文件,处理里面配置的profile
                for (Document document : documents) {
                    if (filter.match(document)) {
                        //将配置文件中配置的spring.profiles.active和
                        //spring.profiles.include的值写入集合profiles中,
                        //上层调用方法会读取profiles集合中的值,并读取对应的配置文件
                        //addActiveProfiles方法只在第一次调用时会起作用,里面有判断
                        addActiveProfiles(document.getActiveProfiles());
                        addIncludedProfiles(document.getIncludeProfiles());
                        loaded.add(document);
                    }
                }
                Collections.reverse(loaded);
                if (!loaded.isEmpty()) {
                    loaded.forEach((document) -> consumer.accept(profile, document));
                    if (this.logger.isDebugEnabled()) {
                        StringBuilder description = getDescription("Loaded config file ", location, resource, profile);
                        this.logger.debug(description);
                    }
                }
            }
            catch (Exception ex) {
                throw new IllegalStateException("Failed to load property source from location '" + location + "'", ex);
            }
        }

该方法首先调用this.resourceLoader.getResource(location);用来判断location路径下的文件是否存在,如果存在,会调用loadDocuments方法对配置文件进行加载:

private List<Document> loadDocuments(PropertySourceLoader loader, String name, Resource resource)
                throws IOException {
            DocumentsCacheKey cacheKey = new DocumentsCacheKey(loader, resource);
            List<Document> documents = this.loadDocumentsCache.get(cacheKey);
            if (documents == null) {
                List<PropertySource<?>> loaded = loader.load(name, resource);
                documents = asDocuments(loaded);
                this.loadDocumentsCache.put(cacheKey, documents);
            }
            return documents;
        }

再内部根据不同的PropertySourceLoader调用相应的load方法和loadProperties(resource)方法

public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
        Map<String, ?> properties = loadProperties(resource);
        if (properties.isEmpty()) {
            return Collections.emptyList();
        }
        return Collections
                .singletonList(new OriginTrackedMapPropertySource(name, Collections.unmodifiableMap(properties), true));
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    private Map<String, ?> loadProperties(Resource resource) throws IOException {
        String filename = resource.getFilename();
        if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) {
            return (Map) PropertiesLoaderUtils.loadProperties(resource);
        }
        return new OriginTrackedPropertiesLoader(resource).load();
    }

由于我们目前的配置文件只有application.properties,也就是文件结尾不是以xml作为扩展名。因此loadProperties方法会进入到new OriginTrackedPropertiesLoader。因此再进入到new OriginTrackedPropertiesLoader(resource).load();。

Map<String, OriginTrackedValue> load(boolean expandLists) throws IOException {
        try (CharacterReader reader = new CharacterReader(this.resource)) {
            Map<String, OriginTrackedValue> result = new LinkedHashMap<>();
            StringBuilder buffer = new StringBuilder();
            while (reader.read()) {
                String key = loadKey(buffer, reader).trim();
                if (expandLists && key.endsWith("[]")) {
                    key = key.substring(0, key.length() - 2);
                    int index = 0;
                    do {
                        OriginTrackedValue value = loadValue(buffer, reader, true);
                        put(result, key + "[" + (index++) + "]", value);
                        if (!reader.isEndOfLine()) {
                            reader.read();
                        }
                    }
                    while (!reader.isEndOfLine());
                }
                else {
                    OriginTrackedValue value = loadValue(buffer, reader, false);
                    put(result, key, value);
                }
            }
            return result;
        }
    }
CharacterReader(Resource resource) throws IOException {
            this.reader = new LineNumberReader(
                    new InputStreamReader(resource.getInputStream(), StandardCharsets.ISO_8859_1));
        }

private String loadKey(StringBuilder buffer, CharacterReader reader) throws IOException {
        buffer.setLength(0);
        boolean previousWhitespace = false;
        while (!reader.isEndOfLine()) {
            // 判断读取到的字节是否为'=' 或者为 ':',如果是则直接返回读取都的buffer内容
            if (reader.isPropertyDelimiter()) {
                reader.read();
                return buffer.toString();
            }
            if (!reader.isWhiteSpace() && previousWhitespace) {
                return buffer.toString();
            }
            previousWhitespace = reader.isWhiteSpace();
            buffer.append(reader.getCharacter());
            reader.read();
        }
        return buffer.toString();
    }

private OriginTrackedValue loadValue(StringBuilder buffer, CharacterReader reader, boolean splitLists)
            throws IOException {
        buffer.setLength(0);
        while (reader.isWhiteSpace() && !reader.isEndOfLine()) {
            reader.read();
        }
        Location location = reader.getLocation();
        while (!reader.isEndOfLine() && !(splitLists && reader.isListDelimiter())) {
            buffer.append(reader.getCharacter());
            reader.read();
        }
        Origin origin = new TextResourceOrigin(this.resource, location);
        return OriginTrackedValue.of(buffer.toString(), origin);
    }

在这个方法里,首先CharacterReader方法将我们的resource也就是配置文件转为了输入流,然后利用reader.read()进行读取,在loadKey方法中我们看到,这里判断读取到的是否为’=’ 或者为 ‘:’,也就是我们在配置文件中以’=‘或者’:'分割的key-value。因此看到这里,我们可以直观的感受到这里应该是读取配置文件,并切分key和value的地方。
最终,对配置文件读取完成后,会将其以key-value的形式封装到一个Map集合中进行返回,然后封装到OriginTrackedMapPropertySource中作为一个MapPropertySource对象。再层层往上回退发现会最终封装成一个asDocuments(loaded);Document对象。最后回到最上层的load方法中,loadDocuments(loader, name, resource);方法即返回我们加载好的配置文件Document对象集合。并对集合中的每一个配置文件document对象进行遍历,调用loaded.forEach((document) -> consumer.accept(profile, document));

6.5.3.3 总结
  1. SpringBoot在启动加载时,会利用事件-监听器模式,就像发布-订阅模式,在不同的阶段利用不同的事件唤醒相应的监听器执行对应的操作。对于配置文件加载关键的监听器是ConfigFileApplicationListener,该监听器会监听ApplicationEnvironmentPreparedEvent事件。
  2. 每个事件event都会包含一个source源来表示该事件最先发生在其上的对象,ApplicationEnvironmentPreparedEvent事件包含的source源是SpringApplication,包含了一组listeners监听器。SpringBoot会根据事件对监听器进行筛选,只筛选出那些支持该事件的监听器,并调用方法唤醒这些监听器执行相应逻辑。
  3. 当ApplicationEnvironmentPreparedEvent事件发生时,会唤醒ConfigFileApplicationListener监听器执行相应逻辑。最主要的加载方法load中,首先会获取到配置文件的搜索路径。如果设置了spring.config.location则会去指定目录下搜索,否则就去默认的搜索目录下classpath:/,classpath:/config/,file:./,file:./config/。
  4. 拿到所有待搜索目录后,遍历每个目录获取需要加载的配置文件。如果指定了spring.config.name,则加载指定名称的配置文件。否则使用默认的application作为配置文件的前缀名。然后,会利用PropertiesPropertySourceLoader和YamlPropertySourceLoader加载后缀名为properties、xml、yml或者yaml的文件。
  5. 拿到文件目录和文件名后,就可以去对应的路径下加载配置文件了。核心的过程是利用输入流读取配置文件,并根据读到的分隔符进行判断来切分配置文件的key和value。并将内容以key-value键值对的形式封装成一个OriginTrackedMapPropertySource,最后再将一个个配置文件封装成Document。最后遍历这些Documents,调用consumer.accept(profile, document));供上层调用访问。

七 自动配置原理

7.1 自动配置原理

springboot自动配置关键在于@SpringBootApplication注解,启动类之所以作为项目启动的入口,也是因为该注解。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
}

前四个注解:是元注解,用来修饰当前注解,没有实际功能,实际上重要的是剩余三个注解。

7.1.1 @ComponentScan

@ComponentScan的功能其实就是自动扫描加载符合条件的组件(@Component,@Service,@Repository)或者用@Bean修饰加入到IOC容器中的组件。
我们可以通过basePackages等属性定制@ComponentScan自动扫描的范围,如果不指定,则默认会从启动类所在包开始扫描。

7.1.2 @SpringBootConfiguration注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {

起作用的只有@Configuration,所以@SpringBootConfiguration功能和@Configuration一样,将被注解类作为配置类,并交给spring容器管理。

7.1.3 @EnableAutoConfiguration注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
 ...
}

除去元注解,还剩@AutoConfigurationPackage和@Import(AutoConfigurationImportSelector.class)

  • @AutoConfigurationPackage
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

	/**
	 * Base packages that should be registered with {@link AutoConfigurationPackages}.
	 * <p>
	 * Use {@link #basePackageClasses} for a type-safe alternative to String-based package
	 * names.
	 * @return the back package names
	 * @since 2.3.0
	 */
	String[] basePackages() default {};

	/**
	 * Type-safe alternative to {@link #basePackages} for specifying the packages to be
	 * registered with {@link AutoConfigurationPackages}.
	 * <p>
	 * Consider creating a special no-op marker class or interface in each package that
	 * serves no purpose other than being referenced by this attribute.
	 * @return the base package classes
	 * @since 2.3.0
	 */
	Class<?>[] basePackageClasses() default {};
}

起作用的是@Import(AutoConfigurationPackages.Registrar.class),继续查看源码:

/**
	 * {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
	 * configuration.
	 */
	static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
			register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
		}

		@Override
		public Set<Object> determineImports(AnnotationMetadata metadata) {
			return Collections.singleton(new PackageImports(metadata));
		}
	}

我们是从启动类的@SpringBootApplication开始,所以元注解metadata中的数据可以定位到该启动类,而new PackageImports(metadata).getPackageNames()结果就是启动类所在的包,即在此包下springboot才会扫描各种文件,从而达到自动配置的目的。这也是为什么文件不能放在启动类上级目录中。

  • @Import(AutoConfigurationImportSelector.class)
    AutoConfigurationImportSelector从字面意思看是自动配置导入选择器,即批量选择自动导入的内容。
  1. getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件。
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

	private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry();

	private static final String[] NO_IMPORTS = {};

	private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);

	private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";

	private ConfigurableListableBeanFactory beanFactory;

	private Environment environment;

	private ClassLoader beanClassLoader;

	private ResourceLoader resourceLoader;

	private ConfigurationClassFilter configurationClassFilter;

	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

  1. 调用List configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类。
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = getConfigurationClassFilter().filter(configurations);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}
  1. 利用工厂加载 Map<String, List> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件。
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();

                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;

                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryImplementationName = var9[var11];
                            result.add(factoryTypeName, factoryImplementationName.trim());
                        }
                    }
                }

                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }
  1. 从META-INF/spring.factories位置来加载一个文件。默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件。
    文件里面写死了spring-boot一启动就要给容器中加载的所有配置类:
# AutoConfigureCache auto-configuration imports
org.springframework.boot.test.autoconfigure.core.AutoConfigureCache=\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration

# AutoConfigureDataJdbc auto-configuration imports
org.springframework.boot.test.autoconfigure.data.jdbc.AutoConfigureDataJdbc=\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration

# AutoConfigureDataJpa auto-configuration imports
org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureDataJpa=\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration

# AutoConfigureDataLdap auto-configuration imports
org.springframework.boot.test.autoconfigure.data.ldap.AutoConfigureDataLdap=\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration

# AutoConfigureDataMongo auto-configuration imports
org.springframework.boot.test.autoconfigure.data.mongo.AutoConfigureDataMongo=\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration

# AutoConfigureDataNeo4j auto-configuration imports
org.springframework.boot.test.autoconfigure.data.neo4j.AutoConfigureDataNeo4j=\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration

# AutoConfigureDataR2dbc auto-configuration imports
org.springframework.boot.test.autoconfigure.data.r2dbc.AutoConfigureDataR2dbc=\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration

# AutoConfigureDataRedis auto-configuration imports
org.springframework.boot.test.autoconfigure.data.redis.AutoConfigureDataRedis=\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration

# AutoConfigureJdbc auto-configuration imports
org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureJdbc=\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration

# AutoConfigureTestDatabase auto-configuration imports
org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase=\
org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

# AutoConfigureJooq auto-configuration imports
org.springframework.boot.test.autoconfigure.jooq.AutoConfigureJooq=\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration

# AutoConfigureJson auto-configuration imports
org.springframework.boot.test.autoconfigure.json.AutoConfigureJson=\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration

# AutoConfigureJsonTesters auto-configuration imports
org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters=\
org.springframework.boot.test.autoconfigure.json.JsonTestersAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration

# AutoConfigureWebClient auto-configuration imports
org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient=\
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
org.springframework.boot.test.autoconfigure.web.reactive.WebTestClientAutoConfiguration

# AutoConfigureWebFlux auto-configuration imports
org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebFlux=\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration

# AutoConfigureMockMvc auto-configuration imports
org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc=\
org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration,\
org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebClientAutoConfiguration,\
org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebDriverAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityConfiguration

# AutoConfigureMockRestServiceServer
org.springframework.boot.test.autoconfigure.web.client.AutoConfigureMockRestServiceServer=\
org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerAutoConfiguration

# AutoConfigureRestDocs auto-configuration imports
org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs=\
org.springframework.boot.test.autoconfigure.restdocs.RestDocsAutoConfiguration

# AutoConfigureTestEntityManager auto-configuration imports
org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureTestEntityManager=\
org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManagerAutoConfiguration

# AutoConfigureWebClient auto-configuration imports
org.springframework.boot.test.autoconfigure.web.client.AutoConfigureWebClient=\
org.springframework.boot.test.autoconfigure.web.client.WebClientRestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration

# AutoConfigureWebMvc auto-configuration imports
org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc=\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration

# AutoConfigureWebServiceClient
org.springframework.boot.test.autoconfigure.webservices.client.AutoConfigureWebServiceClient=\
org.springframework.boot.test.autoconfigure.webservices.client.WebServiceClientTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration

# AutoConfigureMockWebServiceServer
org.springframework.boot.test.autoconfigure.webservices.client.AutoConfigureMockWebServiceServer=\
org.springframework.boot.test.autoconfigure.webservices.client.MockWebServiceServerAutoConfiguration

# DefaultTestExecutionListenersPostProcessors
org.springframework.boot.test.context.DefaultTestExecutionListenersPostProcessor=\
org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener$PostProcessor

# Spring Test ContextCustomizerFactories
org.springframework.test.context.ContextCustomizerFactory=\
org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory,\
org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizerFactory,\
org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizerFactory,\
org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory

# Test Execution Listeners
org.springframework.test.context.TestExecutionListener=\
org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener,\
org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener,\
org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener,\
org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener,\
org.springframework.boot.test.autoconfigure.webservices.client.MockWebServiceServerTestExecutionListener


7.2 按需开启自动配置项

虽然我们127个场景的所有自动配置启动的时候默认全部加载。但xxxxAutoConfiguration按照条件装配规则(@Conditional),最终会按需配置。
需要注意的是,SpringBoot默认会在底层配好所有的组件。但是如果用户自己配置了以用户的优先。

@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
}

自动配置类xxxxxAutoConfiguration ---> 组件 ---> xxxxProperties里面拿值 ----> application.properties

7.3 HttpEncodingAutoConfiguration(Http编码自动配置)

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

/*
 * Copyright 2012-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.autoconfigure.web.servlet;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.boot.web.servlet.server.Encoding;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.filter.CharacterEncodingFilter;

/**
 * {@link EnableAutoConfiguration Auto-configuration} for configuring the encoding to use
 * in web applications.
 *
 * @author Stephane Nicoll
 * @author Brian Clozel
 * @since 2.0.0
 */
 //表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件
@Configuration(proxyBeanMethods = false)
/*启动指定类的,ConfigurationProperties功能;将配置文件中对应的值和HttpEncodingProperties绑定起来;并把
HttpEncodingProperties加入到ioc容器中*/
@EnableConfigurationProperties(ServerProperties.class)
/*Spring底层@Conditional注解(Spring注解版),根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效; 判断当前应用是否是web应用,如果是,当前配置类生效*/
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
判断当前项目有没有这个类 CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass(CharacterEncodingFilter.class)
/* 判断配置文件中是否存在某个配置 spring.http.encoding.enabled;如果不存在,判断也是成立的;
即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
*/
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
	//已经和SpringBoot的配置文件映射了
	private final Encoding properties;
	//只有一个有参构造器的情况下,参数的值就会从容器中拿
	public HttpEncodingAutoConfiguration(ServerProperties properties) {
		this.properties = properties.getServlet().getEncoding();
	}
	//给容器中添加一个组件,这个组件的某些值需要从properties中获取
	@Bean
	@ConditionalOnMissingBean//判断容器没有这个组件
	public CharacterEncodingFilter characterEncodingFilter() {
		CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
		filter.setEncoding(this.properties.getCharset().name());
		filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
		filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
		return filter;
	}

	@Bean
	public LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
		return new LocaleCharsetMappingsCustomizer(this.properties);
	}

	static class LocaleCharsetMappingsCustomizer
			implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {

		private final Encoding properties;

		LocaleCharsetMappingsCustomizer(Encoding properties) {
			this.properties = properties;
		}

		@Override
		public void customize(ConfigurableServletWebServerFactory factory) {
			if (this.properties.getMapping() != null) {
				factory.setLocaleCharsetMappings(this.properties.getMapping());
			}
		}
		@Override
		public int getOrder() {
			return 0;
		}
	}
}

一但这个配置类生效;这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties类中获取
的,这些类里面的每一个属性又是和配置文件绑定的;
所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;配置文件能配置什么就可以参照某个功
能对应的这个属性类。

@ConfigurationProperties(prefix = "spring.http.encoding") //从配置文件中获取指定的值和bean的属
性进行绑定
public class HttpEncodingProperties {
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF‐8");
}

7.4 细节

7.4.1 @Conditional派生注解(Spring注解版原生的@Conditional作用)

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

@Conditional扩展注解作用(判断是否满足当前指定条件)
@ConditionalOnJava系统的java版本是否符合要求
@ConditionalOnBean容器中存在指定Bean;
@ConditionalOnMissingBean容器中不存在指定Bean;
@ConditionalOnExpression满足SpEL表达式指定
@ConditionalOnClass系统中有指定的类
@ConditionalOnMissingClass系统中没有指定的类
@ConditionalOnSingleCandidate容器中只有一个指定的Bean,或者这个Bean是首选Bean
@ConditionalOnProperty系统中指定的属性是否有指定的值
@ConditionalOnResource类路径下是否存在指定资源文件
@ConditionalOnWebApplication当前是web环境
@ConditionalOnNotWebApplication当前不是web环境
@ConditionalOnJndiJNDI存在指定项

7.5 总结

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

八 日志

8.1 日志框架

市面上的日志框架:
JUL、JCL、Jboss-logging、logback、log4j、log4j2、slf4j…

日志门面 (日志的抽象层)日志实现
JCL(Jakarta Commons Logging) SLF4j(Simple LoggingFacade for Java) jboss-loggingLog4j JUL(java.util.logging)Log4j2 Logback

SpringBoot:底层是Spring框架,Spring框架默认是用JCL;
SpringBoot选用 SLF4j和logback;

8.2 SLF4j使用

8.2.1 如何在系统中使用SLF4j

参考链接:https://www.slf4j.org/

以后开发的时候,日志记录方法的调用,不应该来直接调用日志的实现类,而是调用日志抽象层里面的方法;
给系统里面导入slf4j的jar和 logback的实现jar

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(HelloWorld.class);
logger.info("Hello World");
}
}

图示:
在这里插入图片描述
每一个日志的实现框架都有自己的配置文件。使用slf4j以后,配置文件还是做成日志实现框架自己本身的配置文件;

8.2.2 不同的框架使用统一日志

a(slf4j+logback): Spring(commons-logging)、Hibernate(jboss-logging)、MyBatis、xxxx统一日志记录
在这里插入图片描述
如何让系统中所有的日志都统一到slf4j?

  1. 将系统中其他日志框架先排除出去;
  2. 用中间包来替换原有的日志框架;
  3. 导入slf4j其他的实现;

8.3 SpringBoot日志关系

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

SpringBoot使用它来做日志功能:

<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring‐boot‐starter‐logging</artifactId>
</dependency>

底层依赖关系:
在这里插入图片描述总结:

  • SpringBoot底层也是使用slf4j+logback的方式进行日志记录;
  • SpringBoot也把其他的日志都替换成了slf4j;
  • 中间替换包;
@SuppressWarnings("rawtypes")
public abstract class LogFactory {
		static String UNSUPPORTED_OPERATION_IN_JCL_OVER_SLF4J =
"http://www.slf4j.org/codes.html#unsupported_operation_in_jcl_over_slf4j";
		static LogFactory logFactory = new SLF4JLogFactory();

在这里插入图片描述

  • 如果我们要引入其他框架?一定要把这个框架的默认日志依赖移除掉。
    Spring框架用的是commons-logging;
<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring‐core</artifactId>
<exclusions>
		<exclusion>
			<groupId>commons‐logging</groupId>
			<artifactId>commons‐logging</artifactId>
		</exclusion>
</exclusions>
</dependency>

SpringBoot能自动适配所有的日志,而且底层使用slf4j+logback的方式记录日志,引入其他框架的时候,只需要把这个框架依赖的日志框架排除掉即可;

8.4 日志使用

8.4.1 默认配置

SpringBoot默认帮我们配置好了日志:

 Logger logger = LoggerFactory.getLogger(getClass());
    @Test
    void contextLoads() {
        //日志的级别;
//由低到高 trace<debug<info<warn<error
//可以调整输出的日志级别;日志就只会在这个级别以以后的高级别生效
        logger.trace("这是trace日志...");
        logger.debug("这是debug日志...");
//SpringBoot默认给我们使用的是info级别的,没有指定级别的就用SpringBoot默认规定的级别;root级别
        logger.info("这是info日志...");
        logger.warn("这是warn日志...");
        logger.error("这是error日志...");
    }

在这里插入图片描述SpringBoot修改日志的默认配置:

ogging.level.com.atguigu=trace
#logging.path=
# 不指定路径在当前项目下生成springboot.log日志
# 可以指定完整的路径;
#logging.file=G:/springboot.log
# 在当前磁盘的根路径下创建spring文件夹和里面的log文件夹;使用 spring.log 作为默认文件
logging.file.path=/spring/log

在这里插入图片描述

8.4.2 指定配置

给类路径下放上每个日志框架自己的配置文件即可;SpringBoot就不使用他默认配置的了。

Logging SystemCustomization
Logbacklogback-spring.xml , logback-spring.groovy , logback.xml or logback.groovy
Log4j2log4j2-spring.xml or log4j2.xml
JDK (Java Util Logging)logging.properties

logback.xml:直接就被日志框架识别了;
logback-spring.xml:日志框架就不直接加载日志的配置项,由SpringBoot解析日志配置,可以使用SpringBoot的高级Profile功能;

<springProfile name="staging">
<!‐‐ configuration to be enabled when the "staging" profile is active ‐‐>
可以指定某段配置只在某个环境下生效
</springProfile>
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<!‐‐
日志输出格式:
%d表示日期时间,
%thread表示线程名,
%‐5level:级别从左显示5个字符宽度
%logger{50} 表示logger名字最长50个字符,否则按照句点分割。
%msg:日志消息,
%n是换行符
‐‐>
<layout class="ch.qos.logback.classic.PatternLayout">
<springProfile name="dev">
<pattern>%d{yyyy‐MM‐dd HH:mm:ss.SSS} ‐‐‐‐> [%thread] ‐‐‐> %‐5level
%logger{50} ‐ %msg%n</pattern>
</springProfile>
<springProfile name="!dev">
<pattern>%d{yyyy‐MM‐dd HH:mm:ss.SSS} ==== [%thread] ==== %‐5level
%logger{50} ‐ %msg%n</pattern>
</springProfile>
</layout>
</appender>

如果使用logback.xml作为日志配置文件,还要使用profile功能,会有以下错误:

no applicable action for [springProfile]

8.5 切换日志框架

可以按照slf4j的日志适配图,进行相关的切换;
slf4j+log4j的方式:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring‐boot‐starter‐web</artifactId>
	<exclusions>
		<exclusion>
			<artifactId>logback‐classic</artifactId>
			<groupId>ch.qos.logback</groupId>
		</exclusion>
		<exclusion>
			<artifactId>log4j‐over‐slf4j</artifactId>
			<groupId>org.slf4j</groupId>
		</exclusion>
	</exclusions>
</dependency>

<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j‐log4j12</artifactId>
</dependency>

切换为log4j2:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring‐boot‐starter‐web</artifactId>
		<exclusions>
			<exclusion>
				<artifactId>spring‐boot‐starter‐logging</artifactId>
				<groupId>org.springframework.boot</groupId>
			</exclusion>
		</exclusions>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring‐boot‐starter‐log4j2</artifactId>
</dependency>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值