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.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
How Spring Cloud RefreshScope Works: From Annotation to Runtime Refresh

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaBackend DevelopmentSpring BootSpring Cloud@RefreshScopeConfiguration Refresh
Spring Full-Stack Practical Cases
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.