Spring源碼系列 — Envoriment組件


何為Envoriment

Envoriment是集成在Spring上下文容器中的核心組件,在Spring源碼中由Envoriment接口抽象。
在Environment中,有兩大主要概念:

  • Profile:在Spring中profile是針對Bean定義而言,是Bean定義的邏輯分組。通常表現為:dev/test/production等等,對於Bean定義屬於哪個profile是由XML或者Annotation配置決定;
  • Properties:即鍵值對(Keys-Pairs),在Spring中將*.properties文件/JVM系統屬性(JVM System Property)/系統環境變量(JVM Environment Variables)/Properties對象/Map對象抽象為Properties;

Envoriment的能力

Envoriment提供了獲取和設置Profile的能力,可以決定生效哪些Profiles。
同時提供了操作Properties的能力,可以增加/移除整個Properties,能獲取某個Key-Pair。

  • Environment可以獲取系統JVM屬性:-Dspring.profiles.active="..."或者顯示編程式setActiveProfiles("...")生效相應的profile;
  • 可以通過Environment提供的getActiveProfiles和getDefaultProfiles獲取profile;
  • Environment可以決定Bean定義屬於哪個Profile;
  • Environment可以操作上下文中的Properties,提供了add/remove接口;
  • Environment集成PropertyResolver,利用Spring中的Conversion服務多Properties中的屬性進行類型轉化;
  • Environment提供了解析任意Resource資源路徑中的${...}占位,支持嵌套占位解析;

上圖中體現Environment是上下文ApplicationContext中的核心之一,在實現上每個ApplicationContext中都持有Environment實例:

public abstract class AbstractApplicationContext extends DefaultResourceLoader
		implements ConfigurableApplicationContext, DisposableBean {

	......此處省略

	/** Environment used by this context */
	private ConfigurableEnvironment environment;

	......此處省略

}

從源碼分析看Envoriment

Enviroment源碼部分主要由三部分組成:

  • Enviroment接口實現
  • PropertyResolver屬性解析器接口實現
  • PropertySource和PropertySources屬性源

下續UML類圖中描述出Environment通過持有PropertyResolver和PropertySources實現對Properties的操作,Environment自身提供profile的存儲和接口操作。

Tips:
Spring中大量使用接口繼承-實現組合的模式。接口之間繼承,具體實現中通過組合持有的方式達到接口隔離,實現強擴展能力。

Environment接口定義:

public interface Environment extends PropertyResolver {

	// 獲取當前上下文生效應用的profile
	String[] getActiveProfiles();

	// 獲取當前上下文默認的profile
	String[] getDefaultProfiles();

	// 判斷是否接受某個profile,用於決定bean定義屬於哪個profile
	boolean acceptsProfiles(String... profiles);

}

獲取activeProfile和defaultProfile,同時能決定是否接受某個profile。
Environment中繼承PropertyResolver接口:

/**
 * Interface for resolving properties against any underlying source.
 */
public interface PropertyResolver {
	// 判斷源中是否包含該key的屬性
	boolean containsProperty(String key);
	// 獲取源中的key屬性的值
	String getProperty(String key);
	// 獲取並轉換成目標類型
	<T> T getProperty(String key, Class<T> targetType);
	// 解析占位,主要被應用在解析resource路徑中的占位、@Value注解中的占位和
	Bean定義中的占位
	String resolvePlaceholders(String text);
}

PropertyResolver用於解析潛在的源的屬性。潛在源可以是多種形式:properties文件、jvm屬性、jvm環境變量、map等等。

從以上可以推導出Environment具有從源中解析獲取Properties的能力,或者說Environment本身就是屬性解析器。

Environment接口體系中非常重要的成員是ConfigurableEnvironment,它提供了更強的處理能力:

  • 設置上下文容器的profile;
  • 操作Environment中properties;
/**
 * Configuration interface to be implemented by most if not all {@link Environment} types.
 * Provides facilities for setting active and default profiles and manipulating underlying
 * property sources. Allows clients to set and validate required properties, customize the
 * conversion service and more through the {@link ConfigurablePropertyResolver}
 * superinterface.
 */
public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
	// 設置有效的profile
	void setActiveProfiles(String... profiles);
	// 設置默認的profile
	void setDefaultProfiles(String... profiles);
	// 獲取屬性源集合,利用屬性源集合可以操作Environment的properties
	MutablePropertySources getPropertySources();
	// 獲取jvm系統屬性
	Map<String, Object> getSystemProperties();
	// 獲取環境變量
	Map<String, Object> getSystemEnvironment();
	// 合並另一個環境,主要用在父子容器中,子容器繼承合並父容器的Environment
	void merge(ConfigurableEnvironment parent);
}

從javadocs和接口定義上可以看出,該Environment接口主要主要提供配置profile和properties的能力。

ConfigurableEnvironment繼承了ConfigurablePropertyResolver接口,該接口是前文中的PropertyResolver的配置實現:

/**
 * Configuration interface to be implemented by most if not all {@link PropertyResolver}
 * types. Provides facilities for accessing and customizing the
 * {@link org.springframework.core.convert.ConversionService ConversionService}
 * used when converting property values from one type to another.
 */
public interface ConfigurablePropertyResolver extends PropertyResolver {
	// 設置轉換服務,轉換服務是Spring體系中非常重要的基礎組件。用於轉換解析出的property類型
	void setConversionService(ConfigurableConversionService conversionService);
	// 設置占位前綴
	void setPlaceholderPrefix(String placeholderPrefix);
	// 設置占位后綴
	void setPlaceholderSuffix(String placeholderSuffix);
	// 設置property中中key-pair分割符
	void setValueSeparator(String valueSeparator);
	// 設置是否忽略嵌套占位的解析
	void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders);
}

前文提及Environment用於解析Resource路徑中的占位,如:

// 資源路徑中有占位
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
// 資源路徑中有占位
<import resource="classpath:${custome.path}/beans2.xml"></import>

Environment可以利用properties屬性解析這些占位,但是Environment實際委托ConfigurablePropertyResolver解析占位。ConfigurablePropertyResolver可以配置這些占位的前綴和后綴。

Environment可以獲取properties,也是委托ConfigurablePropertyResolver解析獲取,並同時提供轉換服務,可以將property的值轉為所需的目標類型,ConfigurablePropertyResolver提供了設置轉換服務的接口。

上述中主要介紹Environment體系中定義的接口,接下來分析Environment的抽象實現AbstractEnvironment和標准實現StandardEnvironment。考慮到篇幅原因,本文只介紹Environment的核心功能:

  1. Environment如何獲取jvm系統屬性和系統環境變量;
  2. 如何實現profile決定bean屬於哪個profile;
  3. 設置屬性properties原理;
  4. 如何實現屬性獲取和資源路徑占位解析;

1.Environment如何獲取jvm系統屬性和系統環境變量

StandardEnvironment是Spring上下文中標准的Environment,在非web應用中使用該Environment作為ApplicationContext的環境組件:

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 MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
		propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
	}

}

StandardEnvironment實現解析systemEnvironment系統環境變量和systemProperties系統屬性作為Properties。在AbstractApplicationContext中實現:

// 獲取環境Environment
@Override
public ConfigurableEnvironment getEnvironment() {
	if (this.environment == null) {
		this.environment = createEnvironment();
	}
	return this.environment;
}

// 創建StandardEnvironment實例,加載jvm系統屬性和環境變量
protected ConfigurableEnvironment createEnvironment() {
	return new StandardEnvironment();
}

在非web環境中使用StandardEnvironment作為標准實現。在web環境中, 有web上下文容器覆蓋createEnvironment實現,創建StandardServletEnvironment。

當調用StandardEnvironment構造器時,將調用父類AbstractEnvironment的無參構造器:

public AbstractEnvironment() {
	// 自定義屬性源,customizePropertySources為抽象方法
	// 由子類實現
	customizePropertySources(this.propertySources);
	if (logger.isDebugEnabled()) {
		logger.debug("Initialized " + getClass().getSimpleName() + " with PropertySources " + this.propertySources);
	}
}

StandardEnvironment中關於customizePropertySources實現是為了加載jvm系統屬性和環境變量:

@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public Map<String, Object> getSystemProperties() {
	try {
		// 調用System api獲取所有的jvm系統屬性
		return (Map) System.getProperties();
	}
	catch (AccessControlException ex) {
		// 如果設置了訪問權限控制不能訪問所有屬性,則將返回惰性只讀的屬性map
		return (Map) new ReadOnlySystemAttributesMap() {
			@Override
			protected String getSystemAttribute(String attributeName) {
				try {
					// 只獲取指定的屬性
					return System.getProperty(attributeName);
				}
				catch (AccessControlException ex) {
					// 如果仍然無法訪問,則返回null,表示沒有該屬性
					if (logger.isInfoEnabled()) {
						logger.info("Caught AccessControlException when accessing system property '" +
								attributeName + "'; its value will be returned [null]. Reason: " + ex.getMessage());
					}
					return null;
				}
			}
		};
	}
}
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public Map<String, Object> getSystemEnvironment() {
	// 如果設置禁止訪問環境變量,將返回空。抑制訪問環境變量可以通過
	系統屬性spring.getenv.ignore設置:true/false,默認是非抑制
	if (suppressGetenvAccess()) {
		return Collections.emptyMap();
	}
	try {
		// 調用System api獲取返回所有的環境變量
		return (Map) System.getenv();
	}
	catch (AccessControlException ex) {
		// 同jvm系統屬性一樣,惰性只讀特定的環境變量
		return (Map) new ReadOnlySystemAttributesMap() {
			@Override
			protected 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;
				}
			}
		};
	}
}

2.如何實現profile並決定bean屬於哪個profile

在Spring中設置profile可以使Spring應用契合多環境:dev/test/pro等等。其中關鍵點在於:

  • 讓Spring應用知道當前處於什么環境,這可以有系統啟動時攜帶參數:系統屬性等設置決定,則該環境為Spring應用有效的profile;
  • 當前配置的Bean是屬於哪個環境(profile)

Spring容器在解析Bean時會將Bean定義中的profile作為參數傳遞給Environment,由Environment決定該profile是否可以被當前環境接受。這系列的邏輯由Environment的acceptsProfiles接口承擔實現,因為Environment持有當前上下文容器的所有profile(active和default),AbstractEnvironment中實現:

@Override
public boolean acceptsProfiles(String... profiles) {
	// 斷言輸入profile是否為空
	Assert.notEmpty(profiles, "Must specify at least one profile");
	// 循環遍歷每個profile
	for (String profile : profiles) {
		// bean中profile可以以"!"形式表示非
		if (StringUtils.hasLength(profile) && profile.charAt(0) == '!') {
			// 該profile不是有效的profile,返回true
			if (!isProfileActive(profile.substring(1))) {
				return true;
			}
		}
		// 不是以"!"開頭,且是當前有效profile,則返回true
		else if (isProfileActive(profile)) {
			return true;
		}
	}
	return false;
}

再繼續閱讀isProfileActive實現,該方法重要完成參數的profile是否在當前上下文容器有效的profile中:

protected boolean isProfileActive(String profile) {
	// 校驗profile合法性
	validateProfile(profile);
	// 惰性加載當前容器的有效的profile,可能存在多個
	Set<String> currentActiveProfiles = doGetActiveProfiles();
	// 判斷參數profile是否在有效profile中,返回true/false
	return (currentActiveProfiles.contains(profile) ||
			(currentActiveProfiles.isEmpty() && doGetDefaultProfiles().contains(profile)));
}

doGetActiveProfiles主要是惰性Properties中獲取有效的profile:

protected Set<String> doGetActiveProfiles() {
	// 同步修改,防並發
	synchronized (this.activeProfiles) {
		// 如果有效的profiles空,則加載
		if (this.activeProfiles.isEmpty()) {
			// 解析property,獲取有效的profiles
			String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
			if (StringUtils.hasText(profiles)) {
				// 設置有效的profile
				setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
						StringUtils.trimAllWhitespace(profiles)));
			}
		}
		return this.activeProfiles;
	}
}

