@RefreshScope那些事
要说清楚RefreshScope,先要了解Scope
Scope与ApplicationContext生命周期
AbstractBeanFactory#doGetBean创建Bean实例
protected <T> T doGetBean(...){ final RootBeanDefinition mbd = ... if (mbd.isSingleton()) { ... } else if (mbd.isPrototype()) ... } else { String scopeName = mbd.getScope(); final Scope scope = this.scopes.get(scopeName); Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {...}); ... } ... }
Singleton和Prototype是硬编码的,并不是Scope子类。 Scope实际上是自定义扩展的接口
Scope Bean实例交由Scope自己创建,例如SessionScope是从Session中获取实例的,ThreadScope是从ThreadLocal中获取的,而RefreshScope是在内建缓存中获取的。
@Scope 对象的实例化
@RefreshScope 是scopeName="refresh"的 @Scope
... @Scope("refresh") public @interface RefreshScope { ... }
@Scope 的注册 AnnotatedBeanDefinitionReader#registerBean
public void registerBean(...){ ... ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd); abd.setScope(scopeMetadata.getScopeName()); ... definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); }
读取@Scope元数据, AnnotationScopeMetadataResolver#resolveScopeMetadata
public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) { AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor( annDef.getMetadata(), Scope.class); if (attributes != null) { metadata.setScopeName(attributes.getString("value")); ScopedProxyMode proxyMode = attributes.getEnum("proxyMode"); if (proxyMode == null || proxyMode == ScopedProxyMode.DEFAULT) { proxyMode = this.defaultProxyMode; } metadata.setScopedProxyMode(proxyMode); } }
Scope实例对象通过ScopedProxyFactoryBean创建,其中通过AOP使其实现ScopedObject接口,这里不再展开
现在来说说RefreshScope是如何实现配置和实例刷新的
RefreshScope注册
RefreshAutoConfiguration#RefreshScopeConfiguration
@Component @ConditionalOnMissingBean(RefreshScope.class) protected static class RefreshScopeConfiguration implements BeanDefinitionRegistryPostProcessor{ ... registry.registerBeanDefinition("refreshScope", BeanDefinitionBuilder.genericBeanDefinition(RefreshScope.class) .setRole(BeanDefinition.ROLE_INFRASTRUCTURE) .getBeanDefinition()); ... }
RefreshScope extends GenericScope, 大部分逻辑在 GenericScope 中
GenericScope#postProcessBeanFactory 中向AbstractBeanFactory注册自己
public class GenericScope implements Scope, BeanFactoryPostProcessor...{ @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { beanFactory.registerScope(this.name/*refresh*/, this/*RefreshScope*/); ... } }
RefreshScope 刷新过程
入口在ContextRefresher#refresh
refresh() { Map<String, Object> before = ①extract( this.context.getEnvironment().getPropertySources()); ②addConfigFilesToEnvironment(); Set<String> keys = ④changes(before, ③extract(this.context.getEnvironment().getPropertySources())).keySet(); this.context.⑤publishEvent(new EnvironmentChangeEvent(keys)); this.scope.⑥refreshAll(); }
①提取标准参数(SYSTEM,JNDI,SERVLET)之外所有参数变量
②把原来的Environment里的参数放到一个新建的Spring Context容器下重新加载,完事之后关闭新容器
③提起更新过的参数(排除标准参数)
④比较出变更项
⑤发布环境变更事件,接收:EnvironmentChangeListener/LoggingRebinder
⑥RefreshScope用新的环境参数重新生成Bean
重新生成的过程很简单,清除refreshscope缓存幷销毁Bean,下次就会重新从BeanFactory获取一个新的实例(该实例使用新的配置)
RefreshScope#refreshAll
public void refreshAll() { <b>super.destroy();</b> this.context.publishEvent(new RefreshScopeRefreshedEvent()); } GenericScope#destroy public void destroy() { ... Collection<BeanLifecycleWrapper> wrappers = <b>this.cache.clear()</b>; for (BeanLifecycleWrapper wrapper : wrappers) { <b>wrapper.destroy();</b> } }
Spring Cloud Bus 如何触发 Refresh
BusAutoConfiguration#BusRefreshConfiguration 发布一个RefreshBusEndpoint
@Configuration @ConditionalOnClass({ Endpoint.class, RefreshScope.class }) protected static class BusRefreshConfiguration { @Configuration @ConditionalOnBean(ContextRefresher.class) @ConditionalOnProperty(value = "endpoints.spring.cloud.bus.refresh.enabled", matchIfMissing = true) protected static class BusRefreshEndpointConfiguration { @Bean public RefreshBusEndpoint refreshBusEndpoint(ApplicationContext context, BusProperties bus) { return new RefreshBusEndpoint(context, bus.getId()); } } }
RefreshBusEndpoint 会从http端口触发广播RefreshRemoteApplicationEvent事件
@Endpoint(id = "bus-refresh") public class RefreshBusEndpoint extends AbstractBusEndpoint { public void busRefresh() { publish(new RefreshRemoteApplicationEvent(this, getInstanceId(), null)); } }
BusAutoConfiguration#refreshListener 负责接收事件(所有配置bus的节点)
@Bean @ConditionalOnProperty(value = "spring.cloud.bus.refresh.enabled", matchIfMissing = true) @ConditionalOnBean(ContextRefresher.class) public RefreshListener refreshListener(ContextRefresher contextRefresher) { return new RefreshListener(contextRefresher); }
RefreshListener#onApplicationEvent 触发 ContextRefresher
public void onApplicationEvent(RefreshRemoteApplicationEvent event) { Set<String> keys = contextRefresher.refresh(); }
大部分需要更新的服务需要打上@RefreshScope, EurekaClient是如何配置更新的
EurekaClientAutoConfiguration#RefreshableEurekaClientConfiguration
@Configuration @ConditionalOnRefreshScope protected static class RefreshableEurekaClientConfiguration{ @Bean @RefreshScope public EurekaClient eurekaClient(...) { return new CloudEurekaClient(manager, config, this.optionalArgs, this.context); } @Bean @RefreshScope public ApplicationInfoManager eurekaApplicationInfoManager(...) { ... return new ApplicationInfoManager(config, instanceInfo); } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持小牛知识库。
主要内容:核心介绍,NodeSelectorSlot,ClusterBuilderSlot,StatisticSlot,FlowSlot,DegradeSlot,SystemSlot,ProcessorSlotChain,Context,Entry,Node,StatisticSlot核心介绍 在 Sentinel 里面,所有的资源都对应一个资源名称(resourceName),每次资源调用都会创建一个 Entry 对象。Entry 可以通过对主流框架的适配自动创建,也可以通过注解的方式或调用
本文向大家介绍SpringCloud配置刷新原理解析,包括了SpringCloud配置刷新原理解析的使用技巧和注意事项,需要的朋友参考一下 我们知道在SpringCloud中,当配置变更时,我们通过访问http://xxxx/refresh,可以在不启动服务的情况下获取最新的配置,那么它是如何做到的呢,当我们更改数据库配置并刷新后,如何能获取最新的数据源对象呢?下面我们看SpringCloud如何
Xwindow 使用服务器-客户端架构。无论本地图形界面,还是远程图形界面,都以同样的流程工作。这样便不需要分别进行设计和维护。 本地X客户端 ┐ ┌ 键盘 远程X客户端 ┼ X协议 ─ X服务器 ─ 驱动程序┼ 鼠标 远程X客户端 ┘ └ 显示器 Xserver Xwindow 系统服务器端,通过驱动程序(硬件规范)来管理硬件资源。 例如:当我们移动鼠标时,通过驱动程序[窗口
本文向大家介绍Android HandlerThread的使用及原理详解,包括了Android HandlerThread的使用及原理详解的使用技巧和注意事项,需要的朋友参考一下 一、HandlerThread的含义 HandlerThread能够新建拥有Looper的线程。这个Looper能够用来新建其他的Handler。(线程中的Looper)需要注意的是,新建的时候需要被回调。 二、Hand
本文向大家介绍JAVA8 的StringJoiner 使用及原理解析,包括了JAVA8 的StringJoiner 使用及原理解析的使用技巧和注意事项,需要的朋友参考一下 前几天偶然看到jdk1.8突然新增了一个类StringJoiner , 就研究了一下是怎么使用的, 以及看了下它的源码. 使用如下: //我们先看下他的构造方法: 如果只有中间的一个分割字符, 则其左边和右边的字符都是空, a
本文向大家介绍Java中的代理原理及代理使用示例,包括了Java中的代理原理及代理使用示例的使用技巧和注意事项,需要的朋友参考一下 今天再测试Socket编程时,无法连接外网。公司用的是Http的代理。上网搜索也没看太懂,所以花了大量时间来学习。看了HTTP和TCP协议的关系好,才有所明白。现在能通过Socket使用HTTP代理了,结果很简单,过程却好难。 1. 先简要说说HTTP和TCP(具体内