2024年1月1日 星期一

SpringMVC 動態設定 Properties 給 $Value 用的方法 - 使用 PropertySourcesPlaceholderConfigurer

這篇要來記錄在 Spring MVC 中,
要如何動態地設定 Properties 給 $Value 用的方法。

我們都知道在 spring MVC 中,如果設定了 PropertySourcesPlaceholderConfigurer ,
我們可以把 property file 中的 Key-Value 值讀進來,
之後在 @Component, @Controller, @Service, @Bean 等 Spring 為我們裝配的物件中,
就可以使用 @Value 來取得 property value,例如:

PropertySourcePlaceholderConfigurer 的設定,有兩種方法,使用 xml 或 java config:

  1. 使用 xml:
  2. mvc-config.xml:

    <bean id="propertyConfigurer" class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
    	<property name="locations">
    		<list>
    			<value>classpath:xxx1.properties</value>
    			<value>classpath:xxx2.properties</value>
    		</list>
    	</property>
    	<property name="ignoreUnresolvablePlaceholders" value="true" />
    </bean>
    
  3. 使用 java config:
  4. import java.io.IOException;
    import java.util.Properties;
    
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.PropertySource;
    import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
    import org.springframework.core.env.Environment;
    
    @Configuration
    @PropertySource("classpath:xxx1.properties")
    @PropertySource("classpath:xxx2.properties")
    public class MyPropertyConfiguration {
    	
    	@Bean
    	public static PropertySourcesPlaceholderConfigurer setPropertySourcesPlaceholderConfigurer() {
    		PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
    		propertySourcesPlaceholderConfigurer.setIgnoreUnresolvablePlaceholders(true);
    		return propertySourcesPlaceholderConfigurer;
    	}
    }
    
    

Java 中就可以使用 @Value:

@Component
public class Test
	...
	@Value("${xxxPropertyKey}")
	String xxxPropertyKey;
	...
}

但是這樣子因為 property file 的內容本身是寫死的,
如果今天我們想要注入的 @Value 值是專案啟動時動態決定的,
例如從 Database 中讀取而來、從環境變數 (Environment) 中取得、從網路上讀取 (例如 AWS 的 Parameter Store 服務) 等等,
就不能這樣設定了。

我們必須自己實作 PropertySourcesPlaceholderConfigurer 並覆寫掉 (Override) 它的
postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
方法,在裡面實作增添修改 property key-value 值的程式邏輯,
實作的範例如下:

Java:

import java.io.IOException;
import java.util.Properties;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.env.Environment;

@Configuration
@PropertySource("classpath:xxx1.properties")
@PropertySource("classpath:xxx2.properties")
public class MyParameterStorePropertyConfiguration {
	
	//如果原本這邊就有做讀 xxx1.properties 和 xxx2.properties 的 PropertySourcesPlaceholderConfigurer 設定,
	//不能因為多做了 setMyParameterStorePropertySourcesPlaceholderConfigurer_2() 就把 setMyParameterStorePropertySourcesPlaceholderConfigurer_1() 拿掉,
	//不然 xxx1.properties 和 xxx2.properties 的 property 都會被
	//setMyParameterStorePropertySourcesPlaceholderConfigurer_2() 移掉,因為
	//setMyParameterStorePropertySourcesPlaceholderConfigurer_2() 中已經用新的 Properties 取代了原本從 xxx1.properties 和 xxx2.properties 讀到的 property
	@Bean
	public static PropertySourcesPlaceholderConfigurer setMyParameterStorePropertySourcesPlaceholderConfigurer_1() {
		PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
		propertySourcesPlaceholderConfigurer.setIgnoreUnresolvablePlaceholders(true);
        //如果不想用 @PropertySource,也可以直接用程式設定,像下面這樣
        //propertySourcesPlaceholderConfigurer.setLocations(new ClassPathResource("xx1.properties"), new ClassPathResource("xx2.properties"));
		return propertySourcesPlaceholderConfigurer;
	}
	
	@Bean
	public static PropertySourcesPlaceholderConfigurer setMyParameterStorePropertySourcesPlaceholderConfigurer_2(Environment environment) {
    	//可視需要看要不要取得環境變數 (Environment)
		PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer() {
			
			@Override
			public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
				Properties properties = new Properties();				

				//動態地設定 property,key-value 值可能從其他地方來,而非寫死的 property file,
				//例如從 Database 中讀取而來、從環境變數 (Environment) 中取得、從網路上讀取 (例如 AWS 的 Parameter Store 服務) 等等
				//do your logic code here to get xxxKey1, xxxValue1, xxxKey2, xxxValue2, ......
				properties.put(xxxKey1, xxxValue1);
				properties.put(xxxKey2, xxxValue2);
				//......
				
				setProperties(properties);
				super.postProcessBeanFactory(beanFactory);
			}
		};
		propertySourcesPlaceholderConfigurer.setIgnoreUnresolvablePlaceholders(true);
		
		
		return propertySourcesPlaceholderConfigurer;
	}
}

參考資料:

  1. 注入設定檔
  2. Spring PropertyPlaceholderConfigurer load from DB
  3. Spring @Configuration作用