Spring提供配置解析功能
主要有一下xml文件占位符解析和Java的屬性@Value的占位符解析配置這兩種場(chǎng)景進(jìn)行分析和實(shí)現(xiàn)解析阱洪,如下面兩種案例便贵。
xml文件的占位符解析配置
<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>
Java的屬性@Value的占位符解析配置
@Value 注解值進(jìn)行屬性占位符解析和替換
@Value("${config}")
private String config;
PropertyPlaceholderConfigurer和PropertySourcesPlaceholderConfigurer
通過配置xml來實(shí)現(xiàn)對(duì)Classpath下的配置文件的占位符的屬性進(jìn)行注入,或者實(shí)現(xiàn)Java的屬性@Value的占位符解析配置冗荸。
- 在Spring3.1版本之前是通過PropertyPlaceholderConfigurer實(shí)現(xiàn)的承璃。
- 在Spring3.1之后則是通過PropertySourcesPlaceholderConfigurer實(shí)現(xiàn)的。
注意:在Spring Context 3.1或者更高版本中蚌本,缺省使用PropertySourcesPlaceholderConfigurer工具替換了PlaceholderConfigurerSupport盔粹,而<=3.0較老的Spring Context中,為了保持和之前的版本兼容魂毁,缺省還是使用PropertyPlaceholderConfigurer玻佩。
PropertyPlaceholderConfigurer和PropertySourcesPlaceholderConfigurer的實(shí)現(xiàn)分析
- PropertyPlaceholderConfigurer本質(zhì)是基于PlaceholderConfigurerSupport實(shí)現(xiàn)讀取配置的。
- PropertySourcesPlaceholderConfigurer是PlaceholderConfigurerSupport的特殊化實(shí)現(xiàn)席楚。
下圖介紹對(duì)應(yīng)的配置解析的繼承關(guān)系圖譜咬崔。
PropertyPlaceholderConfigurer和PropertySourcesPlaceholderConfigurer的執(zhí)行目標(biāo)
PropertyPlaceholderConfigurer和PropertyPlaceholderConfigurer在使用上并無本質(zhì)的區(qū)別,兩者的根本目標(biāo)是將配置文件生成KV對(duì),真正的注入工作并不由它們本身執(zhí)行垮斯。
PropertySourcesPlaceholderConfigurer它用于解析bean定義中的屬性值郎仆,以及注解@Value的值,使用的屬性來源是當(dāng)前的Spring Environment對(duì)象兜蠕,以及設(shè)置給自己的PropertySources對(duì)象扰肌。
Spring Boot 自動(dòng)配置類 PropertyPlaceholderAutoConfiguration
@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
public class PropertyPlaceholderAutoConfiguration {
@Bean
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
PropertyPlaceholderAutoConfiguration定義一個(gè)PropertySourcesPlaceholderConfigurer bean,該bean作為一個(gè)BeanFactoryPostProcessor熊杨,會(huì)在容器啟動(dòng)時(shí)容器后置處理階段執(zhí)行自己的任務(wù)曙旭。BeanFactoryPostProcessor的優(yōu)先級(jí)又優(yōu)于其余的Bean。因此可以實(shí)現(xiàn)在bean初始化之前的注入晶府。
postProcessBeanFactory方法的執(zhí)行
如果外部指定了this.propertySources, 則直接使用它桂躏,否則從當(dāng)前Spring的Environment 對(duì)象和自身的 #mergeProperties 方法調(diào)用返回的 Properties 對(duì)象構(gòu)建屬性源對(duì)象 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;
}
構(gòu)造一個(gè)基于特定屬性源 this.propertySources 對(duì)屬性值進(jìn)行解析的屬性值解析器PropertySourcesPropertyResolver, 對(duì)容器中所有的 bean 定義中的屬性值,構(gòu)造函數(shù)參數(shù)值川陆。
/**
* 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 {
// 設(shè)置屬性值解析器所使用的占位符格式參數(shù)剂习,缺省為:
// 占位符前綴 ${
propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
// 占位符后綴 }
propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
// 缺省值分隔符 :
propertyResolver.setValueSeparator(this.valueSeparator);
// 結(jié)合屬性 this. ignoreUnresolvablePlaceholders對(duì)propertyResolver 作進(jìn)一步封裝,
// 封裝出來一個(gè) StringValueResolver valueResolver,這是最終要應(yīng)用的屬性值解析器
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);
};
// 調(diào)用基類PlaceholderConfigurerSupport實(shí)現(xiàn)的對(duì)容器中所有 bean定義進(jìn)行遍歷處理屬性值中占位符解析的邏輯
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) 是為一個(gè) LinkedList添加值较沪。在取用的時(shí)候是優(yōu)先從鏈表頭開始取用的鳞绕。 一旦發(fā)現(xiàn)無法找到值,直接就拋異常了尸曼。這個(gè)就對(duì)外體現(xiàn)出 PropertySourcesPlaceholderConfigurer 的唯一性们何。
然而Spring內(nèi)部還是有多個(gè)PropertySourcesPlaceholderConfigurer, 只不過除了排列在隊(duì)首的 PropertySourcesPlaceholderConfigurer之外全都被忽略掉了骡苞。
PropertySourcesPlaceholderConfigurer屬性注入的原理
AbstractApplicationContext#obtainFreshBeanFactory
Spring框架進(jìn)行植入元素注入時(shí)機(jī)
針對(duì)于元素的注入依賴于
AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues1垂蜗。
AbstractApplicationContext#finishBeanFactoryInitialization方法
在Spring初始化流程中,執(zhí)行AbstractApplicationContext#finishBeanFactoryInitialization方法解幽。 該方法里面發(fā)生的主要流程為Spring業(yè)務(wù)Bean初始化。 實(shí)際流程跟Spring Bean的初始化沒有任務(wù)區(qū)別烘苹。
InstantiationAwareBeanPostProcessor
- 通過對(duì)接口 InstantiationAwareBeanPostProcessor 實(shí)現(xiàn)類的方法進(jìn)行執(zhí)行躲株。 僅此而已。
AutowiredAnnotationBeanPostProcessor
- InjectionMetadataInjectionMetadataInjectedElementInjectedElement這個(gè)類是 InstantiationAwareBeanPostProcessor的一個(gè)實(shí)現(xiàn)類镣衡。
@Value和@Autowired注解實(shí)際執(zhí)行
用于@Value和@Autowired注解實(shí)際執(zhí)行方法postProcessPropertyValues調(diào)度實(shí)際調(diào)度InjectedElement子類被注入值的獲取來自于DefaultListableBeanFactory將對(duì)應(yīng)@Value(“${configValue}”)里面的值替換的來源值霜定,是PropertySourcesPlaceholderConfigurer生成的StringValueResolver。
Spring原生的Bean是單例的它直接被儲(chǔ)存在了AbstractBeanFactory執(zhí)行Field.set(Object, Object)或者M(jìn)ethod.invoke(Object, Object[])廊鸥。
所以望浩,可以看出 PropertySourcesPlaceholderConfigurer 或者 PropertyPlaceholderConfigurer僅僅是做了一個(gè)配置文件的解析工作,真正的注入并不由它們完成惰说,而是托付給了Spring 的Bean初始化流程磨德。這兩個(gè)類實(shí)現(xiàn)了BeanFactoryPostProcessor 接口,這個(gè)接口的優(yōu)先級(jí)高于后續(xù)的Spring Bean。
通過解析了的PropertySourcesPlaceholderConfigurer查詢得到元素值典挑。 沒有則拋出異常酥宴,如下源碼:
DefaultListableBeanFactory#doResolveDependency
@Value 注解值進(jìn)行屬性占位符解析和替換
// 獲取注解的 value() 值。被寫死為 Class<? extends Annotation> valueAnnotationType = Value.class;
// 見類 QualifierAnnotationAutowireCandidateResolver
Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
if (value != null) {
if (value instanceof String) {
// 通過PropertySourcesPlaceholderConfigurer寫入的鍵值對(duì)元素獲取元素的值.
// 方法內(nèi)注冊(cè)了多個(gè)StringValueResolver您觉,循環(huán)查找值拙寡。提供者為PropertySourcesPlaceholderConfigurer,因此配置多個(gè)解析器的時(shí)候是以最后的配置為準(zhǔn)的。
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()));
}
讀取配置的方式介紹
xml文件讀取配置信息案例
通過PropertyPlaceholderConfigurer進(jìn)行配置Bean方式
單個(gè)配置文件琳水。
<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>
多個(gè)配置文件
注意這兩種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>
Spring標(biāo)簽方式
<context:property-placeholder location="classpath*:/WEB-INF/mail.properties" />
這總方式的原理就是構(gòu)造一個(gè)PropertySourcesPlaceholderConfigurer, (3.1之前是PropertyPlaceholderConfigurer)
- ContextNamespaceHandler#init
- PropertyPlaceholderBeanDefinitionParser#doParse
注入配置觸發(fā)點(diǎn)
Spring初始化Context的時(shí)候讀取XML配置肆糕, 這個(gè)流程優(yōu)先于Spring普通Bean初始化。配合掃包(<context:component-scan />)得到的Bean進(jìn)而實(shí)現(xiàn)對(duì)XML里面配置的Bean的載入在孝。
- PropertySourcesPlaceholderConfigurer本質(zhì)上是一個(gè)BeanFactoryPostProcessor诚啃。解析XML的流程在BeanFactoryPostProcessor之前, 優(yōu)先將配置文件的路徑以及名字通過Setter傳入PropertySourcesPlaceholderConfigurer浑玛。
總結(jié)Spring Value注入流程
構(gòu)建PropertySourcesPlaceholderConfigurerBean或者PropertyPlaceholderConfigurerBean的組件
配置Spring @Value("val2Inject") 方式獲取配置文件的屬性绍申,需要依賴于在Spring XML里面配置<context:property-placeholder /> 或者PropertySourcesPlaceholderConfigurerBean來添加配置文件的名稱。
讀取到context:property-placeholder標(biāo)簽或者PropertySourcesPlaceholderConfigurer
解析并實(shí)例化一個(gè)PropertySourcesPlaceholderConfigurer顾彰。同時(shí)向其中注入配置文件路徑极阅、名稱PropertySourcesPlaceholderConfigurer自身生成多個(gè)StringValueResolver備用,Bean準(zhǔn)備完畢涨享。-
Spring在初始化非BeanFactoryPostProcessor的Bean的時(shí)候筋搏,AutowiredAnnotationBeanPostProcessor負(fù)責(zé)找到Bean內(nèi)有@Value注解的Field或者M(jìn)ethod
- 通過PropertySourcesPlaceholderConfigurer尋找合適的StringValueResolver并解析得到val值。注入給@Value的Field或Method厕隧。
AutowiredAnnotationBeanPostProcessor負(fù)責(zé)@Autowired和@Value兩個(gè)注解的解析奔脐。
@PropertySource注解配置讀取單個(gè)或多個(gè)配置文件
單個(gè)配置文件:
@PropertySource(value = "classpath:config/application-config.properties")
多個(gè)配置文件:
@PropertySource(value = {"classpath:config/application-config1.properties","classpath:config/application-config2.properties"})
@PropertySource注解使用有兩種方式
@PropertySource + Environment,通過@PropertySource注解將properties配置文件中的值存儲(chǔ)到Spring的Environment中吁讨,Environment接口提供方法去讀取配置文件中的值髓迎,參數(shù)是properties文件中定義的key值。
@PropertySource(PropertySourcesPlaceholderConfigurer) +@Value
@PropertySource + Environment
@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.properties:
config.key=1
config.value=2
測(cè)試類操作
public class Test {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(TestPropertieEnvironment.class);
ServiceConfiguration hc2 = (TestPropertieEnvironment) context.getBean("testPropertieEnvironment");
hc2.properties();
}
}
@PropertySource(PropertySourcesPlaceholderConfigurer)+@Value
PropertySourcesPlaceholderConfigurer是PlaceholderConfigurerSupport的特殊化實(shí)現(xiàn)建丧。它用于解析bean定義中的屬性值排龄,以及注解@Value的值,使用的屬性來源是當(dāng)前的Spring Environment對(duì)象翎朱,以及設(shè)置給自己的PropertySources對(duì)象橄维。
大于3.1更高版本中,缺省使用該工具替換了PlaceholderConfigurerSupport
<=3.0較老的Spring中拴曲,為了保持和之前的版本兼容争舞,缺省還是使用PropertyPlaceholderConfigurer。
創(chuàng)建PropertySourcesPlaceholderConfigurer
創(chuàng)建PropertiesConfig
@Component
@PropertySource(value = "classpath:config/application-config.properties")
public class PropertiesConfig {
@Value("${config.value}")
private String value;
@Value("${config.key}")
private String key;
}
測(cè)試類忽略澈灼!
自定義PropertyPlaceholderConfigurer
@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());
}
}
測(cè)試類
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情況竞川。
Spring4版本的PropertySources的注解
在Spring 4版本中,Spring提供了一個(gè)新的注解——@PropertySources,從名字就可以猜測(cè)到它是為多配置文件而準(zhǔn)備的。
@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 + "]";
}
}