zoukankan      html  css  js  c++  java
  • 依赖配置中心实现注有@ConfigurationProperties的bean相关属性刷新

    配置中心是什么

      配置中心,通过key=value的形式存储环境变量。配置中心的属性做了修改,项目中可以通过配置中心的依赖(sdk)立即感知到。需要做的就是如何在属性发生变化时,改变带有@ConfigurationProperties的bean的相关属性。

    配置中心原理

      在读配置中心源码的时候发现,里面维护了一个Environment,以及ZookeeperPropertySource。当配置中心属性发生变化的时候,清空ZookeeperPropertySource,并放入最新的属性值。

      

    public class ZookeeperPropertySource extends EnumerablePropertySource<Properties>

      

      ZookeeperPropertySource重写了equals和hahscode方法,根据这两个方法可以判定配置中心是否修改了属性。

       

    动态刷新bean属性原理

    实现原理图

      

    动态刷新bean父类

    public abstract class BaseConfigCenterBean implements InitializingBean {
    
        private static Logger LOGGER = LoggerFactory.getLogger(BaseConfigCenterBean.class);
    
        //配置中心是否生效
        protected boolean cfgCenterEffect = false;
    
        public boolean isCfgCenterEffect() {
            this.checkCfgCenterEffect();
            return cfgCenterEffect;
        }
    
        private void checkCfgCenterEffect() {
            boolean tmpCfgCenterEffect = !Objects.isNull(ConfigHelper.getEnvironment());
            if (tmpCfgCenterEffect) {// NOSONAR
                String value = (String) ConfigHelper.getZookeeperPropertySource().getProperty("cfg.center.effect");
                if (StringUtils.isBlank(value)) {
                    tmpCfgCenterEffect = false;
                } else {
                    tmpCfgCenterEffect = Boolean.valueOf(value);
                }
            }
    
            cfgCenterEffect = tmpCfgCenterEffect;
    
            if (cfgCenterEffect) {
                String prefix = this.getConfigPrefix();
                cfgCenterEffect = Arrays.stream(ConfigHelper.getZookeeperPropertySource().getPropertyNames())
                        .filter(keyName -> keyName.indexOf(prefix) == 0)
                        .count() > 0;
                if (!cfgCenterEffect) {
                    LOGGER.info(String.format("配置中心没有发现模块=%s, prefix=%s的配置,将使用本地配置...", this.getModuleName(), prefix));
                }
            }
        }
    
    
        /**
         * 绑定自身目标
         **/
        protected void doBind() {
            Class<? extends BaseConfigCenterBean> clazz = this.getClass();
            if (AopUtils.isCglibProxy(this)) {
                clazz = (Class<? extends BaseConfigCenterBean>) AopUtils.getTargetClass(this);
            }
            BaseConfigCenterBean target = binding(isCfgCenterEffect(), clazz, this.getDefaultResourcePath());
            this.copyProperties(target);
        }
    
        private void copyProperties(BaseConfigCenterBean target) {
            ReflectionUtils.doWithFields(this.getClass(), field -> {
                field.setAccessible(true);
                field.set(this, field.get(target));
            }, field -> AnnotatedElementUtils.isAnnotated(field, ConfigField.class));
        }
    
        /**
         * 绑定其他目标
         *
         * @param clazz 目标类
         **/
        protected <T> T doBind(Class<T> clazz) {
            T target = binding(isCfgCenterEffect(), clazz, this.getDefaultResourcePath());
            if (target instanceof InitializingBean) {
                try {
                    ((InitializingBean) target).afterPropertiesSet();
                } catch (Exception e) {
                    LOGGER.error(String.format("属性初始化失败[afterPropertiesSet], class=%s", ClassUtils.getSimpleName(clazz), e));
                }
            }
            return target;
        }
    
        private <T> T binding(boolean cfgCenterEffect, Class<T> clazz, String defaultResourcePath) {
            Optional<PropertySource> propertySource = Optional.empty();
    
            if (cfgCenterEffect) {
                propertySource = Optional.ofNullable(ConfigHelper.getZookeeperPropertySource());
            } else {
                Optional<ResourcePropertySource> resourcePropertySource = ResourceUtils.getResourcePropertySource(defaultResourcePath);
                if (resourcePropertySource.isPresent()) {
                    propertySource = Optional.ofNullable(resourcePropertySource.get());
                }
            }
    
            if (propertySource.isPresent()) {
                T target;
                try {
                    target = RelaxedConfigurationBinder
                            .with(clazz)
                            .setPropertySources(propertySource.get())
                            .doBind();
                } catch (GeneralException e) {
                    LOGGER.error(String.format("属性绑定失败, class=%s", ClassUtils.getSimpleName(clazz)), e);
                    return null;
                }
                return target;
            }
            return null;
        }
    
    
        @Override
        public void afterPropertiesSet() {
            Class<?> target = this.getClass();
            if (AopUtils.isAopProxy(this)) {
                target = AopUtils.getTargetClass(this);
            }
            LOGGER.info(String.format("%s->%s模块引入配置中心%s...", this.getModuleName(), ClassUtils.getSimpleName(target), (isCfgCenterEffect() ? "生效" : "无效")));
        }
    
        public String getModuleName() {
            return StringUtils.EMPTY;
        }
    
        @Subscribe
        public void listenRefreshEvent(ConfigCenterUtils.ConfigRefreshEvent refreshEvent) {
            if (!refreshEvent.getModuleName().equals(this.getModuleName())) {
                this.refreshForEvent();
            }
        }
    
        //通过事件进行刷新
        public abstract void refreshForEvent();
    
        //获取本地配置默认路径
        public abstract String getDefaultResourcePath();
    
        //获取配置属性的公共前缀
        public abstract String getConfigPrefix();
    }

      1、isCfgCenterEffect方法主要判断项目是否接入了配置中心并且配置中心配有bean中相关的属性。

      2、binding方法主要根据isCfgCenterEffect方法的返回值去加载配置中心的properties还是本地的properties。

      3、getDefaultResourcePath是主要是获取本地资源的默认路径(在没有接入配置中心的情况下)。

      4、getConfigPrefix方法返回bean中配置属性的公共前缀(等同于@ConfigurationProperties中的prefix属性)。

      5、refreshForEvent方法主要是在某个bean感知到配置中心更新属性时异步通知其他bean进行属性的更新。

    bean属性绑定工具类

      动态将propertysource绑定到带有@ConfigurationProperties注解的bean中。

      参考 org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor

    public class RelaxedConfigurationBinder<T> {
        private final PropertiesConfigurationFactory<T> factory;
    
        public RelaxedConfigurationBinder(T object) {
            this(new PropertiesConfigurationFactory<>(object));
        }
    
        public RelaxedConfigurationBinder(Class<?> type) {
            this(new PropertiesConfigurationFactory<>(type));
        }
    
        public static <T> RelaxedConfigurationBinder<T> with(T object) {
            return new RelaxedConfigurationBinder<>(object);
        }
    
        public static <T> RelaxedConfigurationBinder<T> with(Class<T> type) {
            return new RelaxedConfigurationBinder<>(type);
        }
    
        public RelaxedConfigurationBinder(PropertiesConfigurationFactory<T> factory) {
            this.factory = factory;
            ConfigurationProperties properties = getMergedAnnotation(factory.getObjectType(), ConfigurationProperties.class);
            javax.validation.Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
            factory.setValidator(new SpringValidatorAdapter(validator));
            factory.setConversionService(new DefaultConversionService());
            if (!Objects.isNull(properties)) {//NOSONAR
                factory.setIgnoreNestedProperties(properties.ignoreNestedProperties());
                factory.setIgnoreInvalidFields(properties.ignoreInvalidFields());
                factory.setIgnoreUnknownFields(properties.ignoreUnknownFields());
                factory.setTargetName(properties.prefix());
                factory.setExceptionIfInvalid(properties.exceptionIfInvalid());
            }
        }
    
        public RelaxedConfigurationBinder<T> setTargetName(String targetName) {
            factory.setTargetName(targetName);
            return this;
        }
    
        public RelaxedConfigurationBinder<T> setPropertySources(PropertySource<?>... propertySources) {
            MutablePropertySources sources = new MutablePropertySources();
            for (PropertySource<?> propertySource : propertySources) {
                sources.addLast(propertySource);
            }
            factory.setPropertySources(sources);
            return this;
        }
    
        public RelaxedConfigurationBinder<T> setPropertySources(Environment environment) {
            factory.setPropertySources(((ConfigurableEnvironment) environment).getPropertySources());
            return this;
        }
    
        public RelaxedConfigurationBinder<T> setPropertySources(PropertySources propertySources) {
            factory.setPropertySources(propertySources);
            return this;
        }
    
        public RelaxedConfigurationBinder<T> setConversionService(ConversionService conversionService) {
            factory.setConversionService(conversionService);
            return this;
        }
    
        public RelaxedConfigurationBinder<T> setValidator(Validator validator) {
            factory.setValidator(validator);
            return this;
        }
    
        public RelaxedConfigurationBinder<T> setResolvePlaceholders(boolean resolvePlaceholders) {
            factory.setResolvePlaceholders(resolvePlaceholders);
            return this;
        }
    
        public T doBind() throws GeneralException {
            try {
                return factory.getObject();
            } catch (Exception ex) {
                throw new GeneralException("配置绑定失败!", ex);
            }
        }
    }

    配置中心工具类

    public class ConfigCenterUtils {
        private static Logger LOGGER = LoggerFactory.getLogger(ConfigCenterUtils.class);
    
        private static AsyncEventBus eventBus = new AsyncEventBus(Executors.newFixedThreadPool(8));//NOSONAR
    
        private static Properties cfgProperties;
    
        private static Environment environment;
    
        static {
            cfgProperties = new Properties();
            cfgProperties.putAll(ConfigHelper.getZookeeperPropertySource().getProperties());
        }
    
        public static void setEnvironment(Environment environment) {
            ConfigCenterUtils.environment = environment;
        }
    
        public static String getValue(String name) {
            try {
                return PropertiesUtil.getValue(name);
            } catch (Exception e) {
                LOGGER.info("配置中心无效, property name=" + name, e);
            }
            if (Objects.isNull(environment)) {
                LOGGER.info("environment无效,property name=" + name);
                return StringUtils.EMPTY;
            }
            if (!environment.containsProperty(name)) {
                LOGGER.info("environment无配置 property name=" + name);
                return StringUtils.EMPTY;
            }
            return environment.getProperty(name);
        }
    
        public synchronized static boolean propertySourceShouldRefresh(String moduleName, ZookeeperPropertySource newPropertySource) {
            if (!cfgProperties.equals(newPropertySource.getProperties())) {
                cfgProperties.clear();
                cfgProperties.putAll(newPropertySource.getProperties());
                eventBus.post(new ConfigRefreshEvent(moduleName));
                return true;
            }
            return false;
        }
    
        public static <T> T createToRefreshPropertiesBean(Class<T> clazz) {
            Enhancer enhancer = new Enhancer();
            // 设置代理对象父类
            enhancer.setSuperclass(clazz);
            // 标识Spring-generated proxies
            enhancer.setInterfaces(new Class[]{SpringProxy.class});
            // 设置增强
            enhancer.setCallback((MethodInterceptor) (target, method, args, methodProxy) -> {
                ToRefresh toRefresh = AnnotationUtils.findAnnotation(method, ToRefresh.class);
                if (Objects.isNull(toRefresh) || StringUtils.isBlank(toRefresh.method())) {
                    return methodProxy.invokeSuper(target, args);
                }
                Method refreshMethod = ReflectionUtils.findMethod(target.getClass(), toRefresh.method());
                if (Objects.isNull(refreshMethod)) {
                    return methodProxy.invokeSuper(target, args);
                }
                refreshMethod = BridgeMethodResolver.findBridgedMethod(refreshMethod);
                refreshMethod.setAccessible(true);
                refreshMethod.invoke(target, null);
                return methodProxy.invokeSuper(target, args);
            });
    
            T target = (T) enhancer.create();// 创建代理对象
    
            MethodIntrospector.selectMethods(clazz, (ReflectionUtils.MethodFilter) method -> AnnotatedElementUtils.isAnnotated(method, ToInitial.class))
                    .stream().findFirst().ifPresent(method -> {
                method.setAccessible(true);
                try {
                    method.invoke(target, null);
                } catch (Exception e) {
                    LOGGER.error(String.format("初始化异常,class=%s ...", ClassUtils.getSimpleName(clazz)), e);
                }
            });
    
            return target;
        }
    
        public static void registerListener(BaseConfigCenterBean refreshableBean) {
            eventBus.register(refreshableBean);
        }
    
        public static class ConfigRefreshEvent {
            private String moduleName;
    
            public ConfigRefreshEvent(String moduleName) {
                this.moduleName = moduleName;
            }
    
            public String getModuleName() {
                return moduleName;
            }
    
            public void setModuleName(String moduleName) {
                this.moduleName = moduleName;
            }
        }
    }

      这个工具主要作用:

      1、判断配置中心的属性是否发生了变化

      2、为BaseConfigCenterBean子类创建代理类,使属性在getter方法时检测属性是否应该刷新。

      3、提供将BaseConfigCenterBean类型的对象的注册为guava eventbus的监听对象,使之具有根据刷新事件自动刷新自身属性。

    bean后置处理器

    public class ConfigCenterBeanPostProcessor implements BeanPostProcessor {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            if (AnnotatedElementUtils.isAnnotated(bean.getClass(), ConfigCenterBean.class)) {
                BaseConfigCenterBean refreshableBean = (BaseConfigCenterBean) ConfigCenterUtils.createToRefreshPropertiesBean(bean.getClass());
                ConfigCenterUtils.registerListener(refreshableBean);
                return refreshableBean;
            }
            return bean;
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            return bean;
        }
    }

      该后置处理器的作用是对所有BaseConfigCenterBean类型的bean进行处理,生成代理bean,并注册为guava eventbus相应的listener。

    pojo属性绑定配置中心优雅方案1

    @ConfigCenterBean
    @ConfigurationProperties(prefix = "wx.temporary.qrcode")
    @Component
    public class QrcodeConstants extends BaseConfigCenterBean {
    
        private static Logger LOGGER = LoggerFactory.getLogger(QrcodeConstants.class);
    
        //渠道
        @ConfigField //标识该属性来自配置中心
        private List<Scene> channels;
    
        //业务
        @ConfigField
        private List<Scene> bizs;
    
        //业务和渠道映射关系
        @ConfigField
        private Map<String, String> biz2Channel;
    
    
        private Map<String, Scene> channelMap;
    
        private Map<String, Scene> bizMap;
    
        public List<Scene> getChannels() {
            return channels;
        }
    
        public void setChannels(List<Scene> channels) {
            this.channels = channels;
        }
    
        public List<Scene> getBizs() {
            return bizs;
        }
    
        public void setBizs(List<Scene> bizs) {
            this.bizs = bizs;
        }
    
        @ToRefresh(method = "toRefresh")
        public Map<String, Scene> getChannelMap() {
            return channelMap;
        }
    
        @ToRefresh(method = "toRefresh")
        public Map<String, Scene> getBizMap() {
            return bizMap;
        }
    
        @ToRefresh(method = "toRefresh")
        public Map<String, String> getBiz2Channel() {
            return biz2Channel;
        }
    
        public void setBiz2Channel(Map<String, String> biz2Channel) {
            this.biz2Channel = biz2Channel;
        }
    
        @ToInitial
        private void refreshQrcodeProperties() {
            try {
                super.doBind();
    
                //属性处理
                if (CollectionUtils.isEmpty(channels)) {
                    this.channelMap = Maps.newHashMap();
                } else {
                    this.channelMap = channels.stream()
                            .collect(Collectors.toMap(channel -> channel.getType(), Function.identity()));
                }
    
                if (CollectionUtils.isEmpty(bizs)) {
                    this.bizMap = Maps.newHashMap();
                } else {
                    this.bizMap = bizs.stream()
                            .collect(Collectors.toMap(biz -> biz.getType(), Function.identity()));
                }
    
                LOGGER.info(String.format("%s 刷新成功..., 当前配置=%s...", this.getModuleName(), this));
            } catch (Exception e) {
                LOGGER.error("QrcodeConstants 对象属性绑定失败...", e);
            }
        }
    
        private void toRefresh() {
            try {
                if (isCfgCenterEffect()) {
                    ZookeeperPropertySource propertySource = ConfigHelper.getZookeeperPropertySource();
                    if (ConfigCenterUtils.propertySourceShouldRefresh(this.getModuleName(), propertySource)) {
                        this.refreshQrcodeProperties();
                    }
                }
            } catch (Exception e) {
                LOGGER.error("QrcodeConstants 对象属性刷新失败", e);
            }
        }
    
        //刷新事件调用
        @Override
        public void refreshForEvent() {
            this.refreshQrcodeProperties();
        }
    
      //本地资源文件
        @Override
        public String getDefaultResourcePath() {
            return "config/qrcode.properties";
        }
    
      //属性配置 公共前缀(和@ConfigurationProperties prefix 属性一致)
        @Override
        public String getConfigPrefix() {
            return "wx.temporary.qrcode";
        }
    
      //模块名称
        @Override
        public String getModuleName() {
            return "微信临时二维码配置";
        }
    
        @Override
        public String toString() {
            return ReflectionToStringBuilder.toString(this
                    , ToStringStyle.JSON_STYLE
                    , false
                    , false
                    , QrcodeConstants.class);
        }
    
        public static class Scene {
            private String type;
            private String desc;
    
            public String getType() {
                return type;
            }
    
            public void setType(String type) {
                this.type = type;
            }
    
            public String getDesc() {
                return desc;
            }
    
            public void setDesc(String desc) {
                this.desc = desc;
            }
    
            @Override
            public String toString() {
                return ReflectionToStringBuilder.toString(this
                        , ToStringStyle.JSON_STYLE
                        , false
                        , false
                        , Scene.class);
            }
        }
    }

    pojo属性绑定配置中心优雅方案2

    @ConfigCenterBean
    @Component
    public class QrcodeConstants extends BaseConfigCenterBean {
    
        private static Logger LOGGER = LoggerFactory.getLogger(QrcodeConstants.class);
    
        //业务和渠道映射关系
        private Map<String, String> biz2Channel;
    
        //渠道
        private Map<String, Scene> channelMap;
    
        //业务
        private Map<String, Scene> bizMap;
    
        private QrcodeProperties qrcodeProperties;
    
        @ToRefresh(method = "toRefresh")
        public Map<String, Scene> getChannelMap() {
            return channelMap;
        }
    
        @ToRefresh(method = "toRefresh")
        public Map<String, Scene> getBizMap() {
            return bizMap;
        }
    
        @ToRefresh(method = "toRefresh")
        public Map<String, String> getBiz2Channel() {
            return biz2Channel;
        }
    
        public void setBiz2Channel(Map<String, String> biz2Channel) {
            this.biz2Channel = biz2Channel;
        }
    
        public QrcodeProperties getRawQrcodeProperties() {
            return qrcodeProperties;
        }
    
        @ToInitial
        private void refreshQrcodeProperties() {
            try {
                QrcodeProperties qrcodeProperties = super.doBind(QrcodeProperties.class);
                if (Objects.isNull(qrcodeProperties)) {
                    LOGGER.error(String.format("没有加载到%s配置,请检查配置...", this.getModuleName()));
                    return;
                }
    
                this.qrcodeProperties = qrcodeProperties;
    
                //属性处理
                if (CollectionUtils.isEmpty(qrcodeProperties.channels)) {
                    this.channelMap = Maps.newHashMap();
                } else {
                    this.channelMap = qrcodeProperties.channels.stream()
                            .collect(Collectors.toMap(channel -> channel.getType(), Function.identity()));
                }
    
                if (CollectionUtils.isEmpty(qrcodeProperties.bizs)) {
                    this.bizMap = Maps.newHashMap();
                } else {
                    this.bizMap = qrcodeProperties.bizs.stream()
                            .collect(Collectors.toMap(biz -> biz.getType(), Function.identity()));
                }
    
                if (CollectionUtils.isEmpty(qrcodeProperties.getBiz2Channel())) {
                    this.biz2Channel = Maps.newHashMap();
                } else {
                    this.biz2Channel = qrcodeProperties.getBiz2Channel();
                }
    
                LOGGER.info(String.format("%s 刷新成功..., 当前配置=%s...", this.getModuleName(), this));
            } catch (Exception e) {
                LOGGER.error("QrcodeConstants 对象属性绑定失败...", e);
            }
        }
    
        private void toRefresh() {
            try {
                if (isCfgCenterEffect()) {
                    ZookeeperPropertySource propertySource = ConfigHelper.getZookeeperPropertySource();
                    if (ConfigCenterUtils.propertySourceShouldRefresh(this.getModuleName(), propertySource)) {
                        this.refreshQrcodeProperties();
                    }
                }
            } catch (Exception e) {
                LOGGER.error("QrcodeConstants 对象属性刷新失败", e);
            }
        }
    
        @Override
        public void refreshForEvent() {
            this.refreshQrcodeProperties();
        }
    
        @Override
        public String getDefaultResourcePath() {
            return "config/qrcode.properties";
        }
    
        @Override
        public String getConfigPrefix() {
            return "wx.temporary.qrcode";
        }
    
        @Override
        public String getModuleName() {
            return "微信临时二维码配置";
        }
    
        @Override
        public String toString() {
            return new ToStringBuilder(this)
                    .append("biz2Channel", biz2Channel)
                    .append("channelMap", channelMap)
                    .append("bizMap", bizMap)
                    .toString();
        }
    
        @ConfigurationProperties(prefix = "wx.temporary.qrcode")
        public static class QrcodeProperties {
            //渠道
            private List<Scene> channels;
    
            //业务
            private List<Scene> bizs;
    
            //业务和渠道映射关系
            private Map<String, String> biz2Channel;
    
            public List<Scene> getChannels() {
                return channels;
            }
    
            public void setChannels(List<Scene> channels) {
                this.channels = channels;
            }
    
            public List<Scene> getBizs() {
                return bizs;
            }
    
            public void setBizs(List<Scene> bizs) {
                this.bizs = bizs;
            }
    
            public Map<String, String> getBiz2Channel() {
                return biz2Channel;
            }
    
            public void setBiz2Channel(Map<String, String> biz2Channel) {
                this.biz2Channel = biz2Channel;
            }
        }
    
        public static class Scene {
            private String type;
            private String desc;
    
            public String getType() {
                return type;
            }
    
            public void setType(String type) {
                this.type = type;
            }
    
            public String getDesc() {
                return desc;
            }
    
            public void setDesc(String desc) {
                this.desc = desc;
            }
    
            @Override
            public String toString() {
                return ReflectionToStringBuilder.toString(this
                        , ToStringStyle.JSON_STYLE
                        , false
                        , false
                        , Scene.class);
            }
        }
    }

      方案1和方案2略有不同,针对一些属性,我们需要做一些逻辑处理。方案1中将源属性和逻辑之后的属性都放在了同一类中,方案二则是将源属性单独放到一个静态类中,最终处理过后的属性放在了目标类中。另外二者的doBind方法也是有区别的,仔细看一下BaseConfigCenterBean这个类就可以了。

         就先分享这么多了,更多分享请关注我们的技术公众吧!!!

      参考文章:算法和技术SHARING

  • 相关阅读:
    Python模糊查询本地文件夹去除文件后缀(7行代码)
    Python正则表达式
    python的logging模块
    Python中hashlib模块
    Python的os模块
    项目初始化mysql建库和授权
    Add correct host key in /root/.ssh/known_hosts to get rid of this message
    高中典型的等比数学题
    autoenv的使用方法
    celery任务进程关闭
  • 原文地址:https://www.cnblogs.com/hujunzheng/p/9697282.html
Copyright © 2011-2022 走看看