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

Posted 毕永飞

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在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);

以上是关于在Spring-boot中,为@Value注解添加从数据库读取properties支持的主要内容,如果未能解决你的问题,请参考以下文章

Spring-boot 中@Async使用的坑

spring-boot整合SSM框架

spring-boot主要注解

spring-boot 注解集合

spring-boot入门之二——web环境

MyBatis框架之注解开发