配置中心工作流程 

  1. 服务启动时,加载远程配置到配置中心(通过curator在zkServer上创建节点保存配置数据)
  2. 当需要修改配置时,通过配置中心后台控制台修改配置
  3. 配置中心的配置改动会同步到每个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

PropertySourceBootstrapConfiguration#initialize

@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放入新的值

ConfigurationPropertiesRebinder#onApplicationEvent

@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();
        }
    }
}

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