依赖配置中心实现注有@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) {// NOSONARString 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;}@Overridepublic 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;}@Subscribepublic 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));//NOSONARprivate 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 proxiesenhancer.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 {@Overridepublic 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;}@Overridepublic 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;//业务
    @ConfigFieldprivate List<Scene> bizs;//业务和渠道映射关系
    @ConfigFieldprivate 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;}@ToInitialprivate 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);}}//刷新事件调用
    @Overridepublic void refreshForEvent() {this.refreshQrcodeProperties();}//本地资源文件
    @Overridepublic String getDefaultResourcePath() {return "config/qrcode.properties";}//属性配置 公共前缀(和@ConfigurationProperties prefix 属性一致)
    @Overridepublic String getConfigPrefix() {return "wx.temporary.qrcode";}//模块名称
    @Overridepublic String getModuleName() {return "微信临时二维码配置";}@Overridepublic 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;}@Overridepublic 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;}@ToInitialprivate 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);}}@Overridepublic void refreshForEvent() {this.refreshQrcodeProperties();}@Overridepublic String getDefaultResourcePath() {return "config/qrcode.properties";}@Overridepublic String getConfigPrefix() {return "wx.temporary.qrcode";}@Overridepublic String getModuleName() {return "微信临时二维码配置";}@Overridepublic 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;}@Overridepublic String toString() {return ReflectionToStringBuilder.toString(this, ToStringStyle.JSON_STYLE, false, false, Scene.class);}}
}

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

 

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

  参考文章:算法和技术SHARING

转载于:https://www.cnblogs.com/hujunzheng/p/9697282.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/531221.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java接口签名(Signature)实现方案

预祝大家国庆节快乐&#xff0c;赶快迎接美丽而快乐的假期吧&#xff01;&#xff01;&#xff01; 前言 在为第三方系统提供接口的时候&#xff0c;肯定要考虑接口数据的安全问题&#xff0c;比如数据是否被篡改&#xff0c;数据是否已经过时&#xff0c;数据是否可以重复提交…

Git rebase命令实战

一、前言 一句话&#xff0c;git rebase 可以帮助项目中的提交历史干净整洁&#xff01;&#xff01;&#xff01; 二、避免合并出现分叉现象 git merge操作 1、新建一个 develop 分支 2、在develop分支上新建两个文件 3、然后分别执行 add、commit、push 4、接着切换到master分…

windows系统nexus3安装和配置

一、前言 为什么要在本地开发机器上安装nexus&#xff1f;首先声明公司内部是有自己的nexus仓库&#xff0c;但是对上传jar包做了限制&#xff0c;不能畅快的上传自己测试包依赖。于是就自己在本地搭建了一个nexus私服&#xff0c;即可以使用公司nexus私服仓库中的依赖&#xf…

Springmvc借助SimpleUrlHandlerMapping实现接口开关功能

一、接口开关功能 1、可配置化&#xff0c;依赖配置中心 2、接口访问权限可控 3、springmvc不会扫描到&#xff0c;即不会直接的将接口暴露出去 二、接口开关使用场景 和业务没什么关系&#xff0c;主要方便查询系统中的一些状态信息。比如系统的配置信息&#xff0c;中间件的状…

log4j平稳升级到log4j2

一、前言 公司中的项目虽然已经用了很多的新技术了&#xff0c;但是日志的底层框架还是log4j&#xff0c;个人还是不喜欢用这个的。最近项目再生产环境上由于log4j引起了一场血案&#xff0c;于是决定升级到log4j2。 二、现象 虽然生产环境有多个结点分散高并发带来的压力&…

Springboot集成ES启动报错

