SpringCloud的动态配置实现原理

前言

动态配置(变动频率高的,如支付方式,页面展示信息)和静态配置(数据库连接配置等)

  • 核心注解@RefreshScope
    对Bean创建动态代理
  • 核心类RefreshScope
    负责销毁被@RefreshScope注解配置的Bean
  • ContextRefresher
    负责刷新环境Environment,更新Environment中的所有配置值

Spring Cloud负责更新环境Environment以及创建新的动态配置bean,而判断配置是否改变,以及怎么获取新的配置则是由第三方框架实现的,下面的测试都以 Nacos 为例。

第一种方式

使用@ConfigurationProperties 注解,不需要使用 @RefreshScope注解

myuser:
  name: lisi
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties(prefix = "myuser")
public class UserProperties {
    private String name;
}
@RestController
@RequestMapping("/test")
public class TestController {

    @Autowired
    private UserProperties userProperties;
    @Autowired
    private ContextRefresher contextRefresher;

    @GetMapping("/testRefreshExecute")
    @ApiOperation("测试刷新对象执行")
    public void testRefreshExceute() {
        //这个步骤一般由配置中心框架来触发
        contextRefresher.refresh();
    }

    @GetMapping("/testRefreshQuery")
    @ApiOperation("测试刷新对象查询")
    public String testRefreshQuery() {
        return userProperties.getName();
    }

}

测试步骤

  1. /test/testRefreshQuery获取旧的配置值
  2. 修改配置中心的值
  3. Nacos 会通过长连接监听到此修改,调用ContextRefresher的 refresh() 方法,会发送EnvironmentChangeEvent事件,ConfigurationPropertiesRebinder监听此事件并重新绑定属性值(ConfigurationPropertiesBindingPostProcessor)
  4. /test/testRefreshQuery获取新的配置值

这种方式的原理就是重新走Bean的初始化流程。

第二种方式

使用 @RefreshScope 注解

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;

@Data
@Component
@RefreshScope
public class UserProperties {
    @Value("${myuser.name}")
    private String name;
}
@RestController
@RequestMapping("/test")
public class TestController {

    @Autowired
    private UserProperties userProperties;
    @Autowired
    private ContextRefresher contextRefresher;

    @GetMapping("/testRefreshExecute")
    @ApiOperation("测试刷新对象执行")
    public void testRefreshExceute() {
        //这个步骤一般由配置中心框架来触发
        contextRefresher.refresh();
    }

    @GetMapping("/testRefreshQuery")
    @ApiOperation("测试刷新对象查询")
    public String testRefreshQuery() {
        return userProperties.getName();
    }

}
  1. /test/testRefreshQuery获取旧的配置值
  2. 修改配置中心的值
  3. Nacos 会通过长连接监听到此修改,调用ContextRefresher的 refresh() 方法,发送EnvironmentChangeEvent事件,并调用RefreshScope的refreshAll()方法清空Bean缓存。
  4. /test/testRefreshQuery获取新的配置值

@RefreshScope注解会创建动态代理对象,实际类型为ScopedProxyFactoryBean,每次调用方法都会调用SimpleBeanTargetSource的getTarget()方法,RefreshScope的get(beanName, objectFactory)第一次从IOC容器创建,后续走缓存。

@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
	BeanLifecycleWrapper value = this.cache.put(name,
			new BeanLifecycleWrapper(name, objectFactory));
	this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
	try {
		return value.getBean();
	}
	catch (RuntimeException e) {
		this.errors.put(name, e);
		throw e;
	}
}
@Override
public void destroy() {
	List<Throwable> errors = new ArrayList<Throwable>();
	Collection<BeanLifecycleWrapper> wrappers = this.cache.clear();
	for (BeanLifecycleWrapper wrapper : wrappers) {
		try {
			Lock lock = this.locks.get(wrapper.getName()).writeLock();
			lock.lock();
			try {
				wrapper.destroy();
			}
			finally {
				lock.unlock();
			}
		}
	}
}

this.cache.put()方法底层是putIfAbsent(),所以多次使用的是同一个BeanLifecycleWrapper对象。destroy()方法会清空cache,下次使用的就是新创建的BeanLifecycleWrapper了(重新创建 Bean)。

总结

  1. NacosContextRefresher 类的 registerNacosListener() 方法会注册一个监听器,发送 RefreshEvent 事件,RefreshEventListener 来处理此事件,内部就是调用 ContextRefresher 的 refresh() 方法。
  2. 上面两种方式的底层原理都是会重新走 Bean 的初始化过程,在这个过程中就会重新获取配置值。

参考

Spring Cloud动态配置实现原理与源码分析
Nacos共享配置(shared-configs)和扩展配(extension-config)

posted @ 2023-08-24 08:05  strongmore  阅读(282)  评论(0编辑  收藏  举报