spring-envrionment

概述

Spring中的环境Environment,表示的是一个Spring应用在运行时所处的环境,应用在运行时,可以从Environment中获取环境信息

因素

实际在SpringEnvironment环境主要由两个因素组成

  • properties

  • profiles

properties

所谓properties,其实际上就是一组key-value的键值对存储

  • properties属性的来源(属性源)可以有多个,且来源类型有多种
    • properties/yml文件
    • JVM properties
    • JNDI
    • servlet context parameters上下文参数
    • 特殊properties对象
    • ...

properties的主要作用是

  • 用户配置属性源
  • 程序从属性源中获取目标属性

profiles

所谓profiles,就是在逻辑上将properties/bean进行分组,启动程序时可以通过激活不同profile,来实现不同环境的切换

  • 每个profile需要一个当前应用中的唯一名称
  • 只有当profile被激活时,其对应properties/bean才会生效

系统环境

系统环境指的是Java程序运行的环境,其主要包括

  • 系统属性
    • JRE环境的一部分,存储JVM虚拟机与Java应用的一些信息
    • 可以使用System#getProperties方法获取
  • 环境变量
    • Java应用运行的宿主机操作系统的信息
    • 可以使用System#getenv方法获取

属性文件

properties文件

properties文件是常用的属性文件

  • 其在Spring/SpringBoot中都可以使用
  • Spring中提供了PropertiesLoaderUtils工具类可以加载解析properties文件
    • 解析结果封装为Properties实例

properties文件的存储格式:

  • 每行存储,存储格式为key=value
  • 其中List/Array默认对值通过,分割
    • 也可以通过添加[index]后缀自定义,但是需要保证序号连续,序号从0开始
#int
person.id=1
#String
person.name=tom
#List
person.hobby=吃饭,睡觉
#Array
person.family=father,mather
person.color[0]=red
person.color[1]=blue
#map
person.map.k1=v1
person.map.k2=v2
#实体对象
person.pet.type=dog
person.pet.name=旺财

yaml文件

yaml/yml为后缀的文件,是遵循yaml语法的一种配置文件,通常用于SpringBoot项目中

其配置示例如下:

person:
  #int
  id: 1
  #String
  name: tom
  #List/Array
  hobby:
    - 吃饭
    - 睡觉
  family:
    father,
    mather
  #Array特有
# family: [father,mather]
  #map
  map:
    k1: v1
    k2: v2
# map: {k1: v1,k2: v2}
  #实体对象
  pet:
    type: dog
    name: 旺财

yaml语法不详细展开,其中需要注意的是

  • Springboot中使用yaml配置文件,可以在单yaml文件中编写多个配置
  • 其中多个配置使用---进行分割,通过spring.profiles定义配置所属profile
  • 通过spring.profiles.active决定启用那个配置
  • 注意,此配置方式只能在application.yml配置文件中使用
# 公共配置
spring:
  profiles:
    active: pro # 使用名为pro的配置,这里可以切换成dev
  datasource:
    url: jdbc:mysql://localhost:3306/test_db?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
    password: 123456
    username: root
---
# 开发环境配置
spring:
  profiles: dev # profiles属性 代表配置的名称

server:
  port: 8080
---
# 生产环境配置
spring:
  profiles: pro

server:
  port: 80

SpringBoot中提供了YamlPropertySourceLoader用于加载yaml

基类分析

标准环境

类图

StandardEnvironment是一个标准环境对象,我们查看其类图

img

PropertyResolver

PropertyResolver属性解析器顶级接口

// org.springframework.core.env.PropertyResolver
public interface PropertyResolver {
    
    // 判断属性是否存在
    boolean containsProperty(String key);
    
    // 获取属性值
    String getProperty(String key);
    
    // 解析源数据中占位符,使用对应属性值填充
    String resolvePlaceholders(String text);
}

分析可知,PropertyResolver主要对于环境中属性的处理

  • 判断属性是否存在
  • 获取属性值
  • 解析源数据中占位符,使用对应属性值填充

Environment

// org.springframework.core.env.Environment
public interface Environment extends PropertyResolver {
    
    // 获取激活的 profiles
    String[] getActiveProfiles();
    
    // 获取默认的 profiles
    String[] getDefaultProfiles();
    
    // 判断 profiles 是否激活
    boolean acceptsProfiles(String... profiles);
}

分析可知

  • Environment主要针对于profiles

ConfigurablePropertyResolver

// org.springframework.core.env.ConfigurablePropertyResolver
public interface ConfigurablePropertyResolver extends PropertyResolver {
    
    // 类型转换服务器
    ConfigurableConversionService getConversionService();
    
    // 占位符前后缀
    void setPlaceholderPrefix(String placeholderPrefix);
    void setPlaceholderSuffix(String placeholderSuffix);
    
    // 占位符中 propertyName 与 defaultVal 的分隔符
    void setValueSeparator(@Nullable String valueSeparator);
}

分析可知,一个属性解析器可以配置以下属性

  • 类型转换服务器
  • 占位符前后缀
  • 占位符中propertyNamedefaultVal的分隔符
    • 通用占位符格式
      • ${propertyName:defaultVal}
      • 其中分隔符为:

ConfigurableEnvironment

// org.springframework.core.env.ConfigurableEnvironment
public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
    
    // profile 设置
    void setActiveProfiles(String... profiles);
    void addActiveProfile(String profile);
    void setDefaultProfiles(String... profiles);
    
    // 属性源获取
    MutablePropertySources getPropertySources();
    
    // 获取系统属性
    Map<String, Object> getSystemProperties();
    // 获取系统环境信息
    Map<String, Object> getSystemEnvironment();
    
    // 合并父环境 Environment 信息
    void merge(ConfigurableEnvironment parent);
}

分析可知

  • Environment可以配置profiles
  • Environment默认存在包含系统属性/系统环境信息
  • Environment可以合并一个父环境Environment
    • 存在相同属性,子Environment覆盖父Environment

AbstractEnvironment

AbstractEnvironmentEnvironment的抽象基类,其中定义了一些基础属性

// org.springframework.core.env.AbstractEnvironment
public abstract class AbstractEnvironment implements ConfigurableEnvironment {
    
    // 激活 profiles
    public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active";
    private final Set<String> activeProfiles = new LinkedHashSet<>();

    // 默认 profiles
    public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";
	private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());

    // 可变属性源
	private final MutablePropertySources propertySources;

    // 属性解析器
	private final ConfigurablePropertyResolver propertyResolver;
    
    /* 无参构造 */
    public AbstractEnvironment() {
        // 默认无参构造调用重载有参构造
		this(new MutablePropertySources());
	}
    
    /* 有参构造 */
    protected AbstractEnvironment(MutablePropertySources propertySources) {
		this.propertySources = propertySources;
        // 创建属性解析器
		this.propertyResolver = createPropertyResolver(propertySources);
        // 自定义属性配置
		customizePropertySources(propertySources);
	}
}

分析可知,一个Environment包括

  • profiles存储
  • 属性存储源MutablePropertySources
  • 属性解析器ConfigurablePropertyResolver

同时要注意:

  • AbstractEnvironment的构造时,将调用customizePropertySources进行自定义属性源配置
  • 所以AbstractEnvironment子类也会调用customizePropertySources

StandardEnvironment

StandardEnvironment就是一个标准的Environment实现

// org.springframework.core.env.StandardEnvironment
public class StandardEnvironment extends AbstractEnvironment {
    
    // 默认的系统属性源名称
    public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
    public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
    
    protected void customizePropertySources(MutablePropertySources propertySources) {
        // 添加两个默认的系统属性源
		propertySources.addLast(
				new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
		propertySources.addLast(
				new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
	}
}

分析可知

  • 标准环境默认存在两个系统属性源
    • 系统环境信息属性源
    • 系统属性属性源

小结

经过上述分析,一个标准的属性Environment实例包括以下信息:

  • profiles
    • 环境管理逻辑组
  • MutablePropertySources
    • 属性存储源
      • 具体属性存储的对象
    • 默认包含两个系统属性源
      • 系统环境信息属性源
      • 系统属性属性源
  • ConfigurablePropertyResolver
    • 属性解析器
      • 从属性源中解析出属性值
    • 额外包含
      • ConfigurableConversionService
        • 属性值类型转换器
      • 占位符相关
        • placeholderPrefix/placeholderSuffix
          • 占位符的前后缀
          • 默认为${}
        • valueSeparator
          • 占位符中propertyNamedefaultVal的分隔符

属性源

属性源,是真实属性存储的对象

PropertySource

PropertySource是属性源的顶级抽象父类

// org.springframework.core.env.PropertySource
public abstract class PropertySource<T> {
    
    // 属性源的名称
    protected final String name;

    // 属性的取值来源,真实存储
	protected final T source;
    
    // 获取属性值
    public abstract Object getProperty(String name);
}

分析可知

  • 每个属性源都有对应的名称
  • PropertySource管理一个泛型类型的属性source,它是真正存储属性数据的实例

PropertySources

PropertySources是管理多个PropertySource的属性源接口

// org.springframework.core.env.PropertySources
public interface PropertySources extends Iterable<PropertySource<?>> {
    
    // 获取属性源列表的 Stream
    default Stream<PropertySource<?>> stream() {
		return StreamSupport.stream(spliterator(), false);
	}
    
    // 是否包含目标 name 的属性源
    boolean contains(String name);
    
    // 根据名称获取属性源
    PropertySource<?> get(String name);
}

MutablePropertySources

// org.springframework.core.env.MutablePropertySources
public class MutablePropertySources implements PropertySources {
    
    // 数据源列表
    private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    
    // 数据源列表操作函数
    // addFirst、addLast、stream ...
    
    // 属性操作函数
    // contains、get ...
}

分析可知

  • 使用CopyOnWriteArrayList管理多个属性源

为何使用CopyOnWriteArrayList作为存储容器

  • 属性源有优先级顺序,List可以按序从属性源中获取属性
  • 属性源不经常变动,CopyOnWriteArrayList的遍历更加稳定

常用实现

MapPropertySource

MapPropertySource的主要特点就是其中source实例为一个Map<String, Object>类型对象

// org.springframework.core.env.MapPropertySource
public class MapPropertySource extends EnumerablePropertySource<Map<String, Object>> {}

PropertiesPropertySource

PropertiesPropertySourceMapPropertySource的实现,其特点是sourceProperties实例

// org.springframework.core.env.PropertiesPropertySource
public class PropertiesPropertySource extends MapPropertySource {
    
    public PropertiesPropertySource(String name, Properties source) {
		super(name, (Map) source);
	}
}

SystemEnvironmentPropertySource

PropertiesPropertySourceMapPropertySource的实现,其特点在于将会对属性名称进行预处理化匹配,包括

  • 忽略大小写
  • 替换-.字符为_

具体处理查看源码

// org.springframework.core.env.SystemEnvironmentPropertySource
public class SystemEnvironmentPropertySource extends MapPropertySource {
    
    public SystemEnvironmentPropertySource(String name, Map<String, Object> source) {
		super(name, source);
	}
}

StubPropertySource

// org.springframework.core.env.PropertySource.StubPropertySource
public static class StubPropertySource extends PropertySource<Object> {

    public StubPropertySource(String name) {
        super(name, new Object());
    }
}

StubPropertySourcePropertySource中的一个静态内部类,可以发现:

  • StubPropertySource中的source实际是一个Object实例
  • 实际上StubPropertySource并不是一个真正意义上可以使用的属性源,其主要就是用于占位,以便在后续的实际属性源对象准备好后,能够按照预期的搜索顺序进行查找和使用

属性解析

属性解析,就是从属性源中解析出目标属性值

属性解析器

PropertySourcesPropertyResolver

// org.springframework.core.env.PropertySourcesPropertyResolver
public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
    
    // 属性源
    private final PropertySources propertySources;
    
    protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
		if (this.propertySources != null) {
            // 1. 遍历管理的属性源
			for (PropertySource<?> propertySource : this.propertySources) {
                // 2. 尝试从属性中获取
				Object value = propertySource.getProperty(key);
				if (value != null) {	// 3. 当前属性源中能获取到值
					if (resolveNestedPlaceholders && value instanceof String) {
                        // 3.1 进行占位符解析
						value = resolveNestedPlaceholders((String) value);
					}
                    // 3.2 进行类型转换后返回值
					return convertValueIfNecessary(value, targetValueType);
				}
                // 4. 当前属性源中无法获取,则尝试从下个属性源中获取
			}
		}
        // 5. 遍历所有属性源,都无法获取,则返回空
		return null;
	}
}

分析可知:

  • 属性解析器的工作

    • 遍历属性源进行属性获取
    • 对于String类型属性,解析其占位符
      • 实际通过PropertyPlaceholderHelper实例完成
    • 根据返回类型,对属性值进行类型转换
      • 实际通过ConfigurableConversionService实例完成
  • 属性源是有优先级的

    • 由于是通过遍历属性源进行属性获取,那么排在前面的属性源的优先级就高

占位符解析器

PropertyPlaceholderHelper

PropertyPlaceholderHelper主要用于解析字符串中的占位符信息

// org.springframework.util.PropertyPlaceholderHelper
public class PropertyPlaceholderHelper {
    
    // 占位符前后缀
    private final String placeholderPrefix;
	private final String placeholderSuffix;
    
    // 占位符中 propertyName 与 defaultVal 的分隔符
    private final String valueSeparator;
    
    // 是否忽略无法解析的占位符属性
    private final boolean ignoreUnresolvablePlaceholders;
    
    
    // 占位符解析方法
    public String replacePlaceholders(String value, final Properties properties) {
		return replacePlaceholders(value, properties::getProperty);
	}
    
    public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
		return parseStringValue(value, placeholderResolver, null);
	}
}

PropertyPlaceholderHelper提供了replacePlaceholders用于解析字符串中占位符,此方法需要两个参数

  • value
    • 即包含占位符的源字符串
  • Properties/PlaceholderResolver
    • 即解析占位符对应属性时,具体取值的来源

调用的解析方法为parseStringValue,实际使用的是PlaceholdersResolver完成占位符解析工作,具体的解析逻辑此处不赘述,可查看源码

类型转换器

ConversionService

ConversionService是转换服务的顶级接口

// org.springframework.core.convert.ConversionService
public interface ConversionService {
    
    // 类型转换判断
    boolean canConvert(@Nullable Class<?> sourceType, Class<?> targetType);
    boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType);
    
    // 类型转换
    <T> T convert(@Nullable Object source, Class<T> targetType);
    Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType);
}

ConverterRegistry

ConverterRegistry是转换策略顶级接口

// org.springframework.core.convert.converter.ConverterRegistry
public interface ConverterRegistry {
    
    // 添加转换器
    void addConverter(Converter<?, ?> converter);
    void addConverter(GenericConverter converter);
    
    // 添加转换工厂
    void addConverterFactory(ConverterFactory<?, ?> factory);
}

分析可知:

  • 实际转换工作由Converter/ConverterFactory完成

Converter

Converter是转换器的顶级接口

// org.springframework.core.convert.converter.Converter
public interface Converter<S, T> {
    
    // 类型转换
    T convert(S source);
}

ConverterFactory

ConverterFactory是用于创建Converter的工厂

// org.springframework.core.convert.converter.ConverterFactory
public interface ConverterFactory<S, R> {

	<T extends R> Converter<S, T> getConverter(Class<T> targetType);
}

ConfigurableConversionService

ConfigurableConversionService主要用于组合ConversionService、ConverterRegistry接口

// org.springframework.core.convert.support.ConfigurableConversionService
public interface ConfigurableConversionService extends ConversionService, ConverterRegistry {}

DefaultConversionService

DefaultConversionService是默认的ConfigurableConversionService实现

// org.springframework.core.convert.support.DefaultConversionService
public class DefaultConversionService extends GenericConversionService {
    
}

其中通过addDefaultConverters/addCollectionConverters/addScalarConverters添加了大量的常用Converter实现

小结

分析上述基类可知

  • 环境包括profiles/properties两部分
    • profile用于环境切换与控制
    • properties用于属性提供
  • 属性来源于PropertySource
  • 属性不直接从PropertySource中获取,而是通过属性解析器从属性源中获取
  • 基于属性解析器PropertySourcesPropertyResolver的属性解析逻辑,实现了数据源的优先级
  • 属性解析主要包括
    • 属性源中属性值获取
    • 占位符解析
    • 结果类型转换

Spring环境加载源码分析

环境实例创建

对于Spring项目而言,无论是XML配置方式,还是注解配置方式,它们的上下文ClassPathXmlApplicationContext/AnnotationConfigApplicationContext的父类都是AbstractApplicationContext,其中提供了getEnvironment方法用于获取环境实例

// org.springframework.context.support.AbstractApplicationContext
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
    
    private ConfigurableEnvironment environment;
    
    public ConfigurableEnvironment getEnvironment() {
		if (this.environment == null) {
			this.environment = createEnvironment();
		}
		return this.environment;
	}
    
    protected ConfigurableEnvironment createEnvironment() {
		return new StandardEnvironment();
	}
}

分析可知:

  • 实际创建的环境实例是StandardEnvironment实例

属性源获取

1. 系统环境获取

StandardEnvironment构造过程中,默认调用父类构造,而其父类AbstractEnvironment在构造时,将调用customizePropertySources方法

所以跟踪分析StandardEnvironment#customizePropertySources实现

// org.springframework.core.env.StandardEnvironment
public class StandardEnvironment extends AbstractEnvironment {
    
    // 默认的系统属性源名称
    public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
    public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
    
    protected void customizePropertySources(MutablePropertySources propertySources) {
        // 添加两个默认的系统属性源
		propertySources.addLast(
				new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
		propertySources.addLast(
				new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
	}
}

可以发现,在StandardEnvironment中,其customizePropertySources方法中默认添加了两个系统环境信息属性源

所以StandardEnvironment实例在构造时,就会添加两个系统环境相关的属性源

跟进getSystemProperties/getSystemEnvironment分析可知,其实际是基于System实例进行属性获取

  • 系统属性systemProperties
    • System#getProperties
  • 环境变量systemEnvironment
    • System#getenv
  • systemProperties优先级比systemEnvironment

2. @PropertySource加载

基类

@PropertySource

@PropertySourceSpring中提供的用于加载属性源的注解

// org.springframework.context.annotation.PropertySource
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(PropertySources.class)
public @interface PropertySource {
    
    // 属性文件位置
    String name() default "";
    String[] value();
    
    // 属性解析工厂类
    Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
}
PropertySourceFactory

PropertySourceFactory是数据源工厂的顶级接口

// org.springframework.core.io.support.PropertySourceFactory
public interface PropertySourceFactory {
    
    // 根据 IO 资源创建属性源
    PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException;
}

源码分析

处理入口

IOC注解配置解析过程中,在ConfigurationClassParser#processPropertySource中,定义了对于@PropertySource注解解析的过程

// org.springframework.context.annotation.ConfigurationClassParser
class ConfigurationClassParser {
    
    // 默认 PropertySourceFactory 实现
    private static final PropertySourceFactory DEFAULT_PROPERTY_SOURCE_FACTORY = new DefaultPropertySourceFactory();

    private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
        String name = propertySource.getString("name");
        
        // 1. 获取@PropertySource注解value值
        String[] locations = propertySource.getStringArray("value");

        // 2. 创建属性源解析工厂
        // 默认使用 DefaultPropertySourceFactory
        PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ? DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));

        // 3. 遍历处理
        for (String location : locations) {
            // 3.1 尝试解析配置路径中环境信息(如果存在)
            String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
            
            // 3.2 配置资源文件加载
            Resource resource = this.resourceLoader.getResource(resolvedLocation);
            
            // 3.3 通过 PropertySourceFactory 创建 PropertySource
            // 3.4 将创建的 PropertySource 添加到 environment 实例中
            addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
        }
    }
}

分析可知

  • 其实际是通过PropertySourceFactory实例来构建属性源
  • 默认PropertySourceFactory实现为DefaultPropertySourceFactory
属性源创建

默认使用DefaultPropertySourceFactory创建数据源,跟进分析

// org.springframework.core.io.support.DefaultPropertySourceFactory
public class DefaultPropertySourceFactory implements PropertySourceFactory {
    
    public PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException {
		return (name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource));
	}
}

分析可知,其创建的属性源类型为ResourcePropertySource,跟进分析

// org.springframework.core.io.support.ResourcePropertySource
public class ResourcePropertySource extends PropertiesPropertySource {
    
    // IO 资源名称
    private final String resourceName;
    
    public ResourcePropertySource(EncodedResource resource) throws IOException {
		super(getNameForResource(resource.getResource()), PropertiesLoaderUtils.loadProperties(resource));
		this.resourceName = null;
	}
}

分析可知,实际sourcePropertiesLoaderUtils#loadProperties获得,跟进分析

// org.springframework.core.io.support.PropertiesLoaderUtils
public abstract class PropertiesLoaderUtils {
    
    private static final String XML_FILE_EXTENSION = ".xml";

    public static Properties loadProperties(EncodedResource resource) throws IOException {
        Properties props = new Properties();
        // 属性填充
        fillProperties(props, resource);
        return props;
    }

    public static void fillProperties(Properties props, EncodedResource resource) throws IOException {
        fillProperties(props, resource, ResourcePropertiesPersister.INSTANCE);
    }

    static void fillProperties(Properties props, EncodedResource resource, PropertiesPersister persister) {
        String filename = resource.getResource().getFilename();
		
        // 针对不同情况进行属性加载
        if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) {
            stream = resource.getInputStream();
            persister.loadFromXml(props, stream);
        } else if (resource.requiresReader()) {
            reader = resource.getReader();
            persister.load(props, reader);
        } else {
            stream = resource.getInputStream();
            persister.load(props, stream);
        }
    }
}
属性加载

上述加载都是由ResourcePropertiesPersister实例完成

  • xml文件
    • 使用loadFromXml方法加载
  • xml文件
    • 使用load方法加载

这两方法实际都由父类DefaultPropertiesPersister实现

// org.springframework.util.DefaultPropertiesPersister
public class DefaultPropertiesPersister implements PropertiesPersister {
    
    public void load(Properties props, InputStream is) throws IOException {
		props.load(is);
	}

	public void load(Properties props, Reader reader) throws IOException {
		props.load(reader);
	}
    
    public void loadFromXml(Properties props, InputStream is) throws IOException {
		props.loadFromXML(is);
	}
}

可以发现,实际都是由Properties类进行加载

其中对于xml文件与非xml文件处理不同

1. 非xml文件加载
// java.util.Properties
public class Properties extends Hashtable<Object,Object> {
    
    public synchronized void load(Reader reader) throws IOException {
        load0(new LineReader(reader));
    }
    
    public synchronized void load(InputStream inStream) throws IOException {
        load0(new LineReader(inStream));
    }
    
    private void load0 (LineReader lr) throws IOException {
        // 具体处理逻辑
    }
}

其实际上是将所有非xml文件视为properties文件进行处理

  • 逐行解析处理
  • 解析出key/value后存储到Properties
2. xml文件加载
// java.util.Properties
public class Properties extends Hashtable<Object,Object> {
    
    public synchronized void loadFromXML(InputStream in) {
        XmlSupport.load(this, Objects.requireNonNull(in));
        in.close();
    }
}

可以发现,实际加载动作由XmlSupport完成,实际由XmlPropertiesProvider进行解析

默认使用的是PlatformXmlPropertiesProvider,其解析的xml文件格式如下

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
    <entry key="person.age">18</entry>
</properties>
自定义数据源工厂

经过上述分析可知

  • @PropertySource仅加载默认类型文件
    • properties文件
    • 固定格式的xml文件

实际是因为其使用的PropertySourceFactory为默认的DefaultPropertySourceFactory

@PropertySource提供了factory属性用于定义PropertySourceFactory实现,所以可以通过自定义PropertySourceFactory实现类,来解析不同类型的文件

注意点

如果使用@PropertySource加载多个属性文件,越靠后加载的文件,其对应的属性源的优先级越高

// org.springframework.context.annotation.ConfigurationClassParser
class ConfigurationClassParser {
    
    private void addPropertySource(PropertySource<?> propertySource) {
        // ... 
        
        if (this.propertySourceNames.isEmpty()) {
			propertySources.addLast(propertySource);
		} else {
            // 后加载的属性文件,属性源放在前面
			String firstProcessed = this.propertySourceNames.get(this.propertySourceNames.size() - 1);
			propertySources.addBefore(firstProcessed, propertySource);
		}
		this.propertySourceNames.add(name);
    }
}

