ConditionalOnBean doesn't match RefreshScope Bean
Hi!
I'm trying to integrate RefreshScope in my project.
The problem is that @ConditionalOnBean condition doesn't match bean, annotated with @RefreshScope.
It works fine with Spring Boot 2.1.4.RELEASE, but doesn't work with version 2.6.6.
I have debugged and found, that the root cause is in this line: https://github.com/spring-projects/spring-boot/blob/d4a91004b5b04f0151e9b5df65dceb6443a35e42/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java#L186
Is it expected behaviour or it's a bug?
My expectations is that refresh scope bean proxy should trigger real bean creation, because it's injected in provider and it shouldn't be blocked by @ConditionalOnBean condition.
Example
I have three autoconfigurations, executing one after another. TokenProviderAutoConfiguration depends on bean, which can be created or not in TokenClientAutoConfiguration. On this bean I added @RefreshScope. After that TokenProviderAutoConfiguration didn't added to context, because of @ConditionalOnBean condition (but only in later Spring Boot version)
package com.example.demo;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
public class ConditionalOnRefreshScopeBeanTest {
private static final String TOKEN = "some-token";
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(
RefreshAutoConfiguration.class,
TokenClientAutoConfiguration.class,
TokenProviderAutoConfiguration.class,
DummyBeanAutoConfiguration.class
));
@Test
void conditionalOnRefreshScopeBeanTest() {
contextRunner
.run(context -> {
final DummyBean dummyBean = context.getBean(DummyBean.class);
final String dummyToken = dummyBean.getToken();
Assertions.assertEquals(TOKEN, dummyToken);
});
}
@Configuration
public static class TokenClientAutoConfiguration {
@RefreshScope
@ConditionalOnMissingBean
@Bean
public TokenClientFactoryBean tokenClient() {
// token client created via factory bean if it's matters
return new TokenClientFactoryBean();
}
}
@AutoConfigureAfter(TokenClientAutoConfiguration.class)
@Configuration
@ConditionalOnBean(TokenClient.class)
public static class TokenProviderAutoConfiguration {
@Bean
public TokenProvider tvmTicketProvider(TokenClient tokenClient) {
return tokenClient::getTokenFor;
}
}
@AutoConfigureAfter(TokenProviderAutoConfiguration.class)
@Configuration
public static class DummyBeanAutoConfiguration {
@Bean
public DummyBean dummyBean(ObjectProvider<TokenProvider> tokenProviderObjectProvider) throws Exception {
final TokenProvider tokenProvider = tokenProviderObjectProvider.getIfAvailable();
if (tokenProvider != null) {
return new DummyBean(tokenProvider.getToken(1));
} else {
return new DummyBean(null);
}
}
}
public static class DummyBean {
private final String token;
public DummyBean(String token) {
this.token = token;
}
public String getToken() {
return token;
}
}
public static class TokenClientFactoryBean implements FactoryBean<TokenClient>, InitializingBean {
@Override
public TokenClient getObject() throws Exception {
return new TokenClient() {
@Override
public String getTokenFor(int id) {
return TOKEN;
}
@Override
public void close() {
}
};
}
@Override
public Class<?> getObjectType() {
return TokenClient.class;
}
@Override
public void afterPropertiesSet() throws Exception {
// some initialization steps
System.out.println("Token client initialized");
}
}
public interface TokenClient extends AutoCloseable {
String getTokenFor(int id);
// other methods
@Override
void close();
}
@FunctionalInterface
public interface TokenProvider {
String getToken(int id) throws Exception;
}
}
Found, that the problem exactly in factory bean.
On this line for factory bean returned type Object.class, so later on line 668 type is not matched.
I have similar but different problem where my configuration class annotated with @RefreshScope and method annotated with @Bean registers BeanDefinition with null BeanClass. ConditionalOnMissingBean checks match using beanclass so it creates another bean with type even if refreshscope bean already exist