在执行中使用@RefreshScope时如何保持数据一致

时间:2018-03-02 05:36:21

标签: java spring spring-boot spring-cloud

我读了这个Dynamic Configuration Properties in Spring Boot and Spring Cloud 它说那个

  

如果您的方法执行是触发初始化的方法,   然后它甚至发生在同一个线程中。

这是我的代码, TestConfigBean

 package com.my.springdemo.chapter3.test;

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

import javax.annotation.PostConstruct;

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

    @PostConstruct
    private void print(){
        System.out.println("配置为=" + name);
    }
}

控制器

package com.my.springdemo.chapter3.test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.cloud.context.refresh.ContextRefresher;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@ConditionalOnClass(ContextRefresher.class)
@RestController
public class TestController {

    @Autowired
    private TestConfig config;

    @Autowired
    private  ContextRefresher contextRefresher;

    @RequestMapping("/config")
    public String config(){
        System.out.println("before config= " + config.getName() + ", hasCode=" + config.hashCode());
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        contextRefresher.refresh();
        System.out.println("After config= " + config.getName() + ", hasCode=" + config.hashCode());
        return config.getName();
    }

}

然后获取网址:http://localhost:8080/config,并在线程休眠时间(ID 10秒)内更改“ application.properties ”中的配置“ name ”如代码所示。)

事实证明,配置“ name ”之前和之后都有所改变。 为什么?我以错误的方式接受@RefreshScope吗?

1 个答案:

答案 0 :(得分:1)

这是@RefreshScope bean的预期行为。 就像文件说的那样

  

如果事情变得非常繁忙,同一个线程中同一个bean上的两个连续方法执行可能会应用于不同的目标。

@RefreshScope bean不保证同一线程上的多次调用将使用相同的目标bean。

在您的示例中,您调用contextRefresher.refresh();,它将销毁所有刷新范围的bean。第二次调用config.getName()将再次重新初始化bean。这就是为什么你在两次调用之间更改配置的情况下获得不同名称的原因。

  

如果您的方法执行是触发初始化的方法,那么它甚至都发生在同一个线程中。

上面的语句只是意味着bean初始化本身将在同一个线程上执行。它并不意味着您的两个调用将使用相同的目标。

恕我直言,使用@RefreshScope对某些案件来说可能有点风险。

评论中的问题更新

恕我直言,@ConfigurationProperties注释也在Spring Cloud中得到特别处理。如果发生EnvironmentChangedEvent,则使用@ConfigurationProperties注释的Spring bean将使用更改的值重新绑定。

Spring Cloud中的@ConfigurationProperties@RefreshScope bean之间的主要区别在于此过程中的原子性。

Spring Cloud只为@ConfigurationProperties带注释的bean启动重新绑定过程,而不会在EnvironmentChangedEvent发生时创建新的bean实例。这意味着在此过程中(即在此过程结束之前)可能会发生对此bean的任何调用。因此,此bean的用户可以看到任何中间状态(前三个属性已更改。但是应用了两个属性值,并且尚未应用一个属性。在此状态下可以进行任何调用)

如果使用@RefreshScope注释,则会创建一个代理bean,而不是实际的目标bean。如果刷新bean(通过调用refresh()API或其他方式),则会从缓存中删除实际的目标bean。如果发生对bean的任何下一次调用,则重新创建目标bean并再次初始化(此过程由锁同步)。因此,对bean的任何调用总是发生在bean的稳定状态(完成初始化bean之后)

您可以使用@ConfigurationProperties注释@RefreshScope bean。或者,您只能在您的bean上使用@RefreshScope注释,并在其内部字段中添加@Value注释。

无论如何,即使在同一个线程上,@ConfigurationProperties@RefreshScope都不能保证多次调用的预期结果。希望这会有所帮助。