3. 自定义属性源

Environment获取

Spring中提供了EnvironmentAware/ApplicationContextAware接口,用于获取Environment/ApplicationContext实例

并且ApplicationContext可以通过getEnvironment获取Environment实例

添加自定义属性源

在获取到Environment实例后,就可以向其中添加自定义的属性源

举个🌰:

public class EnvironmentAwareBean implements EnvironmentAware {

    @Override
    public void setEnvironment(Environment environment) {
        ConfigurableEnvironment configurableEnvironment = (ConfigurableEnvironment)environment;
        Properties properties = new Properties();
        properties.setProperty("person.sex", "man");
        PropertiesPropertySource propertySource = new PropertiesPropertySource("test", properties);
        configurableEnvironment.getPropertySources().addLast(propertySource);
    }
}

注意点

EnvironmentAware#setEnvironment的处理调用,是在ApplicationContextAwareProcessor进行初始化前置处理过程中,实际就是在IOC工厂实例化对应Bean的过程中调用

  • 而如果此时我们添加的属性源在其他Bean的实例化过程中需要使用到,那么就需要确保EnvironmentAware实现的Bean在前面进行实例化

  • IOC工厂实例化Bean的顺序,只与其注册BeanDefinitionIOC工厂的顺序一致

需要注意的是:

  • @Order注解只适用于部分由Spring管理的Bean的处理的顺序,而无法决定IOC容器加载/实例化Bean的顺序,所以无法使用@Order注解使EnvironmentAware的实现Bean在前面加载

最好的方法就是:

  • 使用IOCXML配置方式
    • 可以在XML中将EnvironmentAware实现放在前面加载
  • 使用IOC中注解配置方式
    • 由于解析入口在@Configuration注解注释的主配置类
      • 可以由主配置类实现EnvironmentAware接口
      • 可以在主配置类中通过@Bean加载EnvironmentAware实现

举个🌰:

@ComponentScan
@Configuration
public class MyConfiguration {

    @Bean
    public EnvironmentAwareBean registerEnvironmentAwareBean() {
        return new EnvironmentAwareBean();
    }
}

SpringBoot环境加载源码分析

前言

profiles相关配置

SpringBoot中提供与profiles相关的配置有spring.profiles.[group/include/active/default]

  • SpringBoot中默认加载名为application的配置文件

  • application配置文件中,可以添加spring.profiles.[group/include/active/default]配置来引入其他配置文件

  • 配置分析

    • 整体的spring.profiles配置解析过程在ConfigDataEnvironment#withProfiles可以查看
    • spring.profiles.include
      • 表示当前配置文件包含的其他配置文件
    • spring.profiles.group
      • 表示profile配置的分组,可以通过active/default激活分组中所有profile
      • 🌰
        • 分组配置为:spring.profiles.group.groupA=dev,uat
        • spring.profiles.active=groupA时,表示激活的profiledev,uat
    • spring.profiles.active
      • 表示激活的profile,将加载对应的application-<profile>配置文件作为属性源
    • spring.profiles.default
      • 如果没有配置spring.profiles.active,则从默认的spring.profiles.default获取激活的profile
        • 所以activedefault是互斥的配置
      • 其默认值为default
  • 注意

    • spring.profiles.[group/include/active/default]只能在application配置文件,或者其他属性源中配置
    • application-<profile>配置文件,不可以嵌套添加spring.profiles.[group/include/active/default]配置
      • 否则将报错,具体校验逻辑在InvalidConfigDataPropertyException#throwOrWarn方法中

一、环境构建阶段

处理入口

SpringBoot项目启动时,在SpringApplication#run方法处理过程中,其中调用了prepareEnvironment方法用于环境准备

// org.springframework.boot.SpringApplication
public class SpringApplication {

    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
                                                       ApplicationArguments applicationArguments) {
        // 1. 根据容器类型创建对应 Environment 对象
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        
        // 2. 获取参数 args 和环境信息中的 profiles 激活的信息
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        ConfigurationPropertySources.attach(environment);
        
        // 3. 通过 ConfigFileApplicationListener (由spring.factories中对应ApplicationListener.class获取) 监听器
        // 加载默认的 application.propertis,以及当前激活的 profiles 对应配置文件,保存到 environment 中
        listeners.environmentPrepared(environment);
        
        // 4. 将 Environment 对象绑定到 Spring 上下文中
        bindToSpringApplication(environment);
        
        if (!this.isCustomEnvironment) {
            environment = new EnvironmentConverter(getClassLoader())
                .convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
        }
        ConfigurationPropertySources.attach(environment);
        return environment;
    }
}

可以发现其通过多个步骤完成环境准备工作,下面我们一一分析

1. Environment实例创建

处理入口

跟进分析getOrCreateEnvironment方法

// org.springframework.boot.SpringApplication
public class SpringApplication {
	
    // 环境实例
    private ConfigurableEnvironment environment;
    
    // 上下文工厂
    private ApplicationContextFactory applicationContextFactory = ApplicationContextFactory.DEFAULT;
    
    // application容器类型
    private WebApplicationType webApplicationType;
    
    private ConfigurableEnvironment getOrCreateEnvironment() {
		if (this.environment != null) {
            // 存在直接返回
			return this.environment;
		}
        // 1. 使用`ApplicationContextFactory`工厂创建`Environment`实例
		ConfigurableEnvironment environment = this.applicationContextFactory.createEnvironment(this.webApplicationType);
        
		if (environment == null && this.applicationContextFactory != ApplicationContextFactory.DEFAULT) {
			environment = ApplicationContextFactory.DEFAULT.createEnvironment(this.webApplicationType);
		}
        
        // 2. 创建失败则返回 ApplicationEnvironment 实例
		return (environment != null) ? environment : new ApplicationEnvironment();
	}
}

实际使用ApplicationContextFactory工厂创建Environment实例,工厂使用默认实现DefaultApplicationContextFactory,跟进分析

// org.springframework.boot.DefaultApplicationContextFactory
class DefaultApplicationContextFactory implements ApplicationContextFactory {
    
    public ConfigurableEnvironment createEnvironment(WebApplicationType webApplicationType) {
		return getFromSpringFactories(webApplicationType, ApplicationContextFactory::createEnvironment, null);
	}
    
    private <T> T getFromSpringFactories(WebApplicationType webApplicationType,
			BiFunction<ApplicationContextFactory, WebApplicationType, T> action, Supplier<T> defaultResult) {
        // 基于 spring.factories 加载 ApplicationContextFactory 实例
		for (ApplicationContextFactory candidate : 
             SpringFactoriesLoader.loadFactories(ApplicationContextFactory.class, getClass().getClassLoader())) {
            // 尝试使用 ApplicationContextFactory 创建环境实例
			T result = action.apply(candidate, webApplicationType);
			if (result != null) {
				return result;
			}
		}
		return (defaultResult != null) ? defaultResult.get() : null;
	}
}

Springboot项目中的ApplicationContextFactory,除了默认的DefaultApplicationContextFactory,还有

  • AnnotationConfigReactiveWebServerApplicationContext中内部Factory
  • AnnotationConfigServletWebServerApplicationContext中内部Factory

而通常使用的是WebApplicationType.SERVLET,其对应AnnotationConfigServletWebServerApplicationContext.Factory处理

// org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext.Factory
static class Factory implements ApplicationContextFactory {
    
    public ConfigurableEnvironment createEnvironment(WebApplicationType webApplicationType) {
			return (webApplicationType != WebApplicationType.SERVLET) ? null : new ApplicationServletEnvironment();
		}
}

最终创建了ApplicationServletEnvironment实例

构造过程

ApplicationServletEnvironment的父类AbstractEnvironment在构造时,将调用customizePropertySources方法

跟进分析ApplicationServletEnvironment#customizePropertySources实现

// org.springframework.web.context.support.StandardServletEnvironment
public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {
    
    public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";
    
    public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";
    
    protected void customizePropertySources(MutablePropertySources propertySources) {
        // 添加两个 servlet 相关数据源
		propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
		propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
		if (jndiPresent && JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
			propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
		}
        // 父类调用
		super.customizePropertySources(propertySources);
	}
}

分析可知:

  • StandardServletEnvironment构造时,添加了servletContextInitParams/servletConfigInitParams两个属性源

    • 此时添加的属性源类型是StubPropertySource
      • 其实际作为占位属性源,内部并不存储属性,仅仅是在MutablePropertySources中占据一个位置,用于优先级排序
    • 后续真实属性源添加位置:WebApplicationContextUtils#initServletPropertySources
  • 通过父类构造,还会添加systemEnvironment/systemProperties这两个属性源

实际上构造过程中添加了四个属性源,其优先级排序为:

  • servletContextInitParams
  • servletConfigInitParams
  • systemProperties
  • systemEnvironment

小结

经过上述处理:

  • 对应SERVLET环境,创建了ApplicationServletEnvironment作为Environment实例
  • ApplicationServletEnvironment构造过程中默认添加了属性源
    • servletContextInitParams
    • servletConfigInitParams
    • systemProperties
    • systemEnvironment

2. 命令行属性源配置

跟进分析configureEnvironment方法

// org.springframework.boot.SpringApplication
public class SpringApplication {

    protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
        // 1. 将 run 方法参数 args 封装为 SimpleCommandLinePropertySource 添加到 environment 中
        configurePropertySources(environment, args);
    }
}

跟进分析configurePropertySources方法

// org.springframework.boot.SpringApplication
public class SpringApplication {

    protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
        // 1. 获取 environment 中的 sources
        MutablePropertySources sources = environment.getPropertySources();
        
        // 2. 配置默认属性源
        if (!CollectionUtils.isEmpty(this.defaultProperties)) {
			DefaultPropertiesPropertySource.addOrMerge(this.defaultProperties, sources);
		}
        
        // 3. 如果 args 参数存在,配置命令行属性源
        if (this.addCommandLineProperties && args.length > 0) {
            // 属性源名称为:commandLineArgs
            String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
            if (sources.contains(name)) {	// 3.1 属性源已存在
                PropertySource<?> source = sources.get(name);
                // 创建 CompositePropertySource 属性源用于封装多个属性源
                CompositePropertySource composite = new CompositePropertySource(name);
                // 属性源类型为:SimpleCommandLinePropertySource
                composite.addPropertySource(new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
                composite.addPropertySource(source);
                // 属性源替换
                sources.replace(name, composite);
            } else {	// 3.2 属性源不存在
                // 直接封装为 SimpleCommandLinePropertySource 对象
                // 保存到 sources 中,且优先级最高
                sources.addFirst(new SimpleCommandLinePropertySource(args));
            }
        }
    }
}

分析可知:

  • run方法参数封装为SimpleCommandLinePropertySource类型属性源
    • 实际对应通过java命令启动时设置的参数
    • java -D<systemPropertyName>=<value> -X<jvmparam>=<value> xxx.jar arg1 arg2 arg3 ...arg1 arg2 arg3 ...部分
  • 此属性源名称为commandLineArgs
  • 此属性源优先级最高

3. Profiles环境准备

处理入口(监听处理)

在上述处理后,将通过SpringApplicationRunListeners进行环境准备,其调用了environmentPrepared方法进行处理

跟踪其调用链,其调用了doWithListeners进行处理

// org.springframework.boot.SpringApplicationRunListeners
class SpringApplicationRunListeners {
    
    // Spring 应用运行监听器
    private final List<SpringApplicationRunListener> listeners;
    
    /* 环境准备 */
    void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
		doWithListeners("spring.boot.application.environment-prepared",
				(listener) -> listener.environmentPrepared(bootstrapContext, environment));
	}
    
    /* 监听处理 */
    private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction,
			Consumer<StartupStep> stepAction) {
        // 遍历监听器,调用其 environmentPrepared 方法进行处理
		this.listeners.forEach(listenerAction);
	}
}

SpringBoot中默认提供的监听器为EventPublishingRunListener,跟踪其environmentPrepared方法实现

// org.springframework.boot.context.event.EventPublishingRunListener
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
    
    // 事件多播器
    private final SimpleApplicationEventMulticaster initialMulticaster;
    
    public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
			ConfigurableEnvironment environment) {
        // 通过事件多播器,多播一个 ApplicationEnvironmentPreparedEvent 事件
		this.initialMulticaster.multicastEvent(
				new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));
	}
}

分析可知:

  • 封装一个ApplicationEnvironmentPreparedEvent类型事件
  • 通过SimpleApplicationEventMulticaster事件多播器进行多播处理

事件多播

实际由SimpleApplicationEventMulticaster#multicastEvent方法进行事件多播处理,跟进分析

// org.springframework.context.event.SimpleApplicationEventMulticaster
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {

    public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
        // 解析事件类型
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        // 根据事件类型,获取其对应事件监听器
        for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			// 遍历监听器进行处理
            // 最终调用 ApplicationListener#onApplicationEvent 方法处理事件
            invokeListener(listener, event);
        }
    }
}

分析可知:

  • 根据事件类型获取对应的事件监听器
  • 遍历监听器调用invokeListener进行事件处理
    • 实际上invokeListener方法最终就是调用ApplicationListener#onApplicationEvent方法处理事件

事件处理

通过getApplicationListeners可以获取多个事件监听器,其中与环境准备相关的实现是为EnvironmentPostProcessorApplicationListener

跟进其onApplicationEvent处理,对于ApplicationEnvironmentPreparedEvent类型事件,其通过onApplicationEnvironmentPreparedEvent方法处理

// org.springframework.boot.env.EnvironmentPostProcessorApplicationListener
public class EnvironmentPostProcessorApplicationListener implements SmartApplicationListener, Ordered {
    
    private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
        // 基于事件对象获取 环境实例/应用实例
		ConfigurableEnvironment environment = event.getEnvironment();
		SpringApplication application = event.getSpringApplication();
        // 通过 getEnvironmentPostProcessors 获取环境前置处理器
		for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(application.getResourceLoader(),
				event.getBootstrapContext())) {
            // 调用 EnvironmentPostProcessor#postProcessEnvironment 进行前置处理
			postProcessor.postProcessEnvironment(environment, application);
		}
	}
}

分析可知:

  • 获取环境前置处理器EnvironmentPostProcessor
  • 遍历调用EnvironmentPostProcessor#postProcessEnvironment进行环境前置处理

环境前置处理

通过getEnvironmentPostProcessors可以获取多个环境前置处理器,其中我们需要关注的实现为ConfigDataEnvironmentPostProcessor

跟进分析其postProcessEnvironment实现

// org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor
public class ConfigDataEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {

    void postProcessEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader,
                                Collection<String> additionalProfiles) {
        // 创建资源加载器
        resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader();
        // 创建 配置数据环境 实例
        // 调用 processAndApply 方法处理
        getConfigDataEnvironment(environment, resourceLoader, additionalProfiles).processAndApply();
    }
}

分析可知

  • 其主要是创建一个ConfigDataEnvironment实例
  • 其次就是调用其processAndApply进行处理

小结

经过上述分析可知:

  • 创建ApplicationServletEnvironment实例作为Environment环境对象
    • 通过初始化构造添加默认属性源
      • servletContextInitParams
      • servletConfigInitParams
      • systemProperties
      • systemEnvironment
  • 解析命令行参数
    • 封装为SimpleCommandLinePropertySource实例,添加到ApplicationServletEnvironment
    • 优先级最高
  • 进行profiles环境准备
    • 重点在于创建ConfigDataEnvironment实例
    • 并调用ConfigDataEnvironment#processAndApply进行前置处理

二、配置数据环境分析

上述分析可知,profiles环境准备主要由ConfigDataEnvironment实例完成,我们将其单独拆分分析

1. 基类分析

配置数据形态相关

ConfigDataLocation

ConfigDataLocation是对配置文件路径的封装结果

// org.springframework.boot.context.config.ConfigDataLocation
public final class ConfigDataLocation implements OriginProvider {

	// 可选前缀
	public static final String OPTIONAL_PREFIX = "optional:";

    // 是否可选
	private final boolean optional;

    // 文件路径
	private final String value;
    
    // 提供静态函数用于构建 ConfigDataLocation 实例
    public static ConfigDataLocation of(String location) {
        // 判断是否可选
		boolean optional = location != null && location.startsWith(OPTIONAL_PREFIX);
        // 截取实际文件路径
		String value = (!optional) ? location : location.substring(OPTIONAL_PREFIX.length());
		if (!StringUtils.hasText(value)) {
			return null;
		}
        // 构建实例
		return new ConfigDataLocation(optional, value, null);
	}
}

分析:

  • 实际就是通过value存储配置文件路径
    • 实际上value可以包含多个文件路径,其使用;进行分割
  • optional:
    • 是可选标志前缀
    • 对于目标配置路径,如果不存在配置数据文件
      • 添加了可选标志前缀,即使不存在,也可以正常启动
      • 否则将启动失败

举个🌰:

  • 源配置路径为:optional:classpath:/;optional:classpath:/config/

  • 其实际对应两个配置路径

    • classpath:/
    • classpath:/config/
  • 上述配置路径封装为ConfigDataLocation,其value值为:classpath:/;optional:classpath:/config/

ConfigDataProperties

ConfigDataProperties是配置文件属性,其用于封装多个ConfigDataLocation实例

// org.springframework.boot.context.config.ConfigDataProperties
class ConfigDataProperties {
    
    private final List<ConfigDataLocation> imports;
}
StandardConfigDataReference

StandardConfigDataReference是由ConfigDataLocation解析后对应的单个具体资源

// org.springframework.boot.context.config.StandardConfigDataReference
class StandardConfigDataReference {
    
    // 对应的配置数据路径
	private final ConfigDataLocation configDataLocation;

    // 对应资源路径名称
	private final String resourceLocation;

    // 所属的文件夹
	private final String directory;

    // 对应的 profile 环境
	private final String profile;

    // 对应的属性源加载器
	private final PropertySourceLoader propertySourceLoader;
}
ConfigDataResource

ConfigDataResource表示单个配置文件资源

// org.springframework.boot.context.config.ConfigDataResource
public abstract class ConfigDataResource {
    
    private final boolean optional;
}
StandardConfigDataResource

StandardConfigDataResource是标准的ConfigDataResource的实现类

// org.springframework.boot.context.config.StandardConfigDataResource
public class StandardConfigDataResource extends ConfigDataResource {
    
    // 配置数据参考
    private final StandardConfigDataReference reference;
    
    // 具体文件资源
    private final Resource resource;
    
    // 是否空目录
    private final boolean emptyDirectory;
}
ConfigDataResolutionResult
// org.springframework.boot.context.config.ConfigDataResolutionResult
class ConfigDataResolutionResult {
    
    // 配置数据路径
    private final ConfigDataLocation location;

    // 配置数据资源
	private final ConfigDataResource resource;

    // 是否 profile 配置
	private final boolean profileSpecific;
}

ConfigDataResolutionResult主要是封装StandardConfigDataResource,而StandardConfigDataResource中又封装了StandardConfigDataReference实例

ConfigData

ConfigData主要封装用于解析资源文件得到的属性源

// org.springframework.boot.context.config.ConfigData
public final class ConfigData {
    
    // 存储属性源
    private final List<PropertySource<?>> propertySources;
}

配置数据路径相关

ConfigDataLocationResolver

ConfigDataLocationResolver配置文件路径解析器,是用于解析配置文件路径的接口

// org.springframework.boot.context.config.ConfigDataLocationResolver
public interface ConfigDataLocationResolver<R extends ConfigDataResource> {
    boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location);
}
ConfigDataLocationResolvers

ConfigDataLocationResolvers是配置文件路径解析器集合类,用于封装多个配置文件路径解析器ConfigDataLocationResolver

// org.springframework.boot.context.config.ConfigDataLocationResolvers
class ConfigDataLocationResolvers {
    
    private final List<ConfigDataLocationResolver<?>> resolvers;
}

配置数据加载相关

ConfigDataLoader

ConfigDataLoader用于加载配置文件

// org.springframework.boot.context.config.ConfigDataLoader
public interface ConfigDataLoader<R extends ConfigDataResource> {
    
    default boolean isLoadable(ConfigDataLoaderContext context, R resource) {
		return true;
	}
    
    ConfigData load(ConfigDataLoaderContext context, R resource);
}
ConfigDataLoaders

ConfigDataLoaders用于封装多个ConfigDataLoader实例

// org.springframework.boot.context.config.ConfigDataImporter
class ConfigDataLoaders {
	
    // 配置文件加载器
	private final List<ConfigDataLoader<?>> loaders;

    // 配置文件加载器所加载的资源类型,实际是 ConfigDataLoader 对应的泛型
	private final List<Class<?>> resourceTypes;
}

贡献者相关

ConfigDataEnvironmentContributor

ConfigDataEnvironmentContributor实际就是属性源的封装

// org.springframework.boot.context.config.ConfigDataEnvironmentContributor
class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironmentContributor> {
    
    // 实际存储属性源
    private final PropertySource<?> propertySource;
    
    // 子贡献者存储
    private final Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children;
    
    // 贡献者类型
    private final Kind kind;
}

贡献者类型有多种,由其内部枚举类Kind定义

  • ROOT

    • 根贡献者
    • 本身不提供配置,通过children存储所有贡献者
  • EXISTING

    • 已存在贡献者
    • 在初始化配置文件之前,已经存在的配置数据/属性源,解析成对应的贡献者
  • INITIAL_IMPORT

    • 初始导入贡献者
    • 在初始化过程中,导入的配置数据
    • 主要由spring.config.location / spring.config.additional-location / spring.config.import指定的路径和默认配置路径中获取
  • UNBOUND_IMPORT

    • 未导入完全贡献者
    • 已经导入的贡献者,但其本身可能会解析出更多贡献者
  • BOUND_IMPORT

    • 导入完全贡献者
    • 已经完成解析的贡献者,其中存储属性源
  • EMPTY_LOCATION

    • 空的贡献者
ConfigDataEnvironmentContributors

ConfigDataEnvironmentContributors用于管理ConfigDataEnvironmentContributor实例

// org.springframework.boot.context.config.ConfigDataEnvironmentContributors
class ConfigDataEnvironmentContributors implements Iterable<ConfigDataEnvironmentContributor> {
    
    // 封装一个根贡献者
    private final ConfigDataEnvironmentContributor root;
}

其中重点是根贡献者root

  • 它本身不包含属性源
  • 其主要通过children管理多个子贡献者,子贡献者才是真实属性源存储者

属性源加载器

PropertySourceLoader

PropertySourceLoader是用于加载具体文件资源,获取PropertySource属性源的接口

// org.springframework.boot.env.PropertySourceLoader
public interface PropertySourceLoader {
    
    // 当前属性源加载器可以处理的文件对应的扩展名
    String[] getFileExtensions();
    
    // 加载资源,获取属性源
    List<PropertySource<?>> load(String name, Resource resource) throws IOException;
}
PropertiesPropertySourceLoader

PropertiesPropertySourceLoader是用于解析扩展名为properties/xml的资源文件

// org.springframework.boot.env.PropertiesPropertySourceLoader
public class PropertiesPropertySourceLoader implements PropertySourceLoader {
    
    /* 文件扩展名 */
    public String[] getFileExtensions() {
		return new String[] { "properties", "xml" };
	}
}

具体load处理细节此处不予分析

YamlPropertySourceLoader

YamlPropertySourceLoader是用于解析扩展名为yml/yaml的资源文件

// org.springframework.boot.env.PropertiesPropertySourceLoader
public class PropertiesPropertySourceLoader implements PropertySourceLoader {
    
    /* 文件扩展名 */
    public String[] getFileExtensions() {
		return new String[] { "yml", "yaml" };
	}
}

具体load处理细节此处不予分析

profiles相关

ConfigDataActivationContext

ConfigDataActivationContext表示配置数据激活状态的上下文

// org.springframework.boot.context.config.ConfigDataActivationContext
class ConfigDataActivationContext {
    
    // 存储 Profiles 实例
    private final Profiles profiles;
}
Profiles

Profiles实例主要用于管理profiles配置情况

// org.springframework.boot.context.config.Profiles
public class Profiles implements Iterable<String> {
    
    // 激活的 profile
    private final List<String> activeProfiles;
    
    // 默认的 profile
    private final List<String> defaultProfiles;
    
    // profile 组别
    // key:组别名称
    // value:组别中包含的 profile,多个使用 `,` 分割
    private final MultiValueMap<String, String> groups;
}

