charlie
charlie

Reputation: 27

how to keep data consistent when using @RefreshScope in a execution

I read this Dynamic Configuration Properties in Spring Boot and Spring Cloud And it said that

If your method execution is the one that triggers the initialization, then it all even happens in the same thread.

This is my code, a 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);
    }
}

A controller:

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

}

Then get url: http://localhost:8080/config, and changed config "name" in my "application.properties" during the thread sleep time(which id 10 seconds as the code shows).

It turns out that the config "name" is changed before and after. Why?Does I take @RefreshScope the wrong way?

Upvotes: 1

Views: 3714

Answers (1)

yongsung.yoon
yongsung.yoon

Reputation: 5589

That is the expected behavior of @RefreshScope bean. Like the document says

Two consecutive method executions on the same bean in the same thread may be applied to different targets if things get really busy.

@RefreshScope bean doesn't guarantee that your multiple invocations on the same thread will use the same target bean.

In your sample, you call contextRefresher.refresh(); and it will destroy all refresh scoped beans. And your second invocation of config.getName() will reinitialize your bean again. That's why you got the different name in case that you changed your config between your two invocations.

If your method execution is the one that triggers the initialization, then it all even happens in the same thread.

Above statement just means that bean initialization itself will be executed on the same thread. It doesn't mean that your two invocations will use the same target.

IMHO, using @RefreshScope could be a little risky for certain cases.

Update on the question in comments

IMHO, @ConfigurationProperties annotation is also specially treated in Spring Cloud. If EnvironmentChangedEvent occurs, Spring beans that are annotated with @ConfigurationProperties will be re-binded with changed values.

The main difference between @ConfigurationProperties in Spring Cloud and @RefreshScope bean is atomicity during the process.

Spring Cloud just starts rebinding process for @ConfigurationProperties annotated beans WITHOUT creating a new instance of bean when EnvironmentChangedEvent occurs. It means that any invocation into this bean can occur during this process (namely before this process ends). As a result, a user of this bean can see any intermediate state (ex three properties are changed. But two properties values are applied and one property are not yet applied. Any invocation can occur on this state)

In case of using @RefreshScope annotation, a proxy bean is created and it is injected instead of your actual target bean. And if the bean is refreshed (by an invocation of refresh() API or another way), your actual target bean is removed from the cache. If any next invocation to your bean occurs, your target bean is re-created and initialized again (and this process is synchronized by the lock). As a result, your any invocation to your bean always occurs on stable state of your bean (after finishing initializing beans)

You can annotation your @ConfigurationProperties bean with @RefreshScope. Or you can only use @RefreshScope annotation with your bean with @Value annotation on its internal fields.

Anyway, neither @ConfigurationProperties nor @RefreshScope can guarantee your expected result for multiple invocations even on the same thread. Hope this helps.

Upvotes: 4

Related Questions