首页 > 其他分享 >BeanFactory后置处理器之PropertySourcesPlaceholderConfigurer

BeanFactory后置处理器之PropertySourcesPlaceholderConfigurer

时间:2023-12-23 19:44:07浏览次数:27  
标签:调用 String 后置 BeanFactory PropertySourcesPlaceholderConfigurer propertySources ne

有的时候,我们需要读取配置文件中的属性,将其作为成员变量赋给对应的Bean,如下通过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.user}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

又或者,使用@Value注解,通过Java代码配置:

public class JdbcBean {

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

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

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

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

那么这个是如何实现的呢?原来,Spring提供了一种配置解析的功能,在Spring3.1版本之前是通过PropertyPlaceholderConfigurer实现的。而3.1之后则是通过PropertySourcesPlaceholderConfigurer 实现的。Spring已经发展到5.x了,所以今天我们主要来解析一下PropertySourcesPlaceholderConfigurer 。

自定义一个PropertySourcesPlaceholderConfigurer

我们可以在代码中,创建一个PropertySourcesPlaceholderConfigurer,并指定它解析的配置文件地址,如下:

@Bean
public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer(){
    PropertySourcesPlaceholderConfigurer propertySources = null;
    try{
        propertySources = new PropertySourcesPlaceholderConfigurer();
        ClassPathResource classPathResource = new ClassPathResource("application.properties");
        propertySources.setLocation(classPathResource);
    }catch(Exception ex){
        ex.printStackTrace();
    }
    return propertySources;
}

使用注解方式

@Component 
@PropertySource("classpath:application.properties")
public class JdbcBean {}

Spring Bean的创建过程

首先我们来看一下Bean的创建过程(AbstractApplicationContext的refresh过程中的一些调用),由于这个过程在这里不是我们主要要讲解的,所以大略体会一下,并且记住其中有一个叫BeanFactoryPostProcessor的,后面我们还会提到它,创建过程如下:

  • 实例化BeanFactoryPostProcessor实现类
  • 调用BeanFactoryPostProcessor#postProcessBeanFactory
  • 实例化BeanPostProcessor实现类
  • 调用InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation
  • 实例化Bean
  • 调用InstantiationAwareBeanProcessor#postProcessAfterInstantiation
  • 调用InstantiationAwareBeanPostProcessor#postProcessPropertyValues
  • 为Bean注入属性
  • 调用BeanNameAware#setBeanName
  • 调用BeanClassLoaderAware#setBeanClassLoader
  • 调用BeanFactoryAware#setBeanFactory
  • 调用BeanPostProcessor#postProcessBeforeInitialization
  • 调用InitializingBean#afterPropertiesSet
  • 调用Bean的init-method
  • 调用BeanPostProcessor#postProcessAfterInitialization

源码分析

数据源加载

在Spring3.1之后,建议使用PropertySourcesPlaceholderConfigurer来取代PropertyPlaceholderConfigurer。

可以发现,其实PropertySourcesPlaceholderConfigurer是BeanFactoryPostProcessor的一个子类,所以在Bean的创建过程中可以得知,在执行过程中会调用postProcessBeanFactory,所以我们查找下对应的方法,定义如下:

@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;
}

我们其实不难发现,属性来源分为两种:

  • 以Environment为属性源的environmentProperties
  • 通过loadProperties(Properties props)加载本地资源文件作为属性源的localProperties。属性源加载完毕后,将占位符替换为属性源中的属性。

占位符解析

属性源都加载完毕,接下来就是占位符的填充,源码如下:

protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
                                 final ConfigurablePropertyResolver propertyResolver) throws BeansException {
    //this.placeholderPrefix为 ${
    propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
    //this.placeholderSuffix为 }
    propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
    //this.valueSeparator为 : 
    propertyResolver.setValueSeparator(this.valueSeparator);
    // 使用lambda表达式创建一个StringValueResolver
    StringValueResolver valueResolver = strVal -> {
        // 解析占位符,此处只能解析占位符
        String resolved = (ignoreUnresolvablePlaceholders ?
                           propertyResolver.resolvePlaceholders(strVal) :
                           propertyResolver.resolveRequiredPlaceholders(strVal));
        if (trimValues) {
            resolved = resolved.trim();
        }
        return (resolved.equals(nullValue) ? null : resolved);
    };
    // 调用父类的doProcessProperties  把属性扫描到Bean的身上去
    doProcessProperties(beanFactoryToProcess, valueResolver);
}
protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
                                   StringValueResolver valueResolver) {

    BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);

    String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
    for (String curName : beanNames) {
        //排除自身&&必须是同一个beanFactory
        if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
            BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
            try {
                visitor.visitBeanDefinition(bd);
            }
            catch (Exception ex) {
                throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
            }
        }
    }

    // 解析别名目标名称和别名中的占位符
    beanFactoryToProcess.resolveAliases(valueResolver);

    //在嵌入值(例如注释属性)中解析占位符
    beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
}

上面就是对PropertySourcesPlaceholderConfigurer工作原理的源码解析,概括来说分为两步:

  • 属性源装配
    • environmentProperties
    • localProperties
  • 占位符解析
    • 解析占位符中的key
    • 将key替换成对应的属性值

PropertySourcesPlaceholderConfigurer因为汇聚了Environment、多个PropertySource;所以它能够控制取值优先级、顺序,并且还提供了访问的方法,后期再想获取也不成问题。

@Autowired
private ApplicationContext applicationContext;
// 通过它,可以把生效的配置都拿到
@Autowired
private PropertySourcesPlaceholderConfigurer configurer;

