Closed as not planned
Description
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.