2. 实例构建

既然工作由ConfigDataEnvironment实例完成,我们首先分析一下此类

实例结构

// org.springframework.boot.context.config.ConfigDataEnvironment
class ConfigDataEnvironment {
    
    // 几个配置路径属性
    static final String IMPORT_PROPERTY = "spring.config.import";
    static final String ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location";
    static final String LOCATION_PROPERTY = "spring.config.location";
    
    // 默认扫描路径
    static final ConfigDataLocation[] DEFAULT_SEARCH_LOCATIONS;
	static {
		List<ConfigDataLocation> locations = new ArrayList<>();
        // 1. src/main/resources/ 2. src/main/resources/config/ 目录下
		locations.add(ConfigDataLocation.of("optional:classpath:/;optional:classpath:/config/"));
        // 1. 项目根目录下 2. 项目根目录下的 config 目录下
		locations.add(ConfigDataLocation.of("optional:file:./;optional:file:./config/;optional:file:./config/*/"));
		DEFAULT_SEARCH_LOCATIONS = locations.toArray(new ConfigDataLocation[0]);
	}
    
    // 原始环境实例
    private final ConfigurableEnvironment environment;
    
    // 配置数据路径解析器
    private final ConfigDataLocationResolvers resolvers;
    
    // 配置数据加载器
    private final ConfigDataLoaders loaders;
    
    // 配置数据环境贡献者管理者
    private final ConfigDataEnvironmentContributors contributors;
}

构造分析

跟进分析ConfigDataEnvironment实例构造处理

// org.springframework.boot.context.config.ConfigDataEnvironment
class ConfigDataEnvironment {

    /* 构造器 */
    ConfigDataEnvironment(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
                          ConfigurableEnvironment environment, ResourceLoader resourceLoader, 
                          Collection<String> additionalProfiles,
                          ConfigDataEnvironmentUpdateListener environmentUpdateListener) {
        // 1. 使用 Binder 封装 environment,便于后续操作
        Binder binder = Binder.get(environment);
        UseLegacyConfigProcessingException.throwIfRequested(binder);
        this.logFactory = logFactory;
        this.logger = logFactory.getLog(getClass());
        this.notFoundAction = binder.bind(ON_NOT_FOUND_PROPERTY, ConfigDataNotFoundAction.class)
            .orElse(ConfigDataNotFoundAction.FAIL);
        this.bootstrapContext = bootstrapContext;
        this.environment = environment;
        // 2. 构建配置数据路径解析器
        this.resolvers = createConfigDataLocationResolvers(logFactory, bootstrapContext, binder, resourceLoader);
        this.additionalProfiles = additionalProfiles;
        this.environmentUpdateListener = (environmentUpdateListener != null) ? environmentUpdateListener
            : ConfigDataEnvironmentUpdateListener.NONE;
        // 3. 构建配置数据加载器
        this.loaders = new ConfigDataLoaders(logFactory, bootstrapContext, resourceLoader.getClassLoader());
        // 4. 创建配置数据环境贡献管理者
        this.contributors = createContributors(binder);
    }
}

ConfigDataEnvironment构造过程中,我们主要需要关注的部分有:

  • 使用组件
    • ConfigDataLocationResolvers
      • 用于解析配置数据文件
    • ConfigDataLoaders
      • 用于加载配置数据
    • ConfigDataEnvironmentContributors
      • 用于封装配置数据信息
1. 构建配置数据路径解析器

跟进createConfigDataLocationResolvers方法

  • 实际创建的是ConfigDataLocationResolvers实例

  • ConfigDataLocationResolvers构造中,通过SpringFactoriesLoader#loadFactoryNames获取ConfigDataLocationResolver实例

  • 其中我们需要关注的是StandardConfigDataLocationResolver

2. 构建配置数据加载器
  • 创建了ConfigDataLoaders实例

  • ConfigDataLoaders构造中,通过SpringFactoriesLoader#loadFactoryNames获取ConfigDataLoader实例

  • 其中我们需要关注的是StandardConfigDataLoader

3. 创建贡献管理者

跟进分析createContributors方法

// org.springframework.boot.context.config.ConfigDataEnvironment
class ConfigDataEnvironment {

    private ConfigDataEnvironmentContributors createContributors(Binder binder) {
        // 获取当前环境存在的属性源
        MutablePropertySources propertySources = this.environment.getPropertySources();
        List<ConfigDataEnvironmentContributor> contributors = new ArrayList<>(propertySources.size() + 10);
        PropertySource<?> defaultPropertySource = null;
        
        // 1. 基于环境已有属性源,创建对应的贡献者
        for (PropertySource<?> propertySource : propertySources) {
            if (DefaultPropertiesPropertySource.hasMatchingName(propertySource)) {
                // 将名称为 defaultProperties 的属性源设置为默认属性源
                defaultPropertySource = propertySource;
            } else {
				// 将属性源封装为类型为 EXISTING 的 ConfigDataEnvironmentContributor 实例
                contributors.add(ConfigDataEnvironmentContributor.ofExisting(propertySource));
            }
        }
        
        // 2. 获取类型为 INITIAL_IMPORT 的 ConfigDataEnvironmentContributor 实例
        contributors.addAll(getInitialImportContributors(binder));
        
        if (defaultPropertySource != null) {
            // 默认属性源也封装为类型为 EXISTING 的 ConfigDataEnvironmentContributor 实例
            contributors.add(ConfigDataEnvironmentContributor.ofExisting(defaultPropertySource));
        }
        
        // 3. 由 ConfigDataEnvironmentContributor 实例集合创建 ConfigDataEnvironmentContributors 实例
        return createContributors(contributors);
    }
}

分析可知:

  • 获取基础的贡献者

    • 已有贡献者
      • 其基于environment中的已有的属性源,逐一构建类型为EXISTINGConfigDataEnvironmentContributor实例
        • 就是用ConfigDataEnvironmentContributor封装属性源
    • 初始导入贡献者
      • 其通过getInitialImportContributors方法,构建类型为INITIAL_IMPORTConfigDataEnvironmentContributor实例
  • 将获取的ConfigDataEnvironmentContributor列表封装为ConfigDataEnvironmentContributors实例

    • ConfigDataEnvironmentContributors就是一个用于管理贡献者的贡献管理者

初始导入贡献者获取

主要分析getInitialImportContributors的处理过程

// org.springframework.boot.context.config.ConfigDataEnvironment
class ConfigDataEnvironment {
    
    // 几个配置路径属性
    static final String IMPORT_PROPERTY = "spring.config.import";
    static final String ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location";
    static final String LOCATION_PROPERTY = "spring.config.location";
    
    private static final ConfigDataLocation[] EMPTY_LOCATIONS = new ConfigDataLocation[0];
    
    // 默认扫描路径
    static final ConfigDataLocation[] DEFAULT_SEARCH_LOCATIONS;
	static {
		List<ConfigDataLocation> locations = new ArrayList<>();
        // 1. src/main/resources/ 2. src/main/resources/config/ 目录下
		locations.add(ConfigDataLocation.of("optional:classpath:/;optional:classpath:/config/"));
        // 1. 项目根目录下 2. 项目根目录下的 config 目录下
		locations.add(ConfigDataLocation.of("optional:file:./;optional:file:./config/;optional:file:./config/*/"));
		DEFAULT_SEARCH_LOCATIONS = locations.toArray(new ConfigDataLocation[0]);
	}
    
    /* 获取初始贡献者 */
    private List<ConfigDataEnvironmentContributor> getInitialImportContributors(Binder binder) {
        List<ConfigDataEnvironmentContributor> initialContributors = new ArrayList<>();
        // 基于配置路径属性值,创建贡献者实例
        addInitialImportContributors(initialContributors, 
                                     bindLocations(binder, IMPORT_PROPERTY, EMPTY_LOCATIONS));
        addInitialImportContributors(initialContributors,
                                     bindLocations(binder, ADDITIONAL_LOCATION_PROPERTY, EMPTY_LOCATIONS));
        addInitialImportContributors(initialContributors,
                                     bindLocations(binder, LOCATION_PROPERTY, DEFAULT_SEARCH_LOCATIONS));
        return initialContributors;
    }
}
细节分析

创建贡献者主要通过bindLocations/addInitialImportContributors方法完成,跟进分析

路径绑定

跟踪bindLocations方法处理,其实际处理分为两个分支:

  • 从属性源中获取对应配置路径值
    • 如果存在配置,将其转换为ConfigDataLocation[]后返回
    • 如果不存在配置,则返回bindLocations参数中的默认结果

其中由findProperty方法完成从属性源中取值的工作

// org.springframework.boot.context.properties.bind.Binder
public class Binder {
    
    private <T> ConfigurationProperty findProperty(ConfigurationPropertyName name, Bindable<T> target, Context context) {
		if (name.isEmpty() || target.hasBindRestriction(BindRestriction.NO_DIRECT_PROPERTY)) {
			return null;
		}
		for (ConfigurationPropertySource source : context.getSources()) {
            // 从属性源中获取对应配置值
			ConfigurationProperty property = source.getConfigurationProperty(name);
			if (property != null) {
				return property;
			}
		}
		return null;
	}
}

属性值转换为ConfigDataLocation[]的过程不予分析

举个🌰:

  • 假设配置了spring.config.import=import1.yaml;import2.yaml属性
  • 则创建一个ConfigDataLocation实例
    • value值为import1.yaml;import2.yaml
贡献者获取

跟进分析addInitialImportContributors方法,其就是基于ConfigDataLocation[]创建ConfigDataEnvironmentContributor实例

// org.springframework.boot.context.config.ConfigDataEnvironment
class ConfigDataEnvironment {
    
    private void addInitialImportContributors(List<ConfigDataEnvironmentContributor> initialContributors,
			ConfigDataLocation[] locations) {
		for (int i = locations.length - 1; i >= 0; i--) {
            // 遍历每个配置路径创建实例
			initialContributors.add(createInitialImportContributor(locations[i]));
		}
	}
}

跟进createInitialImportContributor处理

// org.springframework.boot.context.config.ConfigDataEnvironment
class ConfigDataEnvironment {
    
    private ConfigDataEnvironmentContributor createInitialImportContributor(ConfigDataLocation location) {
		return ConfigDataEnvironmentContributor.ofInitialImport(location);
	}
}

// org.springframework.boot.context.config.ConfigDataEnvironmentContributor
class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironmentContributor> {
    
    static ConfigDataEnvironmentContributor ofInitialImport(ConfigDataLocation initialImport) {
        // 使用数组封装 ConfigDataLocation
		List<ConfigDataLocation> imports = Collections.singletonList(initialImport);
        // 创建一个 ConfigDataProperties 实例,封装 ConfigDataLocation 列表
		ConfigDataProperties properties = new ConfigDataProperties(imports, null);
        // 创建类型为 INITIAL_IMPORT 的 ConfigDataEnvironmentContributor 实例
		return new ConfigDataEnvironmentContributor(Kind.INITIAL_IMPORT, null, null, false, null, null, properties, null, null);
	}
}

分析可知:

  • 使用ConfigDataProperties封装ConfigDataLocation数组
  • 基于ConfigDataProperties实例,创建ConfigDataEnvironmentContributor实例
小结

经过上述分析可知:

  • 配置属性处理

    • 默认从属性源中加载属性值

      • 包括spring.config.import/pring.config.additional-location/spring.config.location配置
      • 注意
        • 如果配置的资源式可选的,需要在配置前面加上optional:表示可选
    • 对属性值进行判断

      • 如果存在,则基于此值创建ConfigDataLocation实例
        • 如果不存在,则返回默认结果
          • 只有spring.config.import对应的默认值存为非空数组DEFAULT_SEARCH_LOCATIONS
            • DEFAULT_SEARCH_LOCATIONS通过静态初始化赋值
  • 使用ConfigDataProperties封装上述得到的ConfigDataLocation数组

  • 基于ConfigDataProperties实例,创建ConfigDataEnvironmentContributor实例

正常不配置spring.config.import/pring.config.additional-location/spring.config.location

  • 其仅仅创建两个类型为INITIAL_IMPORTConfigDataEnvironmentContributor实例
  • 分别对应路径为
    • optional:classpath:/;optional:classpath:/config/
    • optional:file:./;optional:file:./config/;optional:file:./config/*/

至此,完成了ConfigDataEnvironment实例的创建,其结构大致如下

img

3. 处理与应用

ConfigDataEnvironment实例创建完成后,将调用processAndApply方法进行处理

处理入口

跟进分析processAndApply方法

// org.springframework.boot.context.config.ConfigDataEnvironment
class ConfigDataEnvironment {

    void processAndApply() {
        // 1. 创建一个 ConfigDataImporter 实例
        ConfigDataImporter importer = new ConfigDataImporter(this.logFactory, this.notFoundAction, this.resolvers, this.loaders);
        registerBootstrapBinder(this.contributors, null, DENY_INACTIVE_BINDING);
        
        // 2. 三阶段处理
        // 2.1 阶段一:解析初始导入贡献者
        ConfigDataEnvironmentContributors contributors = processInitial(this.contributors, importer);
        
        // 创建 ConfigDataActivationContext 实例
        ConfigDataActivationContext activationContext = createActivationContext(
            contributors.getBinder(null, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE));
        
        // 2.2 阶段一:解析云平台配置
        contributors = processWithoutProfiles(contributors, importer, activationContext);
        
        // 2.3 阶段三:解析激活配置 spring.profile.active 和 spring.profile.groups
        // 处理 阶段一处理后的 profiles 配置
        activationContext = withProfiles(contributors, activationContext);
        // 进行 profiles 处理
        contributors = processWithProfiles(contributors, importer, activationContext);
        
        // 3. 将所有由贡献者解析出来的属性源,添加到 environment 实例中
        applyToEnvironment(contributors, activationContext, importer.getLoadedLocations(), importer.getOptionalLocations());
    }
}

分析可知:

  • 其主要通过三阶段处理贡献者
    • 每个阶段处理不同场景
    • 阶段一:解析处理默认的application文件资源
    • 阶段二:解析处理云平台配置(暂不关注)
    • 阶段三:解析处理profiles对应的application-<profile>文件资源
  • 处理完后,将贡献者中的属性源添加到Environment实例中

阶段处理公共逻辑分析

processAndApply中,其主要通过三个阶段完成处理,其各自对应一个处理函数,跟进分析

// org.springframework.boot.context.config.ConfigDataEnvironment
class ConfigDataEnvironment {

    /* 阶段一处理:无 activationContext,其实就是无云平台,无 profiles */
    private ConfigDataEnvironmentContributors processInitial(ConfigDataEnvironmentContributors contributors,
        	ConfigDataImporter importer) {
        contributors = contributors.withProcessedImports(importer, null);
        registerBootstrapBinder(contributors, null, DENY_INACTIVE_BINDING);
        return contributors;
    }

    /* 阶段二处理:如果配置了云计算平台,此时就会进行云平台解析 */
    private ConfigDataEnvironmentContributors processWithoutProfiles(ConfigDataEnvironmentContributors contributors,
        	ConfigDataImporter importer, ConfigDataActivationContext activationContext) {
        contributors = contributors.withProcessedImports(importer, activationContext);
        registerBootstrapBinder(contributors, activationContext, DENY_INACTIVE_BINDING);
        return contributors;
    }

    /* 阶段三处理:如果配置了 spring.profile.active和spring.profile.groups 则会进行响应解析 */
    private ConfigDataEnvironmentContributors processWithProfiles(ConfigDataEnvironmentContributors contributors,
        	ConfigDataImporter importer, ConfigDataActivationContext activationContext) {
        contributors = contributors.withProcessedImports(importer, activationContext);
        registerBootstrapBinder(contributors, activationContext, ALLOW_INACTIVE_BINDING);
        return contributors;
    }
}

分析可知:

  • 三个阶段处理过程大致类似,都是通过withProcessedImports进行处理

导入处理分析

下面跟进分析三个阶段的公共处理逻辑withProcessedImports

// org.springframework.boot.context.config.ConfigDataEnvironment
class ConfigDataEnvironment {
    
    ConfigDataEnvironmentContributors withProcessedImports(ConfigDataImporter importer, ConfigDataActivationContext activationContext) {
		// 1. 定义导入阶段
        // 第一二阶段为:BEFORE_PROFILE_ACTIVATION(默认配置文件激活前)
        // 第三阶段为:AFTER_PROFILE_ACTIVATION(默认配置文件激活后)
        ImportPhase importPhase = ImportPhase.get(activationContext);

		ConfigDataEnvironmentContributors result = this;
		int processed = 0;
		while (true) {
            // 2. 遍历获取需要处理的 ConfigDataEnvironmentContributor 实例进行处理
			ConfigDataEnvironmentContributor contributor = getNextToProcess(result, activationContext, importPhase);
			
            if (contributor == null) {
                // 没有需要处理的,则返回结果
				return result;
			}
            
            // 3. 解析类型为 UNBOUND_IMPORT 的贡献者
			if (contributor.getKind() == Kind.UNBOUND_IMPORT) {
				ConfigDataEnvironmentContributor bound = contributor.withBoundProperties(result, activationContext);
				result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext,
						result.getRoot().withReplacement(contributor, bound));
				continue;
			}
            
            // 4. 处理满足 isActiveWithUnprocessedImports 判断的贡献者(二类贡献者)
            // 4.1 创建路径解析上下文
			ConfigDataLocationResolverContext locationResolverContext = new ContributorConfigDataLocationResolverContext(
					result, contributor, activationContext);
            // 4.2 创建配置数据加载上下文
			ConfigDataLoaderContext loaderContext = new ContributorDataLoaderContext(this);
            
            // 4.3 获取要解析的配置数据路径
			List<ConfigDataLocation> imports = contributor.getImports();
            
            // 4.4 通过导入器,解析具体的配置数据路径,得到解析结果
            // ConfigDataResolutionResult 包含 location 和 resource
            // ConfigData 包含 PropertiesSource
			Map<ConfigDataResolutionResult, ConfigData> imported = importer.resolveAndLoad(activationContext,
					locationResolverContext, loaderContext, imports);
            
            // 4.5 创建一个新的贡献者,这个贡献者在importPhase阶段的子贡献者就是解析出的数据
			ConfigDataEnvironmentContributor contributorAndChildren = contributor
                .withChildren(importPhase, asContributors(imported));

            // 4.6 替换 root 贡献者中的当前解析贡献者,为上面的新的已被解析的贡献者
            result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext,
					result.getRoot().withReplacement(contributor, contributorAndChildren));
            
			processed++;
		}
	}
}

分析可知:

  • 首先通过getNextToProcess方法进行判断,获取其中需要处理的贡献者
    • 实际上存在两种类型
  • 后续对两种类型的贡献者,分别进行分支处理
贡献者判断

此处仅仅解析需要处理的贡献者,一个贡献者是否需要进行解析,其通过getNextToProcess方法进行判断,返回需要处理的贡献者

// org.springframework.boot.context.config.ConfigDataEnvironment
class ConfigDataEnvironment {
    
    private ConfigDataEnvironmentContributor getNextToProcess(ConfigDataEnvironmentContributors contributors,
			ConfigDataActivationContext activationContext, ImportPhase importPhase) {
		for (ConfigDataEnvironmentContributor contributor : contributors.getRoot()) {
            // 一类:类型为 UNBOUND_IMPORT(未导入完全贡献者)
            // 二类:处于 未处理的导入状态
			if (contributor.getKind() == Kind.UNBOUND_IMPORT
					|| isActiveWithUnprocessedImports(activationContext, importPhase, contributor)) {
				return contributor;
			}
		}
		return null;
	}
}

分析可知,需要处理的贡献者为两类:

  • 一类
    • 类型为UNBOUND_IMPORT(未导入完全贡献者)的贡献者
    • 资源已经处理,但未导入到Environment环境中
  • 二类
    • 通过isActiveWithUnprocessedImports方法筛选的贡献者

其中isActiveWithUnprocessedImportsConfigDataEnvironmentContributor实例的判断逻辑大致如下:

  • propertiesnull
  • properties不为null,但是propertiesactivate属性为null
  • properties不为nullpropertiesactivate属性也不为null,但是传入的activationContextnull
  • properties,propertiesactivate,传入的activationContext都不为为null,并且满足下面两个条件
    • properties中的onCloudPlatformnull
      • 或者properties中的onCloudPlatform不为null,并且和activationContextonCloudPlatform相同
    • properties中的onProfilenull
      • 或者properties中的onProfile不为null,并且和activationContext中的profiles匹配

所以二类贡献者,简化来说就是:

  • 贡献者有properties中指示了有要解析的配置文件路径,但是children中发现并没有对应的解析配置,所以就需要解析
二类贡献者处理

实际上对于二类贡献者,其主要处理逻辑有两部分:

  • 通过resolveAndLoad解析加载配置数据对应数据源
  • 将结果封装为新的贡献者
  • 由新的贡献者与旧的贡献管理者,生成新的贡献管理者,进行替换
解析与加载

跟踪分析resolveAndLoad方法

// org.springframework.boot.context.config.ConfigDataImporter
class ConfigDataImporter {
    
    Map<ConfigDataResolutionResult, ConfigData> resolveAndLoad(ConfigDataActivationContext activationContext,
			ConfigDataLocationResolverContext locationResolverContext, ConfigDataLoaderContext loaderContext,
			List<ConfigDataLocation> locations) {
		// 1. 获取 Profiles
        // 在第三阶段,如果有配置,则有值
        Profiles profiles = (activationContext != null) ? activationContext.getProfiles() : null;
        // 2. 基于 ConfigDataLocation 封装为 ConfigDataResolutionResult 列表
        List<ConfigDataResolutionResult> resolved = resolve(locationResolverContext, profiles, locations);
        // 3. 解析 ConfigDataResolutionResult 得到 ConfigData 结果
        return load(loaderContext, resolved);
	}
}

分析可知,其分为两步走:

  • resolve:解析配置路径
  • load:加载配置资源

此部分分析过程在下述4. 二类贡献者解析与加载部分

解析结果处理

经过ConfigDataImporter#resolveAndLoad处理后,得到Map<ConfigDataResolutionResult, ConfigData> imported结果

下面需要将其封装,得到最终的贡献管理者ConfigDataEnvironmentContributors

分析可知,其实际上又分为两步走:

  • 首先将每个二类贡献者的解析结果Map<ConfigDataResolutionResult, ConfigData> imported封装为一个贡献者实例

    • 先调用ConfigDataEnvironmentContributors#asContributors将其封装为贡献者列表

    • 由当前贡献者调用withChildren,将当前二类贡献者与上面创建的贡献者组合封装为一个新贡献者

  • 创建一个新的贡献管理者,替代旧的贡献管理者

    • 将旧的贡献管理者中管理的贡献者与上面创建的贡献者整合

下面一步步分析

  1. 解析结果封装为贡献者

这个步骤由ConfigDataEnvironmentContributors#asContributors方法完成,跟进分析

// org.springframework.boot.context.config.ConfigDataEnvironmentContributors
class ConfigDataEnvironmentContributors implements Iterable<ConfigDataEnvironmentContributor> {

    private List<ConfigDataEnvironmentContributor> asContributors(
        Map<ConfigDataResolutionResult, ConfigData> imported) {
        // 创建结果列表
        List<ConfigDataEnvironmentContributor> contributors = new ArrayList<>(imported.size() * 5);
        
        // 遍历处理
        imported.forEach((resolutionResult, data) -> {
            ConfigDataLocation location = resolutionResult.getLocation();
            ConfigDataResource resource = resolutionResult.getResource();
            boolean profileSpecific = resolutionResult.isProfileSpecific();
            if (data.getPropertySources().isEmpty()) {
                // 空属性源,创建类型为 EMPTY_LOCATION 的贡献者
                contributors.add(ConfigDataEnvironmentContributor.ofEmptyLocation(location, profileSpecific));
            } else {
                // 遍历 ConfigData 中的属性源
                for (int i = data.getPropertySources().size() - 1; i >= 0; i--) {
                    // 每个属性源创建一个类型为 UNBOUND_IMPORT 的贡献者
                    contributors.add(ConfigDataEnvironmentContributor
                                     .ofUnboundImport(location, resource, profileSpecific, data, i));
                }
            }
        });
        
        return Collections.unmodifiableList(contributors);
    }
}

