当前位置 : 主页 > 编程语言 > java >

【深入浅出Spring原理及实战】「源码原理实战」从底层角度去分析研究PropertySourcesPlaceholderConfigurer的原理及实战注入机制

来源:互联网 收集:自由互联 发布时间:2023-02-04
Spring提供配置解析功能 主要有一下xml文件占位符解析和Java的属性@Value的占位符解析配置这两种场景进行分析和实现解析,如下面两种案例。 xml文件的占位符解析配置 bean id="dataSource"

Spring提供配置解析功能

主要有一下xml文件占位符解析和Java的属性@Value的占位符解析配置这两种场景进行分析和实现解析,如下面两种案例。

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 注解值进行属性占位符解析和替换

@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实现读取配置的。
  • PropertySourcesPlaceholderConfigurer是PlaceholderConfigurerSupport的特殊化实现。

下图介绍对应的配置解析的继承关系图谱。

【深入浅出Spring原理及实战】「源码原理实战」从底层角度去分析研究PropertySourcesPlaceholderConfigurer的原理及实战注入机制_属性值

PropertyPlaceholderConfigurer和PropertySourcesPlaceholderConfigurer的执行目标

PropertyPlaceholderConfigurer和PropertyPlaceholderConfigurer在使用上并无本质的区别,两者的根本目标是将配置文件生成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>(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<? extends Annotation> 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方式

    单个配置文件。
    <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>
    Spring标签方式
    <context:property-placeholder location="classpath*:/WEB-INF/mail.properties" />

    这总方式的原理就是构造一个PropertySourcesPlaceholderConfigurer, (3.1之前是PropertyPlaceholderConfigurer)

    • ContextNamespaceHandler#init
    • PropertyPlaceholderBeanDefinitionParser#doParse
    注入配置触发点

    Spring初始化Context的时候读取XML配置, 这个流程优先于Spring普通Bean初始化。配合扫包(<context:component-scan />)得到的Bean进而实现对XML里面配置的Bean的载入。

    • PropertySourcesPlaceholderConfigurer本质上是一个BeanFactoryPostProcessor。解析XML的流程在BeanFactoryPostProcessor之前, 优先将配置文件的路径以及名字通过Setter传入PropertySourcesPlaceholderConfigurer。

    总结Spring Value注入流程

    构建PropertySourcesPlaceholderConfigurerBean或者PropertyPlaceholderConfigurerBean的组件
  • 配置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
    • 通过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 + "]"; } }
    上一篇:Java根据ip地址获取归属地
    下一篇:没有了
    网友评论