public void getData() {
    Environment environment = applicationContext.getEnvironment();
    PropertySources appliedPropertySources = configurer.getAppliedPropertySources();

    System.out.println(environment.containsProperty("bean.scope")); //false  注意环境里是没有这个key的
    System.out.println(appliedPropertySources);
    // 获取环境的和我们自己导入的
    PropertySource<?> envProperties = appliedPropertySources.get(PropertySourcesPlaceholderConfigurer.ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME);
    PropertySource<?> localProperties = appliedPropertySources.get(PropertySourcesPlaceholderConfigurer.LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME);
    System.out.println(envProperties.getSource() == environment); //true  可以看到这个envProperties的source和环境里的是同一个
    System.out.println(localProperties.containsProperty("bean.scope"));//true 本地配置里是包含这个属性的
}

还有另外一个类PropertyOverrideConfigurer,PropertyOverrideConfigurer类似于PropertySourcesPlaceholderConfigurer,与PropertyPlaceholderConfigurer 不同的是:PropertyOverrideConfigurer 利用属性文件的相关信息,覆盖XML 配置文件中定义。即PropertyOverrideConfigurer允许XML 配置文件中有默认的配置信息。

需要注意的是Properties属性文件:

beanName.property=value  //第一个.前面一定是beanName

请保证这个beanName一定存在。它会根据beanName找到这个bean,然后override这个bean的相关属性值的。

因为这个类使用得相对较少,但使用步骤基本同上,因此此处就不再叙述了。

标签:调用,String,后置,BeanFactory,PropertySourcesPlaceholderConfigurer,propertySources,ne
From: https://www.cnblogs.com/xfeiyun/p/17923022.html

相关文章

  • Spring的Bean后置处理器之AnnotationAwareAspectJAutoProxyCreator
    本文能帮你回答以下几个问题;AnnotationAwareAspectJAutoProxyCreator后置器的作用是什么?SpringAOP自动增强bean是如何实现的。如何在spring上下文添加AnnotationAwareAspectJAutoProxyCreator?如何利用ProxyFactory硬编码实现一个bean的增强?AnnotationAwareAspectJAutoProx......
  • jmeter-后置处理器
    json提取器场景使用:多个接口,第二个接口的某个请求入参需要使用到第一个接口的响应数据中的某字段; 一般接口响应都是json数据,所以针对响应的json数据,提取其中的某字段,一般使用json提取器。比如:接口a搜索库存,获取库存;接口b去购买-最新的库存;    思路:就是将第......
  • Spring的Bean工厂的后置处理器BeanFactoryPostProcessor执行流程
    BeanFactoryPostProcessor作用BeanFactoryPostProcessor是Spring框架中的一个重要接口,用于在BeanFactory加载Bean定义之后、实例化Bean之前对BeanFactory进行自定义修改和扩展。它允许开发人员在Spring容器加载配置文件并创建Bean实例之前对Bean定义进行操作,例如修改属性值、添加......
  • Spring的后置处理器BeanPostProcessor接口的执行流程
    BeanPostProcessor的设计目标主要是提供一种扩展机制,让开发者可以在SpringBean的初始化阶段进行自定义操作。这种设计理念主要体现了Spring的一种重要原则,即“开放封闭原则”。开放封闭原则强调软件实体(类、模块、函数等等)应该对于扩展是开放的,对于修改是封闭的。在这里,Spring容......
  • 一、Spring学习 : 容器---->BeanFactory+ApplicationContext 的多种容器实现
    BeanFactory实现的特点我们来着重讲一下DefaultListableBeanFactory这个实现类:点击查看完整代码packagecom.itvayne.springbootcloudstudy.beanfactory01;importcom.sun.org.slf4j.internal.Logger;importcom.sun.org.slf4j.internal.LoggerFactory;importorg.springf......
  • BeanShell 后置处理程序
     Stringresponse=prev.getResponseDataAsString();StringResponseCode=prev.getResponseCode();//获取状态码(同ResponseCode,String类型)StringresponseHeaders=prev.getResponseHeaders();//prev.getResponseDataAsString();//获取响应体数据(String类型)Stringresponse=pre......
  • 你知道Spring中BeanFactoryPostProcessors是如何执行的吗?
    Spring中的BeanFactoryPostProcessor是在Spring容器实例化Bean之后,初始化之前执行的一个扩展机制。它允许开发者在Bean的实例化和初始化之前对BeanDefinition进行修改和处理,从而对Bean的创建过程进行干预和定制化。BeanFactoryPostProcessor接口定义了一个方法:postProcessBeanFac......
  • python+pytest写测试用例后置清理数据操作
    一、teardown_function函数是为了在每个测试函数def执行后进行数据清理。#引入DbConnect类或者确保它已经被定义fromyour_db_moduleimportDbConnectdefteardown_function():try:print("后置操作-做数据清理,把批注通知删掉")db......
  • Spring5学习随笔-生命周期、自定义类型转换器、后置处理Bean
    学习视频:【孙哥说Spring5:从设计模式到基本应用到应用级底层分析,一次深入浅出的Spring全探索。学不会Spring?只因你未遇见孙哥】第十章、对象的生命周期1.什么是对象的生命周期指的是一个对象创建、存活、消亡的一个完整过程2.为什么要学习对象的生命周期由Spring负责对象的......
  • springboot~ConfigurableListableBeanFactory和ApplicationContext的使用场景
    在工具类中封装getBean,使用哪个接口来实现实事上,在工具类中,实现BeanFactoryPostProcessor和ApplicationContextAware接口后,使用它们构造方法里的对象ConfigurableListableBeanFactory和ApplicationContext都可以很方便的获取spring容器里的bean,而在实际应用时,还有有些不同的,比如在......