Spring Boot v2.4.4源码解析(二)运行时环境Environment

2024-05-07 00:48

本文主要是介绍Spring Boot v2.4.4源码解析(二)运行时环境Environment,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Spring Boot当前应用运行环境Environment

一、引入

在Spring Boot启动函数SpringApplication#run(String... args)中会调用prepareEnvironment(listeners, bootstrapContext, applicationArguments) 函数准备运行时环境。

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {// 创建或者配置环境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;
}

该方法首先会根据getOrCreateEnvironment()方法获取ConfigurableEnvironment;

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

第一次进入该方法environment为空,需要根据webApplicationType 当前应用类型创建,应用类型主要分为三种:

  • SERVLET 普通web应用;
  • REACTIVE 响应式web应用;
  • 其他标准引用

SpringApplication 会在构造方法中调用 WebApplicationType#deduceFromClasspath()推断出应用类型并初始化webApplicationType,具体源码参数WebApplicationType。笔者当前环境为普通web应用,所以最后将会创建StandardServletEnvironment。

二、继承关系

看下StandardServletEnvironment类UML类图:
在这里插入图片描述

三、属性解析

1. 属性解析顶级接口PropertyResolver

类图中顶级接口为PropertyResolver, 该接口定义了一些方法用于针对任何基础源解析属性。

public interface PropertyResolver {// 判断给定的属性键是否可解析, 或者说判断给定key解释出的value是否不为nullboolean containsProperty(String key);// 返回给定key所对应的属性值, 如果给定的key不能解析则返回null@NullableString getProperty(String key);// 返回给定key所对应的属性值, 如果给定的key不能解析则返回defaultValueString getProperty(String key, String defaultValue);// 解析给定key所对应的属性值为目标类型targetType, 如果给定的key不能解析则返回null@Nullable<T> T getProperty(String key, Class<T> targetType);// 解析给定key所对应的属性值, 并将该值转换成目标类型targetType, 如果给定的key不能解析则返回defaultValue<T> T getProperty(String key, Class<T> targetType, T defaultValue);// 返回给定key所对应的属性值, 如果给定的key不能解析则抛IllegalStateException异常String getRequiredProperty(String key) throws IllegalStateException;// 解析给定key所对应的属性值, 并将该值转换成目标类型targetType(非null), 如果给定的key不能解析则抛IllegalStateException异常<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;// 解析给定文本中${...}形式的占位符, 并将解析出占位符名称作为key用getProperty()函数获取属性值并将其替换// 对于不能获得占位符名称属性值且没有默认值的占位符将会被忽略并保持不变,String resolvePlaceholders(String text);// 解析给定文本中${...}形式的占位符, 并将解析出占位符名称作为key用getProperty()函数获取属性值并将其替换// 对于不能获得占位符名称属性值且没有默认值的占位符将会抛出IllegalArgumentException 异常String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;

PropertyResolver 继承链主要有两个分支:

  • PropertyResolver -> ConfigurablePropertyResolver -> AbstractPropertyResolver -> PropertySourcesPropertyResolver分支;
  • PropertyResolver -> Environment -> ConfigurableEnvironment -> AbstractEnvironment ->XXXEnvironment分支;

真正对PropertyResolver接口功能实现是在第一条分支,第二条分支会在AbstractEnvironment 内部组合一个PropertySourcesPropertyResolver实例,并将PropertyResolver接口方法实现代理给PropertySourcesPropertyResolver

2. ConfigurablePropertyResolver

看下PropertyResolver的一个子接口 ConfigurablePropertyResolver, 顾名思义,该接口是可配置的PropertyResolver
ConfigurableXXX成了Spring的一种命名规范,或者说是一种设计模式, 表示裸配置;Spring很多接口都是读写分离的,最顶层接口一般都只会提供只读方法,这是Spring框架设计的一般规律之一。

PropertyResolver接口中有些方法需要将属性值装换成目标类型targetType, 类型转换功能由ConversionService接口提供, 所以ConfigurablePropertyResolver最重要就是定义访问和定制ConversionService方法。除此之外, ConfigurablePropertyResolver扩展定义必要属性校验、占位符前缀、占位符后缀、占位符分隔符等一些列的功能:


public interface ConfigurablePropertyResolver extends PropertyResolver {// 返回对属性值执行类型转换时使用的ConfigurableConversionService// 由于返回的ConversionService具有可配置特性, 所以可以很方便地添加和删除单个Converter// 例如:// ConfigurableConversionService cs = env.getConversionService();// cs.addConverter(new FooConverter());ConfigurableConversionService getConversionService();// 设置用于执行属性值类型转换的ConfigurableConversionService// 注意, 与其重新设置ConversionService, 一种更优雅的方式是通过getConversionService()获取ConfigurableConversionService, 并通过像addConverter等方法删除或者添加单个Convertervoid setConversionService(ConfigurableConversionService conversionService);// 设置占位符前缀void setPlaceholderPrefix(String placeholderPrefix);// 设置占位符后缀void setPlaceholderSuffix(String placeholderSuffix);// 设置占位符与默认值分隔符void setValueSeparator(@Nullable String valueSeparator);// 设置在遇到解析属性值中不能解析的占位符时, 是或否抛异常// 即该方法确定占位符江西是否是严格模式;// 传参为false时, 严格模式, 属性值中遇到不能解析的占位符将会抛异常// 传参为true时, 非严格模式, 属性值中不能解析的占位符将保持原有${...}形式// 在调用getProperty(String)及其重载方法获取属性值且属性值包含不能解析成功的占位符时, 将会其获取该值void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders);// 指定必须存在的属性void setRequiredProperties(String... requiredProperties);// 验证setRequiredProperties指定的每个属性是否存在并能否解析为null值。// 如果遇到任何必须存在的属性不能解析时抛MissingRequiredPropertiesException异常void validateRequiredProperties() throws MissingRequiredPropertiesException;
}

3. AbstractPropertyResolver

AbstractPropertyResolver实现了ConfigurablePropertyResolvergetProperty(String key, Class<T> targetType)之外的所有方法,并且只提供一个抽象方法getPropertyAsRawString(String key)给子类去实现。

public abstract class AbstractPropertyResolver implements ConfigurablePropertyResolver {// volatile 保证double check实现单例线程可见以及防止指令重排序@Nullableprivate volatile ConfigurableConversionService conversionService;// 非严格模式占位符解析器@Nullableprivate PropertyPlaceholderHelper nonStrictHelper;// 严格模式占位符解析器@Nullableprivate PropertyPlaceholderHelper strictHelper;// 占位符解析默认严格模式private boolean ignoreUnresolvableNestedPlaceholders = false;// 默认${private String placeholderPrefix = SystemPropertyUtils.PLACEHOLDER_PREFIX;// 默认是}private String placeholderSuffix = SystemPropertyUtils.PLACEHOLDER_SUFFIX;// 默认是:@Nullableprivate String valueSeparator = SystemPropertyUtils.VALUE_SEPARATOR;// 必要属性private final Set<String> requiredProperties = new LinkedHashSet<>();// double check保证单例@Overridepublic ConfigurableConversionService getConversionService() {ConfigurableConversionService cs = this.conversionService;if (cs == null) {synchronized (this) {cs = this.conversionService;if (cs == null) {cs = new DefaultConversionService();this.conversionService = cs;}}}return cs;}... // 省略get/set@Overridepublic void setRequiredProperties(String... requiredProperties) {Collections.addAll(this.requiredProperties, requiredProperties);}@Overridepublic void validateRequiredProperties() {MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException();for (String key : this.requiredProperties) {if (this.getProperty(key) == null) {ex.addMissingRequiredProperty(key);}}if (!ex.getMissingRequiredProperties().isEmpty()) {throw ex;}}// 所有getProperty()重载方法, getRequiredProperty()重载方法及containsProperty()方法// 最终都会调用getProperty(String key, Class<T> targetType)方法, 这里省略@Overridepublic String resolvePlaceholders(String text) {if (this.nonStrictHelper == null) {this.nonStrictHelper = createPlaceholderHelper(true);}return doResolvePlaceholders(text, this.nonStrictHelper);}@Overridepublic String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {if (this.strictHelper == null) {this.strictHelper = createPlaceholderHelper(false);}return doResolvePlaceholders(text, this.strictHelper);}// 解析给定字符串中的占位符// ignoreUnresolvableNestedPlaceholders确定无法解析的占位符是否应该引发异常或被忽略// 调用getProperty及其重载方法会隐式解析属性值中嵌套的占位符// 相比之下, resolvePlaceholders resolveRequiredPlaceholders不会委托给这个方法// 而是执行它们自己指定的对无法解析的占位符的处理protected String resolveNestedPlaceholders(String value) {if (value.isEmpty()) {return value;}return (this.ignoreUnresolvableNestedPlaceholders ?resolvePlaceholders(value) : resolveRequiredPlaceholders(value));}private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,this.valueSeparator, ignoreUnresolvablePlaceholders);}private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {return helper.replacePlaceholders(text, this::getPropertyAsRawString);}// 类型转换@SuppressWarnings("unchecked")@Nullableprotected <T> T convertValueIfNecessary(Object value, @Nullable Class<T> targetType) {if (targetType == null) {return (T) value;}ConversionService conversionServiceToUse = this.conversionService;if (conversionServiceToUse == null) {if (ClassUtils.isAssignableValue(targetType, value)) {return (T) value;}conversionServiceToUse = DefaultConversionService.getSharedInstance();}return conversionServiceToUse.convert(value, targetType);}// 以原始字符串的形式检索指定的属性, 不会解析嵌套占位符@Nullableprotected abstract String getPropertyAsRawString(String key);

从上面源码可以看出,AbstractPropertyResolver 内部定义了两个PropertyPlaceholderHelper,占位符

