• 手撸一个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);
                        }
                    }
                }
            }
        }

  • 相关阅读:
    静态代理模式
    当对象或属性为空时,如何安全的给默认值
    linux抓取日志
    BigDecimal进行比较
    int与Double类型转换
    通过配置文件的key得值
    ObjectNode ArrayNode JsonNode
    java.lang.NoSuchFieldError报错解决方案
    BigDecimal.setScale 处理java小数点
    Jmeter实现压力测试(多并发测试)
  • 原文地址:https://www.cnblogs.com/changxy-codest/p/14614772.html
Copyright © 2020-2023  润新知