Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ConditionalOnBean doesn't match RefreshScope Bean #1191

Open
p-daniil opened this issue Jan 11, 2023 · 2 comments
Open

ConditionalOnBean doesn't match RefreshScope Bean #1191

p-daniil opened this issue Jan 11, 2023 · 2 comments

Comments

@p-daniil
Copy link

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;

    }
}
@p-daniil
Copy link
Author

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.

@sangmin7648
Copy link

sangmin7648 commented Dec 27, 2023

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants