­

Spring系列15:Environment抽象

本文內容

  1. Environment抽象的2個重要概念
  2. @Profile 的使用
  3. @PropertySource 的使用

Environment抽象的2個重要概念

Environment 接口表示當前應用程序運行環境的接口。對應用程序環境的兩個關鍵方面進行建模:配置文件( profiles )和屬性(properties)。與屬性訪問相關的方法通過 PropertyResolver 超接口公開。環境對象的配置必須通過 ConfigurableEnvironment 接口完成,該接口從所有 AbstractApplicationContext 子類 getEnvironment() 方法返回

環境與配置文件

配置文件是一個命名的、邏輯的 bean 定義組,僅當給定的配置文件處於活動狀態時才向容器註冊。可以將 Bean 分配給配置文件,無論是在 XML 中定義還是通過注釋 @Profile 定義;與配置文件相關的環境對象的作用是確定哪些配置文件(如果有)當前處於活動狀態,以及哪些配置文件(如果有)默認應該是活動的

環境與屬性

屬性在幾乎所有應用程序中都發揮着重要作用,並且可能源自多種來源:屬性文件、JVM 系統屬性、系統環境變量、JNDI、servlet 上下文參數、屬性對象、map等。與屬性相關的環境對象的作用是為用戶提供一個方便的服務接口,用於配置屬性源並從中解析屬性

在 ApplicationContext 中管理的 Bean 可以註冊為 EnvironmentAware 或 @Inject Environment,以便直接查詢配置文件狀態或解析屬性。然而,在大多數情況下,應用程序級別的 bean 不需要直接與 Environment 交互,而是可能必須將 ${…} 屬性值替換為屬性佔位符配置器,例如 PropertySourcesPlaceholderConfigurer,它本身是 EnvironmentAware 並且從 Spring 3.1 開始使用 context:property-placeholder 時默認註冊 ,或是通過java bean的方式註冊到容器中。

PropertySourcesPlaceholderConfigurer 分析可以閱讀上一篇: Spring系列14:IoC容器的擴展點

接口源碼粗覽

接口繼承關係

image-20220121175854817

接口源碼如下提供配置文件相關的接口方法,其繼承的 PropertyResolver 提供屬性相關的接口。

public interface Environment extends PropertyResolver {
    // 當前激活的配置文件列表
    // 設置系統屬性值 spring.profiles.active=xxx 可激活
    // 或是調用 ConfigurableEnvironment#setActiveProfiles(String...)激活
	String[] getActiveProfiles();

	// 當沒有明確設置活動配置文件時,默認配置文件集返回為活動狀態。
	String[] getDefaultProfiles();

	// 返回活動配置文件是否與給定的 Profiles 匹配
	boolean acceptsProfiles(Profiles profiles);

}

PropertyResolver 是針對任何底層源解析屬性的接口,主要接口方法如下。有一個非常重要的實現類是 PropertySourcesPlaceholderConfigurer 。

public interface PropertyResolver {

	// 是否包含屬性
	boolean containsProperty(String key);
	// 獲取屬性值
	String getProperty(String key);
	// 獲取屬性值帶默認值
	String getProperty(String key, String defaultValue);
	// 獲取屬性值
	<T> T getProperty(String key, Class<T> targetType);
	// 獲取屬性值帶默認值
	<T> T getProperty(String key, Class<T> targetType, T defaultValue);

	// 獲取屬性值
	String getRequiredProperty(String key) throws IllegalStateException;
	//  獲取屬性值
	<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;

    // 解析給定文本中的 ${...} 佔位符
	String resolvePlaceholders(String text);

	// 解析給定文本中的 ${...} 佔位符
	String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;

}

ConfigurablePropertyResolver 是大多數 PropertyResolver 類型都將實現的配置接口。提供用於訪問和自定義將屬性值從一種類型轉換為另一種類型時使用的 ConversionService 的工具。

public interface ConfigurablePropertyResolver extends PropertyResolver {

   
   ConfigurableConversionService getConversionService();

  
   void setConversionService(ConfigurableConversionService conversionService);

