• Spring中 <context:property-placeholder 的使用与解析 .properties 配置文件的加载


    转:

    Spring中property-placeholder的使用与解析

    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);
    }
    1. AbstractBeanDefinitionParser
      BeanDefinitionParser接口的默认抽象实现.spring的拿手好戏,这边提供了很多方便使用的api,并使用模板方法设计模式给子类提供自定义实现的钩子
      我们来看看parse时具体的处理逻辑把:
      • 调用钩子parseInternal解析
      • 生成bean id,使用BeanNameGenerator生成,或者直接读取id属性
      • 处理name 与别名aliases
      • 往容器中注册bean
      • 进行事件触发
    2. AbstractSingleBeanDefinitionParser
      解析,定义单个BeanDefinition的抽象父类
      在parseInternal中,解析出parentName,beanClass,source;并使用BeanDefinitionBuilder进行封装

    3. AbstractPropertyLoadingBeanDefinitionParser
      解析property相关的属性,如location,properties-ref,file-encoding,order等

    4. 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定义解析流程

    分类: spring
  • 相关阅读:
    clientWidth和offsetWidth区别 e.pageX和e.clientX区别
    vue笔记(七)组件的生命周期
    vue笔记(七)网络封装
    vue笔记(六)自定义消息弹出
    vue笔记(五)插槽. 路由
    组件中的样式问题,穿透和scale占位, 引入静态资源
    python基础-迭代器
    python基础-装饰器
    python基础-函数对象和闭包
    python基础-名称空间与作用域
  • 原文地址:https://www.cnblogs.com/libin6505/p/10437661.html
Copyright © 2020-2023  润新知