• 在Spring-boot中,为@Value注解添加从数据库读取properties支持


    一般我们会把常用的属性放在工程的classpath文件夹中,以property,yaml或json的格式进行文件存储,便于Spring-boot在初始化时获取。 

    @Value则是Spring一个非常有用的注解,可以在初始化时很方便的对Bean的入参变量进行赋值,例如:

    @Bean
        public BusinessClient businessClient (@Value("http://baseUrl/") String baseUrl) {
            Retrofit retrofit = new Retrofit.Builder()
                        .baseUrl(baseUrl)
                        .addConverterFactory(GsonConverterFactory.create())
                        .build();
            return retrofit.create(BusinessClient .class);

    于是,初始化好的Business Client进行http请求时,默认的baseurl都是”http://baseUrl“。
    实际上,@Value还支持一种特殊的写法:”${some.proptery.key}”,即将property的key值写在花括号中。例如,我有一个property为aerexu.basurl=http://baseUrl2,将上例中的@Value改写成@Value("${aerexu.basurl}"),baseUrl实际获取到的值是http://baseUrl2。 
    这个特性是利用Spring的bean PropertySourcesPlaceholder实现的,Spring boot已经在初始化时帮我们自动实例化了该bean。若是传统的Spring工程,则需要主动实例化,如下:

        @Bean
        public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
            PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
            configurer.setPlaceholderPrefix(PlaceholderConfigurerSupport.DEFAULT_PLACEHOLDER_PREFIX);
            configurer.setPlaceholderSuffix(PlaceholderConfigurerSupport.DEFAULT_PLACEHOLDER_SUFFIX);
            configurer.setValueSeparator(PlaceholderConfigurerSupport.DEFAULT_VALUE_SEPARATOR);  
            return configurer;
        }

    一般情况下,property存在工程中的文件就可以了,但带来的坏处是如果属性需要改变,必须重新发布工程。比如,对接上例中的url,可能会变为https,可能端口会变化。所以,这种类型的属性放在数据库中更合适。
    然而将属性存储在数据库中后,@Value对应的值就无法正常解析了。因此,这里提供一种hack的方法,使得@Value可以正常解析。
    PropertySourcesPlaceholder在解析属性时,都是从ConfigurableEnvironment中进行寻找的。当ConfigurableEnvironment没有存在的属性时,${}写法的@Value就无法解析了。因此,需要通过特殊的处理,将存储在数据库中的属性注入到ConfigurableEnvironment中。本文定义了一个LoadFromDatabasePropertyConfig类实现该功能,其代码如下:

        @Configuration
        @Slf4j
        public class LoadFromDatabasePropertyConfig {
            @Autowired
            private ConfigurableEnvironment env;
            @Autowired
            private SysPropertyResourceMapper propertyResourceMapper;
            @PostConstruct
            public void initializeDatabasePropertySourceUsage() {
                MutablePropertySources propertySources = env.getPropertySources();
                try {
                    Map<String, Object> propertyMap = propertyResourceMapper.selectAll().stream()
                            .collect(Collectors.toMap(SysPropertyResource::getPropertyName, SysPropertyResource::getPropertyValue));
                    Properties properties = new Properties();
                    properties.putAll(propertyMap);
                    PropertiesPropertySource dbPropertySource = new PropertiesPropertySource("dbPropertySource", properties);
                    Pattern p = Pattern.compile("^applicationConfig.*");
                    String name = null;
                    boolean flag = false;
                    for (PropertySource<?> source : propertySources) {
                        if (p.matcher(source.getName()).matches()) {
                            name = source.getName();
                            flag = true;
                            log.info("Find propertySources ".concat(name));
                            break;
                        }
                    }
                    log.info("=========================================================================");
                    if(flag) {
                        propertySources.addBefore(name, dbPropertySource);
                    } else {
                        propertySources.addFirst(dbPropertySource);
                    }
                } catch (Exception e) {
                    log.error("Error during database properties setup", e);
                    throw new RuntimeException(e);
                }
            }
        }

    上述代码的具体思路是将数据库中的所有需要的属性读出,通过Properties类转换为Spring可用的PropertiesPropertySource,并取名为dbPropertySource。随后利用正则匹配,从已有的所有属性中找到名称以applicationConfig开头的属性(该属性即是所有配置在文件中的property所解析成的对象),并将dbPropertySource存储在其之前。这样当文件和数据库中同时存在key相等的属性时,会优先使用数据库中存储的value。
    需要注意的是,上述方案提供的属性解析,必须在数据库相关的bean都实例化完成后才可进行。且为了保证bean在实例化时,数据库属性已经被加入到ConfigurableEnvironment中去了,必须添加@DependsOn注解。上面的BusinessClient的实例化就需更新成:

        @Bean
        @DependsOn("loadFromDatabasePropertyConfig")
        public BusinessClient businessClient (@Value("${aerexu.basurl}") String baseUrl) {
            Retrofit retrofit = new Retrofit.Builder()
                        .baseUrl(baseUrl)
                        .addConverterFactory(GsonConverterFactory.create())
                        .build();
            return retrofit.create(BusinessClient .class);
  • 相关阅读:
    进阶算子
    Scala中sortBy和Spark中sortBy区别
    简单算子演示
    map和FlatMap之间区别?
    RDD
    Spark高可用
    Django Rest Framework
    Scrapy
    asyncio
    BeautifulSoup
  • 原文地址:https://www.cnblogs.com/bevis-byf/p/11491475.html
Copyright © 2020-2023  润新知