該方法中核心的調用是getProperty加載當前上下文有效的profile。在Spring中有效的上下文激活方式:

  • 在屬性文件中配置屬性:spring.profiles.active=dev
  • 在啟動時使用jvm參數:-Dspring.profiles.active=dev
  • env.setActiveProfiles("dev")

getProperty方法中獲取屬性spring.profiles.active的值作為有效的profile:

@Override
public String getProperty(String key) {
	// 屬性解析器解析獲取spring.profiles.active屬性
	return this.propertyResolver.getProperty(key);
}
@Override
public String getProperty(String key) {
	// 獲取屬性值並類型轉換為String
	return getProperty(key, String.class);
}

以上的getProperty都是AbstractPropertyResolver實現,getProperty(key, String.class)在PropertySourcesPropertyResolver實現:

@Override
public <T> T getProperty(String key, Class<T> targetValueType) {
	return getProperty(key, targetValueType, true);
}


// resolveNestedPlaceholders是否解析嵌套占位參數
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() + "'");
			}
			// 獲取屬性源中的key的值
			Object value = propertySource.getProperty(key);
			// 如果值不為空
			if (value != null) {
				// 如果支持嵌套解析且值是String類型
				if (resolveNestedPlaceholders && value instanceof String) {
					// 解析占位
					value = resolveNestedPlaceholders((String) value);
				}
				logKeyFound(key, propertySource, value);
				// 使用轉換服務進行轉換value為目標類型
				return convertValueIfNecessary(value, targetValueType);
			}
		}
	}
	if (logger.isDebugEnabled()) {
		logger.debug("Could not find key '" + key + "' in any property source");
	}
	// 無屬性源,返回null
	return null;
}

上述流程中,獲取到spring.profiles.active屬性后,與Bean定義的profile比較,如果在當前有效的profile中,則該bean定義會被注冊為spring bean。

3.設置屬性properties原理

Environment中的properties分為兩種,一種是Spring框架自身加載的:jvm系統屬性和系統環境變量——前文中介紹StandardEnvironment;另一種是用戶應用自定義的屬性加載入Environment。

在Environment中的properties被抽象成:

public abstract class PropertySource<T> {

	...省略

	protected final String name;

	protected final T source;

	...省略

	public abstract Object getProperty(String name);
}

其實現非常多,其中有MapPropertySource以Map作為source,等等。

public class MapPropertySource extends EnumerablePropertySource<Map<String, Object>> {
	public MapPropertySource(String name, Map<String, Object> source) {
		super(name, source);
	}
	@Override
	public Object getProperty(String name) {
		return this.source.get(name);
	}
	@Override
	public boolean containsProperty(String name) {
		return this.source.containsKey(name);
	}
	@Override
	public String[] getPropertyNames() {
		return StringUtils.toStringArray(this.source.keySet());
	}

}

在應用中屬性源PropertySource來源非常多,有可能是Map對象,也有可能是properies屬性文件,所以Spring又在PropertySource上抽象一層多屬性源PropertySources,通過其保持多PropertySource:

public interface PropertySources extends Iterable<PropertySource<?>> {

	// 測試是否包含指定名稱屬性源
	boolean contains(String name);
	// 通過名稱獲取屬源
	PropertySource<?> get(String name);
}

其標准實現是易變的多屬性源:

public class MutablePropertySources implements PropertySources {

	// List持有多個PropertySource
	private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<PropertySource<?>>();

	// 以下都是操作PropertySource的接口,包括增加移除等操作,體現MutablePropertySources的易變性
	public void addAfter(String relativePropertySourceName, PropertySource<?> propertySource) {
		...省略
	}
	public PropertySource<?> remove(String name) {
		...省略
	}
	public void addBefore(String relativePropertySourceName, PropertySource<?> propertySource) {
		...省略
	}
	public void addFirst(PropertySource<?> propertySource) {
		...省略
	}
	public void addLast(PropertySource<?> propertySource) {
		...省略
	}
}