报错内容 None of the configured nodes are available elasticsearch.yml配置 cluster.name: ftest node.name: node-72 node.master: true node.data: true network.host: 112.122.245.212 http.port: 39200 transport.tcp.port: 39300 discovery.zen.ping.unicast.hosts: [&…

kafka-manager配置和使用

kafka-manager配置 最主要配置就是用于kafka管理器状态的zookeeper主机。这可以在conf目录中的application.conf文件中找到。 kafka-manager.zkhosts"my.zookeeper.host.com:2181" 当然也可以声明为zookeeper集群。 kafka-manager.zkhosts"my.zookeeper.host.co…

kafka告警简单方案

一、前言 为什么要设计kafka告警方案&#xff1f;现成的监控项目百度一下一大堆&#xff0c;KafkaOffsetMonitor、KafkaManager、 Burrow等&#xff0c;具体参考&#xff1a;kafka的消息挤压监控。由于本小组的项目使用的kafka集群并没有被公司的kafka-manager管理&#xff0c;…

RedisCacheManager设置Value序列化器技巧

CacheManager基本配置 请参考博文&#xff1a;springboot2.0 redis EnableCaching的配置和使用 RedisCacheManager构造函数 /*** Construct a {link RedisCacheManager}.* * param redisOperations*/ SuppressWarnings("rawtypes") public RedisCacheManager(RedisOp…

HashMap 源码阅读

前言 之前读过一些类的源码&#xff0c;近来发现都忘了&#xff0c;再读一遍整理记录一下。这次读的是 JDK 11 的代码&#xff0c;贴上来的源码会去掉大部分的注释, 也会加上一些自己的理解。 Map 接口 这里提一下 Map 接口与1.8相比 Map接口又新增了几个方法&#xff1a;   …

SpringMvc接口中转设计(策略+模板方法)

一、前言 最近带着两个兄弟做支付宝小程序后端相关的开发&#xff0c;小程序首页涉及到很多查询的服务。小程序后端服务在我司属于互联网域&#xff0c;相关的查询服务已经在核心域存在了&#xff0c;查询这块所要做的工作就是做接口中转。参考了微信小程序的代码&#xff0c;发…

SpringSecurity整合JWT

一、前言 最近负责支付宝小程序后端项目设计&#xff0c;这里主要分享一下用户会话、接口鉴权的设计。参考过微信小程序后端的设计&#xff0c;会话需要依靠redis。相关的开发人员和我说依靠Redis并不是很靠谱&#xff0c;redis在业务高峰期不稳定&#xff0c;容易出现问题&…

Springboot定时任务原理及如何动态创建定时任务

一、前言 上周工作遇到了一个需求&#xff0c;同步多个省份销号数据&#xff0c;解绑微信粉丝。分省定时将销号数据放到SFTP服务器上&#xff0c;我需要开发定时任务去解析文件。因为是多省份&#xff0c;服务器、文件名规则、数据规则都不一定&#xff0c;所以要做成可配置是有…

转载:ThreadPoolExecutor 源码阅读

前言 之前研究了一下如何使用ScheduledThreadPoolExecutor动态创建定时任务(Springboot定时任务原理及如何动态创建定时任务)&#xff0c;简单了解了ScheduledThreadPoolExecutor相关源码。今天看了同学写的ThreadPoolExecutor 的源码解读&#xff0c;甚是NB&#xff0c;必须转…

使用pdfBox实现pdf转图片,解决中文方块乱码等问题

一、引入依赖 <dependency><groupId>org.apache.pdfbox</groupId><artifactId>fontbox</artifactId><version>2.0.13</version> </dependency> <dependency><groupId>org.apache.pdfbox</groupId><artif…

Spring异步调用原理及SpringAop拦截器链原理

一、Spring异步调用底层原理 开启异步调用只需一个注解EnableAsync Target(ElementType.TYPE) Retention(RetentionPolicy.RUNTIME) Documented Import(AsyncConfigurationSelector.class) public interface EnableAsync {/*** Indicate the async annotation type to be detec…

Spring MVC源码——Root WebApplicationContext

Spring MVC源码——Root WebApplicationContext 打算开始读一些框架的源码,先拿 Spring MVC 练练手,欢迎点击这里访问我的源码注释, SpringMVC官方文档一开始就给出了这样的两段示例: WebApplicationInitializer示例: public class MyWebApplicationInitializer implements Web…

Spring MVC源码——Servlet WebApplicationContext

上一篇笔记(Spring MVC源码——Root WebApplicationContext)中记录了下 Root WebApplicationContext 的初始化代码.这一篇来看 Servlet WebApplicationContext 的初始化代码 DispatcherServlet 是另一个需要在 web.xml 中配置的类, Servlet WebApplicationContext 就由它来创建…

Springboot源码——应用程序上下文分析

前两篇(Spring MVC源码——Root WebApplicationContext 和 Spring MVC源码——Servlet WebApplicationContext)讲述了springmvc项目创建上下文的过程&#xff0c;这一篇带大家了解一下springboot项目创建上下文的过程。 SpringApplication引导类 SpringApplication类用于启动或…

基于zookeeper实现分布式配置中心(一)

最近在学习zookeeper&#xff0c;发现zk真的是一个优秀的中间件。在分布式环境下&#xff0c;可以高效解决数据管理问题。在学习的过程中&#xff0c;要深入zk的工作原理&#xff0c;并根据其特性做一些简单的分布式环境下数据管理工具。本文首先对zk的工作原理和相关概念做一下…