BeanFactory后置处理器之PropertySourcesPlaceholderConfigurer

发布时间 2023-12-23 19:44:19作者: 残城碎梦

有的时候,我们需要读取配置文件中的属性,将其作为成员变量赋给对应的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的相关属性值的。

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