二、SpringBoot框架之配置文件

1,025 阅读16分钟

1. 配置文件

Spring Boot使用一个全局的配置文件,配置文件的名称是固定的。以下两类配置文件都可以被Spring Boot加载

  • application.properties
  • application.yml

配置文件的作用:修改Spring Boot自动配置的默认值,Spring Boot会加载配置文件并帮我们自动配置好。

配置文件一般放置在 src/main/resources 目录或者 类路径下/config

2. YAML相关

2.1 YAML简介

YAML:YAML Ain't Markup Language(不是一种标记语言),还有另一种解释Yet Another Markup Language"(仍是一种标记语言)。YAML作为一种标记语言用于书写配置文件,YAML不同于以往的 xxx.xml 书写的配置文件,YAML以数据为中心,比json、xml等更适合作配置文件,书写的配置文件为 xxx.ymlxxx.yaml

YAML与XML书写配置对比

YAML写法

server:
  port: 8081

XML写法

<server>
    <prot>8081</prot>
</server>

YAML优点:

  • YAML易于人们阅读。
  • YAML数据在编程语言之间是可移植的。
  • YAML匹配敏捷语言的本机数据结构。
  • YAML具有一致的模型来支持通用工具。
  • YAML支持单程处理。
  • YAML具有表现力和可扩展性。
  • YAML易于实现和使用。

2.2 YAML语法

2.2.1 基本语法

基本写法:K:(空格)V 表示一个键值对(注意空格)

  • 使用缩进表示层级关系
  • 缩进时只能使用空格,不能使用Tab缩进
  • 缩进的空格数目不重要,只要相同层级元素左对齐即可
  • 大小写敏感
  • 松散表示,java中对于驼峰命名法,可用原名或使用-代替驼峰。如java中的lastName属性,在yml中使用lastName或 last-name都可正确映射。

2.2.2 值的写法

① 字面量:普通的值(数字,字符串,布尔)

k: v 直接书写,注意空格。

字符串可以写成多行,从第二行开始,必须有一个单空格缩进。换行符会被转为空格

字符串默认不需要加单引号或双引号,引号有不同的意思,不加引号相当于使用单引号。

  • "" 双引号:不会转义字符串里的特殊字符,特殊字符会表示原本的意思。

    例:name: "zhangsan \\ wangwu" 输出:zhangsan \ lisi

  • '' 单引号:会转义字符串里的特殊字符。特殊字符最终只是一个普通的字符串数据。

    例:name: 'zhangsan \\ lisi' 输出:zhangsan \\ lisi

代码示例:

name: zhangsan
age: 12
birthday: 2020/10/14

② 对象、Map(属性和值)(键值对)

  • 对象的一组键值对集合,使用 : 分隔。冒号后加空格来分开键值;

  • 下一行开始写属性和值的对应关系,缩进时要保持左侧对齐;

  • 也可以写在同一行,属性用 {} 包起来,用 , 进行属性分割;

代码示例:

person:
  name: lisi
  age: 23
  sex: male
  birthday: 2020/10/12
dog:
  name: huahua
  age: 2

行内写法:

person: {name: lisi, age: 23, sex: flmale, birthday: 2020/10/12}, dog: {name: huahua, age: 2}

③ 数组(List、Set)

- 表示数组中的一个元素,一组以 开头的行构成一个数组;

也可以使用行内写法,行内写法用 [] 包起来;

代码示例:

list:
-aaa
-bbb
-ccc

行内写法

list:[aaa, bbb, ccc]

④ 文档

多个文档之间用 --- 分隔

代码示例:

server:
  port: 8081
spring:
  profiles:
    active: prod

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

---
server:
  port:8084
spring:
  profiles: prod

YAML语法检验测试地址:YAML语法检验

3. 配置文件值注入

3.1 属性注入

两种方式通过 @Value 注解和 @ConfigurationProperties 注解,均可以从配置文件中获取到值。

