Spring Cloud Config 的 refresh 机制

1. configserver 项目

通过 https://start.spring.io/ 新建 configserver 项目。

需要导入如下依赖,可以在上述网址中选择依赖组件。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-server</artifactId>
</dependency>

在主类中新增 @EnableConfigServer 注解。

@EnableConfigServer
@SpringBootApplication
public class ConfigserverApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigserverApplication.class, args);
    }
}

使用本地文件存储配置文件的方式,在 application.properties 文件中新增如下配置。

spring.application.name=configserver
server.port=8888
spring.profiles.active=native
spring.cloud.config.server.native.search-locations=classpath:/config

resources 目录下新增 config 子目录,并创建 application-dev.ymlapplication-test.yml 文件,文件内容如下所示。

// application-dev.yml
name: "dev-config"

// application-test.yml
name: "test-config"

configserver 项目整体结构如图1 所示。

图1:configserver 项目结构

运行 configserver 项目。

图2:configserver 启动日志

2. config-client-demo 项目

同样可以通过 https://start.spring.io/ 新建 config-client-demo 项目。

需要导入如下依赖,可以在上述网址中选择依赖组件。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.10</version>
</dependency>

application.properties 文件中新增如下配置。

# 开启所有 Actuator Endpoint
management.endpoints.web.exposure.include=*

新增 bootstrap.yml 文件,并加入 Spring Cloud Config 相关配置。

spring:
  cloud:
    config:
      uri: http://localhost:8888 # configserver 地址
      # name-profile 组合对应 configserver 中具体的配置文件,这里映射的是 application-dev.yml
      name: application
      profile: dev

新增 ConfigProperties 类存储对应的配置,并加上 @RefreshScope 用来刷新配置。

@Data
@Component
@RefreshScope
public class ConfigProperties {
    @Value("${name}")
    private String name;
}

新增 TestConfigController 用来提供测试接口。

@RestController
@RequiredArgsConstructor
@RequestMapping("/test/config")
public class TestConfigController {
    @Autowired
    private ConfigProperties configProperties;

    @Value("${name}")
    private String name;

    @GetMapping("/name")
    public String name() {
        System.out.println("------------------------");
        System.out.println(name);
        System.out.println(configProperties.getName());
        System.out.println("------------------------");
        return name;
    }
}

config-client-demo 项目整体结构如图3 所示。

图3:config-client-demo 项目结构

调用 http://localhost:8080/test/config/name 接口,打印结果如下所示。

image.png

3. 修改配置

修改 configserver 中的 application-dev.yml 配置,并重启 configserver 服务。

name: "dev-config-update"

config-client-demo 中调用 http://localhost:8080/actuator/refresh 接口用来刷新配置,然后调用 http://localhost:8080/test/config/name 接口,打印结果如下所示。

image.png

可以发现,有 @RefreshScope 注解的配置被刷新了,否则配置没有被刷新。

4. 源码解读

4.1 refresh 接口

下面我们从 RefreshEndpoint 类开始跟踪。

@Endpoint(id = "refresh")
public class RefreshEndpoint {
    private ContextRefresher contextRefresher;

    // http://localhost:8080/actuator/refresh 接口执行逻辑
    public Collection<String> refresh() {
        Set<String> keys = this.contextRefresher.refresh();
        return keys;
    }
}

后续调用链路为:

  • ContextRefresherrefresh()
  • RefreshScoperefreshAll()
  • GenericScopedestroy()

4.2 GenericScope 类

public class GenericScope implements Scope, BeanFactoryPostProcessor,
        BeanDefinitionRegistryPostProcessor, DisposableBean {

    /**
     * Prefix for the scoped target.
     */
    public static final String SCOPED_TARGET_PREFIX = "scopedTarget.";

    private BeanLifecycleWrapperCache cache = new BeanLifecycleWrapperCache(
            new StandardScopeCache());

    // 清空 Scope 中的缓存对象,这里指 RefreshScope
    public void destroy() {
        Collection<BeanLifecycleWrapper> wrappers = this.cache.clear();
        for (BeanLifecycleWrapper wrapper : wrappers) {
                wrapper.destroy();
        }
    }

    // 获取 name 在 Scope 中的 Spring Bean
    public Object get(String name, ObjectFactory<?> objectFactory) {
        // 底层调用的是 putIfAbsent 方法
        // 如果 cache 存在对应的 name 的 BeanLifecycleWrapper 则直接返回,否则添加
        BeanLifecycleWrapper value = this.cache.put(name,
                new BeanLifecycleWrapper(name, objectFactory));
        try {
            return value.getBean();
        }
    }

    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
            throws BeansException {
        for (String name : registry.getBeanDefinitionNames()) {
            BeanDefinition definition = registry.getBeanDefinition(name);
            if (definition instanceof RootBeanDefinition) {
                RootBeanDefinition root = (RootBeanDefinition) definition;
                if (root.getDecoratedDefinition() != null && root.hasBeanClass()
                        && root.getBeanClass() == ScopedProxyFactoryBean.class) {
                    if (getName().equals(root.getDecoratedDefinition().getBeanDefinition()
                            .getScope())) {
                        // 把 ScopedProxyFactoryBean 替换为 LockedScopedProxyFactoryBean
                        root.setBeanClass(LockedScopedProxyFactoryBean.class);
                        root.getConstructorArgumentValues().addGenericArgumentValue(this);
                        root.setSynthetic(true);
                    }
                }
            }
        }
    }

    private static class BeanLifecycleWrapper {

        private final String name;

        private final ObjectFactory<?> objectFactory;

        private Object bean;

        // 通过 objectFactory 获取 Spring Bean 对象,并缓存在 bean 字段中
        public Object getBean() {
            if (this.bean == null) {
                synchronized (this.name) {
                    if (this.bean == null) {
                        this.bean = this.objectFactory.getObject();
                    }
                }
            }
            return this.bean;
        }
    }
}

