SpringBoot基础-Environment解析

本文深入解析SpringBoot的Environment,从实现EnvironmentAware和CommandLineRunner接口入手,跟踪getProperty源码,揭示属性如何从PropertySources中读取。接着详细分析prepareEnvironment过程,包括getOrCreateEnvironment、configureEnvironment等步骤,探讨不同阶段属性的加载和定制,如默认属性、命令行参数、配置文件等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

获取属性源码跟踪解析

入口方法

@Component
public class EnvironmentDebugger implements CommandLineRunner, EnvironmentAware {

    private Environment environment;

    @Override
    public void run(String... args) throws Exception {
        String study2 = environment.getProperty("study2");
    }

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

实现EnvironmentAware 接口,获取Environment对象
实现CommandLineRunner接口,在容器启动完成后,获取key的值,作为源码跟踪的入口

源码跟踪

根据入口environment.getProperty,一直点进入org.springframework.core.env.PropertySourcesPropertyResolver#getProperty(java.lang.String, java.lang.Class, boolean),可以看到最基础的实现

@Nullable
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
	if (this.propertySources != null) {
		for (PropertySource<?> propertySource : this.propertySources) {
			if (logger.isTraceEnabled()) {
				logger.trace("Searching for key '" + key + "' in PropertySource '" +
						propertySource.getName() + "'");
			}
			Object value = propertySource.getProperty(key);
			if (value != null) {
				if (resolveNestedPlaceholders && value instanceof String) {
					value = resolveNestedPlaceholders((String) value);
				}
				logKeyFound(key, propertySource, value);
				return convertValueIfNecessary(value, targetValueType);
			}
		}
	}
	if (logger.isTraceEnabled()) {
		logger.trace("Could not find key '" + key + "' in any property source");
	}
	return null;
}

方法中可以看出通过key获取属性值,遍历propertySources中存放的PropertySource,并且从中依次取出属性,如果所有都没有取到,则返回NULL

在这里插入图片描述
可以看出propertySources中共存在7中类型的属性,可以找到我们设置的默认属性,并且已键值对存放数据

PropertySource源码

public abstract class PropertySource<T> {

	protected final Log logger = LogFactory.getLog(getClass());

	protected final String name;

	protected final T source;
}

source为存放属性的具体实现

通过获取属性可以看出所有的属性都存放在:propertySources中,只需要搞懂spring容器在启动的时候是如何装载自定义的属性进propertySources就可以

Environment加载属性

入口函数

springApplication.run(args) --> prepareEnvironment

prepareEnvironment

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
		DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
	// Create and configure the environment
	ConfigurableEnvironment environment = getOrCreateEnvironment();
	configureEnvironment(environment, applicationArguments.getSourceArgs());
	ConfigurationPropertySources.attach(environment);
	listeners.environmentPrepared(bootstrapContext, environment);
	DefaultPropertiesPropertySource.moveToEnd(environment);
	configureAdditionalProfiles(environment);
	bindToSpringApplication(environment);
	if (!this.isCustomEnvironment) {
		environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
				deduceEnvironmentClass());
	}
	ConfigurationPropertySources.attach(environment);
	return environment;
}

在prepareEnvironment中,共进行了一下的进步操作:

  1. 创建Environment对象:getOrCreateEnvironment()
  2. 配置属性:configureEnvironment()
  3. 添加configurationProperties属性:ConfigurationPropertySources.attach(environment)
  4. 通过事件发布器发布environment事件:listeners.environmentPrepared(bootstrapContext, environment);
  5. 将defaultProperties属性移到最后面,因为是默认,所有在后面兜底:DefaultPropertiesPropertySource.moveToEnd(environment)
  6. 加载profile配置文件属性:configureAdditionalProfiles(environment)
  7. 绑定SpringApplication对象到Environment中:bindToSpringApplication(environment)

getOrCreateEnvironment

private ConfigurableEnvironment getOrCreateEnvironment() {
	if (this.environment != null) {
		return this.environment;
	}
	switch (this.webApplicationType) {
	case SERVLET:
		return new StandardServletEnvironment();
	case REACTIVE:
		return new StandardReactiveWebEnvironment();
	default:
		return new StandardEnvironment();
	}
}
  • 通过:webApplicationType判断当前容器环境,创建不同的Environment对象

SERVLET环境举例

public class StandardEnvironment extends AbstractEnvironment {

	/** System environment property source name: {@value}. */
	public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";