@ConfigurationProperties:告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定;

@Value:为属性注入一个值;

两种方式获取值比较

@ConfigurationProperties@Value
功能批量注入配置文件中的属性单个指定
松散绑定(松散语法)支持不支持
SpEL不支持支持
JSR303数据校验支持不支持
复杂类型封装支持不支持

无论配置文件时yml还是properties都可以获取到值;

当我们某个业务逻辑只是需要获取配置文件中的某项值,则使用 @Value

当我们编写了一个javaBean和配置文件进行映射时,需要批量注入属性时,使用 @ConfigurationProperties

使用 @Valiadated 注解对javabean进行配置文件注入数据校验:

@Component
@ConfigurationProperties(prefix = "person")
@Validated
public class Person {

//    @Value("${person.last-name}")
    @Email(message = "邮箱验证失败")
    private String lastName;

//    @Value("#{11*2}")
    private Integer age;
    private Boolean boss;

    private Date birthday;
    private Map<String, Object> maps;
    private List<Object> lists;
    private Dog dog;

    private Map<String, Object> mapsInline;
    private List<Object> listsInline;
}

关于 @Validatede 注解的使用,此处不进行详细的介绍,会在后续的博客中进行详细的介绍。

3.2 加载配置文件

@PropertySource@ImportResource 注解可以用来加载配置文件。

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

/**
* 将配置文件中配置的每一个属性的值,映射到这个组件中
* @ConfigurationProperties:告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定;
* prefix = "person":配置文件中person下面的所有属性和类中的属性进行一一映射
*
* 只有这个组件是容器中的组件,才能容器提供的@ConfigurationProperties功能;
* @ConfigurationProperties(prefix = "person")默认从全局配置文件中获取值;
*
*/
@PropertySource(value = {"classpath:person.properties"})
@Component
@ConfigurationProperties(prefix = "person")
//@Validated
public class Person {

//    @Value("${person.last-name}")
//    @Email(message = "邮箱验证失败")
    private String lastName;

//    @Value("#{11*2}")
    private Integer age;
    private Boolean boss;

    private Date birthday;
    private Map<String, Object> maps;
    private List<Object> lists;
    private Dog dog;

    private Map<String, Object> mapsInline;
    private List<Object> listsInline;
}

@ImportResource:导入Spring的配置文件,让Spring配置文件里的内容生效。

  • Spring Boot项目中没有Spring配置文件,我们编写的Spring配置文件默认是不会加载的;
  • 如果想让Spring配置文件生效,需要使用 @ImportSource 注解,标注在主启动类上;

外部Spring配置文件beans.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="hello" class="cn.bruce.springboot.service.HelloService"></bean>
    <bean id="test" class="cn.bruce.springboot.service.TestService"/>
</beans>

主启动类SpringBootApplication

@ImportResource(locations = {"classpath:beans.xml"})
@SpringBootApplication
public class SpringbootApplication {

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

}

Spring官方并不建议导入外部Spring配置文件,建议使用配置类来进行Spring配置,即使用全注解配置

@Configuration:指明当前类是一个配置类。用于替代以前的Spring配置文件

@Configuration
public class MyAppConfig {
	/**
     * @Bean相当于在配置文件中用<bean><bean/>标签,用于注入组件
     */
    @Bean
    public HelloService helloService() {
        System.out.println("配置类@Bean给容器添加组件。。。。");
        return new HelloService();
    }
}

3.3 配置文件占位符

可以在配置文件中使用文件占位符 ${xxxx} 使用随机数或引用属性也可以设置默认值

3.3.1 随机数

使用以下占位符可以生成随机数,还可以指定随机数类型

${random.uuid} :随机生成uuid

${random.value} :随机生成字符串

${random.int} :随机生成int型数字

${random.int(value,[max])} 随机生成数字,指定最大值

${random.long}:随机生成log型数字

${random.long(value,[max])}:随机生成long型数字,可指定最大值

