
主要有一下xml文件占位符解析和Java的屬性@Value的占位符解析配置這兩種場景進行分析和實現解析,如下面兩種案例。
(資料圖)
@Value 注解值進行屬性占位符解析和替換
@Value("${config}")private String config;
通過配置xml來實現對Classpath下的配置文件的占位符的屬性進行注入,或者實現Java的屬性@Value的占位符解析配置。
在Spring3.1版本之前是通過PropertyPlaceholderConfigurer實現的。在Spring3.1之后則是通過PropertySourcesPlaceholderConfigurer實現的。注意:在Spring Context 3.1或者更高版本中,缺省使用PropertySourcesPlaceholderConfigurer工具替換了PlaceholderConfigurerSupport,而<=3.0較老的Spring Context中,為了保持和之前的版本兼容,缺省還是使用PropertyPlaceholderConfigurer。
下圖介紹對應的配置解析的繼承關系圖譜。
PropertyPlaceholderConfigurer和PropertyPlaceholderConfigurer在使用上并無本質的區別,兩者的根本目標是將配置文件生成KV對,真正的注入工作并不由它們本身執行。
PropertySourcesPlaceholderConfigurer它用于解析bean定義中的屬性值,以及注解@Value的值,使用的屬性來源是當前的Spring Environment對象,以及設置給自己的PropertySources對象。
@Configuration@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)public class PropertyPlaceholderAutoConfiguration { @Bean @ConditionalOnMissingBean(search = SearchStrategy.CURRENT) public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { return new PropertySourcesPlaceholderConfigurer(); }}
PropertyPlaceholderAutoConfiguration定義一個PropertySourcesPlaceholderConfigurer bean,該bean作為一個BeanFactoryPostProcessor,會在容器啟動時容器后置處理階段執行自己的任務。BeanFactoryPostProcessor的優先級又優于其余的Bean。因此可以實現在bean初始化之前的注入。
如果外部指定了this.propertySources, 則直接使用它,否則從當前Spring的Environment 對象和自身的 #mergeProperties 方法調用返回的 Properties 對象構建屬性源對象 this.propertySources
@Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { if (this.propertySources == null) { this.propertySources = new MutablePropertySources(); if (this.environment != null) { this.propertySources.addLast( new PropertySource(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) { @Override @Nullable public String getProperty(String key) { return this.source.getProperty(key); } } ); } try { PropertySource> localPropertySource = new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties()); if (this.localOverride) { this.propertySources.addFirst(localPropertySource); } else { this.propertySources.addLast(localPropertySource); } } catch (IOException ex) { throw new BeanInitializationException("Could not load properties", ex); } } processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources)); this.appliedPropertySources = this.propertySources; }
構造一個基于特定屬性源 this.propertySources 對屬性值進行解析的屬性值解析器PropertySourcesPropertyResolver, 對容器中所有的 bean 定義中的屬性值,構造函數參數值。
/** * Visit each bean definition in the given bean factory and attempt to replace ${...} property * placeholders with values from the given properties. */ protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, final ConfigurablePropertyResolver propertyResolver) throws BeansException { // 設置屬性值解析器所使用的占位符格式參數,缺省為: // 占位符前綴 ${ propertyResolver.setPlaceholderPrefix(this.placeholderPrefix); // 占位符后綴 } propertyResolver.setPlaceholderSuffix(this.placeholderSuffix); // 缺省值分隔符 : propertyResolver.setValueSeparator(this.valueSeparator); // 結合屬性 this. ignoreUnresolvablePlaceholders對propertyResolver 作進一步封裝, // 封裝出來一個 StringValueResolver valueResolver,這是最終要應用的屬性值解析器 StringValueResolver valueResolver = strVal -> { String resolved = (this.ignoreUnresolvablePlaceholders ? propertyResolver.resolvePlaceholders(strVal) : propertyResolver.resolveRequiredPlaceholders(strVal)); if (this.trimValues) { resolved = resolved.trim(); } return (resolved.equals(this.nullValue) ? null : resolved); }; // 調用基類PlaceholderConfigurerSupport實現的對容器中所有 bean定義進行遍歷處理屬性值中占位符解析的邏輯 doProcessProperties(beanFactoryToProcess, valueResolver); }
doProcessProperties的方法目的是為了添加解析器StringValueResolver
protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess, StringValueResolver valueResolver) { // ignore .... // New in Spring 2.5: resolve placeholders in alias target names and aliases as well. beanFactoryToProcess.resolveAliases(valueResolver); // New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes. beanFactoryToProcess.addEmbeddedValueResolver(valueResolver); }
這里的ddEmbeddedValueResolver(StringValueResolver) 是為一個 LinkedList添加值。在取用的時候是優先從鏈表頭開始取用的。 一旦發現無法找到值,直接就拋異常了。這個就對外體現出 PropertySourcesPlaceholderConfigurer 的唯一性。
然而Spring內部還是有多個PropertySourcesPlaceholderConfigurer, 只不過除了排列在隊首的 PropertySourcesPlaceholderConfigurer之外全都被忽略掉了。
AbstractApplicationContext#obtainFreshBeanFactory
針對于元素的注入依賴于AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues1。
在Spring初始化流程中,執行AbstractApplicationContext#finishBeanFactoryInitialization方法。 該方法里面發生的主要流程為Spring業務Bean初始化。 實際流程跟Spring Bean的初始化沒有任務區別。
所以,可以看出 PropertySourcesPlaceholderConfigurer 或者 PropertyPlaceholderConfigurer僅僅是做了一個配置文件的解析工作,真正的注入并不由它們完成,而是托付給了Spring 的Bean初始化流程。這兩個類實現了BeanFactoryPostProcessor 接口,這個接口的優先級高于后續的Spring Bean。
通過解析了的PropertySourcesPlaceholderConfigurer查詢得到元素值。 沒有則拋出異常,如下源碼:
DefaultListableBeanFactory#doResolveDependency
@Value 注解值進行屬性占位符解析和替換
// 獲取注解的 value() 值。被寫死為 Class extends Annotation> valueAnnotationType = Value.class;// 見類 QualifierAnnotationAutowireCandidateResolverObject value = getAutowireCandidateResolver().getSuggestedValue(descriptor);if (value != null) { if (value instanceof String) { // 通過PropertySourcesPlaceholderConfigurer寫入的鍵值對元素獲取元素的值. // 方法內注冊了多個StringValueResolver,循環查找值。提供者為PropertySourcesPlaceholderConfigurer,因此配置多個解析器的時候是以最后的配置為準的。 String strVal = resolveEmbeddedValue((String) value); BeanDefinition bd = (beanName != null && containsBean(beanName) ? getMergedBeanDefinition(beanName) : null); value = evaluateBeanDefinitionString(strVal, bd); } TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter()); return (descriptor.getField() != null ? converter.convertIfNecessary(value, type, descriptor.getField()) : converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));}
通過PropertyPlaceholderConfigurer進行配置Bean方式
conf/sqlmap/jdbc.properties UTF-8
注意這兩種value值的寫法
/WEB-INF/mail.properties classpath: conf/sqlmap/jdbc.properties
這總方式的原理就是構造一個PropertySourcesPlaceholderConfigurer, (3.1之前是PropertyPlaceholderConfigurer)
ContextNamespaceHandler#initPropertyPlaceholderBeanDefinitionParser#doParseSpring初始化Context的時候讀取XML配置, 這個流程優先于Spring普通Bean初始化。配合掃包(
@PropertySource(value = "classpath:config/application-config.properties")
@PropertySource(value = {"classpath:config/application-config1.properties","classpath:config/application-config2.properties"})
@Configuration@ComponentScan(basePackages = "com.libo.config")@PropertySource(value = "classpath:config/application-config.properties")public class TestPropertieEnvironment { @Autowired Environment environment; public String properties(){ String key = this.environment.getProperty("config.key"); System.out.println(key); return null; }}
config.key=1config.value=2
public class Test { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(TestPropertieEnvironment.class); ServiceConfiguration hc2 = (TestPropertieEnvironment) context.getBean("testPropertieEnvironment"); hc2.properties(); }}
PropertySourcesPlaceholderConfigurer是PlaceholderConfigurerSupport的特殊化實現。它用于解析bean定義中的屬性值,以及注解@Value的值,使用的屬性來源是當前的Spring Environment對象,以及設置給自己的PropertySources對象。
大于3.1更高版本中,缺省使用該工具替換了PlaceholderConfigurerSupport<=3.0較老的Spring中,為了保持和之前的版本兼容,缺省還是使用PropertyPlaceholderConfigurer。@Component@PropertySource(value = "classpath:config/application-config.properties")public class PropertiesConfig { @Value("${config.value}") private String value; @Value("${config.key}") private String key; }
測試類忽略!
@Configuration@ComponentScan(basePackages = "com.libo.config")public class PropertiesConfiguration2 { @Bean public static PropertyPlaceholderConfigurer configurer() { PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer(); Resource resources = new ClassPathResource( "config/appplication-config.properties" ); ppc.setLocation(resources); return ppc; } @Bean public Configs2 configs2(@Value("${ds.user}") String user, @Value("${key1}") String key1) { Configs2 configs = new Configs2(); configs.setApiKeyId(user); configs.setSecretApiKey(key1); System.out.println("in ServiceConfiguration" + configs); return configs; } }@Servicepublic class TestConfigs2 { @Autowired Configs2 configs2; @Autowired Configs configs; public void testConfigs2() { System.out.println("configs:"+configs.getApiKeyId()); System.out.println("configs2:"+configs2.getApiKeyId()); }}
import org.springframework.context.ApplicationContext;import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class Test { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(ServiceConfiguration2.class); TestConfigs2 hc2 = (TestConfigs2) context.getBean("testConfigs2"); hc2.testConfigs2(); }}
此外需要注意的是:PropertySource是可以支持ignoreResourceNotFound支持無法獲取配置文件的i情況。
在Spring 4版本中,Spring提供了一個新的注解——@PropertySources,從名字就可以猜測到它是為多配置文件而準備的。
@PropertySources({//@PropertySource("classpath:db.properties"),@PropertySource(value="classpath:db.properties", ignoreResourceNotFound=true),@PropertySource("classpath:spring/config.properties") public class AppConfig { @Value("${key1}") private String key1; @Value("${key2}") private String key2; @Override public String toString() { return "AppConfig [key1=" + key1 + ", key2=" + key2 + "]"; } }
標簽: 配置文件