springCloud如何进行配置刷新

在现在许多分布式应用中都使用到了配置中心来进行配置统一管理、下发以及热更新操作,下面我们来分析一下基于SpringCloud微服务体系下如何实现配置的刷新功能。

首先对于Spring中的Bean大家都不陌生,而对于Bean来说有一个关键的属性就是它的生命周期,在Spring中叫做scope。对于早期版本中的Spring中的bean的生命周期

只有singleton和prototype两种,随着支持web应用后又增加了request、session、global session等,随着springCloud之后又增加了一个RefreshScope的类并且增加了

@RfreshScope的注解,注解内部如下:

@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;

}

内部标记了@Scope注解并且ScopedProxyMode值为ScopedProxyMode.TARGET_CLASS。

首先说一下ScopedProxyMode,这个属于@Scope注解中的属性,有四个枚举值可供选择:

  • ScopedProxyMode.DEFAULT    默认代理模式等同于No 不使用代理
  • ScopedProxyMode.No             不使用代理
  • ScopedProxyMode.INTERFACE        使用jdk代理
  • ScopedProxyMode.TARGET_CLASS  使用cglib代理

而@RefreshScope注解内部设置了ScopedProxyMode为ScopedProxyMode.TARGET_CLASS表示使用cgilib代理此对象。

我们可以认为SpringCloud提供了一个新的bean的生命周期种类。这个注解是SpringCloud配置动态刷新的基石。

 

我们看Spring是如何处理@RefreshScope的bean的。

1.bean加载阶段的处理

首先我们看对于添加了@RefreshScope注解的bean如何加载。写个demo如下

@RefreshScope
@Component
@ConfigurationProperties(prefix = "jackding")
public class NameProperties implements InitializingBean {

    private List<String> names;

    public List<String> getNames() {
        return names;
    }

    public void setNames(List<String> names) {
        this.names = names;
    }
}

我们定义一个简单自动装配的Bean添加上@RefeshScope注解标记为一个支持动态刷新的bean。

我这里使用@Coponment注解并且当前这个bean在我启动类的扫描路径内,这种bean通过Spring

内部对@ComponentScan的处理就可以加载,我们看主要看ComponentScanAnnotationParser

加载这个Bean的逻辑。ComponentScanAnnotationParser调用ClassPathBeanDefinitionScanner

的doScan方法,我们看这个方法:

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Assert.notEmpty(basePackages, "At least one base package must be specified");
        Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
        for (String basePackage : basePackages) {
            Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
            for (BeanDefinition candidate : candidates) {
                ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
                candidate.setScope(scopeMetadata.getScopeName());
                String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
                if (candidate instanceof AbstractBeanDefinition) {
                    postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
                }
                if (candidate instanceof AnnotatedBeanDefinition) {
                    AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
                }
                if (checkCandidate(beanName, candidate)) {
                    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                    definitionHolder =
                            AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                    beanDefinitions.add(definitionHolder);
                    registerBeanDefinition(definitionHolder, this.registry);
                }
            }
        }
        return beanDefinitions;
    }

这个方法很好理解根据跟定的包路径扫描所有的@Component注解标记的bean并且注册至容器中并返回。此时重点代码在加粗的地方

--处理scopedProxyMode。

 

    static BeanDefinitionHolder applyScopedProxyMode(
            ScopeMetadata metadata, BeanDefinitionHolder definition, BeanDefinitionRegistry registry) {

        ScopedProxyMode scopedProxyMode = metadata.getScopedProxyMode();
        if (scopedProxyMode.equals(ScopedProxyMode.NO)) {
            return definition;
        }
        boolean proxyTargetClass = scopedProxyMode.equals(ScopedProxyMode.TARGET_CLASS);
        return ScopedProxyCreator.createScopedProxy(definition, registry, proxyTargetClass);
    }

我们接着看createScopeProxy方法。

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

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

        // Create a scoped proxy definition for the original bean name,
        // "hiding" the target bean in an internal target definition.
        RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
        proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName));
        proxyDefinition.setOriginatingBeanDefinition(targetDefinition);
        proxyDefinition.setSource(definition.getSource());
        proxyDefinition.setRole(targetDefinition.getRole());

        proxyDefinition.getPropertyValues().add("targetBeanName", targetBeanName);
        if (proxyTargetClass) {
            targetDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
            // ScopedProxyFactoryBean's "proxyTargetClass" default is TRUE, so we don't need to set it explicitly here.
        }
        else {
            proxyDefinition.getPropertyValues().add("proxyTargetClass", Boolean.FALSE);
        }

        // 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);

        // Register the target bean as separate bean in the factory.
        registry.registerBeanDefinition(targetBeanName, targetDefinition);

        // Return the scoped proxy definition as primary bean definition
        // (potentially an inner bean).
        return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
    }

这段代码意思就是使用原本的BeanName重新构建一个beanDefination,将目标bean隐藏在其内部。并且将目标bean的beanName

改为scopedTarget.targetBeanName的形式并且注册到容器中。此时容器中已经有了我们原始的bean但是名字已经被改掉。

对于当前重新构造的Bean我们暂且称它为代理bean,这个代理bean使用FactoryBean的方式进行构建,这也是我们经常包装代理Bean

的一种常用手段。

将代理bean定义生成后并且将目标bean定义注册至容器后方法返回到ComponentScanAnnotationParser.doScan,我们看到之后会将

返回的bean定义注册至容器,此时对于我们这个bean来说容器内存在两个:

一个是真正的目标bean定义,一个是重新生成的bean定义。

新生成的Bean定义使用我们原始的beanName。并且对于这个代理bean定义会将Scope设置为sigleton。这样我们使用@RefreshScope

注解的bean就像正常的bean使用,我们无须关心背后逻辑,都被Spring屏蔽了。

此时bean定义加载完之后我们看如何生成代理bean。

2.实例化

Spring在加载完bean定义后会实例化所有非懒加载的单例Bean。

我们这个代理bean使用FactoryBean创建一个代理对象。

我们看ScopedProxyFactoryBean的主要两个方法:

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        if (!(beanFactory instanceof ConfigurableBeanFactory)) {
            throw new IllegalStateException("Not running in a ConfigurableBeanFactory: " + beanFactory);
        }
        ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;

        this.scopedTargetSource.setBeanFactory(beanFactory);

        ProxyFactory pf = new ProxyFactory();
        pf.copyFrom(this);
        pf.setTargetSource(this.scopedTargetSource);

        Assert.notNull(this.targetBeanName, "Property 'targetBeanName' is required");
        Class<?> beanType = beanFactory.getType(this.targetBeanName);
        if (beanType == null) {
            throw new IllegalStateException("Cannot create scoped proxy for bean '" + this.targetBeanName +
                    "': Target type could not be determined at the time of proxy creation.");
        }
        if (!isProxyTargetClass() || beanType.isInterface() || Modifier.isPrivate(beanType.getModifiers())) {
            pf.setInterfaces(ClassUtils.getAllInterfacesForClass(beanType, cbf.getBeanClassLoader()));
        }

        // Add an introduction that implements only the methods on ScopedObject.
        ScopedObject scopedObject = new DefaultScopedObject(cbf, this.scopedTargetSource.getTargetBeanName());
        pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject));

        // Add the AopInfrastructureBean marker to indicate that the scoped proxy
        // itself is not subject to auto-proxying! Only its target bean is.
        pf.addInterface(AopInfrastructureBean.class);

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


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

这个对象实现了BeanFactoryAware接口会在对象初始化调用initMethod的时候调用setBeanFactory方法,这个方法我们可以看到

使用AOP生成了代理对象通过getObject方法我们就获取到了已经生成的代理对象。此时真正的bean对象并不会生成,因为它不属

于单例,spring并不会提前实例化,在我们程序中注入的就是这个代理对象,是一个单例bean。当我们调用方法时才会真正的生成。

我们来看下生成真正Bean的逻辑。

对于Aop这里就不细说了,注意上边代码设置了一个targetSource参数,对于TargetSource接口是SpringAop中重要的一个接口,aop

拦截时通过调用TargetSource.getTarget方法获取真正的目标对象。这里ScopedProxyFactoryBean生成我们refreshScope代理bean的

类使用的是SimpleBeanTargetSource,其内部实现:

    public Object getTarget() throws Exception {
        return getBeanFactory().getBean(getTargetBeanName());
    }

就是调用容器获取我们真正的bean。对于Spring源码熟悉的都知道除了单例和原型bean其余的scope类型的bean都会真正调用Scope本身

去生成bean。前边提到了SpringCLoud加入了RefreshScope这个类,就是通过它生成我们的对象。

调用父类genericScope的get方法:

    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;
        }
    }

我们看代码第一句是重点,这里使用了一个cache包装的hashMap做缓存放入一个BeanLifecycleWrapper包装对象并返回,

之后调用BeanLifecycleWrapper的getBean方法。

        public Object getBean() {
            if (this.bean == null) {
                synchronized (this.name) {
                    if (this.bean == null) {
                        this.bean = this.objectFactory.getObject();
                    }
                }
            }
            return this.bean;
        }

我们看这个方法,如果bean其实就是我们真正的对象为Null则会调用objectFactory生成一个反之则返回当前的对象。

这个ObjectFcatory是一个生成object对象的接口在前边通过调用Scope生成对象时以匿名类传入进来,实际上就是

AbstractBeanFactory中生成调用Scope生成对象这一点:

Object scopedInstance = scope.get(beanName, () -> {
                            beforePrototypeCreation(beanName);
                            try {
                                return createBean(beanName, mbd, args);
                            }
                            finally {
                                afterPrototypeCreation(beanName);
                            }
                        });
                        bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);