3.3.2 属性占位符

  • 可以在配置文件中引用以前配置过的属性;
  • 使用 : 来指定默认值;

代码示例:

person.last-name=张三${random.value}
person.age=${random.int(100)}
person.birthday=2020/10/11
person.boss=false
person.maps.k1=v1
person.maps.k2=14
person.lists=a,b,c
person.dog.name=${person.last-name} have a dog
person.dog.age=${initAge:12}

4. 关于Profile

Profile是Spring对不同环境提供不同配置功能的支持,可以通过激活、指定参数等方式快速进行环境切换。

4.1 多Profile文件形式

在编写主配置文件时,文件名可以是 application-{profile}.properties/yml。如:application-dev.propertiesapplication-prod.properties

默认使用 application.properties 里的配置;在 application.properties 文件中激活指定的profile

4.2 多Profile文档块形式

在YAML中使用 --- 来划分文档块,可以使用不同的文档块来配置不同的profile,在默认配置中激活指定的profile

#默认配置
server:
  port: 8081
spring:
  profiles:
    active: dev

---
#开发环境
server:
  port: 8083
spring:
  profiles: dev

---
#生产环境
server:
  port: 8084
spring:
  profiles: prod

4.3 激活指定的Profile

① 在配置文件 application.properties/yml 中指定 spring.profiles.active={profile}}

② 在命令行指定,配置命令 --spring.profiles.active={profile}} 在运行构建好的jar包时添加配置命令

③ 添加虚拟机参数 -Dspring.profiles.active=dev

在IDE中配置命令行参数和虚拟机参数,以IDEA为例:

image-20201014162908270

注意:{profile} 表示环境名称,如:dev、prod等

5. 配置文件的加载

5.1 配置文件加载位置

Spring Boot在启动时会扫描以下位置的 application.propertiesapplication.yml 文件作为Spring Boot的默认配置文件。

  • file:./config/:项目中config文件夹内
  • file:./:项目文件内
  • classpath:/config/:src/resource/config文件夹内
  • classpath:/:src/resource文件夹内

加载的优先级由高到低,高优先级的配置会覆盖低优先级的配置;

Spring Boot会加载这四个位置的全部配置文件,形成互补配置

可以通过 spring.config.location 改变默认配置文件位置;

可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;指定配置文件和默认加载的这些配置文件共同起作用形成互补配置;便于运维时调整项目的配置信息,可以通过命令行调整配置信息。

命令行参数: --spring.config.location=G:/application.properties

如在部署时执行此命令:java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar --spring.config.location=E:/application.properties

5.2 外部配置文件加载顺序

Spring Boot支持多种配置方式,不仅可以在项目内部加载信息,也可以加载外部的配置信息。Spring Boot会加载以下位置的信息,优先级由高到低高优先级的配置会覆盖低优先级的配置,所有的配置会形成互补配置

① 命令行参数。所有的配置都可以在命令行中指定

例如:java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar --server.port=8087 --server.context-path=/abc

② 来自 java:comp/env 的JNDI属性;

③ Java的系统属性 System.getProperties()

④ 操作系统环境;

RandomValuePropertySource 配置的 random.* 属性值;

⑥ jar包外部的application-{profile}.properties或application.yml(带spring.profile)配置文件;

⑦ jar包内部的application-{profile}.properties或application.yml(带spring.profile)配置文件;

⑧ jar包外部的application.properties或application.yml(不带spring.profile)配置文件;

⑨ jar包内部的application.properties或application.yml(不带spring.profile)配置文件;

@Configuration 注解类上的 @PropertySource

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

Spring Boot官方文档: Spring Boot加载顺序

总结:加载jar包内外部配置文件的顺序,由jar包外向jar包内寻找,优先加载带 {profile} 的配置文件,再加载不带 {profile} 的配置文件。即:jar包配置由外到内,由带 {profile} 的到不带 {profile}

