[轉]Spring中property-placeholder的使用與解析


我們在基於spring開發應用的時候,一般都會將數據庫的配置放置在properties文件中.

代碼分析的時候,涉及的知識點概要:

  1. NamespaceHandler 解析xml配置文件中的自定義命名空間
  2. ContextNamespaceHandler 上下文相關的解析器,這邊定義了具體如何解析property-placeholder的解析器
  3. BeanDefinitionParser 解析bean definition的接口
  4. BeanFactoryPostProcessor 加載好bean definition后可以對其進行修改
  5. PropertySourcesPlaceholderConfigurer 處理bean definition 中的占位符

我們先來看看具體的使用吧

property的使用

在xml文件中配置properties文件

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xmlns:context="http://www.springframework.org/schema/context"  xsi:schemaLocation="  http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans-4.2.xsd  http://www.springframework.org/schema/context  http://www.springframework.org/schema/context/spring-context-4.2.xsd"> <context:property-placeholder location="classpath:foo.properties" /> </beans>

這樣/src/main/resources/foo.properties文件就會被spring加載
如果想使用多個配置文件,可以添加order字段來進行排序

使用PropertySource注解配置

Spring3.1添加了@PropertySource注解,方便添加property文件到環境.

@Configuration
@PropertySource("classpath:foo.properties")
public class PropertiesWithJavaConfig {

   @Bean
   public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
      return new PropertySourcesPlaceholderConfigurer();
   }
}

properties的注入與使用

  1. java中使用@Value注解獲取

    @Value( "${jdbc.url}" ) private String jdbcUrl;

還可以添加一個默認值

@Value( "${jdbc.url:aDefaultUrl}" ) private String jdbcUrl;
  1. 在Spring的xml配置文件中獲取

    <bean id="dataSource"> <property name="url" value="${jdbc.url}" /> </bean>

源碼解析

properties配置信息的加載

Spring在啟動時會通過AbstractApplicationContext#refresh啟動容器初始化工作,期間會委托loadBeanDefinitions解析xml配置文件.

    protected final void refreshBeanFactory() throws BeansException { if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); customizeBeanFactory(beanFactory); loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; } } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } }

loadBeanDefinitions通過層層委托,找到DefaultBeanDefinitionDocumentReader#parseBeanDefinition解析具體的bean

    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; if (delegate.isDefaultNamespace(ele)) { parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }

這邊由於不是標准類定義,所以委托BeanDefinitionParserDelegate解析
通過NamespaceHandler查找到對應的處理器是ContextNamespaceHandler,再通過id找到PropertyPlaceholderBeanDefinitionParser解析器解析

    @Override public void init() { // 這就是我們要找的解析器 registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser()); registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser()); registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser()); registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser()); registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser()); registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser()); registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser()); registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser()); }

PropertyPlaceholderBeanDefinitionParser是這一輪代碼分析的重點.
我們來看看它的父類吧.

  1. BeanDefinitionParser
    被DefaultBeanDefinitionDocumentReader用於解析個性化的標簽
    這邊只定義了一個解析Element的parse api

    public interface BeanDefinitionParser { BeanDefinition parse(Element element, ParserContext parserContext); }
  2. AbstractBeanDefinitionParser
    BeanDefinitionParser接口的默認抽象實現.spring的拿手好戲,這邊提供了很多方便使用的api,並使用模板方法設計模式給子類提供自定義實現的鈎子
    我們來看看parse時具體的處理邏輯把:
    • 調用鈎子parseInternal解析
    • 生成bean id,使用BeanNameGenerator生成,或者直接讀取id屬性
    • 處理name 與別名aliases
    • 往容器中注冊bean
    • 進行事件觸發
  3. AbstractSingleBeanDefinitionParser
    解析,定義單個BeanDefinition的抽象父類
    在parseInternal中,解析出parentName,beanClass,source;並使用BeanDefinitionBuilder進行封裝

  4. AbstractPropertyLoadingBeanDefinitionParser
    解析property相關的屬性,如location,properties-ref,file-encoding,order等

  5. PropertyPlaceholderBeanDefinitionParser
    這邊處理的事情不多,就是設置ingore-unresolvable和system-properties-mode

properties文件的加載,bean的實例化

接下來,我們再看看這個bean是在什么時候實例化的,一般類的實例化有2種,一種是單例系統啟動就實例化;一種是非單例(或者單例懶加載)在getBean時實例化.
這邊的觸發卻是通過BeanFcatoryPostProcessor.
BeanFactoryPostProcessor是在bean實例化前,修改bean definition的,比如bean definition中的占位符就是這邊解決的,而我們現在使用的properties也是這邊解決的.

這個是通過PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors實現的.
掃描容器中的BeanFactoryPostProcessor,找到了這邊需要的PropertySourcesPlaceholderConfigurer,並通過容器的getBean實例化

    protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) { PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors()); }

PropertySourcesPlaceholderConfigurer實例化完成后,就直接進行觸發,並加載信息

    OrderComparator.sort(priorityOrderedPostProcessors); invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);

我們再來看看PropertySourcesPlaceholderConfigurer的繼承體系把

    1. BeanFactoryPostProcessor
      定義一個用於修改容器中bean definition的屬性的接口.其實現類在一般類使用前先實例化,並對其他類的屬性進行修改.
      這跟BeanPostProcessor有明顯的區別,BeanPostProcessor是修改bean實例的.

    2. PropertiesLoaderSupport
      加載properties文件的抽象類.
      這邊具體的加載邏輯是委托PropertiesLoaderUtils#fillProperties實現

    3. PropertyResourceConfigurer
      bean definition中占位符的替換就是這個抽象類實現的.
      實現BeanFactoryPostProcessor#postProcessBeanFactory,迭代容器的中的類定義,進行修改
      具體如何修改就通過鈎子processProperties交由子類實現

    4. PlaceholderConfigurerSupport
      使用visitor設計模式,通過BeanDefinitionVisitor和StringValueResolver更新屬性
      StringValueResolver是一個轉化String類型數據的接口,真正更新屬性的api實現竟然是在PropertyPlaceholderHelper#parseStringValue

    5. PropertySourcesPlaceholderConfigurer
      覆寫postProcessorBeanFactory api定義解析流程


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM