Support @Scope @ConfigurationProperties beans
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.
This is working as designed. The Scope annotation defines how it should be used and it matches your sample above.
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.
@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.
Closing in favor of PR #42073. Thanks @nosan!