Understanding the Implementation and Mechanism of Spring Cloud @RefreshScope for Dynamic Bean Refresh
This article explains the purpose, source code, usage steps, and internal working principle of Spring Cloud's @RefreshScope annotation, showing how it leverages scope proxying, refresh events, and bean cache management to achieve dynamic configuration refresh in microservice applications.
The author introduces the @RefreshScope annotation, a commonly used feature in Spring Cloud microservice configuration centers that enables dynamic refreshing of bean property values at runtime.
@RefreshScope is a Spring Cloud annotation designed to allow beans to be refreshed dynamically without restarting the application.
/**
* Convenience annotation to put a
@Bean
definition in
* {@link org.springframework.cloud.context.scope.refresh.RefreshScope} refresh scope.
* Beans annotated this way can be refreshed at runtime and any components that are using
* them will get a new instance on the next method call, fully initialized and injected
* with all dependencies.
*
* @author Dave Syer
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Scope("refresh")
@Documented
public @interface RefreshScope {
/**
* @see Scope#proxyMode()
* @return proxy mode
*/
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}The source shows that RefreshScope is itself annotated with @Scope , which determines the bean's lifecycle scope, and defaults to TARGET_CLASS proxy mode.
Usage example:
Add @RefreshScope to a controller class.
Configure the property in Nacos configuration center.
Modify the configuration; the bean refreshes automatically without restarting.
Removing @RefreshScope disables the automatic refresh.
Principle analysis: the dynamic refresh aims to achieve two core goals – reloading the Environment configuration variables and recreating the Spring beans.
@RefreshScope builds on the scope proxy mechanism provided by @Scope . When a bean is marked with @RefreshScope , Spring adds it to a special "refresh" scope cache. When the configuration center changes, the new values are pushed into the Environment , the bean cache is cleared, and the next bean request triggers a fresh creation, allowing @Value fields to obtain the updated values.
Debugging the bean definition reveals that a bean annotated with @RefreshScope has its scope name set to refresh , which is handled specially in the bean factory.
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
protected
T doGetBean(String name, @Nullable Class
requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
// ...
else {
String scopeName = mbd.getScope();
Scope scope = this.scopes.get(scopeName);
Object scopedInstance = scope.get(beanName, () -> {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
} finally {
afterPrototypeCreation(beanName);
}
});
beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
// ...
}
}The GenericScope implementation provides the actual caching logic:
public class GenericScope implements Scope, BeanFactoryPostProcessor, BeanDefinitionRegistryPostProcessor, DisposableBean {
@Override
public Object get(String name, ObjectFactory
objectFactory) {
BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory));
this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
try {
return value.getBean();
} catch (RuntimeException e) {
this.errors.put(name, e);
throw e;
}
}
private static class BeanLifecycleWrapper {
public Object getBean() {
if (this.bean == null) {
synchronized (this.name) {
if (this.bean == null) {
this.bean = this.objectFactory.getObject();
}
}
}
return this.bean;
}
}
}The cache ensures that a bean is reused until the scope is refreshed; when the cache entry becomes null, the bean creation flow runs again.
The RefreshScope bean itself is registered via RefreshAutoConfiguration , which integrates it into the Spring context.
When the configuration center updates, a RefreshEvent is published. The RefreshEventListener receives the event and invokes ContextRefresher.refresh() , which refreshes the Environment and calls scope.refreshAll() on all custom scopes.
public class RefreshEventListener implements SmartApplicationListener {
public void handle(RefreshEvent event) {
if (this.ready.get()) {
log.debug("Event received " + event.getEventDesc());
Set
keys = this.refresh.refresh();
log.info("Refresh keys changed: " + keys);
}
}
}
public synchronized Set
refresh() {
Set
keys = refreshEnvironment();
this.scope.refreshAll();
return keys;
}The RefreshScope class overrides refreshAll() to clear its cache and publish a RefreshScopeRefreshedEvent . Its destroy() method iterates over cached BeanLifecycleWrapper instances, acquires write locks, and sets the bean reference to null , forcing a new creation on the next request.
public void destroy() {
Collection
wrappers = this.cache.clear();
for (BeanLifecycleWrapper wrapper : wrappers) {
try {
Lock lock = this.locks.get(wrapper.getName()).writeLock();
lock.lock();
try {
wrapper.destroy(); // sets bean to null
} finally {
lock.unlock();
}
} catch (RuntimeException e) {
errors.add(e);
}
}
// handle errors ...
}In summary, @RefreshScope achieves dynamic configuration refresh by coupling a custom scope cache with Spring's environment update mechanism and event-driven refresh logic, allowing beans to be recreated automatically when external configuration changes.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
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.