6. 自动配置的原理

Spring Boot中的配置文件能书写那些内容,以及书写的规则由它的自动配置原理里来决定,因此理解了Spring Boot的自动配置原理也就掌握了Spring Boot配置文件的内容分。

6.1 自动配置原理

Spring Boot在启动的时候会加载主配置类,主配置类中包含 @SpringBootApplication 注解,此注解是个组合注解,其中包含Spring Boot开启自动配置功能的注解 @EnableAutoConfiguration ,以下内容均从此注解的构成和功能展开。

@EnableAutoConfiguration 注解的作用:提示Spring Boot开启自动配置,并加载配置信息。配置信息由Spring Boot自动配置信息和我们书写的配置文件信息共同构成。

6.1.1 @EnableAutoConfiguration 注解加载自动配置类的过程:

@EnableAutoConfiguration 注解源码

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

    /**
     * 指定配置文件中的配置项
     */
	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	/**
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 * @return the classes to exclude
	 */
	Class<?>[] exclude() default {};

	/**
	 * Exclude specific auto-configuration class names such that they will never be
	 * applied.
	 * @return the class names to exclude
	 * @since 1.3.0
	 */
	String[] excludeName() default {};

}

源码可知 @EnableAutoConfiguration 注解由 @AutoConfigurationPackage@Import 注解构成。

  • @AutoConfigurationPackage 注解:自动配置包注解

  • @Import 注解:是Spring的一个底层注解用于给容器注入一个组件。

  • @Import(AutoConfigurationImportSelector.class) :导入 AutoConfigurationImportSelector 类配置的自动配置信息。

AutoConfigurationImportSelector 类实现了 ImportSelector 接口中的 selectImport 方法,用于选择导入的自动配置;而 selectImport 方法中调用 getAutoConfigurationEntry 方法;

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);// 调用此方法进行配置信息加载
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

getAutoConfigurationEntry 方法中调用 getCandidateConfigurations 方法;

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);
}

getCandidateConfigurations 方法中调用 SpringFactoriesLoader 类中的 loadFactoryNames 方法

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
                                                                         getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
                    + "are using a custom packaging, make sure that file is correct.");
    return configurations;
}

调用loadFactoryNames 方法时传入 getSpringFactoriesLoaderFactoryClass() 作为参数,此方法返回 EnableAutoConfiguration 注解,即加载 EnableAutoConfiguration 注解中规定的的默认配置文件信息。

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
 return EnableAutoConfiguration.class;
}

SpringFactoriesLoader 类中包含执行加载外部配置文件信息的方法

public final class SpringFactoriesLoader {

	/**
	 * The location to look for factories.
	 * <p>Can be present in multiple JAR files.
	 * 加载的META-INF/spring.factories路径中获取EnableAutoConfiguration指定的值,
	 * 将这些值作为自动配置类导入到容器中,自动配置类就生效,帮我们进行自动配置工作
	 */
	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";


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

	private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();


	private SpringFactoriesLoader() {
	}