此时就会真正的生成我们的目标对象并且缓存在refreshScope中。

 

总结一下上边的内容:

对于我们定义了@RefreshScope注解的bean,spring在bean定义加载时期会额外创建一个单例类型的bean定义进行替代

并且返回一个代理对象,真正的bean定义也被注册至容器但是不会跟随容器实例化单例Bean而实例化。

其生成有两种方式:

1.RefreshScope接受到ContextRefreshedEvent事件后会根据判断决定是否直接实例化这些真实的bean。

2.如果方法调用时真实的bean没有生成则会实例化并将其放入缓存。

我们通过使用代理对象来操作真正的对象。

 

对象实例化后接下来就是我们使用了,在使用过程中我们更改了配置中心的配置文件进行配置下发时这些Bean中的属性如何刷新呢?

简单来说就是刷新之后我们的真实对象会销毁,再次调用时会生成一个新的对象使用。来看代码:

 

我们直接从ContextRefresher类看,适配springCLoud的这些配置中心感知到配置发生变化后会发布一个RefreshEvent事件告知Spring

cloud配置更改了,这时RefreshEventListener接受到事件后会调用ContextRefresher的refresh方法来处理这次刷新动作。

 

    public synchronized Set<String> refresh() {
        Set<String> keys = refreshEnvironment();
        this.scope.refreshAll();
        return keys;
    }

我们主要看scope.refreshAll方法。这个第一个动作时重新构建一个applicationContext执行一个完整的上下文创建动作,主要目的是生成一个新的

Enviroment对象,里面包含的所有数据源都是配置更新的,然后将新的Enviroment对象中的数据源替换至当前上下文中的Enviroment对象中,这样

进行完第一步后我们当前的上下文中的所有k v配置都是最新的了。

    public void refreshAll() {
        super.destroy();
        this.context.publishEvent(new RefreshScopeRefreshedEvent());
    }

我们看RefreshScope中的方法很简单调用了父类的destroy方法,然后发布一个事件。

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();
                }
            }
            catch (RuntimeException e) {
                errors.add(e);
            }
        }
        if (!errors.isEmpty()) {
            throw wrapIfNecessary(errors.get(0));
        }
        this.errors.clear();
    }

destroy方法会将当前存储的refreshScope类型的真实的对象清空,然后调用一些销毁的回调方法。

前面我们提到了refreshScope真实对象生成后会放入这个cache中以便下次使用,如果缓存中没有

则会生成一个新的。新生成的对象就会通过ioc的populate、initialize方法重新依赖注入新的属性了,

这样我们就是实现了配置的热更新,对于使用者而言没有感觉,我们依旧使用的是我们的代理对象,

但是其背部的真实对象可能重新生成了好几个了。

 

 

对了除了@RefreshScope注解可以更新属性外,上边我们看到ContextRefresher有一步刷新enviroment的

动作,在这个方法内部会发布一个EnviromentChanged的事件,此时ConfigurationPropertiesRebinder会

监听这个事件并且触发rebind方法,调用binder对所有使用@ConfigurationProperties注解的自动装配对象

进行一个重绑定属性的动作。具体方法是调用bean的destory方法之后在调用initializeBean方法:

public boolean rebind(String name) {
        if (!this.beans.getBeanNames().contains(name)) {
            return false;
        }
        if (this.applicationContext != null) {
            try {
                Object bean = this.applicationContext.getBean(name);
                if (AopUtils.isAopProxy(bean)) {
                    bean = ProxyUtils.getTargetObject(bean);
                }
                if (bean != null) {
                    // TODO: determine a more general approach to fix this.
                    // see https://github.com/spring-cloud/spring-cloud-commons/issues/571
                    if (getNeverRefreshable().contains(bean.getClass().getName())) {
                        return false; // ignore
                    }
                    this.applicationContext.getAutowireCapableBeanFactory()
                            .destroyBean(bean);
                    this.applicationContext.getAutowireCapableBeanFactory()
                            .initializeBean(bean, name);
                    return true;
                }
            }
            catch (RuntimeException e) {
                this.errors.put(name, e);
                throw e;
            }
            catch (Exception e) {
                this.errors.put(name, e);
                throw new IllegalStateException("Cannot rebind to " + name, e);
            }
        }
        return false;
    }

 

这样也可以在一定程度上进行属性的刷新。但是对于hashMap这种结构会有问题,主要原因是因为这里调用

destoryBean并不代表讲个bean删除了重新构建一个新的bean,而只是回调一些destory的接口,此时该bean

的属性值都还在,删除kv的时候bean中的kv并不会更新删除,详细可以看看MapBinder如何操作的。

解决方法可以实现DisposableBean的destory方法将map结构的属性设置为Null就可以了。

 

posted @ 2022-09-22 19:41  她曾是他的梦  阅读(712)  评论(0编辑  收藏  举报