  • strictHelper严格模式下占位符解析器;
  • nonStrictHelper非严格模式解析器;

处理占位的核心逻辑在PropertyPlaceholderHelper身上,placeholderResolver lambda表达式即为方法getPropertyAsRawString(String key)。该类不熟悉的可以参考Spring Boot v2.4.4源码分析(一)字符串占位符解析器 PropertyPlaceholderHelper 这篇博文。

另外该类还提供了方法 convertValueIfNecessary(Object value, @Nullable Class<T> targetType) 实现对象类型转换。

4. PropertySourcesPropertyResolver

AbstractPropertyResolver只有一个实现类PropertySourcesPropertyResolver,主要是负责提供数据源。

public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {// 包含一个或多个PropertySource对象的持有者@Nullableprivate final PropertySources propertySources;// 唯一构造函数, 必须制定数据源public PropertySourcesPropertyResolver(@Nullable PropertySources propertySources) {this.propertySources = propertySources;}@Overridepublic boolean containsProperty(String key) {if (this.propertySources != null) {// 挨个判断每个数据源是否包含key属性for (PropertySource<?> propertySource : this.propertySources) {if (propertySource.containsProperty(key)) {return true;}}}return false;}// getProperty(String key), getProperty(String key, Class<T> targetValueType)// 以及getPropertyAsRawString(String key) 都会调用// getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders)方法// 这里省略这几个方法@Nullableprotected <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;}protected void logKeyFound(String key, PropertySource<?> propertySource, Object value) {if (logger.isDebugEnabled()) {logger.debug("Found key '" + key + "' in PropertySource '" + propertySource.getName() +"' with value of type " + value.getClass().getSimpleName());}}

四、环境

接下来看PropertyResolver另外一条分支,也是比较重要分支;

1. Environment

首先,Environment接口直接继承PropertyResolver,该接口代表了当前应用运行的环境。Environment抽象出应用环境两个关键概念:

  • profiles, 一个profile是对bean的一个逻辑命名分组,只有该分组在当前环境下处于激活状态,该分组中的bean才会被注册到IOC容器中。常用的profiledevtestprod等。可以使用XML定义bean时或者通过@Profile注解方式为bean指定profileEnvironment通过getActiveProfiles()方法决定哪些profile处于激活状态,通过getDefaultProfiles()方法指定哪些profile默认激活。
  • properties,与属性相关的方法通过父接口PropertyResolver向外暴露。属性在几乎所有应用中扮演着重要角色,环境中的属性可能源自配置文件、JVM系统属性、系统环境变量、JNDI等等。Environment为用户提供了更便捷的方式去配置属性源和解析属性值。

ApplicationContext管理的bean可以通过实现接口EnvironmentAware 获取应用Environment,如

@Component
public class FooBar implements EnvironmentAware {private Environment environment;@Overridepublic void setEnvironment(Environment environment) {this.environment = environment;}}

或者直接将Environment直接注入到该bean中。
看下Environment源码:

public interface Environment extends PropertyResolver {// 返回当前环境激活的profile// 可以通过设置spring.profiles.active系统属性或者调用ConfigurableEnvironment#setActiveProfiles(String...)激活profile// 如果没有明确指定激活的profile, 将会自动激活getDefaultProfiles()方法返回的默认profileString[] getActiveProfiles();// 默认profileString[] getDefaultProfiles();// 判断给定的profiles是否有一个或者多个处于激活状态// 如果没有明确指定激活的profile, 则此方法相当于判断默profile是否包含一个或者多个给定的profiles// 如果参数可以以'!'开头, 表示逻辑取反// 例如, 如果profile 'p1'处于激活状态或者'p2'处于非激活状态, acceptsProfiles("p1", "!p2")将返回true@Deprecatedboolean acceptsProfiles(String... profiles);// 判断getActiveProfiles()返回的profile是否与给定的Profiles匹配。boolean acceptsProfiles(Profiles profiles);
}

2. ConfigurableEnvironment

Environment也提供可配置接口ConfigurableEnvironment,该接口需要完成两件事:定义配置profile方法和配置property方法,而配置property方法已经在ConfigurablePropertyResolver中定义,直接继承过来即可,所以只需定义配置property方法。

public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {// 指定当前环境激活profile集;// 该方法会使用给定的参数将原有已经存在激活profile完全替换;// 如果传空参数, 相当于清空已经激活的profile集;// 可以使用addActiveProfile()方法保留原有激活profile, 并添加激活profile;void setActiveProfiles(String... profiles);void addActiveProfile(String profile);// 指定默认profile集, 如果没有通过显式指定激活的profile,那么改方法指定的profile集将会被激活void setDefaultProfiles(String... profiles);// 返回当前环境属性源集合PropertySources, 属性解析值时用遍历该PropertySources获取属性值;// 得到PropertySources后, 就可以操作属性源;// 例如, MutablePropertySources提供的addFirst(), addFirst(), addBefore()// addAfter()可以细粒度地控制各属性源之间的顺序;// 这有助于确保某些用户自定义的属性源的搜索优先于默认属性源;MutablePropertySources getPropertySources();Map<String, Object> getSystemProperties();Map<String, Object> getSystemEnvironment();// 将给定的父Environment的活动激活profile, 默认profile和属性源添加到当前Environment的各自集合中;// 对于同时存在于父子Environment中的同名属性源, 子Environment的将被保留,父实例Environment的将会被丢弃;// 激活profile, 默认profile亦是如此;// 在任何情况下, 父Environment都保持不变, // 注意, 在调用merge()之后发生的对父Environment的任何更改都不会反映在子Environment中void merge(ConfigurableEnvironment parent);

3. AbstractEnvironment

AbstractEnvironment对接口ConfigurableEnvironment所有方法提供抽象实现。

public abstract class AbstractEnvironment implements ConfigurableEnvironment {// 系统属性名, 确定Spring是否忽略系统环境变量public static final String IGNORE_GETENV_PROPERTY_NAME = "spring.getenv.ignore";// 系统属性名, 指定当前环境激活profilepublic static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active";// 系统属性名, 指定当前环境默认profilepublic static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";// 默认profile默认值 protected static final String RESERVED_DEFAULT_PROFILE_NAME = "default";// 激活profileprivate final Set<String> activeProfiles = new LinkedHashSet<>();// 默认profileprivate final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());// 属性源集合private final MutablePropertySources propertySources;// 属性解析静态代理类, AbstractEnvironment 属性解析相关功能由propertyResolver实现private final ConfigurablePropertyResolver propertyResolver;public AbstractEnvironment() {this(new MutablePropertySources());}// 实际的构造函数, 需要传数据源集合protected AbstractEnvironment(MutablePropertySources propertySources) {this.propertySources = propertySources;this.propertyResolver = createPropertyResolver(propertySources);// 定制化propertySourcescustomizePropertySources(propertySources);}// ConfigurablePropertyResolver 工厂方法protected ConfigurablePropertyResolver createPropertyResolver(MutablePropertySources propertySources) {return new PropertySourcesPropertyResolver(propertySources);}//  此处省略ConfigurablePropertyResolver get方法// 重要, 重要, 重要, 定制propertySources, 子类主要重写该方法protected void customizePropertySources(MutablePropertySources propertySources) {}//  默认profile默认值protected Set<String> getReservedDefaultProfiles() {return Collections.singleton(RESERVED_DEFAULT_PROFILE_NAME);}@Overridepublic String[] getActiveProfiles() {return StringUtils.toStringArray(doGetActiveProfiles());}// 返回通过setActiveProfiles()显示设置activeProfiles的值// 如果该值为空, 则返回属性spring.profiles.active的解析值protected Set<String> doGetActiveProfiles() {synchronized (this.activeProfiles) {if (this.activeProfiles.isEmpty()) {// 解析属性spring.profiles.active, 看用户是否通过系统属性或者配置文件等方式设置该值String profiles = doGetActiveProfilesProperty();if (StringUtils.hasText(profiles)) {// 解析完成后设置值setActiveProfiles(StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(profiles)));}}return this.activeProfiles;}}@Nullableprotected String doGetActiveProfilesProperty() {return getProperty(ACTIVE_PROFILES_PROPERTY_NAME);}@Overridepublic void setActiveProfiles(String... profiles) {Assert.notNull(profiles, "Profile array must not be null");if (logger.isDebugEnabled()) {logger.debug("Activating profiles " + Arrays.asList(profiles));}synchronized (this.activeProfiles) {// 如ConfigurableEnvironment定义, 先清空原有值, 再将给该参数添加到activeProfilesthis.activeProfiles.clear();for (String profile : profiles) {validateProfile(profile);this.activeProfiles.add(profile);}}}@Overridepublic void addActiveProfile(String profile) {if (logger.isDebugEnabled()) {logger.debug("Activating profile '" + profile + "'");}validateProfile(profile);doGetActiveProfiles();synchronized (this.activeProfiles) {this.activeProfiles.add(profile);}}@Overridepublic String[] getDefaultProfiles() {return StringUtils.toStringArray(doGetDefaultProfiles());}protected Set<String> doGetDefaultProfiles() {synchronized (this.defaultProfiles) {// 如果是默认值, 解析spring.profiles.default, 看用户是否通过系统属性或者配置文件等方式设置该值if (this.defaultProfiles.equals(getReservedDefaultProfiles())) {String profiles = doGetDefaultProfilesProperty();if (StringUtils.hasText(profiles)) {setDefaultProfiles(StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(profiles)));}}return this.defaultProfiles;}}@Overridepublic void setDefaultProfiles(String... profiles) {Assert.notNull(profiles, "Profile array must not be null");synchronized (this.defaultProfiles) {this.defaultProfiles.clear();for (String profile : profiles) {validateProfile(profile);this.defaultProfiles.add(profile);}}}@Override@Deprecatedpublic boolean acceptsProfiles(String... profiles) {Assert.notEmpty(profiles, "Must specify at least one profile");for (String profile : profiles) {if (StringUtils.hasLength(profile) && profile.charAt(0) == '!') {// 取反时, 判断该profile是否非激活if (!isProfileActive(profile.substring(1))) {return true;}}// 判断该profile是否激活else if (isProfileActive(profile)) {return true;}}return false;}@Overridepublic boolean acceptsProfiles(Profiles profiles) {Assert.notNull(profiles, "Profiles must not be null");return profiles.matches(this::isProfileActive);}protected boolean isProfileActive(String profile) {validateProfile(profile);Set<String> currentActiveProfiles = doGetActiveProfiles();// activeProfiles不空时, 判断activeProfiles是否包含// activeProfiles为空时, 判断defaultProfiles是否包含return (currentActiveProfiles.contains(profile) ||(currentActiveProfiles.isEmpty() && doGetDefaultProfiles().contains(profile)));}protected void validateProfile(String profile) {if (!StringUtils.hasText(profile)) {throw new IllegalArgumentException("Invalid profile [" + profile + "]: must contain text");}if (profile.charAt(0) == '!') {throw new IllegalArgumentException("Invalid profile [" + profile + "]: must not begin with ! operator");}}@Overridepublic MutablePropertySources getPropertySources() {return this.propertySources;}// 如果当前SecurityManager允许, 则返回System#getProperties()的值,// 否则返回一个map实现类, 对该map所有key的访问都将调用System#getProperty(String),// 参考AbstractEnvironment#getSystemProperties()方法;// 注意, 大部分Environment实现类, 会默认将系统属性map作为属性源添加到PropertySources中;// 因此, 建议不要直接使用此方法, 除非明确打算绕过其他属性源;@Override@SuppressWarnings({"rawtypes", "unchecked"})public Map<String, Object> getSystemProperties() {try {return (Map) System.getProperties();}catch (AccessControlException ex) {return (Map) new ReadOnlySystemAttributesMap() {@Override@Nullableprotected String getSystemAttribute(String attributeName) {try {return System.getProperty(attributeName);}catch (AccessControlException ex) {if (logger.isInfoEnabled()) {logger.info("Caught AccessControlException when accessing system property '" +attributeName + "'; its value will be returned [null]. Reason: " + ex.getMessage());}return null;}}};}}// 同getSystemProperties(), 只不过调用的是System的getenv()和getenv(String)方法@Override@SuppressWarnings({"rawtypes", "unchecked"})public Map<String, Object> getSystemEnvironment() {if (suppressGetenvAccess()) {return Collections.emptyMap();}try {return (Map) System.getenv();}catch (AccessControlException ex) {return (Map) new ReadOnlySystemAttributesMap() {@Override@Nullableprotected String getSystemAttribute(String attributeName) {try {return System.getenv(attributeName);}catch (AccessControlException ex) {if (logger.isInfoEnabled()) {logger.info("Caught AccessControlException when accessing system environment variable '" +attributeName + "'; its value will be returned [null]. Reason: " + ex.getMessage());}return null;}}};}}// 判断是否能访问 System#getenv()/System#getenv(String)方法protected boolean suppressGetenvAccess() {return SpringProperties.getFlag(IGNORE_GETENV_PROPERTY_NAME);}@Overridepublic void merge(ConfigurableEnvironment parent) {for (PropertySource<?> ps : parent.getPropertySources()) {if (!this.propertySources.contains(ps.getName())) {this.propertySources.addLast(ps);}}String[] parentActiveProfiles = parent.getActiveProfiles();if (!ObjectUtils.isEmpty(parentActiveProfiles)) {synchronized (this.activeProfiles) {Collections.addAll(this.activeProfiles, parentActiveProfiles);}}String[] parentDefaultProfiles = parent.getDefaultProfiles();if (!ObjectUtils.isEmpty(parentDefaultProfiles)) {synchronized (this.defaultProfiles) {this.defaultProfiles.remove(RESERVED_DEFAULT_PROFILE_NAME);Collections.addAll(this.defaultProfiles, parentDefaultProfiles);}}}// ConfigurablePropertyResolver接口实现方法由propertyResolver代理, 这里省略}

AbstractEnvironment留给子类的活就不多了, 只需要定制化属性源就可以了。
PropertySourcesPropertyResolver在构造时会以propertySources作为属性源, 而通过上面的源码可以知道,PropertySourcesPropertyResolver在解析属性值时,会按照属性源顺序依次解析,前面属性源能解析成功就直接返回,即前面属性源具有较高优先级。所以在添加或者删除属性源时顺序就显得格外重要了。

通常会使用两种操作影响数据源顺序:

  • 添加数据源时调用的是addFirst(), addFirst(), addBefore(), addAfter()哪个方法;
public class FooEnvironment extends AbstractEnvironment{protected FooEnvironment(MutablePropertySources propertySources) {super(propertySources);}public static void main(String[] args) {MutablePropertySources mps = new MutablePropertySources();Map<String, Object> map1 = Maps.newHashMap();map1.put("f", "foo");MapPropertySource mapPs1 = new  MapPropertySource("mapPs1", map1);mps.addLast(mapPs1);Map<String, Object> map2 = Maps.newHashMap();map2.put("f", "bar");MapPropertySource mapPs2 = new  MapPropertySource("mapPs2", map1);mps.addLast(mapPs2);Environment env = new FooEnvironment(mps);//  输出fooSystem.err.println(env.resolvePlaceholders("${f}"));}
}

由于map1map2先添加到MutablePropertySources,所以解析占位符时优先使用map1中的属性。

  • 继承AbstractEnvironment构造函数肯定会调用到AbstractEnvironment(MutablePropertySources),该函数内部会调用customizePropertySources(MutablePropertySources),所以在重写该方法时,是先调用父类方法,还是先实现自己定制化逻辑也会影响数据源顺序;
    public class Level1Environment extends AbstractEnvironment {@Overrideprotected void customizePropertySources(MutablePropertySources propertySources) {super.customizePropertySources(propertySources); // no-op from base classpropertySources.addLast(new PropertySourceA(...));propertySources.addLast(new PropertySourceB(...));}}public class Level2Environment extends Level1Environment {@Overrideprotected void customizePropertySources(MutablePropertySources propertySources) {super.customizePropertySources(propertySources); // add all from superclasspropertySources.addLast(new PropertySourceC(...));propertySources.addLast(new PropertySourceD(...));}}

如上,Level2EnvironmentLevel1Environment 的子类,Level2Environment先调用父类的属性源定制化函数,再实现自己的定制化操作,那么最终属性源顺序将是PropertySourceAPropertySourceBPropertySourceCPropertySourceD

4. StandardEnvironment

StandardEnvironment类只重写了customizePropertySources(MutablePropertySources) 方法,将系统变量和系统环境变量加到属性源中。

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";public StandardEnvironment() {}protected StandardEnvironment(MutablePropertySources propertySources) {super(propertySources);}@Overrideprotected void customizePropertySources(MutablePropertySources propertySources) {propertySources.addLast(new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));}

系统变量中主要包含

  • JRE相关的系统属性:java.homejava.library.pathjava.class.pathjava.versionjava.runtime.version等;
  • 文件相关的系统属性:file.separatorpath.separatorline.separator等;
  • 用户相关的系统属性:user.nameuser.homeuser.dir等;
  • OS相关的系统属性:os.nameos.versionos.arch等;

通过-D设置的VM参数也会加入到系统属性中。

@Component
public class FooBar implements EnvironmentAware, CommandLineRunner{private Environment env;@Overridepublic void setEnvironment(Environment environment) {this.env = environment;}@Overridepublic void run(String... args) throws Exception {// 1.8.0_131System.err.println(env.resolvePlaceholders("${java.version}"));// 6.1System.err.println(env.resolvePlaceholders("${os.version}"));}
}

environment property 是系统级的环境变量,系统当中所有的进程都可以访问到。
一般StandardEnvironment 的继承类重写 customizePropertySources(MutablePropertySources)时,会先调用StandardEnvironmentcustomizePropertySources(MutablePropertySources),然后再实现自己定制化逻辑,所以通常系统变量和系统环境变量具有比较高优先级,-D VM参数优先级比配置文件优先级高。

5. StandardServletEnvironment

最后看下StandardServletEnvironment

public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {/** Servlet context init parameters property source name: {@value}. */public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";/** Servlet config init parameters property source name: {@value}. */public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";/** JNDI property source name: {@value}. */public static final String JNDI_PROPERTY_SOURCE_NAME = "jndiProperties";public StandardServletEnvironment() {}protected StandardServletEnvironment(MutablePropertySources propertySources) {super(propertySources);}@Overrideprotected void customizePropertySources(MutablePropertySources propertySources) {propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));}super.customizePropertySources(propertySources);}@Overridepublic void initPropertySources(@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);}

StandardServletEnvironment定制化属性源时添加了Servlet上下文初始化参数和Servlet配置初始化参数,且优先级比父类StandardEnvironment 系统变量和系统环境变量优先级高。

这篇关于Spring Boot v2.4.4源码解析(二)运行时环境Environment的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/965858

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

如何用Docker运行Django项目

本章教程,介绍如何用Docker创建一个Django,并运行能够访问。 一、拉取镜像 这里我们使用python3.11版本的docker镜像 docker pull python:3.11 二、运行容器 这里我们将容器内部的8080端口,映射到宿主机的80端口上。 docker run -itd --name python311 -p