Understanding Spring Cloud RefreshScope: How Dynamic Configuration Refresh Works
This article explains the inner workings of Spring Cloud's RefreshScope, detailing its source code, registration process, refresh endpoint activation, event-driven refresh mechanism, and how beans annotated with @RefreshScope or @ConfigurationProperties are dynamically reloaded without restarting the application.
1 RefreshScope source code
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Scope("refresh")
@Documented
public @interface RefreshScope {
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}The annotation adds @Scope("refresh") to define a custom scope named refresh.
2 Register RefreshScope
public class RefreshAutoConfiguration {
/** Name of the refresh scope */
public static final String REFRESH_SCOPE_NAME = "refresh";
// Register RefreshScope
@Bean
@ConditionalOnMissingBean(RefreshScope.class)
public static RefreshScope refreshScope() {
return new RefreshScope();
}
// Create ContextRefresher – the core component that reloads configuration
@Bean
@ConditionalOnMissingBean
public ContextRefresher contextRefresher(ConfigurableApplicationContext context, RefreshScope scope) {
return new ContextRefresher(context, scope);
}
// Listen to context refresh events
@Bean
public RefreshEventListener refreshEventListener(ContextRefresher contextRefresher) {
return new RefreshEventListener(contextRefresher);
}
}RefreshScope extends GenericScope, which itself implements Scope and several post‑processor interfaces.
3 RefreshScope core class
public class RefreshScope extends GenericScope implements ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, Ordered {
public boolean refresh(String name) {
if (!name.startsWith(SCOPED_TARGET_PREFIX)) {
name = SCOPED_TARGET_PREFIX + name;
}
if (super.destroy(name)) {
this.context.publishEvent(new RefreshScopeRefreshedEvent(name));
return true;
}
return false;
}
@ManagedOperation(description = "Dispose of the current instance of all beans in this scope and force a refresh on next method execution.")
public void refreshAll() {
super.destroy();
this.context.publishEvent(new RefreshScopeRefreshedEvent());
}
}4 Refresh endpoint activation
@Endpoint(id = "refresh")
public class RefreshEndpoint {
private final ContextRefresher contextRefresher;
public RefreshEndpoint(ContextRefresher contextRefresher) {
this.contextRefresher = contextRefresher;
}
@WriteOperation
public Collection<String> refresh() {
Set<String> keys = this.contextRefresher.refresh();
return keys;
}
}Calling /actuator/refresh triggers ContextRefresher.refresh().
5 ContextRefresher implementation
public class ContextRefresher {
public synchronized Set<String> refresh() {
Set<String> keys = refreshEnvironment();
this.scope.refreshAll();
return keys;
}
public synchronized Set<String> refreshEnvironment() {
// Capture current property values
Map<String, Object> before = extract(this.context.getEnvironment().getPropertySources());
// Reload configuration files
addConfigFilesToEnvironment();
// Determine changed keys
Set<String> keys = changes(before, extract(this.context.getEnvironment().getPropertySources())).keySet();
// Publish change event
this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
return keys;
}
}6 ConfigurationProperties rebinding
public class ConfigurationPropertiesRebinder implements ApplicationContextAware, ApplicationListener<EnvironmentChangeEvent> {
private final ConfigurationPropertiesBeans beans;
private ApplicationContext applicationContext;
private final Map<String, Exception> errors = new ConcurrentHashMap<>();
@ManagedOperation
public void rebind() {
errors.clear();
for (String name : beans.getBeanNames()) {
rebind(name);
}
}
@ManagedOperation
public boolean rebind(String name) {
if (!beans.getBeanNames().contains(name) || applicationContext == null) {
return false;
}
Object bean = applicationContext.getBean(name);
if (AopUtils.isAopProxy(bean)) {
bean = ProxyUtils.getTargetObject(bean);
}
if (bean != null) {
// Destroy and re‑initialize the bean
applicationContext.getAutowireCapableBeanFactory().destroyBean(bean);
applicationContext.getAutowireCapableBeanFactory().initializeBean(bean, name);
return true;
}
return false;
}
@Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
if (applicationContext.equals(event.getSource()) || event.getKeys().equals(event.getSource())) {
rebind();
}
}
}The rebinder receives a ConfigurationPropertiesBeans holder created by an auto‑configuration class; it tracks all beans annotated with @ConfigurationProperties so they can be refreshed when an EnvironmentChangeEvent is published.
7 Bean post‑processor for @RefreshScope detection
@Component
public class ConfigurationPropertiesBeans implements BeanPostProcessor, ApplicationContextAware {
private final Map<String, ConfigurationPropertiesBean> beans = new HashMap<>();
private ApplicationContext applicationContext;
private ConfigurableListableBeanFactory beanFactory;
private String refreshScope;
private boolean refreshScopeInitialized;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (isRefreshScoped(beanName)) {
return bean;
}
ConfigurationPropertiesBean propertiesBean = ConfigurationPropertiesBean.get(applicationContext, bean, beanName);
if (propertiesBean != null) {
beans.put(beanName, propertiesBean);
}
return bean;
}
private boolean isRefreshScoped(String beanName) {
if (refreshScope == null && !refreshScopeInitialized) {
refreshScopeInitialized = true;
for (String scope : beanFactory.getRegisteredScopeNames()) {
if (beanFactory.getRegisteredScope(scope) instanceof org.springframework.cloud.context.scope.refresh.RefreshScope) {
refreshScope = scope;
break;
}
}
}
if (beanName == null || refreshScope == null) {
return false;
}
return beanFactory.containsBeanDefinition(beanName) &&
refreshScope.equals(beanFactory.getBeanDefinition(beanName).getScope());
}
public Set<String> getBeanNames() {
return new HashSet<>(beans.keySet());
}
}This post‑processor records beans that are not in the refresh scope but have @ConfigurationProperties, enabling them to be re‑bound when a refresh occurs.
8 RefreshScope refresh handling
public class ContextRefresher {
public synchronized Set<String> refresh() {
Set<String> keys = refreshEnvironment();
this.scope.refreshAll();
return keys;
}
}
public class RefreshScope {
public void refreshAll() {
super.destroy();
this.context.publishEvent(new RefreshScopeRefreshedEvent());
}
}The destroy() method in GenericScope clears cached bean instances, causing the next injection to create fresh objects.
9 Practical example
@RestController
@RequestMapping("/refreshBeanProp")
@RefreshScope
public class RefreshScopeBeanPropController {
@Value("${custom}")
private String custom;
@GetMapping("/get")
public String get() {
return custom;
}
}When /actuator/refresh is invoked, the custom property is re‑loaded, the cached bean is cleared, and a new instance reflecting the updated value is created.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
