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

问题描述 投票:1回答:1

我读了这个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();
    }

}

然后获取url:http://localhost:8080/config,并在线程休眠时间(代码显示为id 10秒)的“application.properties”中更改config“name”。

事实证明,配置“名称”在之前和之后发生了变化。为什么?我以错误的方式接受@RefreshScope吗?

java spring spring-boot spring-cloud
1个回答
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上使用@RefreshScope注释,并在其内部字段中使用@Value注释。

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

© www.soinside.com 2019 - 2024. All rights reserved.