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.
Comment From: snicoll
This is working as designed. The Scope annotation defines how it should be used and it matches your sample above.
Comment From: wilkinsona
The javadoc of @Scope says the following:
When used as a type-level annotation in conjunction with
@Componentindicates the name of a scope to use for instances of the annotated type.
It's not a huge leap from there to @EnableConfigurationProperties or @ConfigurationPropertiesScan defining a bean and wanting to be able to control its scope. I think it's worth at least considering an expansion of when @Scope is honoured to include "components" that are defined through @EnableConfigurationProperties or @ConfigurationPropertiesScan.
Comment From: sfilipiak-inpost
@snicoll I see your point, that defining @ConfigurationProperties beans via @EnableConfigurationProperties or @ConfigurationPropertiesScan is not an orthodox way of defining spring framework beans. However I believe it would be great if spring boot could define those beans in a 'spring framework compatible' way.
Comment From: philwebb
Closing in favor of PR #42073. Thanks @nosan!