在Spring cloud config出來之前, 自己實現了基于ZK的配置中心, 杜絕了本地properties配置文件, 原理很簡單, 只是重載了PropertyPlaceholderConfigurer的mergeProperties():
/**
* 重載合并屬性實現
* 先加載file properties, 然后并入ZK配置中心讀取的properties
*
* @return 合并后的屬性集合
* @throws IOException 異常
*/
@Override
protected Properties mergeProperties() throws IOException {
Properties result = new Properties();
// 加載父類的配置
Properties mergeProperties = super.mergeProperties();
result.putAll(mergeProperties);
// 加載從zk中讀取到的配置
Map<String, String> configs = loadZkConfigs();
result.putAll(configs);
return result;
}
這個實現在spring項目里用起來還是挺順手的, 但是近期部分spring-boot項目里發現這種placeholder的實現跟spring boot的@ConfigurationProperties(prefix = "xxx") 不能很好的配合工作,
也就是屬性沒有被resolve處理, 用@Value的方式確可以讀到, 但是@Value配置起來如果屬性多的話還是挺繁瑣的, 還是傾向用@ConfigurationProperties的prefix, 于是看了下spring boot的文檔發現
PropertySource
order:
* Devtools global settings properties on your home directory (~/.spring-boot-devtools.properties when devtools is active).
* @TestPropertySource annotations on your tests.
* @SpringBootTest#properties annotation attribute on your tests.
* Command line arguments.
* Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable or system property)
* ServletConfig init parameters.
* ServletContext init parameters.
* JNDI attributes from java:comp/env.
* Java System properties (System.getProperties()).
* OS environment variables.
* A RandomValuePropertySource that only has properties in random.*.
* Profile-specific application properties outside of your packaged jar (application-{profile}.properties and YAML variants)
* Profile-specific application properties packaged inside your jar (application-{profile}.properties and YAML variants)
* Application properties outside of your packaged jar (application.properties and YAML variants).
* Application properties packaged inside your jar (application.properties and YAML variants).
* @PropertySource annotations on your @Configuration classes.
* Default properties (specified using SpringApplication.setDefaultProperties).
不難發現其會檢查Java system propeties里的屬性, 也就是說, 只要把mergerProperties讀到的屬性寫入Java system props里即可, 看了下源碼, 找到個切入點
/**
* 重載處理屬性實現
* 根據選項, 決定是否將合并后的props寫入系統屬性, Spring boot需要
*
* @param beanFactoryToProcess
* @param props 合并后的屬性
* @throws BeansException
*/
@Override
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props) throws BeansException {
// 原有邏輯
super.processProperties(beanFactoryToProcess, props);
// 寫入到系統屬性
if (writePropsToSystem) {
// write all properties to system for spring boot
Enumeration<?> propertyNames = props.propertyNames();
while (propertyNames.hasMoreElements()) {
String propertyName = (String) propertyNames.nextElement();
String propertyValue = props.getProperty(propertyName);
System.setProperty(propertyName, propertyValue);
}
}
}
為避免影響過大, 設置了個開關, 是否寫入系統屬性, 如果是spring boot的項目, 就開啟, 這樣對線上非spring boot項目做到影響最小, 然后spring boot的@ConfigurationProperties完美讀到屬性;
具體代碼見: org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
ConfigurationProperties annotation = AnnotationUtils
.findAnnotation(bean.getClass(), ConfigurationProperties.class);
if (annotation != null) {
postProcessBeforeInitialization(bean, beanName, annotation);
}
annotation = this.beans.findFactoryAnnotation(beanName,
ConfigurationProperties.class);
if (annotation != null) {
postProcessBeforeInitialization(bean, beanName, annotation);
}
return bean;
}