分析可知:

  • 其实际就是将上述解析得到的属性源封装为贡献者
  • 处理
    • 如果数据配置路径下没有解析到属性源,则封装类型为EMPTY_LOCATION的贡献者,其中没有属性源
    • 如果数据配置路径下有解析到属性源,则遍历其属性源,封装为贡献者
  • 每个属性源封装为一个类型为UNBOUND_IMPORT的贡献者
    • 其中封装了属性源
  1. 组合二类贡献者与新贡献者

此步骤由二类贡献者调用ConfigDataEnvironmentContributor#withChildren方法完成

// org.springframework.boot.context.config.ConfigDataEnvironmentContributor
class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironmentContributor> {
    
    // 实际存储属性源
    private final PropertySource<?> propertySource;
    
    // 子贡献者存储
    private final Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children;
    
    // 贡献者类型
    private final Kind kind;
    
    /* 基于子贡献者进行构造 */
    ConfigDataEnvironmentContributor withChildren(ImportPhase importPhase,
			List<ConfigDataEnvironmentContributor> children) {
        // 以二类贡献者原有子贡献者作为基础
		Map<ImportPhase, List<ConfigDataEnvironmentContributor>> updatedChildren = new LinkedHashMap<>(this.children);
        // 将新的贡献者列表加入
		updatedChildren.put(importPhase, children);
        
        // 如果是 profile 相关,即第三阶段处理
		if (importPhase == ImportPhase.AFTER_PROFILE_ACTIVATION) {
			moveProfileSpecific(updatedChildren);
		}
        
        // 创建新的贡献者返回,其中包括了 二类贡献者已有的子贡献者 和 二类贡献者解析结果封装的贡献者
		return new ConfigDataEnvironmentContributor(this.kind, this.location, this.resource,
				this.fromProfileSpecificImport, this.propertySource, this.configurationPropertySource, this.properties,
				this.configDataOptions, updatedChildren);
	}
}

分析可知:

  • 实际就是创建一个新的贡献,其管理的子贡献者包括
    • 二类贡献者中原有的子贡献者
    • 二类贡献者解析结果封装的贡献者
  1. 创建新的贡献管理者

其中主要的是root根贡献者,其由原有贡献管理者中管理的root根贡献者调用withReplacement方法完成

// org.springframework.boot.context.config.ConfigDataEnvironmentContributor
class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironmentContributor> {

    // 实际存储属性源
    private final PropertySource<?> propertySource;

    // 子贡献者存储
    private final Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children;

    // 贡献者类型
    private final Kind kind;

    /* 替代根贡献者 */
    ConfigDataEnvironmentContributor withReplacement(ConfigDataEnvironmentContributor existing,
                                                     ConfigDataEnvironmentContributor replacement) {
		// 如果当前二类贡献者就是根贡献者,则直接返回前面处理结果
        if (this == existing) {
            return replacement;
        }
        // 创建子贡献者结果容器
        Map<ImportPhase, List<ConfigDataEnvironmentContributor>> updatedChildren = new LinkedHashMap<>(this.children.size());
        // 遍历根贡献中原有子贡献者
        this.children.forEach((importPhase, contributors) -> {
            List<ConfigDataEnvironmentContributor> updatedContributors = new ArrayList<>(contributors.size());
            for (ConfigDataEnvironmentContributor contributor : contributors) {
                // 处理每个子贡献者,递归调用 withReplacement
                updatedContributors.add(contributor.withReplacement(existing, replacement));
            }
            // 子贡献者结果列表转换为不可变列表
            updatedChildren.put(importPhase, Collections.unmodifiableList(updatedContributors));
        });
        // 封装创建新的根贡献者
        return new ConfigDataEnvironmentContributor(this.kind, this.location, this.resource,
                                                    this.fromProfileSpecificImport, this.propertySource, this.configurationPropertySource, this.properties,
                                                    this.configDataOptions, updatedChildren);
    }

}

分析可知:

  • 其实际就是将原有贡献管理者中管理的二类贡献者,由此二类贡献者处理后得到的新的贡献者进行替代
小结
  • 二类贡献者经过处理,其配置路径对应的资源的资源文件,都将转换为类型为UNBOUND_IMPORT的贡献者
  • 二类贡献者处理得到的类型为UNBOUND_IMPORT的贡献者,实际就是一类贡献者了,由于处理过程中贡献管理者result在不断更新替换,所以所有二类贡献者处理得到的型为UNBOUND_IMPORT的一类贡献者还将再次进行处理
一类贡献者处理

对于一类贡献者,其主要是通过ConfigDataEnvironmentContributor#withBoundProperties封装为一个新的贡献者

// org.springframework.boot.context.config.ConfigDataEnvironmentContributor
class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironmentContributor> {

    // 实际存储属性源
    private final PropertySource<?> propertySource;

    // 子贡献者存储
    private final Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children;

    // 贡献者类型
    private final Kind kind;

    /* 处理一类贡献者 */
    ConfigDataEnvironmentContributor withBoundProperties(Iterable<ConfigDataEnvironmentContributor> contributors,
			ConfigDataActivationContext activationContext) {
        // 获取内部管理的属性源的迭代器
		Iterable<ConfigurationPropertySource> sources = Collections.singleton(getConfigurationPropertySource());
        
        // 创建一个占位符解析器
		PlaceholdersResolver placeholdersResolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(
				contributors, activationContext, this, true);
        
		Binder binder = new Binder(sources, placeholdersResolver, null, null, null);
		UseLegacyConfigProcessingException.throwIfRequested(binder);
        
        // 创建配置数据属性实例
		ConfigDataProperties properties = ConfigDataProperties.get(binder);
		if (properties != null && this.configDataOptions.contains(ConfigData.Option.IGNORE_IMPORTS)) {
			properties = properties.withoutImports();
		}
        
        // 封装为类型为 BOUND_IMPORT 的贡献者
		return new ConfigDataEnvironmentContributor(Kind.BOUND_IMPORT, this.location, this.resource,
				this.fromProfileSpecificImport, this.propertySource, this.configurationPropertySource, properties,
				this.configDataOptions, null);
	}

}
处理过程扩展(跳转)
  • 对于二类贡献者的解析与加载的分析,请查看4. 二类贡献者解析与加载(扩展)
  • 第三阶段对于profiles的解析,请查看5. profils环境处理(扩展)

贡献解析阶段总结(重点)

分析至此,我们做一个贡献解析阶段的总结

  • 在实例构建阶段

    • 主要构建了贡献管理者
    • 基于Environment环境中已有的属性源,创建了EXISTING已存贡献者
    • 创建INITIAL_IMPORT初始贡献者
      • 基于spring.config.import/pring.config.additional-location/spring.config.location配置,创建对应的初始贡献者
      • 如果没有对应配置,默认创建两个初始贡献者,其配置路径对应以下两个路径
        • optional:classpath:/;optional:classpath:/config/
        • optional:file:./;optional:file:./config/;optional:file:./config/*/
  • 处理过程

    • 整个处理过程分为三个阶段,分别处理不同配置的资源文件
      • 阶段一
        • 默认处理配置路径下名称为application的资源文件
          • 资源文件的文件扩展名由PropertySourceLoader决定,默认配置了两个属性源加载器
            • PropertiesPropertySourceLoader
            • 解析扩展名为properties/xml的资源文件
            • YamlPropertySourceLoader
              • 解析扩展名为yml/yaml的资源文件
      • 阶段二
        • 处理云平台相关的资源文件,此处不赘述
      • 阶段三
        • 基于上述阶段处理后已有的贡献者中的属性源,获取profiles激活情况(具体查看5. profiles环境处理)
          • 涉及的配置项有:spring.profiles.[include/group/active/default]
        • 基于profiles激活情况,处理配置路径下名称为application-<profile>的资源文件
          • 资源文件的文件扩展名由PropertySourceLoader决定
  • 阶段处理(实际上,三阶段处理的逻辑大致相同)

    • 首先对贡献管理者中的贡献者进行判断

      • 确定哪些贡献者需要处理,以及根据贡献者分类进行处理分支选择
        • 主要分析了二类贡献者的处理过程
    • 二类贡献者解析与加载

      • 首先解析配置路径,获取候选参考实例

        • 实际就是基于配置路径/资源名称/资源后缀名,进行叉乘,得到所有可能需要加载的资源文件
      • 资源文件加载

        • 上述解析的候选参考对应的资源文件只是可能存在,所以需要加载确定存在的资源文件
        • 将加载到的资源文件通过属性源加载器,解析为属性源实例
        • 将解析得到的属性源,封装为Map<ConfigDataResolutionResult, ConfigData>结果返回
      • 结果贡献管理者封装

        • 基于Map<ConfigDataResolutionResult, ConfigData>结果,封装类型为UNBOUND_IMPORT类型的一类贡献者
      • 贡献管理者替换

        • 将新建的一类贡献者,结合旧的贡献管理者,封装新的贡献管理者
        • 实际作用是将新建的一类贡献者,放置到处理循环中
    • 一类贡献者处理

      • 实际就是基于上述创建的一类贡献者,创建对应的类型为BOUND_IMPORT的贡献者,其中封装了属性源

总而言之就是在配置路径下,加载对应资源文件,通过属性源加载器解析为属性源

应用至环境

在完成阶段处理后,就需要将由解析贡献者得到的属性源添加到Environment环境实例中,此部分由applyToEnvironment方法完成,跟进分析

// org.springframework.boot.context.config.ConfigDataEnvironment
class ConfigDataEnvironment {

    /* 应用到环境 */
    private void applyToEnvironment(ConfigDataEnvironmentContributors contributors,
                                    ConfigDataActivationContext activationContext, Set<ConfigDataLocation> loadedLocations,
                                    Set<ConfigDataLocation> optionalLocations) {
        checkForInvalidProperties(contributors);
        checkMandatoryLocations(contributors, activationContext, loadedLocations, optionalLocations);
        // 1. 获取环境可变属性源
        MutablePropertySources propertySources = this.environment.getPropertySources();
        // 2. 应用 贡献者中属性源 到 可变属性源 中
        applyContributor(contributors, activationContext, propertySources);
        DefaultPropertiesPropertySource.moveToEnd(propertySources);
        
        // 3. 更新 profiles 信息到 Environment 环境实例中,资源文件中存在新的配置,需要同步更新
        Profiles profiles = activationContext.getProfiles();
        this.environment.setDefaultProfiles(StringUtils.toStringArray(profiles.getDefault()));
        this.environment.setActiveProfiles(StringUtils.toStringArray(profiles.getActive()));
        // 触发环境更新事件监听
        this.environmentUpdateListener.onSetProfiles(profiles);
    }
}

分析可知:

  • 其主要处理部分有
    • 将贡献者中属性源应用到Environment环境实例管理的可变属性源中
    • Profiles信息更新到Environment环境实例中
属性源应用处理

属性源应用主要由applyContributor方法完成,跟进分析

// org.springframework.boot.context.config.ConfigDataEnvironment
class ConfigDataEnvironment {

    private void applyContributor(ConfigDataEnvironmentContributors contributors,
                                  ConfigDataActivationContext activationContext, MutablePropertySources propertySources) {
        // 遍历贡献管理者管理的子贡献者
        // 贡献者管理的子贡献者类似于一棵树,以 root 贡献者为根节点,遍历时会以叶子节点(最底层的子贡献者)进行遍历
        for (ConfigDataEnvironmentContributor contributor : contributors) {
            // 获取贡献者中属性源
            PropertySource<?> propertySource = contributor.getPropertySource();
            // 仅处理类型为 BOUND_IMPORT(一类贡献者处理结果),且属性源不为空的
            if (contributor.getKind() == ConfigDataEnvironmentContributor.Kind.BOUND_IMPORT && propertySource != null) {
                // 将属性源添加到可变属性源中
                propertySources.addLast(propertySource);
            }
        }
    }
}
小结

分析可知:

  • 实际应用的过程,就是将贡献者中的属性源提取,添加到Environment环境实例中

4. 二类贡献者解析与加载(扩展)

处理入口(ConfigDataImporter)

解析与加载贡献者的处理过程相对复杂,所以将其抽取出来分析,对应ConfigDataImporter#resolveAndLoad方法处理

// org.springframework.boot.context.config.ConfigDataImporter
class ConfigDataImporter {
    
    Map<ConfigDataResolutionResult, ConfigData> resolveAndLoad(ConfigDataActivationContext activationContext,
			ConfigDataLocationResolverContext locationResolverContext, ConfigDataLoaderContext loaderContext,
			List<ConfigDataLocation> locations) {
		// 1. 获取 Profiles
        // 在第三阶段,如果有配置,则有值
        Profiles profiles = (activationContext != null) ? activationContext.getProfiles() : null;
        
        // 2. 基于 ConfigDataLocation 封装为 ConfigDataResolutionResult 列表
        List<ConfigDataResolutionResult> resolved = resolve(locationResolverContext, profiles, locations);
        
        // 3. 解析 ConfigDataResolutionResult 得到 ConfigData 结果
        return load(loaderContext, resolved);
	}
}

基于前面分析可知,其主要分为两部分:

  • resolve
    • 解析配置路径,获取ConfigDataResolutionResult列表
  • load
    • 加载配置资源,基于ConfigDataResolutionResult获取对应的ConfigData

配置路径解析

首先通过ConfigDataImporter#resolve方法解析配置路径,获取ConfigDataResolutionResult列表

处理入口

跟进分析ConfigDataImporter#resolve方法

// org.springframework.boot.context.config.ConfigDataImporter
class ConfigDataImporter {
    
    // 配置路径解析器
    private final ConfigDataLocationResolvers resolvers;
       
    /* 配置路径解析 */
    private List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolverContext locationResolverContext,
                                                     Profiles profiles, List<ConfigDataLocation> locations) {
        List<ConfigDataResolutionResult> resolved = new ArrayList<>(locations.size());
        for (ConfigDataLocation location : locations) {
            // 遍历解析单个配置路径
            resolved.addAll(resolve(locationResolverContext, profiles, location));
        }
        return Collections.unmodifiableList(resolved);
    }

	/* 单个配置路径解析 */
    private List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolverContext locationResolverContext,
                                                     Profiles profiles, ConfigDataLocation location) {
        return this.resolvers.resolve(locationResolverContext, location, profiles);
    }
}

分析可知:

  • 实际由ConfigDataLocationResolvers#resolve完成解析路径
  • 解析结果为ConfigDataResolutionResult集合
ConfigDataLocationResolvers处理

跟进分析ConfigDataLocationResolvers#resolve处理

// org.springframework.boot.context.config.ConfigDataLocationResolvers
class ConfigDataLocationResolvers {

    // 配置路径解析器列表
    private final List<ConfigDataLocationResolver<?>> resolvers;

    /* 1. 解析入口 */
    List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolverContext context, ConfigDataLocation location,
                                             Profiles profiles) {
        for (ConfigDataLocationResolver<?> resolver : getResolvers()) {
            // 遍历解析器尝试进行配置路径解析
            if (resolver.isResolvable(context, location)) {
                return resolve(resolver, context, location, profiles);
            }
        }
    }
    
    /* 2. 遍历配置路径解析器,尝试进行解析 */
    private List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolver<?> resolver,
			ConfigDataLocationResolverContext context, ConfigDataLocation location, Profiles profiles) {
        // 1. 进行基础解析,不分析 profile
		List<ConfigDataResolutionResult> resolved = resolve(location, false, () -> resolver.resolve(context, location));
		
        if (profiles == null) {
            // 没有配置 profiles 直接返回
			return resolved;
		}
        // 2. 针对 profile 进行解析
		List<ConfigDataResolutionResult> profileSpecific = resolve(location, true,
				() -> resolver.resolveProfileSpecific(context, location, profiles));
        
        // 3. 合并结果
		return merge(resolved, profileSpecific);
	}
    
    /* 3. 具体解析调用 */
    private List<ConfigDataResolutionResult> resolve(ConfigDataLocation location, boolean profileSpecific,
			Supplier<List<? extends ConfigDataResource>> resolveAction) {
        // 通过 Lambda 表达式,实际调用 ConfigDataLocationResolver#resolve/resolveProfileSpecific 方法
		List<ConfigDataResource> resources = nonNullList(resolveAction.get());
        
		List<ConfigDataResolutionResult> resolved = new ArrayList<>(resources.size());
		for (ConfigDataResource resource : resources) {
            // 将解析得到的 ConfigDataResource 封装为 ConfigDataResolutionResult 实例返回
			resolved.add(new ConfigDataResolutionResult(location, resource, profileSpecific));
		}
		return resolved;
	}
}

分析可知:

  • 实际解析工作由ConfigDataLocationResolver实例完成
  • 对于profile判断后,通过两个方法进行处理
    • ConfigDataLocationResolver#resolve
      • 解析非profile的基础处理
    • ConfigDataLocationResolver#resolveProfileSpecific
      • 解析profile的环境处理
ConfigDataLocationResolver处理

上述分析可知,实际解析工作由ConfigDataLocationResolver实例完成

  • ConfigDataLocationResolvers中管理的ConfigDataLocationResolver列表,实际是通过SpringFactoriesLoader#oadFactoryNames获取

  • 其中实际使用的是StandardConfigDataLocationResolver

跟进StandardConfigDataLocationResolver处理分析

// org.springframework.boot.context.config.StandardConfigDataLocationResolver
public class StandardConfigDataLocationResolver {
    
    /* 1. 非 profile 的基础解析 */
    public List<StandardConfigDataResource> resolve(ConfigDataLocationResolverContext context,
			ConfigDataLocation location) throws ConfigDataNotFoundException {
        // 1. 解析配置路径为 StandardConfigDataReference
        // 2. 解析 StandardConfigDataReference
		return resolve(getReferences(context, location.split()));
	}
    
    /* 2. profile 的环境解析 */
    public List<StandardConfigDataResource> resolveProfileSpecific(ConfigDataLocationResolverContext context,
			ConfigDataLocation location, Profiles profiles) {
        // 1. 解析配置路径为 StandardConfigDataReference
        // 2. 解析 StandardConfigDataReference
		return resolve(getProfileSpecificReferences(context, location.split(), profiles));
	}
}

分析可知:

  • 解析步骤

    • 解析配置路径为StandardConfigDataReference

      • 配置路径内容将会由;进行分割,之后进行处理
    • 解析StandardConfigDataReference

  • 对于profile判断

    • profile的基础处理,使用getReferences获取StandardConfigDataResource实例
    • profile的环境处理,使用getProfileSpecificReferences获取StandardConfigDataResource实例
1. 解析路径获取参考实例

首先就是要解析ConfigDataLocation,获取对应的StandardConfigDataResource实例

需要注意的是,源配置路径ConfigDataLocation将会由;进行分割得到ConfigDataLocation[]作为入参

🌰:

  • optional:classpath:/;optional:classpath:/config/
  • 分割后结果
    • optional:classpath:/
    • optional:classpath:/config/
处理入口

首先通过getReferences方法将ConfigDataLocation解析为StandardConfigDataReference

// org.springframework.boot.context.config.StandardConfigDataLocationResolver
public class StandardConfigDataLocationResolver {

    /* 1. 处理多个 ConfigDataLocation */
    private Set<StandardConfigDataReference> getReferences(ConfigDataLocationResolverContext context,
                                                           ConfigDataLocation[] configDataLocations) {
        Set<StandardConfigDataReference> references = new LinkedHashSet<>();
        // 遍历处理使用 ; 分割后的配置路径
        for (ConfigDataLocation configDataLocation : configDataLocations) {
            // 通过 getReferences 处理单个配置路径
            // 将结果添加
            references.addAll(getReferences(context, configDataLocation));
        }
        return references;
    }

    /* 2. 处理单个 ConfigDataLocation */
    private Set<StandardConfigDataReference> getReferences(ConfigDataLocationResolverContext context,
                                                           ConfigDataLocation configDataLocation) {
        String resourceLocation = getResourceLocation(context, configDataLocation);
        
        if (isDirectory(resourceLocation)) {
            // 如果配置路径为文件夹
            return getReferencesForDirectory(configDataLocation, resourceLocation, NO_PROFILE);
        }
        // 如果配置路径为文件资源
        return getReferencesForFile(configDataLocation, resourceLocation, NO_PROFILE);
    }
}

分析可知,对于不同类型路径,其通过不同方法处理,下面一一分析

文件目录解析

对于文件目录类型路径,其通过getReferencesForDirectory方法进行解析,跟进分析

// org.springframework.boot.context.config.StandardConfigDataLocationResolver
public class StandardConfigDataLocationResolver {

    // 默认为 ["application"]
    private final String[] configNames;

    private Set<StandardConfigDataReference> getReferencesForDirectory(
        ConfigDataLocation configDataLocation, String directory, String profile) {
        
        Set<StandardConfigDataReference> references = new LinkedHashSet<>();
        
        // 遍历每个配置名称进行处理
        for (String name : this.configNames) {
            // 获取配置路径下多个符合的资源,解析为 StandardConfigDataReference 实例
            Deque<StandardConfigDataReference> referencesForName = getReferencesForConfigName(
                name, configDataLocation, directory, profile);
            // 添加到结果中
            references.addAll(referencesForName);
        }
        return references;
    }
}

跟进分析getReferencesForConfigName方法

// org.springframework.boot.context.config.StandardConfigDataLocationResolver
public class StandardConfigDataLocationResolver {

    // 包括 YamlPropertySourceLoader/PropertiesPropertySourceLoader
    private final List<PropertySourceLoader> propertySourceLoaders;

    private Deque<StandardConfigDataReference> getReferencesForConfigName(String name,
			ConfigDataLocation configDataLocation, String directory, String profile) {
		Deque<StandardConfigDataReference> references = new ArrayDeque<>();
        // 遍历属性源加载器
		for (PropertySourceLoader propertySourceLoader : this.propertySourceLoaders) {
            // 获取 属性源加载器 处理的文件扩展名,遍历处理
			for (String extension : propertySourceLoader.getFileExtensions()) {
                // 对应每个扩展名,创建一个 StandardConfigDataReference 实例
				StandardConfigDataReference reference = new StandardConfigDataReference(configDataLocation, directory,
						directory + name, profile, extension, propertySourceLoader);
				if (!references.contains(reference)) {
                    // 实例添加
					references.addFirst(reference);
				}
			}
		}
        // 结果返回
		return references;
	}
}

分析可知:

  • 对于文件目录类型,实际就是遍历获取可能的意向文件
    • 名称为application
    • 扩展名由PropertySourceLoader决定
      • 通常为properties/xml/yml/yaml
单一文件解析

跟进分析getReferencesForFile方法

// org.springframework.boot.context.config.StandardConfigDataLocationResolver
public class StandardConfigDataLocationResolver {
    
    // 包括 YamlPropertySourceLoader/PropertiesPropertySourceLoader
    private final List<PropertySourceLoader> propertySourceLoaders;

    private Set<StandardConfigDataReference> getReferencesForFile(
        ConfigDataLocation configDataLocation, String file, String profile) {
        
        // 判断是否扩展模式
        // 🌰:application[.yaml]
        Matcher extensionHintMatcher = EXTENSION_HINT_PATTERN.matcher(file);
        boolean extensionHintLocation = extensionHintMatcher.matches();
        if (extensionHintLocation) {
            // 如果是扩展模式,解析
            // 🌰:application[.yaml] --> application.yaml
            file = extensionHintMatcher.group(1) + extensionHintMatcher.group(2);
        }
        // 遍历属性源加载器
        for (PropertySourceLoader propertySourceLoader : this.propertySourceLoaders) {
            // 判断文件是否符合属性源加载器处理范围
            String extension = getLoadableFileExtension(propertySourceLoader, file);
            if (extension != null) {
                // 获取文件名称;application
                String root = file.substring(0, file.length() - extension.length() - 1);
                // 创建 StandardConfigDataReference 实例
                StandardConfigDataReference reference = new StandardConfigDataReference(
                    configDataLocation, null, root, profile, (!extensionHintLocation) ? extension : null, propertySourceLoader);
                // 返回结果
                return Collections.singleton(reference);
            }
        }
        if (configDataLocation.isOptional()) {
            return Collections.emptySet();
        }
    }
}

分析可知:

  • 对于单一文件类型,要求扩展名符合PropertySourceLoader处理范围
参考实例构建

上述对于资源路径的处理,最终都将构建StandardConfigDataReference实例,跟进分析

// org.springframework.boot.context.config.StandardConfigDataReference
class StandardConfigDataReference {
    
    // 源配置路径示例
    private final ConfigDataLocation configDataLocation;
    