	/**
	 * Load and instantiate the factory implementations of the given type from
	 * {@value #FACTORIES_RESOURCE_LOCATION}, using the given class loader.
	 * <p>The returned factories are sorted through {@link AnnotationAwareOrderComparator}.
	 * <p>If a custom instantiation strategy is required, use {@link #loadFactoryNames}
	 * to obtain all registered factory names.
	 * @param factoryType the interface or abstract class representing the factory
	 * @param classLoader the ClassLoader to use for loading (can be {@code null} to use the default)
	 * @throws IllegalArgumentException if any factory implementation class cannot
	 * be loaded or if an error occurs while instantiating any factory
	 * @see #loadFactoryNames
	 */
	public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
		Assert.notNull(factoryType, "'factoryType' must not be null");
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
		List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
		if (logger.isTraceEnabled()) {
			logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
		}
		List<T> result = new ArrayList<>(factoryImplementationNames.size());
		for (String factoryImplementationName : factoryImplementationNames) {
			result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
		}
		AnnotationAwareOrderComparator.sort(result);
		return result;
	}

	/**
	 * Load the fully qualified class names of factory implementations of the
	 * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
	 * class loader.
	 * @param factoryType the interface or abstract class representing the factory
	 * @param classLoader the ClassLoader to use for loading resources; can be
	 * {@code null} to use the default
	 * @throws IllegalArgumentException if an error occurs while loading factory names
	 * @see #loadFactories
	 */
	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
	}
	
    /**
     * 是整个流程中最终执行动作的方法,此方法使用类加载器加载外部的配置文件,
     * 将配置文件中的配置进行读取并使用LinkedHashMap作为存储结构,
     * 最终返回给配置信息的信息。
     *
     */
	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

	@SuppressWarnings("unchecked")
	private static <T> T instantiateFactory(String factoryImplementationName, Class<T> factoryType, ClassLoader classLoader) {
		try {
			Class<?> factoryImplementationClass = ClassUtils.forName(factoryImplementationName, classLoader);
			if (!factoryType.isAssignableFrom(factoryImplementationClass)) {
				throw new IllegalArgumentException(
						"Class [" + factoryImplementationName + "] is not assignable to factory type [" + factoryType.getName() + "]");
			}
			return (T) ReflectionUtils.accessibleConstructor(factoryImplementationClass).newInstance();
		}
		catch (Throwable ex) {
			throw new IllegalArgumentException(
				"Unable to instantiate factory class [" + factoryImplementationName + "] for factory type [" + factoryType.getName() + "]",
				ex);
		}
	}

}

loadSpringFactories 方法的主要功能:

  • 扫描所有jar包类路径下 META‐INF/spring.factories
  • 把扫描到的这些文件的内容包装成properties对象;
  • 从properties中获取到 EnableAutoConfiguration.class 类对应的值,然后把他们添加在容器中;

经过一系列的方法调用,最终Spring Boot得到了配置文件中的自动配置类相关的信息。通过 @Import 注解将 AutoConfigurationImportSelector 加载到到IoC容器中,通过调用 ImportSelector 接口的 selectImports 方法,就可以拿到配置文件中的配置信息,完成Spring Boot的自动配置。

Spring Boot自动配置文件由 SpringFactoriesLoader 类规定路径和文件,@EnableAutoConfiguration 注解规定配置文件中配置中的值,最终指向了自动配置的信息。

Spring Boot自动配置信息位置:spring-boot-autoconfigure-2.3.4.RELEASE.jar!\META-INF\spring.factories 以下这些配置信息就是Spring Boot自动配置的类。

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
.......,\
.......,\
.......,\
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration

每一个这样的 xxxAutoConfiguration 类都是容器中的一个组件,都会被加载到容器中。Spring Boot通过这些配置类用来做自动配置;每一个自动配置类完成某种功能的自动配置工作;

6.1.2 自动配置类加载配置的原理

自动配置类中完成对相关功能的配置工作,这里是自动配置执行的最后一步

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

自动配置类:HttpEncodingAutoConfiguration

@Configuration(proxyBeanMethods = false)// 指明此类是个配置类,可以为容器中注入组件
@EnableConfigurationProperties(ServerProperties.class)// 启动指定类的ConfigurationProperties功能;将配置文件中对应的值和ServerProperties绑定起来;并把ServerProperties加入到IoC容器中
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)// 判断当前应用是否为web应用,如果是则配置类生效。
//ConditionOnXXX注解是Spring底层注解@Conditional注解的扩展,配置如果满足指定条件则配置生效,否则整个配置不生效。
@ConditionalOnClass(CharacterEncodingFilter.class)// 判断当前项目中是否有CharacterEncodingFilter类,此类是SpringMVC解决乱码问题的过滤器
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true) // 判断配置文件中是否存在某项配置 server.servlet.encoding ;即我们的配置文件中是否配置了server.servlet.encoding项;如果没有配置默认此项配置也是生效的;
public class HttpEncodingAutoConfiguration {

