依赖配置中心实现注有@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
本文来自博客园,作者:hjzqyx,转载请注明原文链接:https://www.cnblogs.com/hujunzheng/p/9697282.html