	/** JVM system properties property source name: {@value}. */
	public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";


	@Override
	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()));
	}
	@Override
	public void initPropertySources(@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {
		WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);
	}
}

可以看出在类种定义了方法:customizePropertySources,
customizePropertySources:

  • 添加:servletContextInitParams属性集
  • 添加:servletConfigInitParams属性集
  • 如果jndi环境,添加jndiProperties属性集
  • 调用付类customizePropertySources方法,初始化java及系统属性参数

supper.customizePropertySources()

public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
	
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";

@Override	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()));
}

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

在StandardServletEnvironment类中,并没有发现在哪里调用:customizePropertySources().此时查看类图,它的的上层父类:StandardEnvironment也没有调用,继续查看上层父类:AbstractEnvironment,此时发现在构造函数中调用了customizePropertySources()
在这里插入图片描述

public AbstractEnvironment() {
	customizePropertySources(this.propertySources);
}

只是由于java在实例化的时候,会先调用父类的构造函数,所在执行了customizePropertySources(),完成相关属性的初始化工作

  • 此时environment中的属性只有四种
    servletConfigInitParams、servletContextInitParams、systemProperties、systemEnvironment

在这里插入图片描述

configureEnvironment

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
	// 添加解析器
	if (this.addConversionService) {
		ConversionService conversionService = ApplicationConversionService.getSharedInstance();
		environment.setConversionService((ConfigurableConversionService) conversionService);
	}
	configurePropertySources(environment, args);
	configureProfiles(environment, args);
}

在方法中通过:configurePropertySources方法来添加:

  • 添加默认属性值
  • 添加命令行属性值

configurePropertySources

protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
	MutablePropertySources sources = environment.getPropertySources();
	DefaultPropertiesPropertySource.ifNotEmpty(this.defaultProperties, sources::addLast);
	if (this.addCommandLineProperties && args.length > 0) {
		String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
		if (sources.contains(name)) {
			PropertySource<?> source = sources.get(name);
			CompositePropertySource composite = new CompositePropertySource(name);
			composite.addPropertySource(
					new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
			composite.addPropertySource(source);
			sources.replace(name, composite);
		}
		else {
			sources.addFirst(new SimpleCommandLinePropertySource(args));
		}
	}
}

1.通过函数:DefaultPropertiesPropertySource.ifNotEmpty(this.defaultProperties, sources::addLast) 将硬编码的defaultProperties对象属性添加在sources中

public static void main(String[] args) throws InterruptedException {
	SpringApplication springApplication = new SpringApplication((SpringBootDemoApplication.class));
    Properties properties = new Properties();
    properties.setProperty("study1", "value_study1");
    springApplication.setDefaultProperties(properties);
    springApplication.run(args);
}

//springApplication.setDefaultProperties(properties)
public void setDefaultProperties(Properties defaultProperties) {
	this.defaultProperties = new HashMap<>();
	for (Object key : Collections.list(defaultProperties.propertyNames())) {
		this.defaultProperties.put((String) key, defaultProperties.get(key));
	}
}

2.判断是否需要添加命令行参数及args是否传入的信息,构建一个SimpleCommandLinePropertySource对象添加到属性列表中
new SimpleCommandLinePropertySource(args)

public SimpleCommandLinePropertySource(String... args) {
	super(new SimpleCommandLineArgsParser().parse(args));
}

调用SimpleCommandLineArgsParser解析器,解析传入的参数信息

SimpleCommandLineArgsParser.parse

class SimpleCommandLineArgsParser {

	/**
	 * Parse the given {@code String} array based on the rules described {@linkplain
	 * SimpleCommandLineArgsParser above}, returning a fully-populated
	 * {@link CommandLineArgs} object.
	 * @param args command line arguments, typically from a {@code main()} method
	 */
	public CommandLineArgs parse(String... args) {
		CommandLineArgs commandLineArgs = new CommandLineArgs();
		for (String arg : args) {
			if (arg.startsWith("--")) {
				String optionText = arg.substring(2);
				String optionName;
				String optionValue = null;
				int indexOfEqualsSign = optionText.indexOf('=');
				if (indexOfEqualsSign > -1) {
					optionName = optionText.substring(0, indexOfEqualsSign);
					optionValue = optionText.substring(indexOfEqualsSign + 1);
				}
				else {
					optionName = optionText;
				}
				if (optionName.isEmpty()) {
					throw new IllegalArgumentException("Invalid argument syntax: " + arg);
				}
				commandLineArgs.addOptionArg(optionName, optionValue);
			}
			else {
				commandLineArgs.addNonOptionArg(arg);
			}
		}
		return commandLineArgs;
	}
	
}

可以看出只对-- 开头的字符串进行解析,根据键值对来获取key,value,添加到属性中

  • 此时environment中的属性值
    servletConfigInitParams、servletContextInitParams、systemProperties、systemEnvironment、commandLineArgs、defaultProperties 在这里插入图片描述

ConfigurationPropertySources.attach

public static void attach(Environment environment) {
	Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
	MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources();
	PropertySource<?> attached = sources.get(ATTACHED_PROPERTY_SOURCE_NAME);
	if (attached != null && attached.getSource() != sources) {
		sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);
		attached = null;
	}
	if (attached == null) {
		sources.addFirst(new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME,
				new SpringConfigurationPropertySources(sources)));
	}
}

主要设置configurationProperties属性值

  • 此时environment属性值
    servletConfigInitParams、servletContextInitParams、systemProperties、systemEnvironment、commandLineArgs、defaultProperties、 configurationProperties
    在这里插入图片描述

listeners.environmentPrepared

DefaultPropertiesPropertySource.moveToEnd

public static final String NAME = "defaultProperties";

public static void moveToEnd(MutablePropertySources propertySources) {
	PropertySource<?> propertySource = propertySources.remove(NAME);
	if (propertySource != null) {
		propertySources.addLast(propertySource);
	}
}

先将defaultProperties删除,然后在添加,目的是将defaultProperties存到最后一个节点,用作兜底操作

configureAdditionalProfiles

private void configureAdditionalProfiles(ConfigurableEnvironment environment) {
	if (!CollectionUtils.isEmpty(this.additionalProfiles)) {
		Set<String> profiles = new LinkedHashSet<>(Arrays.asList(environment.getActiveProfiles()));
		if (!profiles.containsAll(this.additionalProfiles)) {
			profiles.addAll(this.additionalProfiles);
			environment.setActiveProfiles(StringUtils.toStringArray(profiles));
		}
	}
}

获取当前需要加载的配置文件后缀

bindToSpringApplication

protected void bindToSpringApplication(ConfigurableEnvironment environment) {
	try {
		Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
	}
	catch (Exception ex) {
		throw new IllegalStateException("Cannot bind to SpringApplication", ex);
	}
}

将spring.main属性值赋值给:SpringApplication中对应的属性

ConfigurationClassParser

  • 添加@PropertySources属性集,在run的refresh中调用

org.springframework.context.annotation.ConfigurationClassParser#processPropertySource

private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
	String name = propertySource.getString("name");
	if (!StringUtils.hasLength(name)) {
		name = null;
	}
	String encoding = propertySource.getString("encoding");
	if (!StringUtils.hasLength(encoding)) {
		encoding = null;
	}
	String[] locations = propertySource.getStringArray("value");
	Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");
	boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");

	Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
	PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
			DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));

	for (String location : locations) {
		try {
			String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
			Resource resource = this.resourceLoader.getResource(resolvedLocation);
			addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
		}
		catch (IllegalArgumentException | FileNotFoundException | UnknownHostException | SocketException ex) {
			// Placeholders not resolvable or resource not found when trying to open it
			if (ignoreResourceNotFound) {
				if (logger.isInfoEnabled()) {
					logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage());
				}
			}
			else {
				throw ex;
			}
		}
	}
}


private void addPropertySource(PropertySource<?> propertySource) {
	String name = propertySource.getName();
	MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment).getPropertySources();

	if (this.propertySourceNames.contains(name)) {
		// We've already added a version, we need to extend it
		PropertySource<?> existing = propertySources.get(name);
		if (existing != null) {
			PropertySource<?> newSource = (propertySource instanceof ResourcePropertySource ?
					((ResourcePropertySource) propertySource).withResourceName() : propertySource);
			if (existing instanceof CompositePropertySource) {
				((CompositePropertySource) existing).addFirstPropertySource(newSource);
			}
			else {
				if (existing instanceof ResourcePropertySource) {
					existing = ((ResourcePropertySource) existing).withResourceName();
				}
				CompositePropertySource composite = new CompositePropertySource(name);
				composite.addPropertySource(newSource);
				composite.addPropertySource(existing);
				propertySources.replace(name, composite);
			}
			return;
		}
	}

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

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值