How Spring Cloud RefreshScope Works: From Annotation to Runtime Refresh
This article explains the inner workings of Spring Cloud's RefreshScope, covering its annotation definition, registration in the auto‑configuration, the refresh endpoint workflow, event handling, and how beans annotated with @ConfigurationProperties or @RefreshScope are dynamically refreshed at runtime.
Environment: spring‑cloud‑context 2.2.8.RELEASE + spring‑boot 2.3.9.RELEASE.
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 mark the scope name as refresh.
2. Registering RefreshScope
public class RefreshAutoConfiguration {
public static final String REFRESH_SCOPE_NAME = "refresh";
@Bean
@ConditionalOnMissingBean(RefreshScope.class)
public static RefreshScope refreshScope() {
return new RefreshScope();
}
@Bean
@ConditionalOnMissingBean
public ContextRefresher contextRefresher(ConfigurableApplicationContext context, RefreshScope scope) {
return new ContextRefresher(context, scope);
}
@Bean
@ConditionalOnMissingBean
public RefreshEventListener refreshEventListener(ContextRefresher contextRefresher) {
return new RefreshEventListener(contextRefresher);
}
}The RefreshScope class extends GenericScope and implements ApplicationContextAware and ApplicationListener<ContextRefreshedEvent>. Its core methods are defined in the parent class.
public class GenericScope implements Scope, BeanFactoryPostProcessor, BeanDefinitionRegistryPostProcessor, DisposableBean {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
beanFactory.registerScope(this.name, this);
setSerializationId(beanFactory);
}
}During bean retrieval, the registered RefreshScope is looked up in AbstractBeanFactory#doGetBean, and its get method is invoked.
3. Refresh endpoint trigger
@Endpoint(id = "refresh")
public class RefreshEndpoint {
private ContextRefresher contextRefresher;
public RefreshEndpoint(ContextRefresher contextRefresher) { this.contextRefresher = contextRefresher; }
@WriteOperation
public Collection<String> refresh() { return this.contextRefresher.refresh(); }
}The endpoint calls ContextRefresher#refresh, which first refreshes the environment and then invokes RefreshScope#refreshAll.
public class ContextRefresher {
public synchronized Set<String> refresh() {
Set<String> keys = refreshEnvironment();
this.scope.refreshAll();
return keys;
}
public synchronized Set<String> refreshEnvironment() {
Map<String, Object> before = extract(this.context.getEnvironment().getPropertySources());
addConfigFilesToEnvironment();
Set<String> keys = changes(before, extract(this.context.getEnvironment().getPropertySources())).keySet();
this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
return keys;
}
}The EnvironmentChangeEvent is listened to by ConfigurationPropertiesRebinder, which rebinds all beans annotated with @ConfigurationProperties:
public class ConfigurationPropertiesRebinder implements ApplicationContextAware, ApplicationListener<EnvironmentChangeEvent> {
@ManagedOperation
public void rebind() {
for (String name : this.beans.getBeanNames()) {
rebind(name);
}
}
public boolean rebind(String name) {
Object bean = this.applicationContext.getBean(name);
if (bean != null) {
this.applicationContext.getAutowireCapableBeanFactory().destroyBean(bean);
this.applicationContext.getAutowireCapableBeanFactory().initializeBean(bean, name);
return true;
}
return false;
}
}The supporting auto‑configuration creates ConfigurationPropertiesBeans, a BeanPostProcessor that collects beans with @ConfigurationProperties during initialization.
4. RefreshScope refresh handling
When RefreshScope#refreshAll is invoked, it calls the parent GenericScope#destroy to clear cached bean instances and then publishes a RefreshScopeRefreshedEvent.
public void refreshAll() {
super.destroy();
this.context.publishEvent(new RefreshScopeRefreshedEvent());
}The GenericScope#destroy iterates over cached BeanLifecycleWrapper objects, acquiring write locks and invoking their destroy methods to remove old bean instances.
public void destroy() {
Collection<BeanLifecycleWrapper> wrappers = this.cache.clear();
for (BeanLifecycleWrapper wrapper : wrappers) {
Lock lock = this.locks.get(wrapper.getName()).writeLock();
lock.lock();
try { wrapper.destroy(); } finally { lock.unlock(); }
}
this.errors.clear();
}After the cache is cleared, the next injection triggers ObjectFactory#getObject to create a fresh bean instance.
Summary
Triggering /actuator/refresh causes all beans annotated with @ConfigurationProperties to be automatically refreshed without needing @RefreshScope. The @RefreshScope annotation is typically applied to non‑configuration beans that use @Value for property injection, allowing their values to be updated dynamically when the refresh endpoint is called.
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.
