主要有一下xml檔案預留位置解析和Java的屬性@Value的預留位置解析設定這兩種場景進行分析和實現解析,如下面兩種案例。
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
@Value 註解值進行屬性預留位置解析和替換
@Value("${config}")
private String config;
通過設定xml來實現對Classpath下的組態檔的預留位置的屬性進行注入,或者實現Java的屬性@Value的預留位置解析設定。
注意:在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>(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的初始化沒有任務區別。
用於@Value和@Autowired註解實際執行方法postProcessPropertyValues排程實際排程InjectedElement子類被注入值的獲取來自於DefaultListableBeanFactory將對應@Value(「${configValue}」)裡面的值替換的來源值,是PropertySourcesPlaceholderConfigurer生成的StringValueResolver。
Spring原生的Bean是單例的它直接被儲存在了AbstractBeanFactory執行Field.set(Object, Object)或者Method.invoke(Object, Object[])。
所以,可以看出 PropertySourcesPlaceholderConfigurer 或者 PropertyPlaceholderConfigurer僅僅是做了一個組態檔的解析工作,真正的注入並不由它們完成,而是託付給了Spring 的Bean初始化流程。這兩個類實現了BeanFactoryPostProcessor 介面,這個介面的優先順序高於後續的Spring Bean。
通過解析了的PropertySourcesPlaceholderConfigurer查詢得到元素值。 沒有則丟擲異常,如下原始碼:
DefaultListableBeanFactory#doResolveDependency
@Value 註解值進行屬性預留位置解析和替換
// 獲取註解的 value() 值。被寫死為 Class<? extends Annotation> valueAnnotationType = Value.class;
// 見類 QualifierAnnotationAutowireCandidateResolver
Object 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方式
<bean id="propertyConfigurer"class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location">
<value>conf/sqlmap/jdbc.properties</value>
</property>
<property name="fileEncoding">
<value>UTF-8</value>
</property>
</bean>
注意這兩種value值的寫法
<bean id="propertyConfigurer"class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>/WEB-INF/mail.properties</value>
<value>classpath: conf/sqlmap/jdbc.properties</value>
</list>
</property>
</bean>
<context:property-placeholder location="classpath*:/WEB-INF/mail.properties" />
這總方式的原理就是構造一個PropertySourcesPlaceholderConfigurer, (3.1之前是PropertyPlaceholderConfigurer)
Spring初始化Context的時候讀取XML設定, 這個流程優先於Spring普通Bean初始化。配合掃包(<context:component-scan />)得到的Bean進而實現對XML裡面設定的Bean的載入。
設定Spring @Value("val2Inject") 方式獲取組態檔的屬性,需要依賴於在Spring XML裡面設定<context:property-placeholder /> 或者PropertySourcesPlaceholderConfigurerBean來新增組態檔的名稱。
讀取到context:property-placeholder標籤或者PropertySourcesPlaceholderConfigurer
解析並範例化一個PropertySourcesPlaceholderConfigurer。同時向其中注入組態檔路徑、名稱PropertySourcesPlaceholderConfigurer自身生成多個StringValueResolver備用,Bean準備完畢。
Spring在初始化非BeanFactoryPostProcessor的Bean的時候,AutowiredAnnotationBeanPostProcessor負責找到Bean內有@Value註解的Field或者Method
AutowiredAnnotationBeanPostProcessor負責@Autowired和@Value兩個註解的解析。
@PropertySource(value = "classpath:config/application-config.properties")
@PropertySource(value = {"classpath:config/application-config1.properties","classpath:config/application-config2.properties"})
@PropertySource + Environment,通過@PropertySource註解將properties組態檔中的值儲存到Spring的Environment中,Environment介面提供方法去讀取組態檔中的值,引數是properties檔案中定義的key值。
@PropertySource(PropertySourcesPlaceholderConfigurer) +@Value
@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=1
config.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;
}
}
@Service
public 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 + "]";
}
}
本文來自部落格園,作者:洛神灬殤,轉載請註明原文連結:https://www.cnblogs.com/liboware/p/16991441.html,任何足夠先進的科技,都與魔法無異。