【深入淺出Spring原理及實戰】「源碼原理實戰」從底層角度去分析研究PropertySourcesPlaceholderConfigurer的原理及實戰注入機制

2022-12-19 10:14:38 來源:51CTO博客

Spring提供配置解析功能

主要有一下xml文件占位符解析和Java的屬性@Value的占位符解析配置這兩種場景進行分析和實現解析,如下面兩種案例。


(資料圖)

xml文件的占位符解析配置

            

Java的屬性@Value的占位符解析配置

@Value 注解值進行屬性占位符解析和替換

@Value("${config}")private String config;

PropertyPlaceholderConfigurer和PropertySourcesPlaceholderConfigurer

通過配置xml來實現對Classpath下的配置文件的占位符的屬性進行注入,或者實現Java的屬性@Value的占位符解析配置。

在Spring3.1版本之前是通過PropertyPlaceholderConfigurer實現的。在Spring3.1之后則是通過PropertySourcesPlaceholderConfigurer實現的。

注意:在Spring Context 3.1或者更高版本中,缺省使用PropertySourcesPlaceholderConfigurer工具替換了PlaceholderConfigurerSupport,而<=3.0較老的Spring Context中,為了保持和之前的版本兼容,缺省還是使用PropertyPlaceholderConfigurer

PropertyPlaceholderConfigurer和PropertySourcesPlaceholderConfigurer的實現分析
PropertyPlaceholderConfigurer本質是基于PlaceholderConfigurerSupport實現讀取配置的。PropertySourcesPlaceholderConfigurerPlaceholderConfigurerSupport的特殊化實現。

下圖介紹對應的配置解析的繼承關系圖譜。

PropertyPlaceholderConfigurer和PropertySourcesPlaceholderConfigurer的執行目標

PropertyPlaceholderConfigurerPropertyPlaceholderConfigurer在使用上并無本質的區別,兩者的根本目標是將配置文件生成KV對,真正的注入工作并不由它們本身執行。

PropertySourcesPlaceholderConfigurer它用于解析bean定義中的屬性值,以及注解@Value的值,使用的屬性來源是當前的Spring Environment對象,以及設置給自己的PropertySources對象。

Spring Boot 自動配置類 PropertyPlaceholderAutoConfiguration
@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初始化之前的注入。

postProcessBeanFactory方法的執行

如果外部指定了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之外全都被忽略掉了。

PropertySourcesPlaceholderConfigurer屬性注入的原理

AbstractApplicationContext#obtainFreshBeanFactory

Spring框架進行植入元素注入時機

針對于元素的注入依賴于AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues1。

AbstractApplicationContext#finishBeanFactoryInitialization方法

在Spring初始化流程中,執行AbstractApplicationContext#finishBeanFactoryInitialization方法。 該方法里面發生的主要流程為Spring業務Bean初始化。 實際流程跟Spring Bean的初始化沒有任務區別。

InstantiationAwareBeanPostProcessor
通過對接口 InstantiationAwareBeanPostProcessor實現類的方法進行執行。 僅此而已。
AutowiredAnnotationBeanPostProcessor
InjectionMetadataInjectionMetadataInjectedElementInjectedElement這個類是 InstantiationAwareBeanPostProcessor的一個實現類。
@Value和@Autowired注解實際執行
用于@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 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()));}

讀取配置的方式介紹

xml文件讀取配置信息案例

通過PropertyPlaceholderConfigurer進行配置Bean方式

單個配置文件。
        conf/sqlmap/jdbc.properties             UTF-8    
多個配置文件

注意這兩種value值的寫法

                          /WEB-INF/mail.properties              classpath: conf/sqlmap/jdbc.properties         
Spring標簽方式

這總方式的原理就是構造一個PropertySourcesPlaceholderConfigurer, (3.1之前是PropertyPlaceholderConfigurer)

ContextNamespaceHandler#initPropertyPlaceholderBeanDefinitionParser#doParse
注入配置觸發點

Spring初始化Context的時候讀取XML配置, 這個流程優先于Spring普通Bean初始化。配合掃包()得到的Bean進而實現對XML里面配置的Bean的載入。

PropertySourcesPlaceholderConfigurer本質上是一個BeanFactoryPostProcessor。解析XML的流程在BeanFactoryPostProcessor之前, 優先將配置文件的路徑以及名字通過Setter傳入PropertySourcesPlaceholderConfigurer。

總結Spring Value注入流程

構建PropertySourcesPlaceholderConfigurerBean或者PropertyPlaceholderConfigurerBean的組件
配置Spring @Value("val2Inject") 方式獲取配置文件的屬性,需要依賴于在Spring XML里面配置 或者PropertySourcesPlaceholderConfigurerBean來添加配置文件的名稱。讀取到context:property-placeholder標簽或者PropertySourcesPlaceholderConfigurer解析并實例化一個PropertySourcesPlaceholderConfigurer。同時向其中注入配置文件路徑、名稱PropertySourcesPlaceholderConfigurer自身生成多個StringValueResolver備用,Bean準備完畢。Spring在初始化非BeanFactoryPostProcessor的Bean的時候,AutowiredAnnotationBeanPostProcessor負責找到Bean內有@Value注解的Field或者Method通過PropertySourcesPlaceholderConfigurer尋找合適的StringValueResolver并解析得到val值。注入給@Value的Field或Method。AutowiredAnnotationBeanPostProcessor負責@Autowired和@Value兩個注解的解析。

@PropertySource注解配置讀取單個或多個配置文件

單個配置文件:
@PropertySource(value = "classpath:config/application-config.properties")
多個配置文件:
@PropertySource(value = {"classpath:config/application-config1.properties","classpath:config/application-config2.properties"})

@PropertySource注解使用有兩種方式

@PropertySource +Environment,通過@PropertySource注解將properties配置文件中的值存儲到Spring的Environment中,Environment接口提供方法去讀取配置文件中的值,參數是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=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();    }}
@PropertySource(PropertySourcesPlaceholderConfigurer)+@Value

PropertySourcesPlaceholderConfigurer是PlaceholderConfigurerSupport的特殊化實現。它用于解析bean定義中的屬性值,以及注解@Value的值,使用的屬性來源是當前的Spring Environment對象,以及設置給自己的PropertySources對象。

大于3.1更高版本中,缺省使用該工具替換了PlaceholderConfigurerSupport<=3.0較老的Spring中,為了保持和之前的版本兼容,缺省還是使用PropertyPlaceholderConfigurer。
創建PropertySourcesPlaceholderConfigurer
創建PropertiesConfig
@Component@PropertySource(value = "classpath:config/application-config.properties")public class PropertiesConfig {    @Value("${config.value}")    private String value;    @Value("${config.key}")    private String key; }

測試類忽略!

自定義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;    }    }@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情況。

Spring4版本的PropertySources的注解

在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 + "]";    } }

標簽: 配置文件

上一篇:zookeeper可視化界面zkui搭建與配置
下一篇:每日動態!Prometheus Metrics設計的最佳實踐和應用實例