在AbstractEnvironment中持有MutablePropertySources達到Environment中包含properties的目的。

public abstract class AbstractEnvironment implements ConfigurableEnvironment {

	...省略

	// 持有多屬性源
	private final MutablePropertySources propertySources = new MutablePropertySources(this.logger);

	// 持有屬性解析器,使用上述的propertySources構造,保證解析器的屬性源和其實一個
	// 屬性解析器提供了:按層級搜索屬性值、解析獲取屬性值
	private final ConfigurablePropertyResolver propertyResolver =
			new PropertySourcesPropertyResolver(this.propertySources);

	...省略
}

前文中介紹ConfigurableEnvironment時,改配置環境接口提供了獲取多屬性源的接口:
MutablePropertySources getPropertySources(),通過該接口獲取易變的多屬性源可以達到操作Environment中的屬性源的操作:addBefore/addFirst/addAfter/AddLaster/remove等等操作屬性源的接口。

關於設置Environment中的jvm系統屬性和環境變量前文在StandardEnvironment中已經介紹。

4.如何實現屬性獲取

本文的第一張圖中已經畫出通過Environment獲取屬性的流程,Environment通過委托於PropertyResolver完成解析獲取屬性。PropertyResolver充當的角色:

  • 解析獲取屬性,並支持類型轉換;
  • 按層級搜索屬性:即Environment中的PropertySource具有優先級;

關於如何如何解析獲取屬性的原理在前文的profile原理中已經介紹了部分,這里再細化:

// AbstractEnvironment中接口實現
@Override
public String getProperty(String key) {
	// 委托PropertyResolver解析獲取
	return this.propertyResolver.getProperty(key);
}

// AbstractPropertyResolver中實現
@Override
public String getProperty(String key) {
	// 調用泛型接口實現,由PropertySourcesPropertyResolver實現
	// 並指定目標類型
	return getProperty(key, String.class);
}


// PropertySourcesPropertyResolver實現<T> T getProperty(String key, Class<T> targetType)
@Override
public <T> T getProperty(String key, Class<T> targetValueType) {
	// 支持屬性值中的嵌套占位解析
	return getProperty(key, targetValueType, true);
}

// PropertySourcesPropertyResolver實現,支持優先級檢索、嵌套占位解析、值轉換
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
	if (this.propertySources != null) {
		// 這里的for循環遍歷所有的propertySource解析key,只要獲取到value就返回,實現了propertySource的優先級(層級檢索)
		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) {
				// 實現value值中的嵌套占位解析
				if (resolveNestedPlaceholders && value instanceof String) {
					value = resolveNestedPlaceholders((String) value);
				}
				logKeyFound(key, propertySource, value);
				// 轉化value為目標類型,主要使用Spring中的轉換服務
				return convertValueIfNecessary(value, targetValueType);
			}
		}
	}
	if (logger.isDebugEnabled()) {
		logger.debug("Could not find key '" + key + "' in any property source");
	}
	return null;
}

Envoriment中易忽略點

前文中一直介紹Environment能夠解析獲取Spring上下文中的屬性源,且支持解析占位${...},但是:

  • Environment是否支持解析Spring中所有的屬性源
  • Environment是否支持解析Spring應用出現的任意${...}

1.支持環境相關的屬性解析

Environment英文是環境的意思,所以Spring中的Environment組件的屬性解析和${...}也只是和環境相關,與環境以外的屬性源和${...},Environment是不支持的。

Environment中支持的屬性元PropertySource只有以下三種情況:

  • Environment組件自身加載的屬性源,如StandardEnvironment中加載的系統屬性系統環境變量;如StandardServletEnvironment中加載的ServletContext參數和Servlet參數;
  • 通過ConfigralbeEnvironment獲取MutablePropertySources對象,編程式加入的屬性源;
  • 通過@PropertySource注解加載的.properties文件中的屬性;

javadocs中描述@PropertySource:

Annotation providing a convenient and declarative mechanism for adding a {@link org.springframework.core.env.PropertySource PropertySource} to Spring's {@link org.springframework.core.env.Environment Environment}. To be used in conjunction with @{@link Configuration} classes.