4.2 ScopedProxyUtils 类

public abstract class ScopedProxyUtils {

    private static final String TARGET_NAME_PREFIX = "scopedTarget.";

    public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition,
            BeanDefinitionRegistry registry, boolean proxyTargetClass) {

        String originalBeanName = definition.getBeanName();
        BeanDefinition targetDefinition = definition.getBeanDefinition();
        String targetBeanName = getTargetBeanName(originalBeanName);

        // 为 originalBeanName 创建一个 scoped proxy 的 RootBeanDefinition
        RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
        proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName));
        proxyDefinition.setOriginatingBeanDefinition(targetDefinition);
        proxyDefinition.setSource(definition.getSource());
        proxyDefinition.setRole(targetDefinition.getRole());

        // 给 ScopedProxyFactoryBean 中的 targetBeanName 字段赋值
        proxyDefinition.getPropertyValues().add("targetBeanName", targetBeanName);

        // Copy autowire settings from original bean definition.
        proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
        proxyDefinition.setPrimary(targetDefinition.isPrimary());
        if (targetDefinition instanceof AbstractBeanDefinition) {
            proxyDefinition.copyQualifiersFrom((AbstractBeanDefinition) targetDefinition);
        }

        // The target bean should be ignored in favor of the scoped proxy.
        targetDefinition.setAutowireCandidate(false);
        targetDefinition.setPrimary(false);

    
        // targetBeanName 关联目标 bean,注册到 Spring 容器中
        registry.registerBeanDefinition(targetBeanName, targetDefinition);

        // 返回 originalBeanName 关联的 scoped proxy definition
        return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
    }

    /**
     * 生成目标bean的名称
     *
     *      originalBeanName 是 scoped proxy 的名称(代理类)
     *      targetBeanName 是 scoped proxy 持有的目标bean 的名称(被代理类)
     */
    // 
    public static String getTargetBeanName(String originalBeanName) {
        return TARGET_NAME_PREFIX + originalBeanName;
    }

}

生成 scoped proxy 的调用链路:

  • AbstractApplicationContext 类中的 refresh() 方法
  • AbstractApplicationContext 类中的 invokeBeanFactoryPostProcessors() 方法
  • PostProcessorRegistrationDelegate 类中的 invokeBeanFactoryPostProcessors() 方法
  • PostProcessorRegistrationDelegate 类中的 invokeBeanDefinitionRegistryPostProcessors() 方法
  • ConfigurationClassPostProcessor 类中的 postProcessBeanDefinitionRegistry() 方法
  • ConfigurationClassPostProcessor 类中的 processConfigBeanDefinitions() 方法
  • ConfigurationClassParser 类中的 parse() 方法
  • ConfigurationClassParser 类中的 processConfigurationClass() 方法
  • ComponentScanAnnotationParser 类中的 parse() 方法
  • ClassPathBeanDefinitionScanner 类中的 doScan() 方法
  • AnnotationConfigUtils 类中的 applyScopedProxyMode() 方法
  • ScopedProxyCreator 类中的 createScopedProxy() 方法
  • ScopedProxyUtils 类中的 createScopedProxy() 方法

4.3 ScopedProxyFactoryBean 类

public class ScopedProxyFactoryBean extends ProxyConfig
        implements FactoryBean<Object>, BeanFactoryAware, AopInfrastructureBean {

    // 从 Spring 容器中通过 getBean() 方法获取 TargetSource
    private final SimpleBeanTargetSource scopedTargetSource = new SimpleBeanTargetSource();

    // ScopedProxyUtils 中的 getTargetBeanName() 方法生成
    private String targetBeanName;

    // 代理对象的缓存
    private Object proxy;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;

        this.scopedTargetSource.setBeanFactory(beanFactory);

        // 通过 ProxyFactory 生成代理对象
        ProxyFactory pf = new ProxyFactory();
        pf.copyFrom(this);
        // 设置代理对象的 TargetSource 为 scopedTargetSource (SimpleBeanTargetSource)
        pf.setTargetSource(this.scopedTargetSource);


        Class<?> beanType = beanFactory.getType(this.targetBeanName);
        if (!isProxyTargetClass() || beanType.isInterface() || Modifier.isPrivate(beanType.getModifiers())) {
            pf.setInterfaces(ClassUtils.getAllInterfacesForClass(beanType, cbf.getBeanClassLoader()));
        }

        ScopedObject scopedObject = new DefaultScopedObject(cbf, this.scopedTargetSource.getTargetBeanName());
        pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject));

        pf.addInterface(AopInfrastructureBean.class);

        this.proxy = pf.getProxy(cbf.getBeanClassLoader());
    }


    public Object getObject() {
        if (this.proxy == null) {
            throw new FactoryBeanNotInitializedException();
        }
        return this.proxy;
    }

}