    // 资源路径
    private final String resourceLocation;
	
    // 资源所处文件目录
	private final String directory;

    // 对应的 profile
	private final String profile;

    // 加载此资源对应的 属性源加载器
	private final PropertySourceLoader propertySourceLoader;
    
    /* 构造 */
    StandardConfigDataReference(ConfigDataLocation configDataLocation, String directory, String root, String profile,
			String extension, PropertySourceLoader propertySourceLoader) {
        // 保存源配置路径示例
		this.configDataLocation = configDataLocation;
        // 判断 profile,定义后缀
		String profileSuffix = (StringUtils.hasText(profile)) ? "-" + profile : "";
        // 定义资源路径
		this.resourceLocation = root + profileSuffix + ((extension != null) ? "." + extension : "");
		this.directory = directory;
		this.profile = profile;
        // 保存对应的 属性源加载器
		this.propertySourceLoader = propertySourceLoader;
	}
}

分析可知:

  • 参考实例StandardConfigDataReference主要保存两部分信息
    • 配置路径信息
    • 当前配置资源对应的属性源加载器PropertySourceLoader
属性源加载器

上述处理过程中,通过PropertySourceLoader获取处理范围

实际上PropertySourceLoader是由SpringFactoriesLoader#loadFactories获取,常用的实现有:

  • PropertiesPropertySourceLoader
    • 解析扩展名为properties/xml的资源文件
  • YamlPropertySourceLoader
    • 解析扩展名为yml/yaml的资源文件
小结
  • 实际通过路径解析器解析配置路径,其得到的StandardConfigDataReference被称为配置数据参考
    • 其为何被称为参考,因为它只是可能存在的资源,表示需要关注的资源范围

实际上以源配置路径optional:classpath:/;optional:classpath:/config/ 为例,其最终解析后得到的结果如下所示:

img
2. 参考解析

经过上述路径解析后,我们得到了StandardConfigDataReference集合,其中包含了所有需要关注的资源

处理入口

下面就是需要将它们解析获取真实资源,跟进分析resolve方法

// org.springframework.boot.context.config.StandardConfigDataLocationResolver
public class StandardConfigDataLocationResolver {
    
    // 路径资源加载器
    private final LocationResourceLoader resourceLoader;
    
    /* 参考集合解析 */
    private List<StandardConfigDataResource> resolve(Set<StandardConfigDataReference> references) {
		List<StandardConfigDataResource> resolved = new ArrayList<>();
        // 遍历参考信息进行解析
		for (StandardConfigDataReference reference : references) {
			resolved.addAll(resolve(reference));
		}
		if (resolved.isEmpty()) {
			resolved.addAll(resolveEmptyDirectories(references));
		}
		return resolved;
	}
    
    /* 单个配置参考解析 */
    private List<StandardConfigDataResource> resolve(StandardConfigDataReference reference) {
		if (!this.resourceLoader.isPattern(reference.getResourceLocation())) {
            // 资源路径不包含 * ,解析得到单个资源
			return resolveNonPattern(reference);
		}
        // 资源路径不包含 * ,解析得到多个资源
		return resolvePattern(reference);
	}
}

分析可知:

  • 此时配置路径有两种
    • 配置路径中不包含*,表示单一资源文件
      • 使用resolveNonPattern方法处理
    • 配置路径中包含*,表示多个资源文件
      • 使用resolvePattern方法处理
资源路径解析

对于配置数据参考实例的路径,将通过resolveNonPattern/resolvePattern方法进行处理,跟进分析

// org.springframework.boot.context.config.StandardConfigDataLocationResolver
public class StandardConfigDataLocationResolver {
    
    // LocationResourceLoader 实例
    private final LocationResourceLoader resourceLoader;
    
	/* 资源路径不包含 * ,解析得到单个资源 */
    private List<StandardConfigDataResource> resolveNonPattern(StandardConfigDataReference reference) {
        Resource resource = this.resourceLoader.getResource(reference.getResourceLocation());
        if (!resource.exists() && reference.isSkippable()) {
            logSkippingResource(reference);
            // 如果资源不存在,则返回空
            return Collections.emptyList();
        }
        // 否则封装为 StandardConfigDataResource 实例返回
        return Collections.singletonList(createConfigResourceLocation(reference, resource));
    }
    
    /* 资源路径不包含 * ,解析得到多个资源 */
    private List<StandardConfigDataResource> resolvePattern(StandardConfigDataReference reference) {
		List<StandardConfigDataResource> resolved = new ArrayList<>();
		for (Resource resource : this.resourceLoader.getResources(reference.getResourceLocation(), ResourceType.FILE)) {
			if (!resource.exists() && reference.isSkippable()) {
				logSkippingResource(reference);
			} else {
                // 如果资源存在,则封装为 StandardConfigDataResource 实例返回
				resolved.add(createConfigResourceLocation(reference, resource));
			}
		}
		return resolved;
	}
}

分析可知:

  • 首先通过LocationResourceLoader尝试进行资源加载
    • 加载成功,则封装为StandardConfigDataResource实例
    • 加载失败,则记录日志并跳过
资源加载

实际资源加载工作都是由LocationResourceLoader进行,跟进分析其getResource方法

// org.springframework.boot.context.config.LocationResourceLoader
class LocationResourceLoader {
    
    // 默认为 DefaultResourceLoader 实例
    private final ResourceLoader resourceLoader;
    
    Resource getResource(String location) {
        // 对路径进行前置处理
		validateNonPattern(location);
		location = StringUtils.cleanPath(location);
		if (!ResourceUtils.isUrl(location)) {
			location = ResourceUtils.FILE_URL_PREFIX + location;
		}
        // 加载资源
		return this.resourceLoader.getResource(location);
	}
    
}

跟进分析资源加载

// org.springframework.core.io.DefaultResourceLoader
public class DefaultResourceLoader implements ResourceLoader {

    public Resource getResource(String location) {
        // 不同格式名称文件,处理方式不同
        if (location.startsWith("/")) {
            return getResourceByPath(location);
        } else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
            return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
        } else {
            URL url = new URL(location);
            return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
        }
    }
}
3.小结

经过上述分析可知:

  • 基于源ConfigDataLocation进行解析,源ConfigDataLocation将会通过;进行分割,得到独立的配置数据路径
  • 🌰
    • optional:classpath:/;optional:classpath:/config/
    • 分割后结果
      • optional:classpath:/
      • optional:classpath:/config/
  • 对每个ConfigDataLocation进行解析,获取StandardConfigDataResource参考实例
    • 实际就是获取对应PropertySourceLoader的所有可能的文件资源
  • 解析StandardConfigDataResource
    • 对于真实存在的资源,创建对应的StandardConfigDataResource实例
    • 对于不存在的参考,输出日志后跳过即可
4. 结果封装

经过上述处理后得到StandardConfigDataResource实例,在ConfigDataLocationResolvers#resolve中最终会将其封装为ConfigDataResolutionResult进行返回

// org.springframework.boot.context.config.ConfigDataLocationResolvers
class ConfigDataLocationResolvers {

    /* 3. 具体解析调用 */
    private List<ConfigDataResolutionResult> resolve(ConfigDataLocation location, boolean profileSpecific,
			Supplier<List<? extends ConfigDataResource>> resolveAction) {
        // 通过 Lambda 表达式,实际调用 ConfigDataLocationResolver#resolve/resolveProfileSpecific 方法
		List<ConfigDataResource> resources = nonNullList(resolveAction.get());
        
		List<ConfigDataResolutionResult> resolved = new ArrayList<>(resources.size());
		for (ConfigDataResource resource : resources) {
            // 将解析得到的 ConfigDataResource 封装为 ConfigDataResolutionResult 实例返回
			resolved.add(new ConfigDataResolutionResult(location, resource, profileSpecific));
		}
		return resolved;
	}
}

示例结果如下:

img

配置数据加载

ConfigDataImporter#resolveAndLoad中,通过resolve方法获取List<ConfigDataResolutionResult>列表后,将通过load加载获取ConfigData

处理入口

跟进分析ConfigDataImporter#load方法

// org.springframework.boot.context.config.ConfigDataImporter
class ConfigDataImporter {

    // 配置数据加载器
	private final ConfigDataLoaders loaders;
    
    /* 加载获取配置数据 */
    private Map<ConfigDataResolutionResult, ConfigData> load(ConfigDataLoaderContext loaderContext,
        	List<ConfigDataResolutionResult> candidates) throws IOException {
        // 创建结果容器
        Map<ConfigDataResolutionResult, ConfigData> result = new LinkedHashMap<>();
        // 遍历处理
        for (int i = candidates.size() - 1; i >= 0; i--) {
            ConfigDataResolutionResult candidate = candidates.get(i);
            ConfigDataLocation location = candidate.getLocation();
            ConfigDataResource resource = candidate.getResource();
            
            if (resource.isOptional()) {
                this.optionalLocations.add(location);
            }
            if (this.loaded.contains(resource)) {
                this.loadedLocations.add(location);
            } else {
                // 使用 配置数据加载器 加载资源,获取配置数据
                ConfigData loaded = this.loaders.load(loaderContext, resource);
                if (loaded != null) {
                    this.loaded.add(resource);
                    this.loadedLocations.add(location);
                    // 将结果保存
                    result.put(candidate, loaded);
                }
            }
        }
        return Collections.unmodifiableMap(result);
    }
}

分析可知:

  • 实际加载工作由ConfigDataLoaders实例完成
资源加载

跟进分析ConfigDataLoaders#load方法

// org.springframework.boot.context.config.ConfigDataImporter
class ConfigDataLoaders {
	
    // 配置文件加载器
	private final List<ConfigDataLoader<?>> loaders;
    
    /*  */
    <R extends ConfigDataResource> ConfigData load(ConfigDataLoaderContext context, R resource) throws IOException {
        // 1. 根据资源类型,获取对应的 加载器
		ConfigDataLoader<R> loader = getLoader(context, resource);
        // 2. 调用 ConfigDataLoader#load 进行加载
		return loader.load(context, resource);
	}
}

基于上述分析,实际上我们使用的是StandardConfigDataLoader,跟进分析

// org.springframework.boot.context.config.StandardConfigDataLoader
public class StandardConfigDataLoader implements ConfigDataLoader<StandardConfigDataResource> {
    
    /* 资源加载 */
    public ConfigData load(ConfigDataLoaderContext context, StandardConfigDataResource resource)
			throws IOException, ConfigDataNotFoundException {
		if (resource.isEmptyDirectory()) {
			return ConfigData.EMPTY;
		}
        // 资源不存在,将报错
		ConfigDataResourceNotFoundException.throwIfDoesNotExist(resource, resource.getResource());
        
        // 获取参考实例
		StandardConfigDataReference reference = resource.getReference();
		Resource originTrackedResource = OriginTrackedResource.of(resource.getResource(), Origin.from(reference.getConfigDataLocation()));
		String name = String.format("Config resource '%s' via location '%s'", resource, reference.getConfigDataLocation());
		
        // 获取参考实例中存储的 属性源加载器,使用它加载资源文件,得到属性源
        List<PropertySource<?>> propertySources = reference.getPropertySourceLoader().load(name, originTrackedResource);
		PropertySourceOptions options = (resource.getProfile() != null) ? PROFILE_SPECIFIC : NON_PROFILE_SPECIFIC;
        
        // 将属性源封装为 ConfigData 实例
		return new ConfigData(propertySources, options);
	}
}

分析可知:

  • 实际是通过参考实例StandardConfigDataReference中存储的属性源加载器PropertySourceLoader进行加载,获取属性源
    • 对应属性源加载器,在上述有分析
  • 将加载资源得到的属性源,封装为ConfigData实例返回
配置数据实例构建

跟进分析一下ConfigData实例

// org.springframework.boot.context.config.ConfigData
public final class ConfigData {
    
    // 存储属性源
    private final List<PropertySource<?>> propertySources;
    
    public ConfigData(Collection<? extends PropertySource<?>> propertySources, PropertySourceOptions propertySourceOptions) {
		// 存储属性源,设置为不可变列表
        this.propertySources = Collections.unmodifiableList(new ArrayList<>(propertySources));
	}
}

小结

在整个二类贡献者解析/加载过程中,配置数据的形态转换过程如下图所示:

img

5. profiles环境处理(扩展)

处理入口

ConfigDataEnvironment#processAndApply处理过程中,第三阶段就是处理profile环境相关

// org.springframework.boot.context.config.ConfigDataEnvironment
class ConfigDataEnvironment {

    void processAndApply() {
        // 2.2 阶段一:解析云平台配置
        ConfigDataActivationContext activationContext = createActivationContext(
            contributors.getBinder(null, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE));
        
        // 2.3 阶段三:解析激活配置 spring.profile.active 和 spring.profile.groups
        // 处理 阶段一处理后的 profiles 配置
        activationContext = withProfiles(contributors, activationContext);
        // 进行 profiles 处理
        contributors = processWithProfiles(contributors, importer, activationContext);

    }
}

分析可知:

  • 初始创建ConfigDataActivationContext实例
  • 处理阶段一处理后的profiles配置
  • 进行profiles处理

此时有个前提,经过前两阶段的处理后,此时ConfigDataEnvironmentContributors包含的贡献者有:

  • 包括原始的EXISTING贡献者
    • 即在ApplicationServletEnvironment本身具有的属性源,包括实例实例化过程中添加的属性源
    • 从这些属性源中获取对应属性值
  • 包括INITIAL_IMPORT贡献者
    • 即解析application资源文件获取的属性源中获取对应属性值

上下文创建

跟进分析createActivationContext处理

// org.springframework.boot.context.config.ConfigDataEnvironment
class ConfigDataEnvironment {

    private ConfigDataActivationContext createActivationContext(Binder initialBinder) {
        // 创建上下文实例
        return new ConfigDataActivationContext(this.environment, initialBinder);
    }
}

实际创建了ConfigDataActivationContext,此时内部Profiles = null

profiles解析

通过withProfiles方法解析profiles配置,跟进分析

// org.springframework.boot.context.config.ConfigDataEnvironment
class ConfigDataEnvironment {
    
    // 附加 profiles
    private final Collection<String> additionalProfiles;

    /* profiles 解析 */
    private ConfigDataActivationContext withProfiles(ConfigDataEnvironmentContributors contributors,
                                                     ConfigDataActivationContext activationContext) {
        Binder binder = contributors.getBinder(activationContext,
        	(contributor) -> !contributor.hasConfigDataOption(ConfigData.Option.IGNORE_PROFILES),
        	BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE);
		
        // 1. 创建 profiles 集合
        Set<String> additionalProfiles = new LinkedHashSet<>(this.additionalProfiles);
        // 2. 解析 spring.profiles.include 配置
        additionalProfiles.addAll(getIncludedProfiles(contributors, activationContext));
        // 3. 封装 Profiles 实例
        Profiles profiles = new Profiles(this.environment, binder, additionalProfiles);
        // 4. 将 Profiles 实例保存到 ConfigDataActivationContext 中
        return activationContext.withProfiles(profiles);
    }
}

分析可知:

  • 通过getIncludedProfiles方法解析spring.profiles.include配置
  • 创建Profiles实例
  • Profiles实例设置到ConfigDataActivationContext上下文中
includeProfiles解析

跟进分析getIncludedProfiles方法

// org.springframework.boot.context.config.ConfigDataEnvironment
class ConfigDataEnvironment {
    
    // 
    public static final String INCLUDE_PROFILES_PROPERTY_NAME = "spring.profiles.include";
	static final ConfigurationPropertyName INCLUDE_PROFILES = ConfigurationPropertyName.of(Profiles.INCLUDE_PROFILES_PROPERTY_NAME);

    /* profiles 解析 */
    private Collection<? extends String> getIncludedProfiles(ConfigDataEnvironmentContributors contributors,
			ConfigDataActivationContext activationContext) {
        // 创建一个 占位符解析器
		PlaceholdersResolver placeholdersResolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(
				contributors, activationContext, null, true);
        
		Set<String> result = new LinkedHashSet<>();
		for (ConfigDataEnvironmentContributor contributor : contributors) {
            // 获取配置数据贡献者对应的属性源
			ConfigurationPropertySource source = contributor.getConfigurationPropertySource();
            // 要求:1. 属性源存在;2. 贡献者不会忽略 spring.profiles.include 与 spring.profiles.active
			if (source != null && !contributor.hasConfigDataOption(ConfigData.Option.IGNORE_PROFILES)) {
				Binder binder = new Binder(Collections.singleton(source), placeholdersResolver);
                // 从属性源中解析获取 spring.profiles.include 值,includes 参数为解析结果
				binder.bind(Profiles.INCLUDE_PROFILES, STRING_LIST).ifBound((includes) -> {
					// 结果添加
					result.addAll(includes);
				});
			}
		}
		return result;
	}
}

分析可知:

  • 实际就是从贡献者中解析出spring.profiles.include配置值
Profiles实例构建
// org.springframework.boot.context.config.Profiles
public class Profiles implements Iterable<String> {
    
    // 激活的 profile
    private final List<String> activeProfiles;

    // 默认的 profile
    private final List<String> defaultProfiles;

    // profile 组别
    // key:组别名称
    // value:组别中包含的 profile,多个使用 `,` 分割
    private final MultiValueMap<String, String> groups;
    
    Profiles(Environment environment, Binder binder, Collection<String> additionalProfiles) {
        // 1. 解析环境中 spring.profiles.group 配置,得到 groups 组别
		this.groups = binder.bind("spring.profiles.group", STRING_STRINGS_MAP).orElseGet(LinkedMultiValueMap::new);
        
        // 2. 调用 getActivatedProfiles 获取激活的 profiles
        // 调用 expandProfiles,通过 groups 扩展 profiles
		this.activeProfiles = expandProfiles(getActivatedProfiles(environment, binder, additionalProfiles));
        
        // 3. 调用 getDefaultProfiles 获取默认的 profiles
        // 调用 expandProfiles,通过 groups 扩展 profiles
		this.defaultProfiles = expandProfiles(getDefaultProfiles(environment, binder));
	}
}

分析可知:

  • 从贡献者中获取spring.profiles.group配置,得到groups
  • 获取激活的profiles配置
    • 来源
      • Environment环境/贡献者中获取spring.profiles.active配置
      • 包含前面解析得到的spring.profiles.include配置
    • 默认为空列表
  • 获取默认的profiles配置
    • 来源
      • Environment环境/贡献者中获取spring.profiles.default配置
    • 默认列表为["default"],对应application-default文件资源
  • 通过groups扩展
    • 实际可能profile值对应groups#key,则此key对应的value也要添加到profiles
    • profile对应group组别配置
小结

分析可知:

  • 完成了对于profiles相关属性的解析
    • groups组别
      • spring.profiles.group配置
    • 激活的profiles
      • spring.profiles.include/spring.profiles.active配置
    • 默认的profiles
      • spring.profiles.default配置
  • 将上述结果封装为Profiles实例

profiles处理

经过上述处理后,后续将调用processWithProfiles进行处理

处理入口

processWithProfiles阶段处理在前面3. 处理与应用中有分析,对应具体贡献者解析,在4. 二类贡献者解析与加载中有分析

其中特殊的点在于ConfigDataLocationResolvers处理中,对于基础解析与profiles解析,在调用resolve方法时,其传入lambda对应的resolveAction不一致

  • 基础解析,调用ConfigDataLocationResolver#resolve处理
  • profiles解析,调用ConfigDataLocationResolver#resolveProfileSpecific处理

具体代码如下:

// org.springframework.boot.context.config.ConfigDataLocationResolvers
class ConfigDataLocationResolvers {

    /* 2. 遍历配置路径解析器,尝试进行解析 */
    private List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolver<?> resolver,
			ConfigDataLocationResolverContext context, ConfigDataLocation location, Profiles profiles) {
        // 1. 进行基础解析,不分析 profile
		List<ConfigDataResolutionResult> resolved = resolve(location, false, () -> resolver.resolve(context, location));
		
        if (profiles == null) {
            // 没有配置 profiles 直接返回
			return resolved;
		}
        // 2. 针对 profiles 进行解析
		List<ConfigDataResolutionResult> profileSpecific = resolve(location, true,
				() -> resolver.resolveProfileSpecific(context, location, profiles));
        
        // 3. 合并结果
		return merge(resolved, profileSpecific);
	}
    
    /* 3. 具体解析调用 */
    private List<ConfigDataResolutionResult> resolve(ConfigDataLocation location, boolean profileSpecific,
			Supplier<List<? extends ConfigDataResource>> resolveAction) {
        // 通过 Lambda 表达式,实际调用 ConfigDataLocationResolver#resolve/resolveProfileSpecific 方法
		List<ConfigDataResource> resources = nonNullList(resolveAction.get());
        
		List<ConfigDataResolutionResult> resolved = new ArrayList<>(resources.size());
		for (ConfigDataResource resource : resources) {
            // 将解析得到的 ConfigDataResource 封装为 ConfigDataResolutionResult 实例返回
			resolved.add(new ConfigDataResolutionResult(location, resource, profileSpecific));
		}
		return resolved;
	}
}

所以我们主要分析ConfigDataLocationResolver#resolveProfileSpecific处理

资源获取

跟进分析ConfigDataLocationResolver#resolveProfileSpecific处理

// org.springframework.boot.context.config.StandardConfigDataLocationResolver
public class StandardConfigDataLocationResolver {
    
    /* 2. profile 的环境解析 */
    public List<StandardConfigDataResource> resolveProfileSpecific(ConfigDataLocationResolverContext context,
			ConfigDataLocation location, Profiles profiles) {
        // 1. 解析配置路径为 StandardConfigDataReference
        // 2. 解析 StandardConfigDataReference
		return resolve(getProfileSpecificReferences(context, location.split(), profiles));
	}
}

分析可知:

  • 与基础解析不同的是
    • 基础解析,调用StandardConfigDataLocationResolver#getReferences获取参考实例
      • 对应4 -> 1. 解析路径获取参考实例分析
    • profiles解析,调用StandardConfigDataLocationResolver#getProfileSpecificReferences获取参考实例

至于后续调用StandardConfigDataLocationResolver#resolve解析参考实例的过程,在4 -> 2. 参考解析 中有分析

参考获取

跟进分析StandardConfigDataLocationResolver#getProfileSpecificReferences处理

// org.springframework.boot.context.config.StandardConfigDataLocationResolver
public class StandardConfigDataLocationResolver {

    /* 获取 profiles 对应 参考实例 */
    private Set<StandardConfigDataReference> getProfileSpecificReferences(ConfigDataLocationResolverContext context,
        	ConfigDataLocation[] configDataLocations, Profiles profiles) {
        Set<StandardConfigDataReference> references = new LinkedHashSet<>();
        // 遍历 profiles
        for (String profile : profiles) {
            // 遍历通过 , 分割后的配置数据路径
            for (ConfigDataLocation configDataLocation : configDataLocations) {
                String resourceLocation = getResourceLocation(context, configDataLocation);
                // 调用 getReferences 获取参考实例
                // 将获取到的参考实例添加到集合中
                references.addAll(getReferences(configDataLocation, resourceLocation, profile));
            }
        }
        return references;
    }
}

跟进分析getReferences方法

// org.springframework.boot.context.config.StandardConfigDataLocationResolver
public class StandardConfigDataLocationResolver {

    private Set<StandardConfigDataReference> getReferences(ConfigDataLocation configDataLocation,
			String resourceLocation, String profile) {
		if (isDirectory(resourceLocation)) {
            // 如果配置路径为文件夹
			return getReferencesForDirectory(configDataLocation, resourceLocation, profile);
		}
        // 如果配置路径为文件资源
		return getReferencesForFile(configDataLocation, resourceLocation, profile);
	}
}

分析可知:

  • 其也存在单一文件/文件目录的两个分支,与基础解析不同的是,其传入的profile不是null

对于getReferencesForDirectory/getReferencesForFile方法处理,其传入的profile是有值或者null的区别在于构建StandardConfigDataReference时构造处理

// org.springframework.boot.context.config.StandardConfigDataReference
class StandardConfigDataReference {
    
    StandardConfigDataReference(ConfigDataLocation configDataLocation, String directory, String root, String profile,
			String extension, PropertySourceLoader propertySourceLoader) {
		this.configDataLocation = configDataLocation;
        // 存在 profile 时,定义后缀为 -<profile>
		String profileSuffix = (StringUtils.hasText(profile)) ? "-" + profile : "";
        // 存在 profile 时,追加文件后缀
		this.resourceLocation = root + profileSuffix + ((extension != null) ? "." + extension : "");
		this.directory = directory;
		this.profile = profile;
		this.propertySourceLoader = propertySourceLoader;
	}
}