    // 与SpringBoot配置文件进行映射	
	private final Encoding properties;

    // 在只有一个有参构造器的情况下,参数值会从容器中获取
	public HttpEncodingAutoConfiguration(ServerProperties properties) {
		this.properties = properties.getServlet().getEncoding();
	}

	@Bean // 向容器中添加一个组件,这个组件中的参数值需要从properties中
	@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;
		}

	}

}

所有的配置文件中能配置的属性都是在 xxxProperties 属性类中封装;配置文件能配置那些内容可以参考某个自动配置类的属性类。如配置类 HttpEncodingAutoConfiguration 中的属性配置类 ServerProperties

自动配置属性类:ServceProperties

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)// //从配置文件中获取指定的值和bean的属性进行绑定
public class ServerProperties {
 
    // 如此处设置uri的默认编码为UTF-8,同样也可以在配置文件中进行指定
    private Charset uriEncoding = StandardCharsets.UTF_8;
    ......
}

ConditionOnXXX 注解是Spring底层注解 @Conditional 注解的扩展,用于判断当前配置类是否满足某些条件,如果满足则配置类生效。配置类如果生效则向IoC容器中注入各种组件,而这些组件的属性是从与之相对应的 xxxProperties 类中获取的,而 xxxProperties 配置类中的每个属性也是和配置文件中的配置项相对应的。

关于各种类和注解说明:

  • @ConditionOnXXX:判断当前配置类是否满足条件;
  • xxxAutoConfiguration:自动配置类;用于给容器中注入组件
  • xxxProperties:与自动配置类相对应的属性类;封装配置文件中的相关属性
  • yml/properties:文件中的配置项和属性类中的值相对应;

Spring Boot自动配置总结:

① Spring Boot中包含大量的自动配置类 xxxAutoConfiguration

② Spring Boot在启动的时候会加载自动配置类;

③ 当配置类中的约束条件满足时,自动配置类会加载到IoC容器中;

④ 自动配置类中的配置属性类 xxxProperties 中设置了可以配置的属性;

⑤ Spring Boot会将配置属性类中的属性和配置文件中的数据项进行映射;

对我们使用Spring Boot的启示:

① Spring Boot在启动时会加载大量的自动配置;

② 在使用Spring Boot时我们可以查看我们所需的功能Spring Boot有没有相关的自动配置类;

③ 如果有则查看此自动配置类配置了那些组件;(如果我们需要的组件已经加载,则不需要进行配置)

④ 在给容器中自动配置添加组件时,会从自动配置的属性类中加载某些属性,我们可以在配置文件中指定这些属性的值;

6.2 @Conditional 派生注解

ConditionOnXXX 注解是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存在指定项

自动配置类在一定的条件下才能生效,我们可以通过在配置文件中配置打开debug属性 debug=true ,即可查看被Spring Boot加载的配置类,这样就不需要我们逐个查看各个自动配置类的加载条件。

控制台打印的信息

============================
CONDITIONS EVALUATION REPORT
============================


Positive matches:// 加载的自动配置类
-----------------

   AopAutoConfiguration matched:
      - @ConditionalOnProperty (spring.aop.auto=true) matched (OnPropertyCondition)

   AopAutoConfiguration.ClassProxyingConfiguration matched:
      - @ConditionalOnMissingClass did not find unwanted class 'org.aspectj.weaver.Advice' (OnClassCondition)
      - @ConditionalOnProperty (spring.aop.proxy-target-class=true) matched (OnPropertyCondition)
......

Negative matches:// 未加载的自动配置类
-----------------

   ActiveMQAutoConfiguration:
      Did not match:// 未加载的原因
         - @ConditionalOnClass did not find required class 'javax.jms.ConnectionFactory' (OnClassCondition)
......