0
点赞
收藏
分享

微信扫一扫

Python中实现跑马灯效果

minute_5 03-27 22:30 阅读 2

BeanFactory后置处理器之PropertySourcesPlaceholderConfigurer

  1. Spring 中的实例
  2. SpringBoot 中的实例

Spring原来xml配置的时候,经常使用application.properties的配置数据库源码

<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 {
	@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 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的相关属性值的。

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

Springboot多属性文件配置

配置文件后缀有两种: .properties和.yml

要完成多属性配置需要自定义PropertySourcesPlaceholderConfigurer 这个Bean

properties配置方法

/**
   * 这里必须是static函数
   * 如果不是 application.propertise 将读取不到
   * application.properties 是默认加载的,这里配置自己的properties就好
   * @return
  */
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
    PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
    // properties 加载方式
    Resource[] resources = {
        //new ClassPathResource("application.properties"),
        //ClassPathResource针对的是resource目录下的文件
        new ClassPathResource("jdbc.properties")
    };
    configurer.setLocations(resources);
    return configurer;
}

注意:这个Bean 的函数必须是static的,否则会加载不到application.properties中的内容

这里不需要将application.properties也加进来,因为application.properties是默认加进来的,这里只要写其他的属性文件就好了

yml配置方法

@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
    PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();

    // yml 加载方式
    YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
    //这里可以传入多个自定义属性文件
    yaml.setResources(new ClassPathResource("jdbc.yml"));
    configurer.setProperties(yaml.getObject());

    configurer.setLocations(resources);
    return configurer;
}

yml 和properties的配置的注意点是一样的,只是需要YamlPropertiesFactoryBean来加载

  1. 如果引用到属性文件中的值

    jdbc.propertise

    jdbc.url=jdbc:mysql://localhost:3306/test?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8
    jdbc.driver=com.mysql.cj.jdbc.Driver
    jdbc.user=root
    jdbc.password=root@123
    

    JdbcConfig.java

    @Data
    @Configuration
    // 这里不需要了
    //@PropertySource("classpath:jdbc.properties")
    public class JdbcConfig {
    
       @Value("${jdbc.url}")
       private String url;
    
       @Value("${jdbc.driver}")
       private String driver;
    
       @Value("${jdbc.user}")
       private String user;
    
       @Value("${jdbc.password}")
       private String password;
    }
    

    这里用@Configuration或@Component都可以获得到值,

    注意:这里不需要使用@PropertySource了,直接用就可以了

    在Controller中使用(其他地方也可,这里是举例)

    public class TestController {
    
       @Autowired
       private JdbcConfig jdbcConfig;
    
    
       @PostMapping("/hello")
       public String Hello() {
           
           return jdbcConfig.getUrl() + "  " + jdbcConfig.getDriver() + " " + jdbcConfig.getUser() + " " + jdbcConfig.getPassword();
       }
    
    

更简洁的写法

@Configuration
public class SystemConfig {

    private static List<Resource> resourceList = new ArrayList<>();

    static {
        resourceList.add(new ClassPathResource("jdbc.properties"));
    }

    @Value("${spring.datasource.password}")
    private String datasourcePassword;

    /**
     * 这里必须是static函数
     * 如果不是 application.propertise 将读取不到
     * application.properties 是默认加载的,这里配置自己的properties就好
     * @return
     */
    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
        // properties 加载方式
        configurer.setLocations(resourceList.stream().toArray(Resource[]::new));
        return configurer;
    }

    public String getDatasourcePassword() {
        return datasourcePassword;
    }
}

完结

举报

相关推荐

0 条评论