分析可知:

  • 存在profile时,将获取application-<profile>对应的参考实例

经过上述处理,其获取的参考实例如下:

img

小结

经过上述分析可知:

  • 基于spring.profiles.[group/include/active/default]配置,构建Profiles实例,其中存储profiles信息
    • 取值来源
      • ApplicationServletEnvironment本身具有的属性源,包括实例实例化过程中添加的属性源
      • 解析application资源文件获取的属性源
  • 在参考获取部分,其获取的是application-<profile>对应的参考实例
    • 所以后续处理的也是这部分application-<profile>对应资源

其他解析工作,与基础解析部分一致,此处不赘述

6. 总结

基于上述分析,可知:

  • 一切基于ConfigDataEnvironment实例及其processAndApply方法作为入口
  • 创建贡献管理者,并获取初始导入贡献者
    • 配置路径获取
      • 基于spring.profiles.[group/include/active/default]配置,获取配置路径
        • 没有则使用默认配置路径
          • optional:classpath:/;optional:classpath:/config/
          • optional:file:./;optional:file:./config/;optional:file:./config/*/
    • 配置路径封装为贡献者
  • 阶段处理
    • 总体分为三阶段处理
    • 每个阶段进行贡献者处理
    • 处理贡献者,获取对应属性源
  • 将获取的属性源,应用到Environment环境中

环境属性源应用

环境实例获取

Spring中,提供了Aware接口,其中,获取环境Environment实例可以通过EnvironmentAware/ApplicationContextAware完成

// 方式一:EnvironmentAware
@Component
public class MyEnvironmentAware implements EnvironmentAware {

    private Environment environment;

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }
}


// 方式二:ApplicationContextAware
@Component
public class MyApplicationContextAware implements ApplicationContextAware {

    private ApplicationContext applicationContext;
    private Environment environment;


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
        // 从上下文中获取环境实例
        this.environment = applicationContext.getEnvironment();
    }
}

@EnableConfigurationProperties使用

简介

在使用@ConfigurationProperties注解前,首先通过@EnableConfigurationProperties开启配置功能

// org.springframework.boot.context.properties.EnableConfigurationProperties
@Target(ElementType.TYPE)
@Import(EnableConfigurationPropertiesRegistrar.class)
public @interface EnableConfigurationProperties {
    
    // 属性验证器对应的 BeanName
    String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator";
    
    // 需要注入的添加了 @ConfigurationProperties 注解的类
    // 主要针对不属于 Spring Bean 的类
    Class<?>[] value() default {};
}

分析可知:

  • EnableConfigurationProperties主要作用是通过@Import注解注入EnableConfigurationPropertiesRegistrar实例

注册分析

基于Spring分析可知,对于@Import注入的类,基于其情况分为不同处理,而EnableConfigurationPropertiesRegistrar类是ImportBeanDefinitionRegistrar实现,所以主要关注其registerBeanDefinitions方法实现

// org.springframework.boot.context.properties.EnableConfigurationPropertiesRegistrar
class EnableConfigurationPropertiesRegistrar implements ImportBeanDefinitionRegistrar {

	/* 注册 Bean */
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        // 1. 注册基础结构 Bean
        registerInfrastructureBeans(registry);
        
        // 2. 注册方法验证排除筛选器
        registerMethodValidationExcludeFilter(registry);
        
        // 3. ConfigurationPropertiesBeanRegistrar 处理
        // 创建 ConfigurationPropertiesBeanRegistrar 实例
        ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry);
        // 调用 ConfigurationPropertiesBeanRegistrar#register
        getTypes(metadata).forEach(beanRegistrar::register);
    }
}

分析可知,整个过程分为三部分:

  • 注册基础结构Bean
  • 注册方法验证排除筛选器
    • IOC容器中注册MethodValidationExcludeFilter实例定义
  • ConfigurationPropertiesBeanRegistrar处理

基础结构Bean注册

跟进分析registerInfrastructureBeans方法

// org.springframework.boot.context.properties.EnableConfigurationPropertiesRegistrar
class EnableConfigurationPropertiesRegistrar implements ImportBeanDefinitionRegistrar {

	/* 注册基础结构 Bean */
    static void registerInfrastructureBeans(BeanDefinitionRegistry registry) {
        // 1. 向 IOC 容器中注册 ConfigurationPropertiesBindingPostProcessor 定义
        // 2. 向 IOC 容器中注册 ConfigurationPropertiesBinder 定义
		ConfigurationPropertiesBindingPostProcessor.register(registry);
        
        // 3. 向 IOC 容器中注册 BoundConfigurationProperties 定义
		BoundConfigurationProperties.register(registry);
	}
}

分析可知,其实际就是向IOC容器中注册以下实例定义

  • ConfigurationPropertiesBindingPostProcessor
  • ConfigurationPropertiesBinder
    • 包括注册ConfigurationPropertiesBinder.Factory工厂实例
    • 实际由ConfigurationPropertiesBinder.Factory工厂实例创建
  • BoundConfigurationProperties

配置属性实例注册处理

配置属性实例注册处理主要分为两部分:

  • 通过getTypes获取需要注册的配置属性实例类型
  • 通过ConfigurationPropertiesBeanRegistrar将配置属性实例定义注册到IOC实例中
配置属性类获取

跟进分析getTypes方法

// org.springframework.boot.context.properties.EnableConfigurationPropertiesRegistrar
class EnableConfigurationPropertiesRegistrar implements ImportBeanDefinitionRegistrar {

    private Set<Class<?>> getTypes(AnnotationMetadata metadata) {
        // 1. 获取添加了 @EnableConfigurationProperties 注解的配置类中的 @EnableConfigurationProperties 注解信息
        // 2. 获取 @EnableConfigurationProperties 注解中 value 数组
        // 3. 将其封装为 Set 集合返回
		return metadata.getAnnotations()
			.stream(EnableConfigurationProperties.class)
			.flatMap((annotation) -> Arrays.stream(annotation.getClassArray(MergedAnnotation.VALUE)))
			.filter((type) -> void.class != type)
			.collect(Collectors.toSet());
	}
}

分析可知:

  • getTypes方法主要用于获取@EnableConfigurationProperties注解的value
    • 对应需要进行配置属性处理的添加了@ConfigurationProperties注解的类
配置属性类注册

上述获取了配置属性类集合,将其遍历进行注册到IOC容器中,跟进分析ConfigurationPropertiesBeanRegistrar#register方法处理

// org.springframework.boot.context.properties.ConfigurationPropertiesBeanRegistrar
final class ConfigurationPropertiesBeanRegistrar {
    
    // IOC注册器
    private final BeanDefinitionRegistry registry;

    // IOC实例工厂
	private final BeanFactory beanFactory;
    
    /* 注册 */
    void register(Class<?> type) {
        // 获取配置属性类上的 @ConfigurationProperties 注解信息
		MergedAnnotation<ConfigurationProperties> annotation = MergedAnnotations
			.from(type, SearchStrategy.TYPE_HIERARCHY)
			.get(ConfigurationProperties.class);
        // 进行注册
		register(type, annotation);
	}
    
    /* 注册 */
    void register(Class<?> type, MergedAnnotation<ConfigurationProperties> annotation) {
        // 获取 beanName
		String name = getName(type, annotation);
		if (!containsBeanDefinition(name)) {
            // 如果 IOC 容器中不存在,则注册定义
			registerBeanDefinition(name, type, annotation);
		}
	}
}

跟进分析registerBeanDefinition方法处理

// org.springframework.boot.context.properties.ConfigurationPropertiesBeanRegistrar
final class ConfigurationPropertiesBeanRegistrar {
    
    // IOC注册器
    private final BeanDefinitionRegistry registry;

    // IOC实例工厂
	private final BeanFactory beanFactory;
    
    /* 注册定义 */
    private void registerBeanDefinition(String beanName, Class<?> type, MergedAnnotation<ConfigurationProperties> annotation) {
		// 如果配置属性类上没有添加 @ConfigurationProperties,则报错
        Assert.state(annotation.isPresent(), "...");
        // 创建定义 BeanDefinition 并注册到 IOC 容器中
		this.registry.registerBeanDefinition(beanName, createBeanDefinition(beanName, type));
	}
}
小结

分析可知,此部分主要工作如下:

  • 获取@EnableConfigurationProperties注解中value值对应的配置属性类
  • 将配置属性类注册到IOC容器中
    • 前提是此配置属性类需要添加@ConfigurationProperties注解,否则报错

总结

添加@EnableConfigurationProperties注解,其前置处理如下:

  • 通过@Import注解导入EnableConfigurationPropertiesRegistrar实例,进行前置处理
  • 处理过程主要是向IOC容器中注入实例BeanDefinition定义
    • 基础结构Bean
      • ConfigurationPropertiesBindingPostProcessor
      • ConfigurationPropertiesBinder
      • BoundConfigurationProperties
    • 方法验证排除筛选器
      • MethodValidationExcludeFilter
    • 配置属性类
      • @EnableConfigurationProperties注解的value值对应的配置属性类
        • 要求配置属性类添加@ConfigurationProperties注解

@ConfigurationProperties使用

简介

@ConfigurationPropertiesSpringBoot中提供用于属性配置化的一个注解

// org.springframework.boot.context.properties.ConfigurationProperties
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ConfigurationProperties {
    
    // 绑定的属性前缀
    @AliasFor("prefix")
	String value() default "";
    @AliasFor("value")
	String prefix() default "";
    
    // 是否忽略类型错误字段
    boolean ignoreInvalidFields() default false;
    
    // 是否忽略属性错误(不存在)字段
    boolean ignoreUnknownFields() default true;
}

注意:

  • @ConfigurationProperties可以结合添加了@Bean的方法使用,其作用是对此方法返回对象实例进行属性绑定
  • 添加@ConfigurationProperties注解的Bean需要交由Spring管理
    • 结合@Component使用
    • 通过@EnableConfigurationProperties注解引入

通过@ConfigurationProperties注解,可以从环境中取值,将指定前缀对应的属性注入到对象属性中

使用示例🌰:

@Component
@ConfigurationProperties(prefix = "prefix")
public class ConfigProperties {
    
    // 对应 prefix.enable 属性
    private boolean enable;
    
    // 对应 prefix.mode 属性
    private String mode;
}

基类

ConfigurationPropertiesBean

ConfigurationPropertiesBean主要是封装添加了@ConfigurationProperties注解的SpringBean的信息

// org.springframework.boot.context.properties.ConfigurationPropertiesBean
public final class ConfigurationPropertiesBean {
    
    // 源对象 beanName
    private final String name;
    
    // 源对象实例
    private final Object instance;
    
    // 源对象上 ConfigurationProperties 注解信息
    private final ConfigurationProperties annotation;
    
    // 由 instance 封装为 Bindable 实例
    private final Bindable<?> bindTarget;
}

分析可知,ConfigurationPropertiesBean主要封装了SpringBean的以下信息:

  • beanName
  • SpringBean实例本身
  • SpringBean实例上面的@ConfigurationProperties注解信息

处理入口

IOC流程中,在创建实例后,通过AbstractAutowireCapableBeanFactory#initializeBean初始化实例的过程中,将调用applyBeanPostProcessorsBeforeInitialization方法

// org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {
    
    public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName){
        Object result = existingBean;
        // 遍历注册的BeanPostProcessor,调用postProcessBeforeInitialization方法进行初始化前处理
        for (BeanPostProcessor processor : getBeanPostProcessors()) {
            Object current = processor.postProcessBeforeInitialization(result, beanName);
            if (current == null) {
                return result;
            }
            result = current;
        }
        return result;
    }
}

注意:

  • 通过BeanPostProcessor进行初始化前置处理,其中需要关注的是ConfigurationPropertiesBindingPostProcessor,主要通过它完成配置化属性

  • ConfigurationPropertiesBindingPostProcessor实例由@EnableConfigurationProperties处理过程导入

实例初始化前置处理

跟进分析ConfigurationPropertiesBindingPostProcessor#postProcessBeforeInitialization实现

// org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor+
public class ConfigurationPropertiesBindingPostProcessor
		implements BeanPostProcessor, PriorityOrdered, ApplicationContextAware, InitializingBean {
    
    // 应用上下文
    private ApplicationContext applicationContext;
    
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        // 1. 封装实例为配置化属性Bean
        // 2. 调用 bind 方法进行属性绑定
		bind(ConfigurationPropertiesBean.get(this.applicationContext, bean, beanName));
		return bean;
	}
    
}

分析可知,其主要分为两步

  • 封装配置属性实例为ConfigurationPropertiesBean
  • 调用bind方法进行属性绑定
封装配置属性Bean

封装过程跟进分析ConfigurationPropertiesBean#get处理

// org.springframework.boot.context.properties.ConfigurationPropertiesBean
public final class ConfigurationPropertiesBean {
    
    /* 实例获取 */
    public static ConfigurationPropertiesBean get(ApplicationContext applicationContext, Object bean, String beanName) {
        // 1. 查找创建此实例的工厂方法(如果是通过 @Bean 注解注入),可能为空
		Method factoryMethod = findFactoryMethod(applicationContext, beanName);
        
        // 2. 创建 ConfigurationPropertiesBean 实例
		return create(beanName, bean, bean.getClass(), factoryMethod);
	}
}

跟进create方法进行ConfigurationPropertiesBean实例创建

// org.springframework.boot.context.properties.ConfigurationPropertiesBean
public final class ConfigurationPropertiesBean {

    private static ConfigurationPropertiesBean create(String name, Object instance, Class<?> type, Method factory) {
        // 1. 获取配置的 @ConfigurationProperties 注解信息
        ConfigurationProperties annotation = findAnnotation(instance, type, factory, ConfigurationProperties.class);
        
        if (annotation == null) {
            // 没有配置此注解,直接返回 null
            return null;
        }
        
        // 解析配置的 @Validated
        Validated validated = findAnnotation(instance, type, factory, Validated.class);
        Annotation[] annotations = (validated != null) ? 
            new Annotation[] { annotation, validated } : new Annotation[] { annotation };
        
        // 如果是添加 @Bean 注解的 factoryMethod,根据其返回值类型,获取需要进行数据绑定的目标类型
        ResolvableType bindType = (factory != null) ? ResolvableType.forMethodReturnType(factory) : ResolvableType.forClass(type);
        
        // 将 class类型/注解 一起封装
        Bindable<Object> bindTarget = Bindable.of(bindType).withAnnotations(annotations);
        if (instance != null) {
            bindTarget = bindTarget.withExistingValue(instance);
        }
        
        // 将上述信息封装构建 ConfigurationPropertiesBean 实例
        return new ConfigurationPropertiesBean(name, instance, annotation, bindTarget);
    }
}

分析可知:

  • 主要处理添加了@ConfigurationProperties注解的SpringBean,将其封装为ConfigurationPropertiesBean

  • ConfigurationPropertiesBean主要包括了以下内容

    • 对象名称beanName
    • 对象实例SpringBean
    • 对象所添加的注解信息
    • 由对象实例封装得到的Bindable实例
属性绑定

有上述过程封装了ConfigurationPropertiesBean实例后,下面就要对目标实例进行属性绑定

跟进分析bind方法

// org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor+
public class ConfigurationPropertiesBindingPostProcessor
    implements BeanPostProcessor, PriorityOrdered, ApplicationContextAware, InitializingBean {
    
    // 配置属性绑定器
    private ConfigurationPropertiesBinder binder;

    private void bind(ConfigurationPropertiesBean bean) {
        if (bean == null || hasBoundValueObject(bean.getName())) {
            // 1. 没有添加 @ConfigurationProperties 注解
            // 2. 已经完成过绑定动作
            return;
        }

        // 通过 ConfigurationPropertiesBinder 实例进行绑定
        this.binder.bind(bean);
    }
}

实际绑定工作由ConfigurationPropertiesBinder实例完成

小结

分析可知:

  • 实际就是对添加了@ConfigurationProperties注解的实例进行初始化前置处理(属性绑定)
  • 具体处理过程
    • 将配置属性实例封装为ConfigurationPropertiesBean
    • ConfigurationPropertiesBinder实例处理ConfigurationPropertiesBean实例,完成属性绑定动作

绑定过程分析

属性绑定过程ConfigurationPropertiesBinder#bind方法完成,跟进分析

// org.springframework.boot.context.properties.ConfigurationPropertiesBinder
class ConfigurationPropertiesBinder {

    // 从上下文中获取的属性源
    private final PropertySources propertySources;
    
    // 绑定器
    private volatile Binder binder;

    /* 绑定处理入口 */
    BindResult<?> bind(ConfigurationPropertiesBean propertiesBean) {
        // 1. 封装的绑定目标,其中包括 class类型/注解信息
        Bindable<?> target = propertiesBean.asBindTarget();
        // 2. 获取 @ConfigurationProperties 注解信息
        ConfigurationProperties annotation = propertiesBean.getAnnotation();
        
        // 3. 获取目标的绑定处理器
        BindHandler bindHandler = getBindHandler(target, annotation);
        
        // 4.1 获取 Binder 实例
        // 4.2 调用 Binder#bind 进行绑定,基于 属性前缀、绑定目标、绑定处理器
        return getBinder().bind(annotation.prefix(), target, bindHandler);
    }
}

分析可知,其中重点部分为:

  • 获取绑定处理器BindHandler
  • 获取Binder实例
  • 调用Binder#bind进行绑定处理

绑定处理器获取

跟进分析ConfigurationPropertiesBinder#getBindHandler方法

// org.springframework.boot.context.properties.ConfigurationPropertiesBinder
class ConfigurationPropertiesBinder {

    /* 获取绑定处理器 */
    private <T> BindHandler getBindHandler(Bindable<T> target, ConfigurationProperties annotation) {
        // 1. 基于 @Validated 注解获取 校验器
		List<Validator> validators = getValidators(target);
        // 2. 获取初始的 IgnoreTopLevelConverterNotFoundBindHandler 绑定处理器
		BindHandler handler = getHandler();
        // 3. 包装为 ConfigurationPropertiesBindHandler 实例
		handler = new ConfigurationPropertiesBindHandler(handler);
        
		if (annotation.ignoreInvalidFields()) {
            // 忽略无效类型字段,包装为 IgnoreErrorsBindHandler
			handler = new IgnoreErrorsBindHandler(handler);
		}
		if (!annotation.ignoreUnknownFields()) {
            // 忽略未知属性,包装为 NoUnboundElementsBindHandler,对应 UnboundElementsSourceFilter 过滤器
			UnboundElementsSourceFilter filter = new UnboundElementsSourceFilter();
			handler = new NoUnboundElementsBindHandler(handler, filter);
		}
		if (!validators.isEmpty()) {
            // 存在校验器,包装为 ValidationBindHandler
			handler = new ValidationBindHandler(handler, validators.toArray(new Validator[0]));
		}
        
		for (ConfigurationPropertiesBindHandlerAdvisor advisor : getBindHandlerAdvisors()) {
            // 存在增强,则使用增强器增强
			handler = advisor.apply(handler);
		}
        
        // 返回最终的绑定处理器 
		return handler;
	}
    
    /* 获取初始处理器 */
    private IgnoreTopLevelConverterNotFoundBindHandler getHandler() {
		BoundConfigurationProperties bound = BoundConfigurationProperties.get(this.applicationContext);
		return (bound != null)
				? new IgnoreTopLevelConverterNotFoundBindHandler(new BoundPropertiesTrackingBindHandler(bound::add))
				: new IgnoreTopLevelConverterNotFoundBindHandler();
	}
}

分析可知:

  • 实际得到的绑定处理器,是根据条件,通过多级绑定处理器父子关联的结果

绑定器获取

跟进分析getBinder方法

// org.springframework.boot.context.properties.ConfigurationPropertiesBinder
class ConfigurationPropertiesBinder {
    
    // 绑定器
    private volatile Binder binder;
    
    private Binder getBinder() {
		if (this.binder == null) {
			this.binder = new Binder(getConfigurationPropertySources(), getPropertySourcesPlaceholdersResolver(),
					getConversionServices(), getPropertyEditorInitializer(), null,
					ConfigurationPropertiesBindConstructorProvider.INSTANCE);
		}
		return this.binder;
	}
}

分析可知:

  • 如果没有,则直接通过构造实例化Binder实例

绑定处理

具体数据绑定处理由Binder完成,其绑定过程请查看扩展部分的数据绑定机制

@Profile使用

简介

基于Profiles配置,可以加载不同的application-<profile>配置文件作为属性源,体现了不同环境的差异性

实际对于SpringBean,也可以基于profiles配置,进行不同环境的配置,其就是使用@Profile注解

首先分析一下@Profile注解

// org.springframework.context.annotation.Profile
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(ProfileCondition.class)
public @interface Profile {

	String[] value();
}

实际上@Profile的作用:

  • 可以结合@Component/@Bean注解,对SpringBean添加profile标记
  • 仅当Spring.profils.active/default激活的profile匹配时,才会注册此SpringBeanIOC容器中

源码分析

匹配入口

类路径扫描

ClassPathBeanDefinitionScanner进行类路径扫描时,将对扫描的组件进行condition判断

调用链跟踪如下:

// org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan
// org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#findCandidateComponents
// org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents
// org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#isCandidateComponent

// org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider
public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware {
    
    // 条件评估器
    private ConditionEvaluator conditionEvaluator;
    
    private boolean isConditionMatch(MetadataReader metadataReader) {
		if (this.conditionEvaluator == null) {
			this.conditionEvaluator = new ConditionEvaluator(getRegistry(), this.environment, this.resourcePatternResolver);
		}
        // 由条件评估器判断是否跳过此组件
		return !this.conditionEvaluator.shouldSkip(metadataReader.getAnnotationMetadata());
	}
}
注解定义读取

AnnotatedBeanDefinitionReader进行注解定义读取注册时,将对扫描的组件进行condition判断

调用链跟踪如下:

// org.springframework.context.annotation.AnnotatedBeanDefinitionReader
public class AnnotatedBeanDefinitionReader {
    
    private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,
			@Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,
			@Nullable BeanDefinitionCustomizer[] customizers) {
        
        AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
        // 由条件评估器判断是否跳过此组件
		if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
			return;
		}
        
        // ...
    }
}

条件评估器评估

由上述分析可知,组件评估工作都由ConditionEvaluator条件评估器完成,跟进分析

// org.springframework.context.annotation.ConditionEvaluator
class ConditionEvaluator {
    
    public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
        // 1. 没有添加注解;2. 有添加注解,但是没有 @Conditional 注解
        if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
			return false;
		}
        
        // 解析注解,获取 @Conditional 注解对应的 Condition 实例
        List<Condition> conditions = new ArrayList<>();
		for (String[] conditionClasses : getConditionClasses(metadata)) {
			for (String conditionClass : conditionClasses) {
				Condition condition = getCondition(conditionClass, this.context.getClassLoader());
				conditions.add(condition);
			}
		}
        
        // 对 Condition 实例进行排序
        AnnotationAwareOrderComparator.sort(conditions);
        
        for (Condition condition : conditions) {
			ConfigurationPhase requiredPhase = null;
			if (condition instanceof ConfigurationCondition) {
				requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
			}
            // 重点在于调用 Condition#matches 判断是否匹配条件
			if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
				return true;
			}
		}

		return false;
    }
}

匹配逻辑

实际上@Profile注解重点在于其通过@Conditional引入了ProfileCondition,跟进分析ProfileCondition

// org.springframework.context.annotation.ProfileCondition
class ProfileCondition implements Condition {

	@Override
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 1. 获取 SpringBean 上添加的 @Profile 注解的属性
		MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
		if (attrs != null) {
			for (Object value : attrs.get("value")) {
                // 2. 将激活的 profile 与 @Profile 注解 value 属性进行匹配
                // 如果有重合部分,则返回 true,表示此 @Conditional 条件满足
				if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {
					return true;
				}
			}
			return false;
		}
        // 如果 @Profile 注解没有定义 value 值,则表示所有 profile 都匹配
		return true;
	}
}

分析可知:

  • ProfileCondition中主要就是将@Profilevalue值与环境Environment实例中激活的profile进行匹配

总结

基于源码分析可知:

  • @Profile注解,通过@Conditional引入了ProfileCondition
  • 通过ProfileCondition#matches进行条件匹配,实现@Profile注解的多环境功能

