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

来源:互联网 发布:优化网站方法 编辑:程序博客网 时间:2024/04/29 10:49

一般我们会把常用的属性放在工程的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);
4 0
原创粉丝点击