Nacos动态配置刷新
环境
springboot |
3.0.5 |
|
springCloud |
2022.0.3 |
|
springCloudAlibaba |
2022.0.0.0-RC2 |
|
探索
共有两种方式来获取nacos的配置
@ConfigurationProperties
@Value+@RefreshScope
配置类形式
原理:通过监听nacos端的配置动态变化
使用springboot的事件监听机制
监听器如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationReadyEvent) { handle((ApplicationReadyEvent) event); } else if (event instanceof RefreshEvent) { handle((RefreshEvent) event); } }
public void handle(RefreshEvent event) { if (this.ready.get()) { log.debug("Event received " + event.getEventDesc()); Set<String> keys = this.refresh.refresh(); log.info("Refresh keys changed: " + keys); } }
|
跳转到ContextRefresher
类中
1 2 3 4 5 6 7 8 9
| public synchronized Set<String> refreshEnvironment() { Map<String, Object> before = extract(this.context.getEnvironment().getPropertySources()); updateEnvironment(); Set<String> keys = changes(before, extract(this.context.getEnvironment().getPropertySources())).keySet(); this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys)); return keys; }
|
下面是更新配置的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| @Override protected void updateEnvironment() { addConfigFilesToEnvironment(); }
ConfigurableApplicationContext addConfigFilesToEnvironment() { ConfigurableApplicationContext capture = null; try { StandardEnvironment environment = copyEnvironment(getContext().getEnvironment());
Map<String, Object> map = new HashMap<>(); map.put("spring.jmx.enabled", false); map.put("spring.main.sources", ""); map.put("spring.main.web-application-type", "NONE"); map.put(BOOTSTRAP_ENABLED_PROPERTY, Boolean.TRUE.toString()); environment.getPropertySources().addFirst(new MapPropertySource(REFRESH_ARGS_PROPERTY_SOURCE, map));
SpringApplicationBuilder builder = new SpringApplicationBuilder(Empty.class).bannerMode(Banner.Mode.OFF) .web(WebApplicationType.NONE).environment(environment); builder.application().setListeners( Arrays.asList(new BootstrapApplicationListener(), new BootstrapConfigFileApplicationListener())); capture = builder.run(); if (environment.getPropertySources().contains(REFRESH_ARGS_PROPERTY_SOURCE)) { environment.getPropertySources().remove(REFRESH_ARGS_PROPERTY_SOURCE); } MutablePropertySources target = getContext().getEnvironment().getPropertySources(); String targetName = null; for (PropertySource<?> source : environment.getPropertySources()) { String name = source.getName(); if (target.contains(name)) { targetName = name; } if (!this.standardSources.contains(name)) { if (target.contains(name)) { target.replace(name, source); } else { if (targetName != null) { target.addAfter(targetName, source); targetName = name; } else { target.addFirst(source); targetName = name; } } } } } finally { ConfigurableApplicationContext closeable = capture; while (closeable != null) { try { closeable.close(); } catch (Exception e) { } if (closeable.getParent() instanceof ConfigurableApplicationContext) { closeable = (ConfigurableApplicationContext) closeable.getParent(); } else { break; } } } return capture; }
|
更新配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| private boolean rebind(String name, ApplicationContext appContext) { try { Object bean = appContext.getBean(name); if (AopUtils.isAopProxy(bean)) { bean = ProxyUtils.getTargetObject(bean); } if (bean != null) { if (getNeverRefreshable().contains(bean.getClass().getName())) { return false; } appContext.getAutowireCapableBeanFactory().destroyBean(bean); appContext.getAutowireCapableBeanFactory().initializeBean(bean, name); return true; } } catch (RuntimeException e) { this.errors.put(name, e); throw e; } catch (Exception e) { this.errors.put(name, e); throw new IllegalStateException("Cannot rebind to " + name, e); } return false; }
|
Value形式
原理:改变Bean实例化的方式,通过spring广播机制把Bean删除之后再重新实例化
Spring框架真是神奇,Bean的操作基本都可以进行定制
1 2 3 4
| @RefreshScope public class TestController { @Value("${fang.test}") private String configValue;
|
- 在Spring Ioc容器中,不走寻常的创建Bean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| else { String scopeName = mbd.getScope(); if (!StringUtils.hasLength(scopeName)) { throw new IllegalStateException("No scope name defined for bean '" + beanName + "'"); } Scope scope = this.scopes.get(scopeName); if (scope == null) { throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'"); } try { Object scopedInstance = scope.get(beanName, () -> { beforePrototypeCreation(beanName); try { return createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } }); beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); } catch (IllegalStateException ex) { throw new ScopeNotActiveException(beanName, scopeName, ex); } } }
|
在ContextRefresher
直接对Bean的缓存管理器进行清除
总结
从这一个小功能能看出cloud基本都是基于boot
而配置动态更新这一功能,更是将Ioc容器玩出花来了
属实是springboot中Bean的最佳应用了