扩展

数据绑定机制

简介

当我们拥有Environment环境实例后,通常用它来干嘛呢?

  • 从其中的属性源中获取所需属性值
  • 将属性值赋值到所需的目标上
  • 赋值过程中可能包括类型类型转换

Spring为了简化上述步骤,提供了Binder数据绑定机制,以下述代码为例

@Setter
@Component
public class BinderTest {

    @Autowired
    private Environment environment;

    /* 普通方式 */
    private void common() {
        String propertyName = "age";
        // 从环境中获取属性值
        String propertyValue = environment.getProperty(propertyName);
        // 将属性值赋值到目标
        // 其中包括自定义的类型转换
        Integer result = Integer.parseInt(propertyValue);
    }

    /* 数据绑定方式 */
    private void bind() {
        String propertyName = "age";
        BindResult<Integer> bindResult = Binder.get(environment).bind(propertyName, Integer.class);
        Integer result = bindResult.get();
    }
}

上述只是以简单的示例进行比对,实际上数据绑定的目标结果可能存在的类型有:

  • 基本类型/String类型
  • 聚合类型
    • Map/Collection/Array
  • 对象类型

而且对象类型可以嵌套不同类型的属性,通过普通方式手动实现数据绑定,比较麻烦,而Binder数据绑定机制可以帮我们快速实现

基类

绑定器

Binder

Binder是数据绑定的入口

// org.springframework.boot.context.properties.bind.Binder
public class Binder {
 
    // 不可绑定类型:Object、Class
    private static final Set<Class<?>> NON_BEAN_CLASSES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(Object.class, Class.class)));

    // 配置属性源-迭代器
	private final Iterable<ConfigurationPropertySource> sources;

    // 占位符解析器
	private final PlaceholdersResolver placeholdersResolver;

    // 绑定转换器,用于类型转换
	private final BindConverter bindConverter;

    // 绑定处理器,用于绑定过程扩展处理
	private final BindHandler defaultBindHandler;

    // 数据对象绑定器-列表
	private final List<DataObjectBinder> dataObjectBinders;
}

分析可知,Binder实例中封装了绑定过程中所需的工具实例

  • 属性源
    • 用于获取属性值
  • 占位符解析器
    • 用于解析属性名称/属性值中的占位符
  • 转换器
    • 用于类型转换
  • 绑定处理器
    • 用于绑定处理过程中的扩展处理
  • 数据对象绑定器
    • 处理对象类型时,需要递归处理其属性组成,所需的特殊绑定器
数据对象绑定器
DataObjectBinder

DataObjectBinder是针对于对象类型进行属性的递归绑定时的特殊绑定器,它是数据对象绑定器的顶级接口

// org.springframework.boot.context.properties.bind.DataObjectBinder
interface DataObjectBinder {
    
    /* 数据绑定处理 */
    <T> T bind(ConfigurationPropertyName name, Bindable<T> target, Context context, DataObjectPropertyBinder propertyBinder);
    
    /* 创建新的目标实例 */
    <T> T create(Bindable<T> target, Context context);
}
JavaBeanBinder

JavaBeanBinderDataObjectBinder实现类,其主要针对于可变JavaBean类型的数据绑定

// org.springframework.boot.context.properties.bind.JavaBeanBinder
class JavaBeanBinder implements DataObjectBinder {
    
    // 常量实例
    static final JavaBeanBinder INSTANCE = new JavaBeanBinder();
}
数据对象属性绑定器
DataObjectPropertyBinder

DataObjectPropertyBinder是针对于对象实例的属性进行数据绑定的绑定器,它是顶级接口

// org.springframework.boot.context.properties.bind.DataObjectPropertyBinder
interface DataObjectPropertyBinder {
    
    /* 属性绑定 */
    Object bindProperty(String propertyName, Bindable<?> target);
}
聚合绑定器
AggregateBinder

AggregateBinder是聚合结果类型绑定器的抽象父类

// org.springframework.boot.context.properties.bind.AggregateBinder
abstract class AggregateBinder<T> {
    
    // 持有上下文
    private final Context context;
}
IndexedElementsBinder

IndexedElementsBinder是针对于Array/Collection的索引元素绑定处理的抽象父类

// org.springframework.boot.context.properties.bind.IndexedElementsBinder
abstract class IndexedElementsBinder<T> extends AggregateBinder<T> {}
ArrayBinder

ArrayBinderIndexedElementsBinder实现类,其针对于Array类型绑定处理的绑定器

// org.springframework.boot.context.properties.bind.ArrayBinder
class ArrayBinder extends IndexedElementsBinder<Object> {}
CollectionBinder

CollectionBinderIndexedElementsBinder实现类,其针对于Collection类型绑定处理的绑定器

// org.springframework.boot.context.properties.bind.CollectionBinder
class CollectionBinder extends IndexedElementsBinder<Collection<Object>> {}
MapBinder

MapBinder是针对于Map类型绑定处理的绑定器

// org.springframework.boot.context.properties.bind.MapBinder
class MapBinder extends AggregateBinder<Map<Object, Object>> {}

绑定上下文

绑定上下文主要用于在绑定过程中记录上下文信息

BindContext
// org.springframework.boot.context.properties.bind.BindContext
public interface BindContext {
    
    // 获取绑定器
    Binder getBinder();
    
    // 获取深度
    int getDepth();
    
    // 获取属性源迭代器
    Iterable<ConfigurationPropertySource> getSources();
    
    // 获取绑定配置属性
    ConfigurationProperty getConfigurationProperty();
}
Context
// org.springframework.boot.context.properties.bind.Binder.Context
final class Context implements BindContext {
    
    // 记录下钻深度
    private int depth;
    
    // 属性源列表
    private final List<ConfigurationPropertySource> source = Arrays.asList((ConfigurationPropertySource) null);
    
    // 配置属性
    private ConfigurationProperty configurationProperty;
    
    // 绑定过程中记录数据对象类型的队列
    private final Deque<Class<?>> dataObjectBindings = new ArrayDeque<>();
}

绑定处理器

BindHandler

BindHandler是绑定处理器的顶级接口

// org.springframework.boot.context.properties.bind.BindHandler
public interface BindHandler {
    
    // 默认实例
    BindHandler DEFAULT = new BindHandler() {};
    
    /* 绑定开始时调用 */
    default <T> Bindable<T> onStart(ConfigurationPropertyName name, Bindable<T> target, BindContext context) {
		return target;
	}
    
    /* 绑定成功时调用 */
    default Object onSuccess(ConfigurationPropertyName name, Bindable<?> target, BindContext context, Object result) {
		return result;
	}
    
    /* 绑定过程中创建新实例返回时调用 */
    default Object onCreate(ConfigurationPropertyName name, Bindable<?> target, BindContext context, Object result) {
		return result;
	}
    
    /* 绑定失败时调用 */
    default Object onFailure(ConfigurationPropertyName name, Bindable<?> target, BindContext context, Exception error) throws Exception {
		throw error;
	}
    
    /* 完成绑定后调用 */
    default void onFinish(ConfigurationPropertyName name, Bindable<?> target, BindContext context, Object result) throws Exception {
	}
}

分析可知:

  • BindHandler提供了绑定过程中不同时机的回调处理函数
AbstractBindHandler

AbstractBindHandler是实现了BindHandler接口的抽象基类

// org.springframework.boot.context.properties.bind.AbstractBindHandler
public abstract class AbstractBindHandler implements BindHandler {
    
    // 父子关联,关联父绑定处理器
    private final BindHandler parent;
}

其他

Bindable

Bindable用于封装可绑定实例,主要对其进行数据绑定操作

// org.springframework.boot.context.properties.bind.Bindable
public final class Bindable<T> {
    
    // 实例类型
    private final ResolvableType type;

    // 实例包装类型,对应基本类型
	private final ResolvableType boxedType;

    // 实例提供者
	private final Supplier<T> value;

    // 注解数组
	private final Annotation[] annotations;

    // 绑定限制
	private final EnumSet<BindRestriction> bindRestrictions;
}
BindRestriction

BindRestriction表示绑定限制枚举

// org.springframework.boot.context.properties.bind.Bindable.BindRestriction
public enum BindRestriction {
    
    // 不直接绑定属性,主要对应 @ConfigurationProperties 的前缀处理
    NO_DIRECT_PROPERTY
}
ConfigurationPropertyName

配置属性名称,主要用于封装属性名称/属性名称前缀

// org.springframework.boot.context.properties.source.ConfigurationPropertyName
public final class ConfigurationPropertyName implements Comparable<ConfigurationPropertyName> {
    
    // 属性名称
    private String string;
}
ConfigurationProperty

配置属性

// org.springframework.boot.context.properties.source.ConfigurationProperty
public final class ConfigurationProperty implements OriginProvider, Comparable<ConfigurationProperty> {
    
    // 配置属性名称
    private final ConfigurationPropertyName name;

    // 配置属性值
	private final Object value;

    // 对应的配置属性源
	private final ConfigurationPropertySource source;

    // 属性起源
	private final Origin origin;
}

绑定处理过程分析

构建Binder实例

Binder实例作为数据绑定的入口,首先就需要构建一个Binder实例

Binder类中提供了两种实例构建方式

  • 普通构造器构造
  • get方法构造

如果对于Binder中的功能属性组件没有特殊定义,通常使用get方法构造即可

// org.springframework.boot.context.properties.bind.Binder
public class Binder {
    
    /* 由 Environment 实例构建 Binder 实例 */
    public static Binder get(Environment environment) {
		return get(environment, null);
	}
}

绑定处理入口

绑定处理实际由Binder#bind方法完成,其存在多个重载方法

image-20240703111310432

  1. 其中多个重载方法的统一入口为以下重载
// org.springframework.boot.context.properties.bind.Binder
public class Binder {

    /* 核心重载方法 */
    public <T> BindResult<T> bind(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler) {
        // 1. 调用重载方法,获取绑定结果
        T bound = bind(name, target, handler, false);
        // 2. 封装绑定结果为 BindResult 实例
        return BindResult.of(bound);
    }
}

bind重载存在三个参数:

  • name
    • 由属性名称/属性名称前缀封装为ConfigurationPropertyName实例
  • target
    • 封装具体数据绑定目标的Bindable实例
    • 其中封装了数据绑定目标的具体信息
  • handler
    • 用于绑定过程中处理的BindHandler
    • 实际由父子层级包装多个BindHandler实例
  1. 跟进bind重载
// org.springframework.boot.context.properties.bind.Binder
public class Binder {

    private <T> T bind(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler, boolean create) {
        // 1. 入参信息校验
		Assert.notNull(name, "Name must not be null");
		Assert.notNull(target, "Target must not be null");
        // 2. 设置默认绑定处理器
		handler = (handler != null) ? handler : this.defaultBindHandler;
        // 3. 创建绑定上下文
		Context context = new Context();
        // 4. 调用重载方法,获取绑定结果
		return bind(name, target, handler, context, false, create);
	}
}

分析可知:

  • 此重载添加了新的入参create
    • 其主要在于如果获取绑定结果为空,是否创建默认结果
  • 此方法中重点在于创建了Context上下文实例
    • 此上下文用于后续整个绑定过程
    • 注意,此时Context应用的是Binder实例中管理的属性源
  1. 继续跟进分析Binder#bind方法重载
// org.springframework.boot.context.properties.bind.Binder
public class Binder {

    // 属性源迭代器
    private final Iterable<ConfigurationPropertySource> sources;

    // 占位符解析器
    private final PlaceholdersResolver placeholdersResolver;

    // 类型转换器
    private final BindConverter bindConverter;

    /* 绑定处理,基于 属性前缀、绑定目标、绑定处理器 */
    private <T> T bind(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler, Context context,
                       boolean allowRecursiveBinding, boolean create) {
        try {
            // 绑定前置处理
            Bindable<T> replacementTarget = handler.onStart(name, target, context);

            if (replacementTarget == null) {
                // 如果 target 本身就为 null,则直接处理绑定结果为 null
                return handleBindResult(name, target, handler, context, null, create);
            }

            target = replacementTarget;
            // 进行对象绑定
            Object bound = bindObject(name, target, handler, context, allowRecursiveBinding);
            // 处理绑定结果
            return handleBindResult(name, target, handler, context, bound, create);
        } catch (Exception ex) {
            // 绑定处理异常时处理
            return handleBindError(name, target, handler, context, ex);
        }
    }
}

分析可知:

  • 此重载方法真正开始进行绑定动作

  • 其绑定过程的主线分为以下部分

    • 首先通过绑定处理器进行前置处理

    • 对象绑定处理,获取绑定对象结果

    • 处理绑定结果

  • 如果绑定过程出现异常,则通过handleBindError方法处理

绑定处理过程拆分

下面对bind的多个部分进行拆分

1. 处理器前置处理

第一部分为调用BindHandler#onStart进行前置处理,其中需要重点关注的是ConfigurationPropertiesBindHandler的实现

// org.springframework.boot.context.properties.ConfigurationPropertiesBinder.ConfigurationPropertiesBindHandler
private static class ConfigurationPropertiesBindHandler extends AbstractBindHandler {

    /* 开始绑定动作的前置处理 */
    public <T> Bindable<T> onStart(ConfigurationPropertyName name, Bindable<T> target, BindContext context) {
        // 对于添加 @ConfigurationProperties 注解,设置 NO_DIRECT_PROPERTY 绑定限制(表示不直接绑定)
        return isConfigurationProperties(target.getType().resolve())
            ? target.withBindRestrictions(BindRestriction.NO_DIRECT_PROPERTY) : target;
    }

    /* 判断是否添加 @ConfigurationProperties 注解 */
    private boolean isConfigurationProperties(Class<?> target) {
        return target != null && MergedAnnotations.from(target).isPresent(ConfigurationProperties.class);
    }
}

分析可知:

  • 实际是针对与@ConfigurationProperties注解
    • 有添加@ConfigurationProperties注解
      • Bindable实例设置NO_DIRECT_PROPERTY 绑定限制(表示不直接绑定)后返回
      • 其作用具体看后续处理
    • 没有添加@ConfigurationProperties注解
      • 则返回原Bindable实例
2. 对象绑定处理

第二部分为对象绑定处理,获取绑定对象结果

其主要由Binder#bindObject方法完成,跟进分析

// org.springframework.boot.context.properties.bind.Binder
public class Binder {

    /* 对象绑定处理 */
    private <T> Object bindObject(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler,
                                  Context context, boolean allowRecursiveBinding) {
        // 1. 首先尝试基于属性名称从上下文中获取属性
        ConfigurationProperty property = findProperty(name, target, context);
        
        if (property == null && context.depth != 0 && containsNoDescendantOf(context.getSources(), name)) {
            // 1. 无法通过属性名称获取属性值;2. 此时上下文不是在初始深度;3. 此属性没有下级属性
            return null;
        }
        
        // 2. 如果是 map/collection/array,则使用聚合绑定
        AggregateBinder<?> aggregateBinder = getAggregateBinder(target, context);
        if (aggregateBinder != null) {
            return bindAggregate(name, target, handler, context, aggregateBinder);
        }
        
        // 3. 普通属性处理
        if (property != null) {
            // 如果已经获取到属性,则直接绑定属性
            return bindProperty(target, context, property);
        }
        // 4. 绑定数据对象
        return bindDataObject(name, target, handler, context, allowRecursiveBinding);
    }
}

分析可知:

  • 对于属性的处理,其主要分为三类
    • 聚合属性处理bindAggregate
      • 属性为聚合属性
    • 普通属性处理bindProperty
      • 属性为非聚合属性,且能通过属性名称获取值
    • 数据对象处理bindDataObject

对于不同类型属性的具体处理过程由后续分析

3. 绑定结果处理

第三部分为处理绑定结果,将获取的属性结果绑定到目标实例中

其由Binder#handleBindResult方法完成,跟进分析

// org.springframework.boot.context.properties.bind.Binder
public class Binder {

    /* 处理绑定结果 */
    private <T> T handleBindResult(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler,
                                   Context context, Object result, boolean create) throws Exception {
        // 1. 结果存在
        if (result != null) {
            // 绑定处理器调用 onSuccess 处理
            result = handler.onSuccess(name, target, context, result);
            // 使用类型转换器,将结果转换为所需类型
            result = context.getConverter().convert(result, target);
        }
        
        // 2. 结果为空,但是需要创建
        if (result == null && create) {
            // 创建结果
            result = create(target, context);
            // 绑定处理器调用 onCreate 处理
            result = handler.onCreate(name, target, context, result);
            // 使用类型转换器,将结果转换为所需类型
            result = context.getConverter().convert(result, target);
            // 断言处理
            Assert.state(result != null, () -> "Unable to create instance for " + target.getType());
        }
        
        // 绑定处理器调用 onFinish 处理
        handler.onFinish(name, target, context, result);
        // 使用类型转换器,将结果转换为所需类型
        // 返回结果
        return context.getConverter().convert(result, target);
    }
    
    /* 创建结果 */
    private Object create(Bindable<?> target, Context context) {
		for (DataObjectBinder dataObjectBinder : this.dataObjectBinders) {
            // 遍历使用 DataObjectBinder 进行结果创建
			Object instance = dataObjectBinder.create(target, context);
			if (instance != null) {
                // 创建成功返回
				return instance;
			}
		}
        // 创建失败返回
		return null;
	}
}

分析可知,其中重点需要关注的部分有:

  • 绑定处理器BindHandleronSuccess/onCreate处理
  • 结果对象的创建
  • 结果的类型转换

总结

整体的绑定过程可以总结为以下部分:

  • 构建Binder实例
    • 基于Binder#bind方法作为数据绑定处理的入口
    • 其中封装属性源,用于属性值获取
  • 创建绑定上下文Context
    • Context用于记录以下信息
      • 当前处理的属性
      • 递归深度
  • 进行绑定处理
    • 绑定处理器前置处理
    • 对象绑定处理
      • 针对不同结果类型,进行不同处理
    • 绑定结果处理
      • 将得到的结果与目标进行绑定
      • 包括类型转换处理

不同结果类型处理

Binder#bindObject方法中,针对不同结果类型提供了不同的处理,下面就一一分析

简单示例

此处提供一个简单示例,用于展示对于不同结果类型的处理

@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "boss")
public class MyConfigurationProperties {
	
    private String name;

    private Integer age;

    // 小伙伴
    private LittlePartner partner;

    // 爱好
    private List<String> hobbies;

    // 别名
    private String[] alias;

    // 信息
    private Map<String, String> infos;
}


@Getter
@Setter
public class LittlePartner {

    private String name;

    private Integer age;

}
boss:
  name: 张三
  age: 16
  partner:
    name: 李四
    age: 17
  hobbies:
    - 吃饭
    - 睡觉
  alias: [张三丰,张上人]
  infos:
    heights: 178
    weight: 60

处理入口

其主要由Binder#bindObject方法完成,跟进分析

// org.springframework.boot.context.properties.bind.Binder
public class Binder {

    /* 对象绑定处理 */
    private <T> Object bindObject(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler,
                                  Context context, boolean allowRecursiveBinding) {
        // 1. 首先尝试基于属性名称从上下文中获取属性
        ConfigurationProperty property = findProperty(name, target, context);
        
        if (property == null && context.depth != 0 && containsNoDescendantOf(context.getSources(), name)) {
            // 1. 无法通过属性名称获取属性值;2. 此时上下文不是在初始深度;3. 此属性没有下级属性
            return null;
        }
        
        // 2. 如果是 map/collection/array,则使用聚合绑定
        // 2.1 获取对应的 AggregateBinder 实例
        AggregateBinder<?> aggregateBinder = getAggregateBinder(target, context);
        if (aggregateBinder != null) {
            // 2.2 调用 bindAggregate 进行聚合绑定
            return bindAggregate(name, target, handler, context, aggregateBinder);
        }
        
        // 3. 普通属性处理
        if (property != null) {
            // 如果已经获取到属性,则直接绑定属性
            return bindProperty(target, context, property);
        }
        // 4. 绑定数据对象
        return bindDataObject(name, target, handler, context, allowRecursiveBinding);
    }
}

尝试属性值获取

首先尝试基于属性名称从上下文中获取属性,跟进分析findProperty方法

// org.springframework.boot.context.properties.bind.Binder
public class Binder {

    /* 属性获取 */
    private <T> ConfigurationProperty findProperty(ConfigurationPropertyName name, Bindable<T> target, Context context) {
        if (name.isEmpty() || target.hasBindRestriction(BindRestriction.NO_DIRECT_PROPERTY)) {
            // 1. 属性名为空
            // 2. 目标由 @ConfigurationProperties 注解注释,所以设置了 NO_DIRECT_PROPERTY 的绑定拒绝策略
            return null;
        }
        
        // 遍历属性源,实际是 Binder 实例管理的属性源
        for (ConfigurationPropertySource source : context.getSources()) {
            // 尝试从属性源中获取属性
            ConfigurationProperty property = source.getConfigurationProperty(name);
            if (property != null) {
                // 属性获取成功,返回
                return property;
            }
        }
        // 属性获取失败,返回空
        return null;
    }
}

分析可知:

  • 以下两种类型不尝试进行属性值获取
    • 属性名为空
    • 目标由@ConfigurationProperties注解注释
      • ConfigurationPropertiesBindHandler的前置处理部分,添加了NO_DIRECT_PROPERTY绑定限制
  • 否则遍历属性源,基于属性名称获取属性值

1. 数据对象结果类型处理

对于数据对象结果类型,其通过Binder#bindDataObject方法完成处理,跟进分析

// org.springframework.boot.context.properties.bind.Binder
public class Binder {

    private Object bindDataObject(ConfigurationPropertyName name, Bindable<?> target, BindHandler handler,
			Context context, boolean allowRecursiveBinding) {
        // 1. 前置判断
		if (isUnbindableBean(name, target, context)) {
            // 如果绑定目标类是不可绑定对象,则返回空
            // 包括 基础类型、Object、Class
			return null;
		}
        
        // 获取绑定目标类
		Class<?> type = target.getType().resolve(Object.class);
		if (!allowRecursiveBinding && context.isBindingDataObject(type)) {
            // 1. 不允许递归绑定,即绑定下级;2. 此类型已经完成绑定
            // 意思是,绑定对象类型的下级递归了此绑定对象类型,返回空
			return null;
		}
        
        // 2. 进行绑定处理
        // 2.1 通过 lambda 表达式创建 DataObjectPropertyBinder(对象属性绑定器)
        // 实际是递归调用 bind 方法,只是其属性名称为 prefix.propertyName
		DataObjectPropertyBinder propertyBinder = (propertyName, propertyTarget) -> 
            bind(name.append(propertyName), propertyTarget, handler, context, false, false);
        
        // 2.2 上下文处理数据对象
        // 通过 lambda 表达式创建 Supplier 用于实例获取
        // 调用 Context#withDataObject 处理
		return context.withDataObject(type, () -> {
			for (DataObjectBinder dataObjectBinder : this.dataObjectBinders) {
                // 使用 DataObjectBinder 进行绑定工作
				Object instance = dataObjectBinder.bind(name, target, context, propertyBinder);
				if (instance != null) {
                    // 绑定成功,返回实例
					return instance;
				}
			}
            // 绑定失败,返回空
			return null;
		});
	}
}

分析可知,其中重点的部分有:

  • 上下文处理
  • 基于lambda表达式创建的用于实例获取的Supplier部分
  • 基于lambda表达式创建的用于对象属性绑定的DataObjectPropertyBinder部分
上下文处理

首先分析Context#withDataObject方法处理

// org.springframework.boot.context.properties.bind.Binder.Context
final class Context implements BindContext {

    // 记录下钻深度
    private int depth;

    // 绑定过程中记录数据对象类型的队列
    private final Deque<Class<?>> dataObjectBindings = new ArrayDeque<>();

    /* 记录绑定过程中数据对象类型 */
    private <T> T withDataObject(Class<?> type, Supplier<T> supplier) {
        // 加入类型
        this.dataObjectBindings.push(type);
        try {
            // 进行深度处理
            return withIncreasedDepth(supplier);
        } finally {
            // 处理完毕,弹出类型
            this.dataObjectBindings.pop();
        }
    }

    /* 深度处理 */
    private <T> T withIncreasedDepth(Supplier<T> supplier) {
        // 增加深度
        increaseDepth();
        try {
            // 由 Supplier 提供实例
            return supplier.get();
        } finally {
            // 处理完毕,减少深度
            decreaseDepth();
        }
    }
}