public class SimpleBeanTargetSource extends AbstractBeanFactoryBasedTargetSource {

    // 在代理方法中获取目标对象
    public Object getTarget() throws Exception {
        // 直接从 Spring 容器中获取
        return getBeanFactory().getBean(getTargetBeanName());
    }

}

LockedScopedProxyFactoryBeanGenericScope 的内部类。

public static class LockedScopedProxyFactoryBean<S extends GenericScope>
        extends ScopedProxyFactoryBean implements MethodInterceptor {

    private final S scope;

    private String targetBeanName;

    public void setBeanFactory(BeanFactory beanFactory) {
        super.setBeanFactory(beanFactory);
        Object proxy = getObject();
        if (proxy instanceof Advised) {
            // 把当前 MethodInterceptor 放到 Advice 的首位
            // 即让代理方法执行时,先执行此 MethodInterceptor 的 invoke 方法
            Advised advised = (Advised) proxy;
            advised.addAdvice(0, this);
        }
    }

    public Object invoke(MethodInvocation invocation) throws Throwable {
        Method method = invocation.getMethod();
        if (AopUtils.isEqualsMethod(method) || AopUtils.isToStringMethod(method)
                || AopUtils.isHashCodeMethod(method)
                || isScopedObjectGetTargetObject(method)) {
            return invocation.proceed();
        }
        Object proxy = getObject();
        try {
            if (proxy instanceof Advised) {
                Advised advised = (Advised) proxy;
                ReflectionUtils.makeAccessible(method);
                return ReflectionUtils.invokeMethod(method,
                        advised.getTargetSource().getTarget(), // 调用 SimpleBeanTargetSource 的 getTarget() 从 Spring 容器获取 Bean
                        invocation.getArguments());
            }
            return invocation.proceed();
        }
    }

}

5. 配置刷新分析

再次贴出 TestConfigController 的代码。

@RestController
@RequiredArgsConstructor
@RequestMapping("/test/config")
public class TestConfigController {
    @Autowired
    private ConfigProperties configProperties;

    @Value("${name}")
    private String name;

    @GetMapping("/name")
    public String name() {

        System.out.println("------------------------");
        System.out.println(name);
        System.out.println(configProperties.getName());
        System.out.println("------------------------");
        return name;
    }
}

debug 结果如下所示。

image.png

从上图可知,configProperties 是一个 CglibAopProxy 代理类,因此调用其 getName() 方法会进入 DynamicAdvisedInterceptorintercept() 方法。

调用链路如下所示:

  • DynamicAdvisedInterceptor 类的 intercept() 方法。
  • LockedScopedProxyFactoryBean 类的 invoke() 方法。
  • AbstractBeanFactory 类的 getBean() 方法,通过 ScopedProxyUtils 中的 getTargetBeanName() 方法生成的 name
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {


    public Object getBean(String name) throws BeansException {
        return doGetBean(name, null, null, false);
    }

    protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
            @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {

        final String beanName = transformedBeanName(name);
        Object bean;


        // Create bean instance.
        if (mbd.isSingleton()) {
            sharedInstance = getSingleton(beanName, () -> {
                try {
                    return createBean(beanName, mbd, args);
                }
            });
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
        }

        else if (mbd.isPrototype()) {
            // It's a prototype -> create a new instance.
            Object prototypeInstance = null;
            try {
                beforePrototypeCreation(beanName);
                prototypeInstance = createBean(beanName, mbd, args);
            }
            finally {
                afterPrototypeCreation(beanName);
            }
            bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
        }

        else {
            String scopeName = mbd.getScope();
            // 获取到 RefreshScope
            final Scope scope = this.scopes.get(scopeName);
            try {
                // 调用 RefreshScope 父类 GenericScope 的 get() 方法
                Object scopedInstance = scope.get(beanName, () -> {
                    beforePrototypeCreation(beanName);
                    try {
                        return createBean(beanName, mbd, args);
                    }
                    finally {
                        afterPrototypeCreation(beanName);
                    }
                });
                bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
            }
        }
        return (T) bean;
    }
}

通过 4.1 refresh 接口 可以知道在调用 /refresh 接口后,会触发 GenericScopedestroy() 逻辑,从而清空缓存。

之后, GenericScopeget() 方法会重新通过 objectFactorycreateBean() 生成新的对象。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容