   // 設置佔位符前綴 默認的 "${"怎麼來的
   void setPlaceholderPrefix(String placeholderPrefix);

   // 設置佔位符後綴 默認的 "}"怎麼來的
   void setPlaceholderSuffix(String placeholderSuffix);

  // 設置佔位符值分分隔符 默認的 ":"怎麼來的
   void setValueSeparator(@Nullable String valueSeparator);

   void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders);

   void setRequiredProperties(String... requiredProperties);

   void validateRequiredProperties() throws MissingRequiredPropertiesException;

}

ConfigurableEnvironment是大多數環境類型都將實現的配置接口。提供用於設置活動和默認配置文件以及操作基礎屬性源的工具。允許客戶端通過 ConfigurablePropertyResolver 超級接口設置和驗證所需屬性、自定義轉換服務等。

public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {

	void setActiveProfiles(String... profiles);

	void addActiveProfile(String profile);

	void setDefaultProfiles(String... profiles);

	MutablePropertySources getPropertySources();

	// 關鍵的系統屬性 System#getProperties()
	Map<String, Object> getSystemProperties();

	// 關鍵的系統環境  System#getenv()
	Map<String, Object> getSystemEnvironment();

	void merge(ConfigurableEnvironment parent);
 }

@Profile 的使用

@Profile 表示當一個或多個profiles處於活動狀態時,組件有資格註冊。可以通過以下的方式設置活躍的一個或是多個配置文件:

  • 編程方式:ConfigurableEnvironment#setActiveProfiles(String…)
  • 啟動參數: -Dspring.profiles.active=”profile1,profile2″
  • xml配置方式:

使用案例

來看一個實際場景:不同環境要求在容器中注入不同類型的的數據源,dev環境使用H2,生產環境prod使用Mysql,default環境使用 HSQL。

定義不同環境的數據源,並標識 @Profile

@Configuration
@ComponentScan
public class AppConfig {

    // 測試環境數據源H2
    @Profile("dev")
    @Bean
    public DataSource devDataSource() {
        DataSource dataSource = new DataSource();
        dataSource.setType("H2");
        dataSource.setUrl("jdbc:h2:xxxxxx");
        return dataSource;
    }
    // 生產環境數據源mysql
    @Profile("prod")
    @Bean
    public DataSource prodDataSource() {
        DataSource dataSource = new DataSource();
        dataSource.setType("mysql");
        dataSource.setUrl("jdbc:mysql:xxxxxx");
        return dataSource;
    }

    // default 環境的 HSQL
    @Profile("default")
    @Bean
    public DataSource defaultDataSource() {
        DataSource dataSource = new DataSource();
        dataSource.setType("HSQL");
        dataSource.setUrl("jdbc:HSQL:xxxxxx");
        return dataSource;
    }

}

測試程序,首先不指定 profile

@org.junit.Test
public void test_profile() {
    AnnotationConfigApplicationContext context =
            new AnnotationConfigApplicationContext();
//        context.getEnvironment().setActiveProfiles("prod");
    context.register(AppConfig.class);
    context.refresh();
    DataSource dataSource = context.getBean(DataSource.class);
    System.out.println(dataSource.getType());
    context.close();
}
// 輸出結果
HSQL

從結果可知,註冊到容器中的 default 環境對應的 HSQL

指定 profile 為 prod ,觀察輸出

context.getEnvironment().setActiveProfiles("prod")
// 結果
mysql

從結果可知,註冊到容器中的 prod 環境對應的 mysql 。

支持邏輯操作符

支持與或非操作組合

  • &
  • |

組合&和|必須使用小括號

反例:production & us-east | eu-central

正例:production & (us-east | eu-central)

使用 @Profile 自定義組合註解

定義組合註解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}

使用

@Configuration
@Production
public class MyConfiguration {
}

如果@Configuration 類用@Profile 標記,則與該類關聯的所有@Bean 方法和@Import 注釋都將被繞過,除非一個或多個指定的配置文件處於活動狀態。