該注解提供便捷聲明式的將PropertySource增加的Environment中,該注解需要結合@ Configuration共同使用。如:

@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        // 通過Environment獲取屬性
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}

以上的java config可以將類路徑上的com/myco/app.properties文件中的屬性加載到Environment中,並可以通過Environment獲取。

在Spring中還有另外一種加載屬性的方式,但是該方式主要是為了處理${...}占位問題,並非Environment中的屬性,所以無法通過Environment獲取其中的屬性:

<context:property-placeholder location="classpath:com/foo/jdbc.properties"/>

以上配置Spring上下文會加載com/foo/jdbc.properties並且實例化PropertySourcesPlaceholderConfigurer用於解析@Value注解中的占位和Xml中Bean定義的占位。在該PropertySourcesPlaceholderConfigurer中也持有MutablePropertySources成員用於存儲Properties。所以Spring中的properties不是都能成Environment中獲取的。有些與環境無關的properties屬於PropertySourcesPlaceholderConfigurer。PropertySourcesPlaceholderConfigurer也實現EnvironmentAware接口,持有Environment組件,所以Spring中的properties在PropertySourcesPlaceholderConfigurer都被包含。

2.支持環境相關的占位解析

Environment中支持的占位${...}解析只與環境相關,前文中介紹只有Resource路徑中的占位Environment才負責解析。

Tips:
關於@Value注解和Bean定義的占位,由PropertySourcesPlaceholderConfigurer負責解析,后續文章會詳解。

填坑resolvePath

上一篇文章中留下坑點,ClassPathXmlApplicationContext在設置配置文件路徑時涉及到配置文件路徑的解析問題,暫時擱置到本篇文章中詳解。因為資源路徑的解析由Environment組件負責路徑中的占位解析替換,故需要深入Environment組件后才能更好的理解配置文件路徑的解析原理。

回顧上章中的解析點:

public void setConfigLocations(String... locations) {
	if (locations != null) {
		Assert.noNullElements(locations, "Config locations must not be null");
		this.configLocations = new String[locations.length];
		for (int i = 0; i < locations.length; i++) {
			// 解析配置文件路徑
			this.configLocations[i] = resolvePath(locations[i]).trim();
		}
	}
	else {
		this.configLocations = null;
	}
}

解析配置文件路徑的邏輯由resolvePath完成,主要是解析資源配置文件中的默認占位${...}或者自定義占位:

protected String resolvePath(String path) {
	// 獲取該ApplicationContext上下文對應的Environment
	// 委托Environment解析配置文件路徑
	return getEnvironment().resolveRequiredPlaceholders(path);
}

這里重點看Environment解析的細節:

// 由AbstractEnvironment中該方法實現
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
	// 委托PropertyResolver解析
	return this.propertyResolver.resolveRequiredPlaceholders(text);
}

// 由AbstractPropertyResolver實現
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
	// 解析需要工具類PropertyPlaceholderHelper,如果無,則創建
	if (this.strictHelper == null) {
		this.strictHelper = createPlaceholderHelper(false);
	}
	// 解析處理
	return doResolvePlaceholders(text, this.strictHelper);
}

再進一不深入解析處理doResolvePlaceholders邏輯前,先看下PropertyPlaceholderHelper工具類細節:

public class PropertyPlaceholderHelper {
	private static final Map<String, String> wellKnownSimplePrefixes = new HashMap<String, String>(4);
	static {
		wellKnownSimplePrefixes.put("}", "{");
		wellKnownSimplePrefixes.put("]", "[");
		wellKnownSimplePrefixes.put(")", "(");
	}
	// 占位符前綴
	private final String placeholderPrefix;
	// 占位符后綴
	private final String placeholderSuffix;
	private final String simplePrefix;
	// key-pair分隔符
	private final String valueSeparator;
	// 忽略解析占位的flag
	private final boolean ignoreUnresolvablePlaceholders;
}

工具類主要是幫助解析String中的占位符,需要匹配String中占位符前綴位置、后綴位置,然后截取占位符中的內容,再將內容作為PropertyResolver參數,從PropertySoures中按優先級檢索屬性值,再將屬性值替換占位,即完成配置文件完整路徑的解析。