分析可知,上下文主要完成以下工作:

  • 记录绑定过程中数据对象类型
  • 深度处理
实例获取

实例获取工作由lambda表达式创建的Supplier实例完成

() -> {
    for (DataObjectBinder dataObjectBinder : this.dataObjectBinders) {
        // 使用 DataObjectBinder 进行绑定工作
        Object instance = dataObjectBinder.bind(name, target, context, propertyBinder);
        if (instance != null) {
            // 绑定成功,返回实例
            return instance;
        }
    }
    // 绑定失败,返回空
    return null;
}

分析可知:

  • 绑定工作实际由DataObjectBinder实例完成
  • DataObjectBinder存在两种实现
    • ValueObjectBinder
      • 用于处理不可变值对象
    • JavaBeanBinder
      • 用于处理可变JavaBean
处理入口

我们需要关注的是JavaBeanBinder处理,跟进分析JavaBeanBinder#bind

// org.springframework.boot.context.properties.bind.JavaBeanBinder
class JavaBeanBinder implements DataObjectBinder {
    
    /* 绑定处理 */
    public <T> T bind(ConfigurationPropertyName name, Bindable<T> target, Context context, DataObjectPropertyBinder propertyBinder) {
        // 目标结果不为空 & 存在此属性名称对应的后代属性
        boolean hasKnownBindableProperties = target.getValue() != null && hasKnownBindableProperties(name, context);
        // 基于 Bindable 封装 Bean 实例
        Bean<T> bean = Bean.get(target, hasKnownBindableProperties);
        if (bean == null) {
            return null;
        }
        // 获取实例贡献者
        BeanSupplier<T> beanSupplier = bean.getSupplier(target);
        // 调用重载方法
        boolean bound = bind(propertyBinder, bean, beanSupplier, context);
        // 返回结果
        return (bound ? beanSupplier.get() : null);
    }
}

分析可知,上述过程重要的有三部分:

  • 封装Bean实例
  • 创建BeanSupplier实例
  • 调用重载方法进行绑定
封装Bean实例

跟进分析Bean#get方法

// org.springframework.boot.context.properties.bind.JavaBeanBinder.Bean
static class Bean<T> {

    // 对象类型信息
    private final ResolvableType type;
    private final Class<?> resolvedType;

    // 对象属性信息
    private final Map<String, BeanProperty> properties = new LinkedHashMap<>();

    /* 获取 Bean 实例 */
    static <T> Bean<T> get(Bindable<T> bindable, boolean canCallGetValue) {
        // 获取对象类型信息
        ResolvableType type = bindable.getType();
        Class<?> resolvedType = type.resolve(Object.class);
        // 获取实例贡献者
        Supplier<T> value = bindable.getValue();
        T instance = null;

        if (canCallGetValue && value != null) {
            // 获取实例
            instance = value.get();
            // 更新对象类型信息
            resolvedType = (instance != null) ? instance.getClass() : resolvedType;
        }
        if (instance == null && !isInstantiable(resolvedType)) {
            // 实例为空,且为不可实例化类型,则返回空
            return null;
        }
        Bean<?> bean = Bean.cached;
        if (bean == null || !bean.isOfType(type, resolvedType)) {
            // 构建 Bean 实例
            bean = new Bean<>(type, resolvedType);
            cached = bean;
        }
        return (Bean<T>) bean;
    }

    /* 构造 */
    Bean(ResolvableType type, Class<?> resolvedType) {
        // 记录对象类型信息
        this.type = type;
        this.resolvedType = resolvedType;
        // 设置对象属性
        addProperties(resolvedType);
    }
}

分析可知:

  • Bean实例主要作用是封装了对象属性信息
重载bind方法分析
// org.springframework.boot.context.properties.bind.JavaBeanBinder
class JavaBeanBinder implements DataObjectBinder {
	
    /* 对象绑定重载 */
    private <T> boolean bind(DataObjectPropertyBinder propertyBinder, Bean<T> bean, BeanSupplier<T> beanSupplier, Context context) {
        boolean bound = false;
        for (BeanProperty beanProperty : bean.getProperties().values()) {
            // 遍历所有属性,进行绑定
            bound |= bind(beanSupplier, propertyBinder, beanProperty);
            context.clearConfigurationProperty();
        }
        return bound;
    }
    
    /* 对象属性绑定 */
    private <T> boolean bind(BeanSupplier<T> beanSupplier, DataObjectPropertyBinder propertyBinder, BeanProperty property) {
        // 获取属性名称与类型
		String propertyName = property.getName();
		ResolvableType type = property.getType();
        
		Supplier<Object> value = property.getValue(beanSupplier);
        // 获取注解信息
		Annotation[] annotations = property.getAnnotations();
        
        // 由 DataObjectPropertyBinder 实例完成属性绑定工作
		Object bound = propertyBinder.bindProperty(propertyName,
				Bindable.of(type).withSuppliedValue(value).withAnnotations(annotations));
        
		if (bound == null) {
            // 如果属性绑定失败,则返回
			return false;
		}
        
		if (property.isSettable()) {
            // 属性绑定成功过,则将属性设置到对象实例中
			property.setValue(beanSupplier, bound);
		} else if (value == null || !bound.equals(value.get())) {
            // 没有 setter 则报错
			throw new IllegalStateException("No setter found for property: " + property.getName());
		}
		return true;
	}
}

分析可知:

  • 对于对象绑定而言,实际上就是遍历其属性进行绑定
  • 属性绑定的动作由DataObjectPropertyBinder实例完成
  • 如果属性绑定成功,则通过setter方法设置到对象实例中
对象属性绑定

属性绑定的动作由DataObjectPropertyBinder实例完成,而DataObjectPropertyBinderLambda表达式构建

(propertyName, propertyTarget) -> 
    bind(name.append(propertyName), propertyTarget, handler, context, false, false)

分析可知:

  • name为属性所属对象本身对应的属性名称前缀
  • 属性对应属性名称为:<name>.<propertyName>
  • 对于属性的绑定,实际是递归调用Binder#bind方法,将属性本身作为一个可绑定项进行处理
    • 具体绑定处理过程基于上述分析可知
    • 不同属性基于属性类型不同,分为不同结果类型进行处理
    • 属性也可为内嵌对象类型

2. 普通结果类型处理

普通结果类型包括基本类型/String类型

对于普通类型,在Binder#bindObject存在以下两种情况

  • 能基于属性名称,从属性源中获取属性值
    • 继续调用Binder#bindProperty进行处理
  • 不能基于属性名称,从属性源中获取属性值
    • 返回null

跟进分析Binder#bindProperty方法处理

// org.springframework.boot.context.properties.bind.Binder
public class Binder {
    
    /* 简单类型属性绑定 */
    private <T> Object bindProperty(Bindable<T> target, Context context, ConfigurationProperty property) {
        context.setConfigurationProperty(property);
        // 1. 获取属性值
        Object result = property.getValue();
        // 2. 处理属性值中的占位符
        result = this.placeholdersResolver.resolvePlaceholders(result);
        // 3. 对属性值进行类型转换
        result = context.getConverter().convert(result, target);
        // 返回结果
        return result;
    }
}

分析可知:

  • 简单属性的处理步骤十分简单
    • 获取属性值
    • 处理属性值中的占位符
    • 对属性值进行类型转换

3. 聚合结果类型处理

对于聚合结果类型

// org.springframework.boot.context.properties.bind.Binder
public class Binder {

    /* 对象绑定处理 */
    private <T> Object bindObject(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler,
                                  Context context, boolean allowRecursiveBinding) {
        
        // ...
        
        // 2. 如果是 map/collection/array,则使用聚合绑定
        // 2.1 获取对应的 AggregateBinder 实例
        AggregateBinder<?> aggregateBinder = getAggregateBinder(target, context);
        if (aggregateBinder != null) {
            // 2.2 调用 bindAggregate 进行聚合绑定
            return bindAggregate(name, target, handler, context, aggregateBinder);
        }
        
        // ...
    }
}

分析可知,其主要分为两部分:

  • 聚合绑定器获取
  • bindAggregate方法处理
聚合绑定器获取

跟进分析getAggregateBinder方法对于聚合属性类型,首先根据其类型获取对应的AggregateBinder

// org.springframework.boot.context.properties.bind.Binder
public class Binder {

    private AggregateBinder<?> getAggregateBinder(Bindable<?> target, Context context) {
        // 1. 从 Bindable 实例中获取目标类型
        Class<?> resolvedType = target.getType().resolve(Object.class);
        
        // 2. 根据类型,获取其对应的聚合绑定器
        if (Map.class.isAssignableFrom(resolvedType)) {
            return new MapBinder(context);
        }
        if (Collection.class.isAssignableFrom(resolvedType)) {
            return new CollectionBinder(context);
        }
        if (target.getType().isArray()) {
            return new ArrayBinder(context);
        }
        
        // 3. 如果目标类型不是聚合类型,则返回空
        return null;
    }
}

分析可知:

  • 主要针对三种聚合属性,与其对应的聚合绑定器类型
    • MapMapBinder
    • CollectionCollection
    • ArrayArrayBinder
聚合绑定入口

跟进分析bindAggregate方法

// org.springframework.boot.context.properties.bind.Binder
public class Binder {

    private <T> Object bindAggregate(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler,
                                     Context context, AggregateBinder<?> aggregateBinder) {
        // 1. 基于 Lambda 创建一个 AggregateElementBinder 实例
        AggregateElementBinder elementBinder = (itemName, itemTarget, source) -> {
            // 是否允许递归绑定
            boolean allowRecursiveBinding = aggregateBinder.isAllowRecursiveBinding(source);
            // 递归绑定
            Supplier<?> supplier = () -> bind(itemName, itemTarget, handler, context, allowRecursiveBinding, false);
            // 上下文中添加属性源,再返回绑定结果
            return context.withSource(source, supplier);
        };
        
        // 2. 上下文加深,同时进行聚合绑定操作
        return context.withIncreasedDepth(() -> aggregateBinder.bind(name, target, elementBinder));
    }
    
}

分析可知,其主要由两部分组成:

  • 构建AggregateElementBinder实例
  • 使用AggregateBinder#bind进行绑定操作
聚合绑定

跟进分析AggregateBinder#bind方法

// org.springframework.boot.context.properties.bind.AggregateBinder
abstract class AggregateBinder<T> {

    /* 聚合绑定 */
    final Object bind(ConfigurationPropertyName name, Bindable<?> target, AggregateElementBinder elementBinder) {
        // 1. 由子类实现 bindAggregate 方法完成聚合类型绑定,返回结果
        Object result = bindAggregate(name, target, elementBinder);
        Supplier<?> value = target.getValue();
        if (result == null || value == null) {
            // 绑定失败,则直接返回 null
            return result;
        }
        // 绑定成功,则由子类实现 merge 方法完成结果聚合
        return merge((Supplier<T>) value, (T) result);
    }
}

分析可知:

  • 实际聚合绑定处理由子类实现的bindAggregate/merge方法完成

3.1 数组结果类型绑定

对于数组结果类型,其绑定工作由ArrayBinder完成,跟进分析其bindAggregate方法实现

// org.springframework.boot.context.properties.bind.ArrayBinder
class ArrayBinder extends IndexedElementsBinder<Object> {

    protected Object bindAggregate(ConfigurationPropertyName name, Bindable<?> target, AggregateElementBinder elementBinder) {
        // 1. 构建一个结果封装贡献者
        IndexedCollectionSupplier result = new IndexedCollectionSupplier(ArrayList::new);
        // 2. 获取结果类型信息
        ResolvableType aggregateType = target.getType();
        ResolvableType elementType = target.getType().getComponentType();
        // 3. 索引绑定
        bindIndexed(name, target, elementBinder, aggregateType, elementType, result);
        
        if (result.wasSupplied()) {
            // 如果绑定成功,则将 List 结果转换为 Array 进行返回
            List<Object> list = (List<Object>) result.get();
            Object array = Array.newInstance(elementType.resolve(), list.size());
            for (int i = 0; i < list.size(); i++) {
                Array.set(array, i, list.get(i));
            }
            return array;
        }
        // 如果绑定失败,返回 null
        return null;
    }
}

分析可知:

  • 实际绑定操作由父类IndexedElementsBinder#bindIndexed完成

3.2 集合结果类型绑定

对于集合结果类型,其绑定工作由CollectionBinder完成,跟进分析其bindAggregate方法实现

// org.springframework.boot.context.properties.bind.CollectionBinder
class CollectionBinder extends IndexedElementsBinder<Collection<Object>> {
    
    protected Object bindAggregate(ConfigurationPropertyName name, Bindable<?> target, AggregateElementBinder elementBinder) {
        // 获取集合类型信息
		Class<?> collectionType = (target.getValue() != null) ? List.class : target.getType().resolve(Object.class);
		ResolvableType aggregateType = ResolvableType.forClassWithGenerics(List.class, target.getType().asCollection().getGenerics());
		ResolvableType elementType = target.getType().asCollection().getGeneric();
		// 创建一个 IndexedCollectionSupplier 实例用于结果封装
        IndexedCollectionSupplier result = new IndexedCollectionSupplier(
				() -> CollectionFactory.createCollection(collectionType, elementType.resolve(), 0));
        // 调用 bindIndexed 方法进行绑定处理
		bindIndexed(name, target, elementBinder, aggregateType, elementType, result);
        
		if (result.wasSupplied()) {
            // 绑定成功,返回结果
			return result.get();
		}
        // 绑定失败,返回空
		return null;
	}
}

分析可知:

  • 实际绑定操作由父类IndexedElementsBinder#bindIndexed完成

3.3 索引元素绑定器

上述数组结果类型与集合结果类型,其最后都是由IndexedElementsBinder完成工作

索引元素绑定器IndexedElementsBinder,是ArrayBinder/CollectionBinder的抽象父类,其主要针对于多个索引项的处理

配置情况

对于Array/Collection类型的配置,其存在两种情况:

  • 值为多个
  • 值为单个

其对应的配置也不同

  1. 值为单个
boss:
  # Collection
  hobbies: 吃饭
  # Array
  alias: 张三丰
  1. 值为多个
boss:
  # Collection
  hobbies:
    - 吃饭
    - 睡觉
  # Array
  alias: [张三丰,张上人]

其由YamlPropertySourceLoader加载解析后的结果也不同,其解析后与properties文件配置类似

  1. 值为单个
boss.hobbies=吃饭
boss.alias=张三丰
  1. 值为多个
boss.hobbies[0]=吃饭
boss.hobbies[1]=睡觉
boss.alias[0]=张三丰
boss.alias[1]=张上人

可以发现,当值为多个时,其属性名称变为name[index]IndexedElementsBinder就是针对这个区别进行特殊处理的绑定器

绑定分析

绑定处理由IndexedElementsBinder#bindIndexed方法完成,跟进分析

// org.springframework.boot.context.properties.bind.IndexedElementsBinder
abstract class IndexedElementsBinder<T> extends AggregateBinder<T> {
    
    /* 索引绑定 */
    protected final void bindIndexed(ConfigurationPropertyName name, Bindable<?> target,
			AggregateElementBinder elementBinder, ResolvableType aggregateType, ResolvableType elementType,
			IndexedCollectionSupplier result) {
		for (ConfigurationPropertySource source : getContext().getSources()) {
            // 遍历属性源,调用重载方法进行绑定
			bindIndexed(source, name, target, elementBinder, result, aggregateType, elementType);
            
			if (result.wasSupplied() && result.get() != null) {
                // 绑定成功则返回,退出循环
				return;
			}
		}
	}
}

分析可知,其遍历多个属性源,调用重载方法进行绑定,继续跟进

// org.springframework.boot.context.properties.bind.IndexedElementsBinder
abstract class IndexedElementsBinder<T> extends AggregateBinder<T> {
    
    /* 索引绑定 */
    private void bindIndexed(ConfigurationPropertySource source, ConfigurationPropertyName root, Bindable<?> target,
			AggregateElementBinder elementBinder, IndexedCollectionSupplier collection, ResolvableType aggregateType,
			ResolvableType elementType) {
        // 尝试从属性源中获取属性值
		ConfigurationProperty property = source.getConfigurationProperty(root);
		if (property != null) {
            // 1. 能直接获取属性值,则将值进行绑定
			getContext().setConfigurationProperty(property);
			bindValue(target, collection.get(), aggregateType, elementType, property.getValue());
		} else {
            // 2. 不能直接获取属性值,则获取属性索引顺序值
			bindIndexed(source, root, elementBinder, collection, elementType);
		}
	}
}

分析可知:

  • 对于单值情况,可以直接根据属性名称获取值,则调用bindValue绑定值
  • 对于多值情况,需要调用重载bindIndexed方法进行处理
单值处理

跟进分析bindValue方法

// org.springframework.boot.context.properties.bind.IndexedElementsBinder
abstract class IndexedElementsBinder<T> extends AggregateBinder<T> {
    
    /* 单值情况绑定 */
    private void bindValue(Bindable<?> target, Collection<Object> collection, ResolvableType aggregateType,
			ResolvableType elementType, Object value) {
        
		if (value == null || value instanceof CharSequence && ((CharSequence) value).length() == 0) {
            // 如果值为空/空字符串,则直接返回
			return;
		}
        // 使用转换器将值转换为所需的 Array 类型,其中包括了
		Object aggregate = convert(value, aggregateType, target.getAnnotations());
        // 将 Array 类型结果转换为 Collection 类型结果,并将其保存到 入参 collection 实例中
		ResolvableType collectionType = ResolvableType.forClassWithGenerics(collection.getClass(), elementType);
		Collection<Object> elements = convert(aggregate, collectionType);
		collection.addAll(elements);
	}
    
}

需要注意的是:

  • 对于值,在转换过程中,将会使用分隔符进行分割处理
    • 具体处理过程在DelimitedStringToArrayConverter#convert中,默认分割符为,
    • 可以通过添加@Delimiter注解自定义分割符
多值处理

对于多值情况,跟进分析重载bindIndexed方法

// org.springframework.boot.context.properties.bind.IndexedElementsBinder
abstract class IndexedElementsBinder<T> extends AggregateBinder<T> {
    
    /* 索引绑定 */
    private void bindIndexed(ConfigurationPropertySource source, ConfigurationPropertyName root,
			AggregateElementBinder elementBinder, IndexedCollectionSupplier collection, ResolvableType elementType) {
        // 1. 从属性源中获取索引子代:name[i]
		MultiValueMap<String, ConfigurationPropertyName> knownIndexedChildren = getKnownIndexedChildren(source, root);
        
        // 遍历处理,从索引 0 开始
		for (int i = 0; i < Integer.MAX_VALUE; i++) {
            // 拼接索引子代属性名称:name[i]
			ConfigurationPropertyName name = root.append((i != 0) ? "[" + i + "]" : INDEX_ZERO);
            // 通过 AggregateElementBinder 实例获取 索引子代属性值
			Object value = elementBinder.bind(name, Bindable.of(elementType), source);
            
			if (value == null) {
                // 获取属性值失败,则退出循环
				break;
			}
            // 获取属性值成功
            // 将对应索引移除
			knownIndexedChildren.remove(name.getLastElement(Form.UNIFORM));
            // 将值添加
			collection.get().add(value);
		}
        // 经过处理,如果还存在已知的索引子代属性没有成功获取属性值,则报错
		assertNoUnboundChildren(source, knownIndexedChildren);
	}
    
}

分析可知:

  • 索引子代索引序号必须按顺序从0号索引开始
    • 主要针对于properties配置,因为yaml配置是不需要自定义索引序号的
  • 按序获取索引子代属性值,必须要有值,否则影响后续索引序号取值
  • 对于索引子代的处理,由AggregateElementBinder完成
索引子代处理

在多值情况处理中,对于索引子代的处理,由AggregateElementBinder完成

而此AggregateElementBinder实例为Binder#bindAggregate方法中通过Lambda表达式构建

AggregateElementBinder elementBinder = (itemName, itemTarget, source) -> {
    // 是否允许递归绑定
    boolean allowRecursiveBinding = aggregateBinder.isAllowRecursiveBinding(source);
    // 递归绑定
    Supplier<?> supplier = () -> bind(itemName, itemTarget, handler, context, allowRecursiveBinding, false);
    // 上下文中添加属性源,再返回绑定结果
    return context.withSource(source, supplier);
};

分析可知:

  • 其实际就是以itemName(name[i])作为属性名称,递归调用Binder#bind方法获取属性值
小结

分析可知:

  • 索引元素绑定器IndexedElementsBinder作为ArrayBinder/CollectionBinder的抽象父类,其提供了bindIndexed统一处理数组/集合结果类型绑定
  • 实际处理过程中,都是将其视为Collection集合结果类型进行处理

3.4 Map结果类型绑定

对于Map结果类型,其绑定工作由MapBinder完成,跟进分析其bindAggregate方法实现

// org.springframework.boot.context.properties.bind.MapBinder
class MapBinder extends AggregateBinder<Map<Object, Object>> {
    
    protected Object bindAggregate(ConfigurationPropertyName name, Bindable<?> target, AggregateElementBinder elementBinder) {
        // 创建 Map 实例存储结果
		Map<Object, Object> map = CollectionFactory
				.createMap((target.getValue() != null) ? Map.class : target.getType().resolve(Object.class), 0);
		Bindable<?> resolvedTarget = resolveTarget(target);
        // 基于属性源,判断是否存在后代属性
		boolean hasDescendants = hasDescendants(name);
        
        // 遍历属性源处理
		for (ConfigurationPropertySource source : getContext().getSources()) {
			if (!ConfigurationPropertyName.EMPTY.equals(name)) {
				ConfigurationProperty property = source.getConfigurationProperty(name);
				if (property != null && !hasDescendants) {
					getContext().setConfigurationProperty(property);
					return getContext().getConverter().convert(property.getValue(), target);
				}
                // 过滤属性源中以 name 为前缀的属性,封装为新的属性源
				source = source.filter(name::isAncestorOf);
			}
            
            // 构建 EntryBinder 实例
            // 调用 bindEntries 绑定 map#entry 属性
			new EntryBinder(name, resolvedTarget, elementBinder).bindEntries(source, map);
		}
        // 返回绑定结果
		return map.isEmpty() ? null : map;
	}
}

分析可知,主要关注的部分为:

  • 首先创建Map作为结果存储
  • 将属性源中name的后代属性过滤封装为新的属性源
    • 属性名称以name为前缀
    • 比如示例中以boss.infos为前缀的属性:boss.infos.heights
条目绑定

跟进分析EntryBinder

// org.springframework.boot.context.properties.bind.MapBinder.EntryBinder
private class EntryBinder {

    // map 的根属性明细
    // 示例中为 boss.infos
    private final ConfigurationPropertyName root;

    // 封装了 AggregateElementBinder
    private final AggregateElementBinder elementBinder;

    // Map 集合的类型信息
    private final ResolvableType mapType;
    private final ResolvableType keyType;
    private final ResolvableType valueType;

    /* 条目绑定 */
    void bindEntries(ConfigurationPropertySource source, Map<Object, Object> map) {
        if (source instanceof IterableConfigurationPropertySource) {
            for (ConfigurationPropertyName name : (IterableConfigurationPropertySource) source) {
                // 遍历属性源中属性,实际是经过过滤后的,仅保留以 root 对应的属性名称为前缀的属性列表

                // 1. 封装条目类型信息为 Bindable 示例
                Bindable<?> valueBindable = getValueBindable(name);
                // 2. 封装条目名称为 ConfigurationPropertyName 示例
                ConfigurationPropertyName entryName = getEntryName(source, name);
                // 3. 基于条目名称,解析出 map#key 
                Object key = getContext().getConverter().convert(getKeyName(entryName), this.keyType);
                // 4.1 基于条目名称,由 AggregateElementBinder 获取条目值
                // 4.2 基于 key:条目值,保存到 Map 中
                map.computeIfAbsent(key, (k) -> this.elementBinder.bind(entryName, valueBindable));
            }
        }
    }
}

分析可知:

  • 实际处理以rootName为前缀的条目属性
    • MapBinder#bindAggregate处理过程中通过过滤封装新的属性源
  • 对于单个条目,实际是通过AggregateElementBinder示例完成绑定操作
    • 实际就是3.3 -> 索引子代处理部分
posted @ 2024-07-23 10:43  南极洲小卡  阅读(76)  评论(0)    收藏  举报