@RefreshScope in Configuration class

Mykeul picture Mykeul · Jul 1, 2019 · Viewed 10k times · Source

I have a spring boot application. I am using Spring Cloud Config to externalize properties - through Git. Everything works fine. I would like the beans to be refreshed when I issue the actuator refresh endpoint. Beans are refreshed eagerly as expected by doing the following:

@EventListener
public void onRefreshScopeRefreshed(final RefreshScopeRefreshedEvent event) {
    logger.info("Received Refresh event. Refreshing all beans...");
    for (String beanName : applicationContext.getBeanDefinitionNames()) {
        Class<?> beanClass = applicationContext.getBean(beanName).getClass();
        if(beanClass.getName().contains("SpringCGLIB")) {
            logger.info("Proxied bean: bean name: " + beanName + " - Bean class: " + applicationContext.getBean(beanName).getClass());
        } else {
            logger.info("Regular Bean: Bean name: " + beanName + " - Bean class: " + applicationContext.getBean(beanName).getClass());
        }
        applicationContext.getBean(beanName).getClass(); // to cause refresh eagerly
    }
}

The only thing not working as expected is when I annotate a Configuration class with @refreshScope (meaning at the class level), beans declared in this class are not refreshed if they don't have themselves @RefreshScope in the bean declaration.

Here the bean is not refreshed:

@Configuration
@RefreshScope
public class DraftsClientConfiguration {

    @Bean
    MyBean aBean() {
        return new MyBean();
    }
}

Here is the log from my RefreshListener class: We can see that in this case, there is only one bean that is not proxied.

RefreshListener - Regular Bean: Bean name: draftsServiceClient - Bean class: class com.citi.qi.athena.drafts.DraftsServiceClient

But here the bean is refreshed:

@Configuration
public class DraftsClientConfiguration {

    @RefreshScope
    @Bean
    MyBean aBean() {
        return new MyBean();
    }
}

In this second case, we have two beans (should it be the case?), one proxied and one not proxied.

RefreshListener - Regular Bean: Bean name: scopedTarget.draftsServiceClient - Bean class: class com.citi.qi.athena.drafts.DraftsServiceClient
RefreshListener - Proxied bean: bean name: draftsServiceClient - Bean class: class com.citi.qi.athena.drafts.DraftsServiceClient$$EnhancerBySpringCGLIB$$bbfd1caf

According to Spring doc, beans should be refreshed by annotating @RefreshScope at the configuration class level. There is no need to specify @RefreshScope for every bean declaration of the configuration class. Am I missing something?

By the way, I am checking if the bean is refreshed or not by putting a breakpoint in the bean declaration.

Second question: I guess I should have only one proxied bean and not two beans as we can see in the second case?

Answer

M. Deinum picture M. Deinum · Jul 1, 2019

Your understanding is a bit off and all of that is stated in the documentation.

From the javadoc of @RefreshScope.

The implementation involves creating a proxy for every bean in the scope,

So you will get 2 instances of the bean. 1 Proxy which will actually wrap an full instance of the bean. When being refreshed, the proxy will survice and the actual instance will be replaced.

From the Spring Cloud Reference Guide:

@RefreshScope works (technically) on an @Configuration class, but it might lead to surprising behavior. For example, it does not mean that all the @Beans defined in that class are themselves in @RefreshScope. Specifically, anything that depends on those beans cannot rely on them being updated when a refresh is initiated, unless it is itself in @RefreshScope. In that case, it is rebuilt on a refresh and its dependencies are re-injected. At that point, they are re-initialized from the refreshed @Configuration).

So while it might be technically possible using references to those beans might not refresh, unless those are also marked as @RefreshScope.

In short the solution is to explicitly mark which beans need to be in @RefreshScope by either annotating the class as @RefreshScope or the @Bean method.