手擼一個SpringBoot配置中心實現配置動態刷新


業務需求

SpringBoot項目配置信息大多使用@Value注解或者@ConfigurationProperties注解讀取配置信息,線上項目經常需要對某些配置進行調整,如果每次都需要修改配置文件再重新發布服務,難免會導致服務中斷。

尤其是在分布式系統中多個服務節點都需要修改配置文件的場景,基於此配置中心也應運而生。

如果我們的項目使用了SpringCloud,那么可選的配置中心有很多,比如Nacos、spring-cloud-starter-config、Apollo等,這些配置中心都需要借助SpringCloud架構才能實現配置刷新。

這里我們的應用沒有集成SpringCloud,也不想因為配置中心而讓應用架構變重,所以需要基於SpringBoot基礎實現一個輕量級的配置動態刷新功能。

 

遠程配置中心

對配置資源進行管理,所有應用連接配置中心讀取配置信息,可選用的配置中心包括:

1、配置中心中間件,例如:Nacos;

2、數據庫存儲;

3、Git倉庫;

4、文件系統;

5、其它具備持久化存儲及訪問功能的中間件。

 

說點聽得懂的實現原理

想要實現配置動態刷新可以從Spring的Bean初始化和屬性值注入原理入手,這里我們跳過原理分析階段,感興趣的同學自行百度,建議大家可以直接看下Spring的PropertyPlaceholderAutoConfiguration類。

刷新配置信息僅需簡單兩步:

1、刷新Spring的Environment環境變量;

2、刷新Spring托管的Bean實例的屬性值;

 

刷新Environment

/**
     * 刷新環境變量
     * @param properties
     */
    private MutablePropertySources refreshEnvironment(Properties properties) {
        Map<String, Object> props = new HashMap<>();
        properties.stringPropertyNames().stream().forEach(key ->
                props.put(key, properties.getProperty(key))
        );
        // 獲取spring的environment
        MutablePropertySources mutablePropertySources = environment.getPropertySources();
        // 添加遠程配置信息
        mutablePropertySources.addFirst(new MapPropertySource("remoteConfig", props));

        return mutablePropertySources;
    }

 

刷新Bean實例

需要注意一下幾點:

1、我們不用自己解析@Value的value,通過Spring提供的PropertySourcesPropertyResolver.resolveRequiredPlaceholders即可從環境變量中獲取對應的屬性值;

2、@Value可以使用EL表達式,注入的屬性類型可以是String、List等對象,通過Spring提供的SpelExpressionParser類實現EL表達式解析和運算取值。

/**
     * 刷新Bean實例的屬性值
     * @param bean
     * @param propertyResolver
     */
    private void refreshBean(Object bean, ConfigurablePropertyResolver propertyResolver) {

        // 定義EL表達式解釋器
        SpelExpressionParser spelExpressionParser;
        spelExpressionParser = new SpelExpressionParser();
        TemplateParserContext templateParserContext;
        templateParserContext = new TemplateParserContext();

        String keyResolver, valueResolver = null;
        Object parserValue;
        // 遍歷Bean實例所有屬性
        for (Field field : bean.getClass().getDeclaredFields()) {
            // 判斷field是否含有@Value注解
            if (field.isAnnotationPresent(Value.class)) {
                // 讀取Value注解占位符
                keyResolver = field.getAnnotation(Value.class).value();
                try {
                    // 讀取屬性值
                    valueResolver = propertyResolver.resolveRequiredPlaceholders(keyResolver);
                    // EL表達式解析
                    // 兼容形如:@Value("#{'${url}'.split(',')}")含有EL表達式的情況
                    parserValue = spelExpressionParser.parseExpression(valueResolver, templateParserContext).getValue(field.getType());

                } catch (IllegalArgumentException e) {
                    log.warn("{}", e.getMessage());
                    continue;
                }
                // 判斷配置項是否存在
                if (Objects.nonNull(valueResolver)) {
                    field.setAccessible(true);
                    try {
                        field.set(bean, parserValue);
                        continue;
                    } catch (IllegalAccessException e) {
                        log.error("{}刷新屬性值出錯, bean: [{}], field: [{}], value: [{}]",
                                TAG, bean.getClass().getName(), field.getName(), valueResolver);
                    }
                }
            }
        }
    }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM