配置中心工作流程
- 服务启动时,加载远程配置到配置中心(通过curator在zkServer上创建节点保存配置数据)
- 当需要修改配置时,通过配置中心后台控制台修改配置
- 配置中心的配置改动会同步到每个server上(节点数据修改触发wach回调事件更新数据到environment)
代码演示
1、本地配置配置文件
(1) 演示代码
1)本地配置文件application.yml
server.port: 8081
spring.freemarker.suffix: .ftl
resilience4j.circuitbreaker.failureRateThreshold: 50
resilience4j.circuitbreaker.ringBufferSizeInClosedState: 100
resilience4j.circuitbreaker.ringBufferSizeInHalfOpenState: 20
resilience4j.circuitbreaker.waitDurationInopenState: 60000
2)测试Controller类
@RestController
@RequestMapping("/hello")
public class HelloController {
@Autowired
Environment env;
@Value("${resilience4j.circuitbreaker.failureRateThreshold}")
int failureRateThreshold;
@Value("${resilience4j.circuitbreaker.ringBufferSizeInClosedState}")
int ringBufferSizeInClosedState;
@Value("${resilience4j.circuitbreaker.ringBufferSizeInHalfOpenState}")
int ringBufferSizeInHalfOpenState;
@Value("${resilience4j.circuitbreaker.waitDurationInopenState}")
long waitDurationInopenState;
@RequestMapping("/sayHello")
public String sayHello(@RequestParam("name") String name) {
System.out.println("env failureRateThreshold="+env.getProperty("resilience4j.circuitbreaker.failureRateThreshold"));
System.out.println("@Value failureRateThreshold=" + failureRateThreshold);
return "hello, " + name;
}
}
3)访问localhost:8081/hello/sayHello?name=Lucifer结果如下
缺陷是,如果配置需要修改,集群情况下多个项目都需要多次修改,并且需要重启项目。
(2) 本地配置实现原理
Spring doc Externalized Configuration: https://docs.spring.io/spring-boot/docs/2.7.2/reference/htmlsingle/#features.external-config
1)加载spring配置文件application.properties或者application.yml
SpringApplication#run() -> SpringApplication#prepareEnvironment() -> SpringApplicationRunListeners.environmentPrepared() -> PropertySourceLoader.load()
针对yml配置文件:YamlPropertySourceLoader.load()
针对properties配置文件:PropertiesPropertySourceLoader.load()
读取配置文件加载为PropertySource,最终加载到Environment中
2)@Value实现原理
a、使用BeanPostProcessor解析类上的@Value字段
b、获取到字段上的@Value字段,比如上面的failureRateThreshold属性
c、解析@Value字段的value属性值,比如上面的resilience4j.circuitbreaker.failureRateThreshold
d、从environment中的属性配置源OriginTrackedMapPropertySource中寻找对应的key
e、根据key获取到对应的value值
f、通过field反射的方式设置value值
源码调用过程
SpringApplication.run(args) -> refreshContext(context) -> refresh(context) -> ServletWebServerApplicationContext.refresh() -> AbstractApplicationContext.refresh() -> finishbeanfactoryInitialization(beanfactory) -> DefaultListablebeanfactory.preInstantiateSingletons() -> Abstractbeanfactory.getBean(name) -> doGetBean(name, requiredType, args, typeCheckOnly) -> 1) DefaultSingletonBeanRegistry.getSingleton(beanName, singletonFactory) -> 2) AbstractAutowireCapablebeanfactory.createBean(beanName, mbd, args) -> doCreateBean(beanName, mbd, args) -> populateBean(beanName, mbd, beanWrapper) -> AutowiredAnnotationBeanPostProcessor.postProcessproperties(propertyValues, bean, beanName) -> InjectionMetadata.inject(bean, beanName, pvs) -> AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(bean, beanName, pvs) -> resolveFieldValue(field, bean, beanName) -> DefaultListablebeanfactory.resolveDependency() -> doResolveDependency() -> Abstractbeanfactory.resolveEmbeddedValue(value) ->
(3) 本地配置存在的问题
1)修改application.properties中的属性值,在不重启项目的情况下不会自动更新
2)如果有多个微服务项目需要用到该属性值,就只能在各自项目中维护一份,不利于管理
2、Spring生态中的扩展机制
(1) 常见扩展机制
扩展机制就是不修改Spring生态源码,能够通过Spring提供的扩展把一些想要的代码放到启动流程中
初始化器ApplicationContextinitializer 事件监听机制 BeanPostProcessor beanfactoryPostProcessor ApplicationRunner
(2) 技术选型ApplicationContextinitializer
之所以选择初始化器,是因为我们需要在刷新上下文,使用Environment之前将Environment准备好,将zookeeper中的配置拉取更新到Environment中
1)初始化器 ApplicationContextinitializer
public class SpringApplication {
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class[]{primarySource}, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
public SpringApplication(Class<?>... primarySources) {
this((ResourceLoader)null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = Collections.emptySet();
this.isCustomEnvironment = false;
this.lazyInitialization = false;
this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
this.applicationStartup = ApplicationStartup.DEFAULT;
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
// Spring在启动的时候会通过SPI的方式读取spring.factories中所有的ApplicationContextinitializer类型,并实例化存储到list中
this.setinitializers(this.getSpringFactoriesInstances(ApplicationContextinitializer.class));
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
}
}
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
ConfigurableApplicationContext context = null;
this.configureHeadlessproperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment);
context = this.createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
Duration tiMetakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), tiMetakenToStartup);
}
listeners.started(context, tiMetakenToStartup);
this.callRunners(context, applicationArguments);
} catch (Throwable var12) {
this.handleRunFailure(context, var12, listeners);
throw new IllegalStateException(var12);
}
try {
Duration tiMetakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, tiMetakenToReady);
return context;
} catch (Throwable var11) {
this.handleRunFailure(context, var11, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var11);
}
}
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
this.postProcessApplicationContext(context);
// 应用初始化器
this.applyInitializers(context);
listeners.contextPrepared(context);
bootstrapContext.close(context);
if (this.logStartupInfo) {
this.logStartupInfo(context.getParent() == null);
this.logStartupProfileInfo(context);
}
ConfigurableListablebeanfactory beanfactory = context.getbeanfactory();
beanfactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanfactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanfactory instanceof AbstractAutowireCapablebeanfactory) {
((AbstractAutowireCapablebeanfactory)beanfactory).setAllowCircularReferences(this.allowCircularReferences);
if (beanfactory instanceof DefaultListablebeanfactory) {
((DefaultListablebeanfactory)beanfactory).setAllowBeanDeFinitionOverriding(this.allowBeanDeFinitionOverriding);
}
}
if (this.lazyInitialization) {
context.addbeanfactoryPostProcessor(new LazyInitializationbeanfactoryPostProcessor());
}
context.addbeanfactoryPostProcessor(new SpringApplication.PropertySourceOrderingbeanfactoryPostProcessor(context));
Set<Object> sources = this.getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
this.load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}
初始化器回调方法,回调初始化context
protected void applyInitializers(ConfigurableApplicationContext context) {
Iterator var2 = this.getinitializers().iterator();
while(var2.hasNext()) {
ApplicationContextinitializer initializer = (ApplicationContextinitializer)var2.next();
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(), ApplicationContextinitializer.class);
Assert.isinstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}
3、基于Spring初始化器手写zookeeper配置中心
pom.xml引入curator去操作zookeeper api
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.2.1</version>
</dependency>
(1) 自定义初始化器 ZkConfigApplicationContextinitializer
public class ZkConfigApplicationContextinitializer implements ApplicationContextinitializer {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
CuratorFramework curatorFramework = CuratorFrameworkFactory.builder()
.connectString("192.168.1.104")
.connectionTimeoutMs(2000)
/**
* ExponentialBackoffRetry指数衰减重试:
* baseSleepTimeMs * Math.max(1, this.random.nextInt(1 << retryCount + 1))
* RetryNTimes:重试N次
* RetryOneTime:重试一次
* RetryUntilElapsed:重试直到达到规定时间
*/
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.sessionTimeoutMs(20000)
.build();
curatorFramework.start();//启动zookeeper客户端curator
// 将zookeeper节点保存的配置数据加载到environment中
try {
// 可以将zookeeper节点路径配置到bootstrap.properties
byte[] bytes = curatorFramework.getData().forPath("/zookeeper/resilience4j");
Map map = new ObjectMapper().readValue(new String(bytes), Map.class);
System.out.println("从zookeeper server获取的值:" + map);
// 将存有值的Map保存到env中的PropertySource中
MapPropertySource mapPropertySource = new MapPropertySource("resilience4j-env", map);
ConfigurableEnvironment environment = applicationContext.getEnvironment();
// 将从zookeeper中获取的数据放到environment中的头部位置
// 因为spring从environment中取值是从前往后遍历寻找,匹配到就返回
environment.getPropertySources().addFirst(mapPropertySource);
} catch (Exception e) {
e.printstacktrace();
}
// 设置永久监听,当zookeeper对应节点的数据发生改变,修改environment中的值
CuratorCache curatorCache = CuratorCache.build(curatorFramework, "/zookeeper/resilience4j", CuratorCache.Options.SINGLE_NODE_CACHE);
CuratorCacheListener listener = CuratorCacheListener.builder().forAll(new CuratorCacheListener() {
// 一旦"/zookeeper/resilience4j"节点发生变化就会触发此回调事件
@Override
public void event(Type type, ChildData oldData, ChildData data) {
if (Type.NODE_CHANGED.equals(type)) {
System.out.println("监听到事件类型:" + type + ",旧数据:" + new String(oldData.getData()) + ",新数据:" + new String(data.getData()));
try {
Map updateDataMap = new ObjectMapper().readValue(new String(data.getData()), Map.class);
ConfigurableEnvironment environment = applicationContext.getEnvironment();
environment.getPropertySources().replace("resilience4j-env", new MapPropertySource("resilience4j-env", updateDataMap));
} catch (JsonProcessingException e) {
e.printstacktrace();
}
}
}
}).build();
curatorCache.listenable().addListener(listener);
curatorCache.start();
}
}
(2) SPI配置
新建 src/main/resources/meta-inf/spring.factories
org.springframework.context.ApplicationContextinitializer=\
com.example.zk.initializer.ZkConfigApplicationContextinitializer
(3) 测试
1)项目启动时可以发现自定义初始化器已经被加载到初始化列表中
2)zookeeper节点/zookeeper/resilience4j配置数据
3)第一次访问localhost:8081/hello/sayHello?name=Lucifer结果如下:
通过zk client修改 resilience4j.circuitbreaker.failureRateThreshold 值为60
4)api服务不需要重启,再次访问api结果如下:
可以发现environment中对应的值已经更新,但是@Value注解拿到的值依然是旧值,没有更新
(4) 解决@Value没有更新问题
1)创建一个类去保存bean与@Value字段的关系
public class BeanField {
private Field field; //@Value的字段
private Object instance; //加了@Value字段的类
public BeanField(Field field, Object instance) {
this.field = field;
this.instance = instance;
}
public Field getField() { return field; }
public void setField(Field field) { this.field = field; }
public Object getInstance() { return instance; }
public void setInstance(Object instance) { this.instance = instance; }
}
2)创建一个注解去标识哪些类需要对@Value修饰的属性做更新
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ValueRefreshScope {}
3)将注解 ValueRefreshScope 添加到 HelloController 类上
@RestController
@RequestMapping("/hello")
@ValueRefreshScope
public class HelloController {
//...............
}
4)利用Spring后置处理器扩展机制,自定义BeanPostProcessor
封装所有可能修改值的@Value修饰的属性和其所属的bean
@Component
public class ValueRefreshScopeBeanPostProcessor implements BeanPostProcessor {
// Key: @Value对应的配置key, Value: @Value注解对应的字段和所属的类
private Map<String, BeanField> beanFieldMap = new HashMap<>();
public Map<String, BeanField> getBeanFieldMap() {
return beanFieldMap;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Class<?> clazz = bean.getClass();
// 只对有@ValueRefreshScope注解修饰的类做处理
if (clazz.isAnnotationPresent(ValueRefreshScope.class)) {
System.out.println("ValueRefreshScopeBeanPostProcessor#postProcessAfterInitialization("+beanName+"), clazz=" + clazz);
// 只对有@Value注解修饰的属性做处理
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(Value.class)) {
Value annotation = field.getAnnotation(Value.class);
// 获取@Value注解的value如:${resilience4j.circuitbreaker.failureRateThreshold}
String value = annotation.value();
// 截取具体的属性key,例如:"resilience4j.circuitbreaker.failureRateThreshold"
value = value.substring(2, value.indexOf("}"));
beanFieldMap.put(value, new BeanField(field, bean));
}
}
}
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
5)完善ZkConfigApplicationContextinitializer,当对应的zookeeper节点数据变更,触发的回调事件中更新@Value的属性值
在ZkConfigApplicationContextinitializer#initialize的zookeeper节点监听回调事件event方法中追加下面代码
// 重新注入变更的值到@Value对应的属性
ValueRefreshScopeBeanPostProcessor valueRefreshScopeBeanPostProcessor = applicationContext.getBean("valueRefreshScopeBeanPostProcessor", ValueRefreshScopeBeanPostProcessor.class);
Map<String, BeanField> beanFieldMap = valueRefreshScopeBeanPostProcessor.getBeanFieldMap();
for(Map.Entry<String, BeanField> entry : beanFieldMap.entrySet()) {
if (updateDataMap.containsKey(entry.getKey())) {
BeanField beanField = beanFieldMap.get(entry.getKey());
Field field = beanField.getField();
Object updateValue = environment.getProperty(entry.getKey(), field.getType());
field.setAccessible(true); //设置私有属性也可以访问
// 反射更新字段的值,相当于Spring中AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement#inject方法
field.set(beanField.getInstance(), updateValue);
}
}
修改后ZkConfigApplicationContextinitializer完整的代码如下:
public class ZkConfigApplicationContextinitializer implements ApplicationContextinitializer {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
CuratorFramework curatorFramework = CuratorFrameworkFactory.builder()
.connectString("192.168.1.104")
.connectionTimeoutMs(2000)
/**
* ExponentialBackoffRetry指数衰减重试:
* baseSleepTimeMs * Math.max(1, this.random.nextInt(1 << retryCount + 1))
* RetryNTimes:重试N次
* RetryOneTime:重试一次
* RetryUntilElapsed:重试直到达到规定时间
*/
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.sessionTimeoutMs(20000)
.build();
curatorFramework.start();//启动zookeeper客户端curator
// 将zookeeper节点保存的配置数据加载到environment中
try {
// 可以将zookeeper节点路径配置到bootstrap.properties
byte[] bytes = curatorFramework.getData().forPath("/zookeeper/resilience4j");
Map<String, Object> map = new ObjectMapper().readValue(new String(bytes), Map.class);
System.out.println("从zookeeper server获取的值:" + map);
// 将存有值的Map保存到env中的PropertySource中
MapPropertySource mapPropertySource = new MapPropertySource("resilience4j-env", map);
ConfigurableEnvironment environment = applicationContext.getEnvironment();
// 将从zookeeper中获取的数据放到environment中的头部位置
// 因为spring从environment中取值是从前往后遍历寻找,匹配到就返回
environment.getPropertySources().addFirst(mapPropertySource);
} catch (Exception e) {
e.printstacktrace();
}
// 设置永久监听,当zookeeper对应节点的数据发生改变,修改environment中的值
CuratorCache curatorCache = CuratorCache.build(curatorFramework, "/zookeeper/resilience4j", CuratorCache.Options.SINGLE_NODE_CACHE);
CuratorCacheListener listener = CuratorCacheListener.builder().forAll(new CuratorCacheListener() {
// 一旦"/zookeeper/resilience4j"节点发生变化就会触发此回调事件
@Override
public void event(Type type, ChildData oldData, ChildData data) {
if (Type.NODE_CHANGED.equals(type)) {
System.out.println("监听到事件类型:" + type + ",旧数据:" + new String(oldData.getData()) + ",新数据:" + new String(data.getData()));
try {
// 更新zookeeper节点变更的数据到Environment
Map<String, Object> updateDataMap = new ObjectMapper().readValue(new String(data.getData()), Map.class);
ConfigurableEnvironment environment = applicationContext.getEnvironment();
environment.getPropertySources().replace("resilience4j-env", new MapPropertySource("resilience4j-env", updateDataMap));
// 重新注入变更的值到@Value对应的属性
ValueRefreshScopeBeanPostProcessor valueRefreshScopeBeanPostProcessor = applicationContext.getBean("valueRefreshScopeBeanPostProcessor", ValueRefreshScopeBeanPostProcessor.class);
Map<String, BeanField> beanFieldMap = valueRefreshScopeBeanPostProcessor.getBeanFieldMap();
for(Map.Entry<String, BeanField> entry : beanFieldMap.entrySet()) {
if (updateDataMap.containsKey(entry.getKey())) {
BeanField beanField = beanFieldMap.get(entry.getKey());
Field field = beanField.getField();
Object updateValue = environment.getProperty(entry.getKey(), field.getType());
field.setAccessible(true); //设置私有属性也可以访问
// 反射更新字段的值,相当于Spring中AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement#inject方法
field.set(beanField.getInstance(), updateValue);
}
}
} catch (JsonProcessingException e) {
e.printstacktrace();
} catch (illegalaccessexception e) {
e.printstacktrace();
}
}
}
}).build();
curatorCache.listenable().addListener(listener);
curatorCache.start();
}
}
6)再次测试
通过zookeeper客户端工具修改 resilience4j.circuitbreaker.failureRateThreshold 值为80
触发监听回调事件,api服务不重启,再次访问 api 结果如下:
可以发现zookeeper节点配置数据变更后,api中environment和@Value中的值都立即更新了
4、Spring Cloud Zookeeper实现配置中心
Spring Cloud Zookeeper官网:https://spring.io/projects/spring-cloud-zookeeper
(1) 代码演示
1)创建spring-cloud-zookeeper的spring boot项目,Spring Boot版本为2.6.8
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.8</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.2.1</version>
</dependency>
</dependencies>
2)定义Spring Cloud的版本管理
<!--定义版本的管理-->
<dependencyManagement>
<dependencies>
<!--定义spring cloud的版本-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.3</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
3)引入spring cloud zookeeper配置中心的依赖
<!-- spring cloud zookeeper config 配置中心-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-config</artifactId>
</dependency>
4)引入bootstrap.yaml文件需要的依赖
<!-- bootstrap.yaml文件所需依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
5)在bootstrap.yaml文件中编写配置中心相关的配置
spring:
profiles:
active: dev
application:
name: resilience4j # 找哪一个ZNode节点 resilience4j-dev
cloud:
zookeeper:
config:
root: zookeeper # 相当于 /zookeeper/resilience4j-dev
profile-separator: "-"
enabled: true
connect-string: 192.168.1.104:2181
6)在application.yml中不需要配置
server.port: 8081
7)在zookeeper上创建对应节点并配置数据
8)在需要注入配置数据的类上加@RefreshScope注解
@RestController
@RequestMapping("/hello")
//@ValueRefreshScope
@RefreshScope
public class HelloController {
@Autowired
Environment env;
@Value("${resilience4j.circuitbreaker.failureRateThreshold}")
int failureRateThreshold;
@Value("${resilience4j.circuitbreaker.ringBufferSizeInClosedState}")
int ringBufferSizeInClosedState;
@Value("${resilience4j.circuitbreaker.ringBufferSizeInHalfOpenState}")
int ringBufferSizeInHalfOpenState;
@Value("${resilience4j.circuitbreaker.waitDurationInopenState}")
long waitDurationInopenState;
@RequestMapping("/sayHello")
public String sayHello(@RequestParam("name") String name) {
System.out.println("env failureRateThreshold="+env.getProperty("resilience4j.circuitbreaker.failureRateThreshold"));
System.out.println("@Value failureRateThreshold=" + failureRateThreshold);
return "hello, " + name;
}
}
9)访问api测试
修改zookeeper节点数据
修改zookeeper节点数据会触发监听事件,自动更新配置数据
再次访问api,发现数据已经自动更新了,不需要任何其他操作
(2) 源码分析
1)自定义初始化器PropertySourceBootstrapConfiguration加载zookeeper数据到environment
@EnableConfigurationProperties({PropertySourceBootstrapProperties.class})
public class PropertySourceBootstrapConfiguration implements ApplicationContextinitializer<ConfigurableApplicationContext>, Ordered {
public static final String BOOTSTRAP_PROPERTY_SOURCE_NAME = "bootstrapProperties";
private int order = -2147483638;
public void initialize(ConfigurableApplicationContext applicationContext) {
List<PropertySource<?>> composite = new ArrayList();
AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
boolean empty = true;
ConfigurableEnvironment environment = applicationContext.getEnvironment();
Iterator var5 = this.propertySourceLocators.iterator();
while(true) {
Collection source;
do {
do {
if (!var5.hasNext()) {
if (!empty) {
MutablePropertySources propertySources = environment.getPropertySources();
String logConfig = environment.resolvePlaceholders("${logging.config:}");
LogFile logFile = LogFile.get(environment);
Iterator var15 = environment.getPropertySources().iterator();
while(var15.hasNext()) {
PropertySource<?> p = (PropertySource)var15.next();
if (p.getName().startsWith("bootstrapProperties")) {
propertySources.remove(p.getName());
}
}
this.insertPropertySources(propertySources, composite);
this.reinitializeLoggingSystem(environment, logConfig, logFile);
this.setLogLevels(applicationContext, environment);
this.handleIncludedProfiles(environment);
}
return;
}
PropertySourceLocator locator = (PropertySourceLocator)var5.next();
source = locator.locateCollection(environment);
} while(source == null);
} while(source.size() == 0);
List<PropertySource<?>> sourceList = new ArrayList();
Iterator var9 = source.iterator();
while(var9.hasNext()) {
PropertySource<?> p = (PropertySource)var9.next();
if (p instanceof EnumerablePropertySource) {
EnumerablePropertySource<?> enumerable = (EnumerablePropertySource)p;
sourceList.add(new BootstrapPropertySource(enumerable));
} else {
sourceList.add(new SimpleBootstrapPropertySource(p));
}
}
composite.addAll(sourceList);
empty = false;
}
}
private void insertPropertySources(MutablePropertySources propertySources, List<PropertySource<?>> composite) {
MutablePropertySources incoming = new MutablePropertySources();
List<PropertySource<?>> reversedComposite = new ArrayList(composite);
Collections.reverse(reversedComposite);
Iterator var5 = reversedComposite.iterator();
while(var5.hasNext()) {
PropertySource<?> p = (PropertySource)var5.next();
incoming.addFirst(p);
}
PropertySourceBootstrapProperties remoteProperties = new PropertySourceBootstrapProperties();
Binder.get(this.environment(incoming)).bind("spring.cloud.config", Bindable.ofInstance(remoteProperties));
PropertySource p;
Iterator var9;
if (remoteProperties.isAllowOverride() && (remoteProperties.isOverrideNone() || !remoteProperties.isOverrideSystemProperties())) {
if (remoteProperties.isOverrideNone()) {
var9 = composite.iterator();
while(var9.hasNext()) {
p = (PropertySource)var9.next();
propertySources.addLast(p);
}
} else {
if (propertySources.contains("systemEnvironment")) {
if (!remoteProperties.isOverrideSystemProperties()) {
var9 = reversedComposite.iterator();
while(var9.hasNext()) {
p = (PropertySource)var9.next();
propertySources.addAfter("systemEnvironment", p);
}
} else {
var9 = composite.iterator();
while(var9.hasNext()) {
p = (PropertySource)var9.next();
propertySources.addBefore("systemEnvironment", p);
}
}
} else {
var9 = composite.iterator();
while(var9.hasNext()) {
p = (PropertySource)var9.next();
propertySources.addLast(p);
}
}
}
} else {
var9 = reversedComposite.iterator();
while(var9.hasNext()) {
p = (PropertySource)var9.next();
propertySources.addFirst(p);
}
}
}
}
2)基于事件监听机制,回调更新数据:先将原有bean销毁,然后重新初始化bean放入新的值
@Component
@ManagedResource
public class ConfigurationPropertiesRebinder implements ApplicationContextAware, ApplicationListener<EnvironmentChangeEvent> {
private ConfigurationPropertiesBeans beans;
private ApplicationContext applicationContext;
private Map<String, Exception> errors = new ConcurrentHashMap();
public ConfigurationPropertiesRebinder(ConfigurationPropertiesBeans beans) {
this.beans = beans;
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@ManagedOperation
public void rebind() {
this.errors.clear();
Iterator var1 = this.beans.getBeanNames().iterator();
while(var1.hasNext()) {
String name = (String)var1.next();
this.rebind(name);
}
}
@ManagedOperation
public boolean rebind(String name) {
if (!this.beans.getBeanNames().contains(name)) {
return false;
} else {
// 将原有bean销毁
this.applicationContext.getAutowireCapablebeanfactory().destroyBean(bean);
// 重新初始化bean注入新的属性值
this.applicationContext.getAutowireCapablebeanfactory().initializeBean(bean, name);
return true;
}
}
@ManagedAttribute
public Set<String> getNeverRefreshable() {
String neverRefresh = this.applicationContext.getEnvironment().getProperty("spring.cloud.refresh.never-refreshable", "com.zaxxer.hikari.HikariDataSource");
return StringUtils.commaDelimitedListToSet(neverRefresh);
}
// 事件监听机制,当zookeeper节点数据变更,触发此回调方法重新绑定数据
public void onApplicationEvent(EnvironmentChangeEvent event) {
if (this.applicationContext.equals(event.getSource()) || event.getKeys().equals(event.getSource())) {
this.rebind();
}
}
}