原文网址:Spring Cloud--@RefreshScope动态刷新的原理_IT利刃出鞘的博客-CSDN博客
本文介绍Spring Cloud的@RefreshScope动态刷新的原理。
Spring的作用域有:single(单例)、prototype(多例)等(详见:Spring--Bean的作用域(scope)--使用/详解_IT利刃出鞘的博客-CSDN博客)。
SpringCloud新增了一个自定义的作用域:refresh(可以理解为“动态刷新”),改变了Bean的管理方式,使其可以通过外部化配置(.yml或.properties)的刷新,在不需要重启应用的情况下热加载新的外部化配置的值。
这个scope是如何做到热加载的呢?RefreshScope主要做了以下动作:
一个Bean想自动加载配置,就要打上@RefreshScope注解,看看这个注解
- @Target({ ElementType.TYPE, ElementType.METHOD })
- @Retention(RetentionPolicy.RUNTIME)
- @Scope("refresh")
- @Documented
- public @interface RefreshScope {
-
- /**
- * @see Scope#proxyMode()
- * @return proxy mode
- */
- ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
-
- }
RefreshScope有一个属性:proxyMode=ScopedProxyMode.TARGET_CLASS,这个是AOP动态代理用。
上边标注了 @Scope("refresh") ,将Bean的Scope变为refresh。SpringBoot的启动类上我们都会加上@SpringBootApplication注解(里面是一个@ComponentScan),它会扫描包中通过注解注册的Bean,扫描到带有@RefreshScope注解的Bean时,将其BeanDefinition的scope变为refresh。
创建一个Bean的时候,会去BeanFactory的doGetBean方法创建Bean,不同scope有不同的创建方式:AbstractBeanFactory的doGetBean方法:
- protected
T doGetBean(final String name, - @Nullable final Class
requiredType, - @Nullable final Object[] args, boolean typeCheckOnly)
- throws BeansException {
-
- //....
-
- // Create bean instance.
- // 单例Bean的创建
- if (mbd.isSingleton()) {
- sharedInstance = getSingleton(beanName, () -> {
- try {
- return createBean(beanName, mbd, args);
- }
- //...
- });
- bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
- }
-
- // 原型Bean的创建
- else if (mbd.isPrototype()) {
- // It's a prototype -> create a new instance.
- // ...
- try {
- prototypeInstance = createBean(beanName, mbd, args);
- }
- //...
- bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
- }
-
- else {
- // 由上面的RefreshScope注解可以知道,这里scopeName=refresh
- String scopeName = mbd.getScope();
- // 获取Refresh的Scope对象
- final Scope scope = this.scopes.get(scopeName);
- if (scope == null) {
- throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
- }
- try {
- // 让Scope对象去管理Bean
- Object scopedInstance = scope.get(beanName, () -> {
- beforePrototypeCreation(beanName);
- try {
- return createBean(beanName, mbd, args);
- }
- finally {
- afterPrototypeCreation(beanName);
- }
- });
- bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
- }
- //...
- }
- }
这里scope.get获取的Scope对象为RefreshScope。可见,创建Bean还是由IOC来做(createBean方法),但是获取Bean由RefreshScope对象的get方法去获取,其get方法在父类GenericScope中实现。
RefreshScope继承了GenericScope类,最终调用的是GenericScope的get方法:
- public class GenericScope
- implements Scope, BeanFactoryPostProcessor, BeanDefinitionRegistryPostProcessor, DisposableBean {
-
- public Object get(String name, ObjectFactory < ? > objectFactory) {
- // 将Bean缓存下来
- BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory));
- this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
- try {
- // 创建Bean,只会创建一次,后面直接返回创建好的Bean
- return value.getBean();
- } catch (RuntimeException e) {
- this.errors.put(name, e);
- throw e;
- }
- }
-
- // 其他代码
- }
- private final ScopeCache cache;
-
- // 对应上面的 BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory));
- public BeanLifecycleWrapper put(String name, BeanLifecycleWrapper value) {
- return (BeanLifecycleWrapper) this.cache.put(name, value);
- }
-
这里的ScopeCache对象是一个HashMap:
- public class StandardScopeCache implements ScopeCache {
-
- private final ConcurrentMap
cache = new ConcurrentHashMap(); -
- //...
-
- public Object get(String name) {
- return this.cache.get(name);
- }
-
- // 如果不存在,才会put进去
- public Object put(String name, Object value) {
- // result若不等于null,表示缓存存在了,不会进行put操作
- Object result = this.cache.putIfAbsent(name, value);
- if (result != null) {
- // 直接返回旧对象
- return result;
- }
- // put成功,返回新对象
- return value;
- }
- }
这里是将Bean包装成一个对象,缓存在一个Map中,下次如果再GetBean,还是那个旧的BeanWrapper。
回到Scope的get方法,接下来就是调用BeanWrapper的getBean方法:
- // 实际Bean对象,缓存下来了
- private Object bean;
-
- public Object getBean() {
- if (this.bean == null) {
- synchronized (this.name) {
- if (this.bean == null) {
- this.bean = this.objectFactory.getObject();
- }
- }
- }
- return this.bean;
- }
可以看出,BeanWrapper中的bean变量是实际Bean,如果第一次get肯定为空,就会调用BeanFactory的createBean方法创建Bean,创建出来之后就会一直保存下来。
由此可见,RefreshScope管理了Scope=Refresh的Bean的生命周期。
配置中心修改配置后,有两种方式可以动态刷新Bean的配置值,(SpringCloud-Bus、Nacos都是这么实现的):
不管是什么方式,都会调用ContextRefresher#refresh()方法.
- // 这就是我们上面一直分析的Scope对象(实际上可以看作一个保存refreshBean的Map)
- private RefreshScope scope;
-
- public synchronized Set
refresh() { - // 更新上下文中Environment外部化配置值
- Set
keys = refreshEnvironment(); - // 调用scope对象的refreshAll方法
- this.scope.refreshAll();
- return keys;
- }
刷新的方案
我们一般使用@Value、@ConfigurationProperties去获取配置变量值,其底层在IOC中则是通过上下文的Environment对象去获取property值,然后IOC时通过反射写到Bean对象。
如果我们更新Environment里的Property值,重新创建一次RefreshBean,再进行一次上述的依赖注入,就能完成配置热加载了,@Value的变量值就可以加载为最新的了。
这里说的刷新Environment对象并重新依赖注入是上边refresh()中这两个方法做的事情:
- Set keys = refreshEnvironment();
- this.scope.refreshAll();
简单介绍如何刷新Environment里的Property值
- public synchronized Set
refreshEnvironment() { - // 获取刷新配置前的配置信息,对比用
- Map
before = extract( - this.context.getEnvironment().getPropertySources());
- // 刷新Environment
- addConfigFilesToEnvironment();
-
- // 这里上下文的Environment已经是新的值了
- // 进行新旧对比,结果返回有变化的值
- Set
keys = changes(before, extract(this.context.getEnvironment().getPropertySources())).keySet(); - this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
- return keys;
- }
-
重点是addConfigFilesToEnvironment方法(刷新Environment)
- ConfigurableApplicationContext addConfigFilesToEnvironment() {
- ConfigurableApplicationContext capture = null;
- try {
- // 从上下文拿出Environment对象,copy一份
- StandardEnvironment environment = copyEnvironment(
- this.context.getEnvironment());
- // SpringBoot启动类builder,准备新做一个Spring上下文启动
- SpringApplicationBuilder builder = new SpringApplicationBuilder(Empty.class)
- // banner和web都关闭,因为只是想单纯利用新的Spring上下文构造一个新的Environment
- .bannerMode(Mode.OFF).web(WebApplicationType.NONE)
- // 传入我们刚刚copy的Environment实例
- .environment(environment);
- // 启动上下文
- capture = builder.run();
- // 这个时候,通过上下文SpringIOC的启动,刚刚Environment对象就变成带有最新配置值的Environment了
- // 获取旧的外部化配置列表
- MutablePropertySources target = this.context.getEnvironment()
- .getPropertySources();
- String targetName = null;
- // 遍历这个最新的Environment外部化配置列表
- for (PropertySource> source : environment.getPropertySources()) {
- String name = source.getName();
- if (target.contains(name)) {
- targetName = name;
- }
- // 某些配置源不做替换,读者自行查看源码
- // 一般的配置源都会进入if语句
- if (!this.standardSources.contains(name)) {
- if (target.contains(name)) {
- // 用新的配置替换旧的配置
- target.replace(name, source);
- }
- else {
- //....
- }
- }
- }
- }
- //....
- }
这里是SpringBoot启动上下文那种方法,新做了一个Spring上下文。因为Spring启动后会对上下文中的Environment进行初始化,获取最新配置,所以这里利用Spring的启动,获取了最新的Environment对象,然后去替换旧的上下文中的Environment对象中的配置值。
经过上述动作,此时上下文中的配置值已经是最新的了。回到ContextRefresher的refresh方法,它会调用Scope的refreshAll方法:
- public void refreshAll() {
- // 销毁Bean
- super.destroy();
- this.context.publishEvent(new RefreshScopeRefreshedEvent());
- }
-
- public void destroy() {
- List
errors = new ArrayList(); - // 缓存清空
- Collection
wrappers = this.cache.clear(); - // ...
- }
还记得上面的“管理Bean的获取”关于缓存的讨论吗,cache变量是一个Map保存着RefreshBean实例,这里直接就将Map清空了。
回到BeanFactory的doGetBean的流程中,从IOC容器中获取RefreshBean是交给RefreshScope的get方法做的:
- public Object get(String name, ObjectFactory> objectFactory) {
- // 由于刚刚清空了缓存Map,这里就会put一个新的BeanLifecycleWrapper实例
- BeanLifecycleWrapper value = this.cache.put(name,
- new BeanLifecycleWrapper(name, objectFactory));
- this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
- try {
- // 在这里是新的BeanLifecycleWrapper实例调用getBean方法
- return value.getBean();
- }
- catch (RuntimeException e) {
- this.errors.put(name, e);
- throw e;
- }
- }
- public Object getBean() {
- // 由于是新的BeanLifecycleWrapper实例,这里一定为null
- if (this.bean == null) {
- synchronized (this.name) {
- if (this.bean == null) {
- // 调用IOC容器的createBean,再创建一个Bean出来
- this.bean = this.objectFactory.getObject();
- }
- }
- }
- return this.bean;
- }
此时RefreshBean被IOC容器重新创建出来了,经过IOC的依赖注入功能,@Value就是一个新的配置了。到这里热加载功能实现基本结束。
可以看出只要从IOC容器中getBean,那么拿到的RefreshBean一定是带有最新配置值的Bean。