createPlaceholderHelper方法中主要是創建工具類,指定工具類的占位前后綴:

public static final String PLACEHOLDER_PREFIX = "${";
public static final String PLACEHOLDER_SUFFIX = "}";

private String placeholderPrefix = SystemPropertyUtils.PLACEHOLDER_PREFIX;
private String placeholderSuffix = SystemPropertyUtils.PLACEHOLDER_SUFFIX;


private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
	return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
			this.valueSeparator, ignoreUnresolvablePlaceholders);
}

從以上可以看出Spring默認的占位符是${}。

// AbstractPropertyResolver中實現
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
	// 工具類通過回調的方式完成解析,實現匿名內部類
	return helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() {
		@Override
		public String resolvePlaceholder(String placeholderName) {
			// helper負責獲取占位符中的內容
			// AbstractPropertyResolver將占位符中內容作為key,解析對應的屬性值
			return getPropertyAsRawString(placeholderName);
		}
	});
}
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
	Assert.notNull(value, "'value' must not be null");
	// 解析占位屬性值
	return parseStringValue(value, placeholderResolver, new HashSet<String>());
}
protected String parseStringValue(
		String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
	StringBuilder result = new StringBuilder(value);
	// 獲取占位符前綴位置
	int startIndex = value.indexOf(this.placeholderPrefix);
	如果存在占位,則解析,否則返回
	while (startIndex != -1) {
		// 獲取占位符后綴位置
		int endIndex = findPlaceholderEndIndex(result, startIndex);
		// 如果存在,則繼續解析,否則設置startIndex為-1,使while退出
		if (endIndex != -1) {
			// 根據前綴和后綴位置獲取占位符中的內容
			String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
			String originalPlaceholder = placeholder;
			// 判斷是否有循環嵌套,這里不支持循環嵌套占位
			if (!visitedPlaceholders.add(originalPlaceholder)) {
				throw new IllegalArgumentException(
						"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
			}
			// 遞歸解析嵌套占位
			placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
			// 從propertySources中解析占位內容對應的實際屬性值
			String propVal = placeholderResolver.resolvePlaceholder(placeholder);
			if (propVal == null && this.valueSeparator != null) {
				int separatorIndex = placeholder.indexOf(this.valueSeparator);
				if (separatorIndex != -1) {
					String actualPlaceholder = placeholder.substring(0, separatorIndex);
					String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
					propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
					if (propVal == null) {
						propVal = defaultValue;
					}
				}
			}
			if (propVal != null) {
				// 如果屬性值不為空,則需要遞歸解析屬性值中是否也有嵌套占位
				propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
				// 將獲取的屬性值替換占位
				result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
				if (logger.isTraceEnabled()) {
					logger.trace("Resolved placeholder '" + placeholder + "'");
				}
				startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
			}
			else if (this.ignoreUnresolvablePlaceholders) {
				// Proceed with unprocessed value.
				startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
			}
			else {
				throw new IllegalArgumentException("Could not resolve placeholder '" +
						placeholder + "'" + " in value \"" + value + "\"");
			}
			// 處理完一個占位,移除
			visitedPlaceholders.remove(originalPlaceholder);
		}
		else {
			startIndex = -1;
		}
	}
	// 返回完整的解析結果
	return result.toString();
}

關於getPropertyAsRawString從PropertySources中解析占位內容對應的屬性值,這里不在詳細介紹,邏輯與前文中的profile和屬性解析流程一致。

通過Environment提供的Resource路徑占位解析能力,從而可以得到完整的配置文件路徑,Spring上下文可以根據配置文件路徑,解析配置中的Bean配置,從而完成后續的Bean解析等等工作。

總結

本文介紹Spring上下文容器的核心組件Environment的能力和實現細節。Environment主要提供能力:

  • profile
  • Properties

主要是以上兩方面的維護操作。

參考

Environment abstraction
Spring 中無處不在的 Properties
Properties with Spring


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM