Skip to content

Support @Scope @ConfigurationProperties beans #41668

Closed as not planned
Closed as not planned
@sfilipiak-inpost

Description

@sfilipiak-inpost

When using @ConfigurationProperties via either @ConfigurationPropertiesScan or @EnableConfigurationProperties, bean scope specified via @Scope is not respected.
On the other hand, when defining @ConfigurationProperties with additional @Configuration/@Component/@Bean annotation, specified scope is respected

Provided below is a sample application using spring boot 3.3.2

package configbug;

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@SpringBootApplication
@EnableConfigurationProperties(ConfigBugApplication.MyProperties.class)
public class ConfigBugApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConfigBugApplication.class, args);
    }

    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    @ConfigurationProperties("my.properties")
    public static class MyProperties {
        private String value;
    }

    @Component
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    @ConfigurationProperties("my.other.properties")
    public static class MyOtherProperties {
        private String value;
    }

    @Configuration
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    @ConfigurationProperties("my.yet.another.properties")
    public static class MyYetAnotherProperties {
        private String value;
    }

    @ConfigurationProperties("my.yet.yet.another.properties")
    public static class MyYetYetAnotherProperties {
        private String value;
    }

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    MyYetYetAnotherProperties myYetYetAnotherProperties() {
        return new MyYetYetAnotherProperties();
    }

    @EventListener
    public void ready(ApplicationReadyEvent event) {
        var applicationContext = (AnnotationConfigApplicationContext) event.getApplicationContext();
        System.out.printf("MyProperties scope = '%s'%n", applicationContext.getBeanDefinition("my.properties-configbug.ConfigBugApplication$MyProperties").getScope());
        System.out.printf("MyOtherProperties scope = '%s'%n", applicationContext.getBeanDefinition("configBugApplication.MyOtherProperties").getScope());
        System.out.printf("MyYetAnotherProperties scope = '%s'%n", applicationContext.getBeanDefinition("configBugApplication.MyYetAnotherProperties").getScope());
        System.out.printf("MyYetYetAnotherProperties scope = '%s'%n", applicationContext.getBeanDefinition("myYetYetAnotherProperties").getScope());
    }

}

It prints following result

MyProperties scope = ''
MyOtherProperties scope = 'prototype'
MyYetAnotherProperties scope = 'prototype'
MyYetYetAnotherProperties scope = 'prototype'

It seems that the following if statement in ConfigurationPropertiesScanRegistrar is causing it to behave differently

private void register(ConfigurationPropertiesBeanRegistrar registrar, Class<?> type) {
        if (!isComponent(type)) {
	        registrar.register(type);
        }
}

private boolean isComponent(Class<?> type) {
        return MergedAnnotations.from(type, SearchStrategy.TYPE_HIERARCHY).isPresent(Component.class);
}

and the registrar itself registers properties with default scope.

Metadata

Metadata

Assignees

No one assigned

    Labels

    status: supersededAn issue that has been superseded by another

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions