Spring Environment(一)API 使用
Spring 系列目錄(https://www.cnblogs.com/binarylei/p/10198698.html)
Spring 3.1 提供了新的屬性管理 API,而且功能非常強大且很完善,對於一些屬性配置信息都應該使用新的 API 來管理。位於 org.springframework.core.env 包內。
Spring Environment 屬性配置管理系列文章:
一、新的屬性管理 API
PropertySource:屬性源,key-value 屬性對抽象,比如用於配置數據PropertyResolver:屬性解析器,用於解析相應 key 的 valueEnvironment:環境,本身是一個 PropertyResolver,但是提供了 Profile 特性,即可以根據環境得到相應數據(即激活不同的 Profile,可以得到不同的屬性數據,比如用於多環境場景的配置(正式機、測試機、開發機 DataSource 配置)Profile:剖面,只有激活的剖面的組件/配置才會注冊到 Spring 容器,類似於 maven 中 profile
也就是說,新的 API 主要從配置屬性、解析屬性、不同環境解析不同的屬性、激活哪些組件/配置進行注冊這幾個方面進行了重新設計,使得 API 的目的更加清晰,而且功能更加強大。
@Test
public void test() {
Environment env = new StandardEnvironment();
// 1. 操作系統的環境變量
Map<String, Object> systemEnvironment = ((StandardEnvironment) env).getSystemEnvironment();
Assert.assertNotNull(systemEnvironment);
// 2. JVM 屬性配置
Map<String, Object> systemProperties = ((StandardEnvironment) env).getSystemProperties();
Assert.assertNotNull(systemProperties);
// 3. 屬性
Assert.assertEquals("UTF-8", env.getProperty("file.encoding"));
Assert.assertTrue(env.containsProperty("file.encoding"));
// 4. 剖面 spring.profiles.default(默認為 default) spring.profiles.active
// 只要有一個返回 true acceptsProfiles 方法就返回 true,!a 為不包含該 profiles
Assert.assertTrue(env.acceptsProfiles("default"));
Assert.assertTrue(env.acceptsProfiles("a", "default"));
Assert.assertFalse(env.acceptsProfiles("a"));
Assert.assertTrue(env.acceptsProfiles("!a", "b"));
}
二、PropertySource

public abstract class PropertySource<T> {
// 給數據源起個名稱
protected final String name;
// 數據源,可能為 Map 或 Properties ...
protected final T source;
public boolean containsProperty(String name) {
return (getProperty(name) != null);
}
public abstract Object getProperty(String name);
}
PropertySource 非常類似於 Map,數據源可來自 Map、Properties、Resource 等。PropertySource 接口有兩個特殊的子類:StubPropertySource 用於占位用,ComparisonPropertySource 用於集合排序,不允許獲取屬性值。
2.1 PropertySource 實現類
MapPropertySource 的屬性來自於一個 Map,而 ResourcePropertySource 的屬性來自於一個 properties 文件,另外還有如 PropertiesPropertySource,其屬性來自 Properties,ServletContextPropertySource 的屬性來自 ServletContext 上下文初始化參數等等,大家可以查找 PropertySource 的繼承層次查找相應實現。
@Test
public void PropertySourceTest() throws IOException {
PropertySource mapPropertySource = new MapPropertySource("map",
Collections.singletonMap("key", "source1"));
Assert.assertEquals("value1", mapPropertySource.getProperty("key"));
ResourcePropertySource resourcePropertySource = new ResourcePropertySource(
"resource", "classpath:resources.properties");
Assert.assertEquals("value2", resourcePropertySource.getProperty("key"));
}
2.2 CompositePropertySource
CompositePropertySource 提供了組合 PropertySource 的功能,查找順序就是注冊順序。
@Test
public void CompositePropertySourceTest() throws IOException {
PropertySource propertySource1 = new MapPropertySource("source1",
Collections.singletonMap("key", "value1"));
PropertySource propertySource2 = new MapPropertySource("source2",
Collections.singletonMap("key", "value2"));
CompositePropertySource compositePropertySource = new CompositePropertySource("composite");
compositePropertySource.addPropertySource(propertySource1);
compositePropertySource.addPropertySource(propertySource2);
Assert.assertEquals("value1", compositePropertySource.getProperty("key"));
}
2.3 PropertySources

另外還有一個 PropertySources,從名字可以看出其包含多個 PropertySource。默認提供了一個 MutablePropertySources 實現,可以調用 addFirst 添加到列表的開頭,addLast 添加到末尾,另外可以通過 addBefore(propertySourceName, propertySource) 或 addAfter(propertySourceName, propertySource) 添加到某個 propertySource 前面/后面;最后大家可以通過 iterator 迭代它,然后按照順序獲取屬性。
注意:PropertySource 的順序非常重要,因為 Spring 只要讀到屬性值就返回。
@Test
public void PropertySourcesTest() throws IOException {
PropertySource propertySource1 = new MapPropertySource("source1",
Collections.singletonMap("key", "value1"));
PropertySource propertySource2 = new MapPropertySource("source2",
Collections.singletonMap("key", "value2"));
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(propertySource1);
propertySources.addLast(propertySource2);
Assert.assertEquals("value1", propertySources.get("source1").getProperty("key"));
Assert.assertEquals("value2", propertySources.get("source2").getProperty("key"));
}
到目前我們已經有屬性了,接下來需要更好的 API 來解析屬性了。
三、PropertyResolver
PropertyResolver 的使用參考:https://www.cnblogs.com/binarylei/p/10284826.html
四、Environment
Environment 是對 JDK 環境、Servlet 環境、Spring 環境的抽象;每個環境都有自己的配置數據,如 System.getProperties()、System.getenv() 等可以拿到 JDK 環境數據;ServletContext.getInitParameter()可以拿到 Servlet 環境配置數據等等;也就是說 Spring 抽象了一個 Environment 來表示環境配置。
public interface Environment extends PropertyResolver {
String[] getActiveProfiles();
String[] getDefaultProfiles();
// @since 5.1 廢棄,改用 Profiles(Profiles.of("dev"))
@Deprecated
boolean acceptsProfiles(String... profiles);
boolean acceptsProfiles(Profiles profiles);
}
從 API 上可以看出,除了可以解析相應的屬性信息外,還提供了剖面相關的 API。目的是:可以根據剖面有選擇的進行注冊組件/配置。比如對於不同的環境注冊不同的組件/配置(正式機、測試機、開發機等的數據源配置)。它的主要幾個實現如下所示:
MockEnvironment:模擬的環境,用於測試時使用;StandardEnvironment:標准環境,普通 Java 應用時使用,會自動注冊 System.getProperties() 和 System.getenv()到環境;StandardServletEnvironment:標准 Servlet 環境,其繼承了 StandardEnvironment,Web 應用時使用,除了 StandardEnvironment 外,會自動注冊 ServletConfig(DispatcherServlet)、ServletContext 及 JNDI 實例到環境;
4.1 web.xml 配置 Servlet 屬性
<context-param>
<param-name>myConfig</param-name>
<param-value>hello</param-value>
</context-param>
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
</servlet>
使用 StandardServletEnvironment 加載時,默認除了 StandardEnvironment 的兩個屬性外,還有另外三個屬性:servletContextInitParams(ServletContext)、servletConfigInitParams(ServletConfig)、jndiProperties(JNDI)。
4.2 Environment 獲取
(1) 注解
@Autowired
Environment env;
(2) ApplicationContext
applicationContext.getEnvironment();
五、Profile
profile 剖面,大體意思是:我們程序可能從某幾個剖面來執行應用,比如正式機環境、測試機環境、開發機環境等,每個剖面的配置可能不一樣(比如開發機可能使用本地的數據庫測試,正式機使用正式機的數據庫測試)等;因此呢,就需要根據不同的環境選擇不同的配置;如果 maven 中的 profile 的概念。
profile 有兩種:
- 默認的:通過 "spring.profiles.default" 屬性獲取,如果沒有配置默認值是 "default"
- 明確激活的:通過 "spring.profiles.active" 獲取
查找順序是:先進性明確激活的匹配,如果沒有指定明確激活的(即集合為空)就找默認的;配置屬性值從 Environment 讀取。
profile 設置方式常見的有三種:
(1) -D 傳入系統參數
-Dspring.profiles.active=dev
(2) web 環境
<context-param>
<param-name>spring.profiles.active</param-name>
<param-value>dev</param-value>
</context-param>
(3) xml 配置
通過在 beans 標簽上加上 profile 屬性,這樣當我們激活相應的 profile 時,此 beans 標簽下的 bean 就會注冊,如下所示:
<beans>
<beans profile="dev">
<bean id="dataSource" class="...">
</bean>
</beans>
<beans profile="test">
<bean id="dataSource" class="...">
</bean>
</beans>
</beans>
啟動應用時設置相應的 "spring.profiles.active" 即可。另外,如果想指定一個默認的,可以使用
(4) 注解配置
Java Config 方式的 Profile,功能等價於 XML 中的
@Profile("dev")
@Configuration
@PropertySource(value = "classpath:resources.properties", ignoreResourceNotFound = false)
public class AppConfig {
@Bean
public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
Spring4 提供了一個新的 @Conditional 注解,請參考 http://jinnianshilongnian.iteye.com/blog/1989379
(5) @ActiveProfiles()
在測試時,有時候不能通過系統啟動參數/上下文參數等指定 Profile,此時 Spring 測試框架提供了 @ActiveProfiles() 注解,示例如下:
@ActiveProfiles("test")
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = GenericConfig.class)
public class GenricInjectTest {
}
到此整個 Spring 的屬性管理 API 就介紹完了,對於屬性管理,核心是 Environment。
參考:
- 《pring3.1新屬性管理API:PropertySource、Environment、Profile》:https://jinnianshilongnian.iteye.com/blog/2000183
每天用心記錄一點點。內容也許不重要,但習慣很重要!