使用xml指定 profile

標籤中的 profile元素的可以指定配置文件。

<?xml version="1.0" encoding="UTF-8"?>
<beans profile="prod"
        xmlns="//www.springframework.org/schema/beans"
       xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="//www.springframework.org/schema/beans //www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="com.crab.spring.ioc.demo13.DataSource" id="dataSource">
        <property name="type" value="mysql"/>
        <property name="url" value="jdbc:mysql/xxxxx"/>
    </bean>

</beans>

PropertySource 抽象

Spring 的 Environment 抽象在可配置的屬性源層次結構上提供搜索操作。來看下案例如何從Spring 容器獲取屬性。

@org.junit.Test
public void test_property_source() {
    ApplicationContext ctx = new GenericApplicationContext();
    Environment env = ctx.getEnvironment();
    boolean containsMyProperty = env.containsProperty("my-property");
    System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);
}

PropertySource 是對任何鍵值對源的簡單抽象。Spring 的 StandardEnvironment 配置了兩個 PropertySource 對象:

  • 一個表示一組 JVM 系統屬性 (System.getProperties())

  • 一個表示一組系統環境變量 (System.getenv())

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

}

在屬性源中查找屬性是否存在的優先級順序如下,從高到低:

  1. ServletConfig parameters (web上下文)
  2. ServletContext parameters (web.xml context-param entries)
  3. JNDI environment variables (java:comp/env/ entries)
  4. JVM system properties (-D command-line arguments)
  5. JVM system environment (operating system environment variables)

自定義 PropertySource

自定義 MyPropertySource 實現 Property 提供基於 Map 屬性鍵值對的屬性源

/**
 * 自定義 PropertySource
 * @author zfd
 * @version v1.0
 * @date 2022/1/22 22:13
 * @關於我 請關注公眾號 螃蟹的Java筆記 獲取更多技術系列
 */
public class MyPropertySource extends PropertySource<Map<String, Object>> {


    public MyPropertySource(String name, Map<String, Object> source) {
        super(name, source);
    }

    public MyPropertySource(String name) {
        super(name);
    }

    @Override
    public Object getProperty(String name) {
        return this.source.get(name);
   }
 }

添加到Spring 容器環境中,優先級最高

@org.junit.Test
    public void test_custom_property_source() {

    ConfigurableApplicationContext ctx = new GenericApplicationContext();
    MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
    Map<String, Object> map = new HashMap<>();
    map.put("my-property", "xxx");
    sources.addFirst(new MyPropertySource("myPropertySource",map));
    // true
    boolean containsMyProperty = ctx.getEnvironment().containsProperty("my-property");
    System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);
}

@PropertySource 使用

相比上面的編程式添加 PropertySource,@PropertySource 註解為將 PropertySource 添加到 Spring 的環境中提供了一種方便且聲明性的機制。直接看案例。

app.properties配置

testBean.name=xxx

配置類

@Configuration
// 注入配置文件
@PropertySource("classpath:demo13/app.properties")
public class AppConfig3 {

    @Autowired
    private Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testBean.name"));
        return testBean;
    }
}

測試結果觀察

    @org.junit.Test
    public void test_property_source_annotation() {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(AppConfig3.class);
        TestBean testBean = context.getBean(TestBean.class);
        System.out.println(testBean.getName());
    }
// 結果
xxx

@PropertySource 中指定配置文件也是可以使用佔位符${…}的。如果環境中屬性值my.config.path已經存在則進行解析,否則使用默認值demo13

@Configuration
// 注入配置文件
@PropertySource("classpath:${my.config.path:demo13}/app.properties")
public class AppConfig3 {}

總結

本文介紹了Spring中的Environment抽象的2個重要概念:Bean定義配置文件和屬性源。同時介紹了@Profile使用和@PropertySource 的使用。

本篇源碼地址: //github.com/kongxubihai/pdf-spring-series/tree/main/spring-series-ioc/src/main/java/com/crab/spring/ioc/demo13
知識分享,轉載請註明出處。學無先後,達者為先!

Tags: