diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrar.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrar.java index ac209a29de5c..458ac8d874f4 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrar.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrar.java @@ -16,12 +16,19 @@ package org.springframework.boot.context.properties; +import org.springframework.aop.scope.ScopedProxyUtils; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.ListableBeanFactory; -import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.boot.context.properties.bind.BindMethod; +import org.springframework.context.annotation.AnnotationScopeMetadataResolver; +import org.springframework.context.annotation.ScopeMetadata; +import org.springframework.context.annotation.ScopeMetadataResolver; +import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; @@ -38,6 +45,8 @@ */ final class ConfigurationPropertiesBeanRegistrar { + private static final ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver(); + private final BeanDefinitionRegistry registry; private final BeanFactory beanFactory; @@ -75,17 +84,25 @@ private void registerBeanDefinition(String beanName, Class type, MergedAnnotation annotation) { Assert.state(annotation.isPresent(), () -> "No " + ConfigurationProperties.class.getSimpleName() + " annotation found on '" + type.getName() + "'."); - this.registry.registerBeanDefinition(beanName, createBeanDefinition(beanName, type)); + BeanDefinitionReaderUtils.registerBeanDefinition(createBeanDefinition(beanName, type), this.registry); } - private BeanDefinition createBeanDefinition(String beanName, Class type) { + private BeanDefinitionHolder createBeanDefinition(String beanName, Class type) { BindMethod bindMethod = ConfigurationPropertiesBean.deduceBindMethod(type); RootBeanDefinition definition = new RootBeanDefinition(type); BindMethodAttribute.set(definition, bindMethod); if (bindMethod == BindMethod.VALUE_OBJECT) { definition.setInstanceSupplier(() -> ConstructorBound.from(this.beanFactory, beanName, type)); } - return definition; + ScopeMetadata metadata = scopeMetadataResolver.resolveScopeMetadata(new AnnotatedGenericBeanDefinition(type)); + definition.setScope(metadata.getScopeName()); + BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(definition, beanName); + ScopedProxyMode scopedProxyMode = metadata.getScopedProxyMode(); + if (scopedProxyMode.equals(ScopedProxyMode.NO)) { + return definitionHolder; + } + boolean proxyTargetClass = scopedProxyMode.equals(ScopedProxyMode.TARGET_CLASS); + return ScopedProxyUtils.createScopedProxy(definitionHolder, this.registry, proxyTargetClass); } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrarTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrarTests.java index 66989ada160f..a6c33ed7d0ff 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrarTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrarTests.java @@ -20,12 +20,15 @@ import org.junit.jupiter.api.Test; +import org.springframework.aop.scope.ScopedProxyFactoryBean; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.boot.context.properties.bind.BindMethod; +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; @@ -44,12 +47,38 @@ class ConfigurationPropertiesBeanRegistrarTests { private final ConfigurationPropertiesBeanRegistrar registrar = new ConfigurationPropertiesBeanRegistrar( this.registry); + @Test + void registerScopedBeanDefinition() { + String beanName = "scopedbeancp-" + ScopedBeanConfigurationProperties.class.getName(); + this.registrar.register(ScopedBeanConfigurationProperties.class); + BeanDefinition beanDefinition = this.registry.getBeanDefinition(beanName); + assertThat(beanDefinition).isNotNull(); + assertThat(beanDefinition.getBeanClassName()).isEqualTo(ScopedBeanConfigurationProperties.class.getName()); + assertThat(beanDefinition.getScope()).isEqualTo(BeanDefinition.SCOPE_PROTOTYPE); + } + + @Test + void registerScopedBeanDefinitionWithProxyMode() { + String beanName = "scopedbeancp-" + ProxyScopedBeanConfigurationProperties.class.getName(); + this.registrar.register(ProxyScopedBeanConfigurationProperties.class); + BeanDefinition proxiedBeanDefinition = this.registry.getBeanDefinition(beanName); + assertThat(proxiedBeanDefinition).isNotNull(); + assertThat(proxiedBeanDefinition.getBeanClassName()).isEqualTo(ScopedProxyFactoryBean.class.getName()); + String targetBeanName = (String) proxiedBeanDefinition.getPropertyValues().get("targetBeanName"); + assertThat(targetBeanName).isNotNull(); + BeanDefinition beanDefinition = this.registry.getBeanDefinition(targetBeanName); + assertThat(beanDefinition).isNotNull(); + assertThat(beanDefinition.getBeanClassName()).isEqualTo(ProxyScopedBeanConfigurationProperties.class.getName()); + assertThat(beanDefinition.getScope()).isEqualTo(BeanDefinition.SCOPE_PROTOTYPE); + } + @Test void registerWhenNotAlreadyRegisteredAddBeanDefinition() { String beanName = "beancp-" + BeanConfigurationProperties.class.getName(); this.registrar.register(BeanConfigurationProperties.class); BeanDefinition definition = this.registry.getBeanDefinition(beanName); assertThat(definition).isNotNull(); + assertThat(definition.getScope()).isEqualTo(BeanDefinition.SCOPE_SINGLETON); assertThat(definition.getBeanClassName()).isEqualTo(BeanConfigurationProperties.class.getName()); } @@ -99,6 +128,18 @@ static class BeanConfigurationProperties { } + @ConfigurationProperties(prefix = "scopedbeancp") + @Scope(BeanDefinition.SCOPE_PROTOTYPE) + static class ScopedBeanConfigurationProperties { + + } + + @ConfigurationProperties(prefix = "scopedbeancp") + @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS, value = BeanDefinition.SCOPE_PROTOTYPE) + static class ProxyScopedBeanConfigurationProperties { + + } + static class NoAnnotationConfigurationProperties { } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java index 449d3fb48dc0..5c2ba0e6725e 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java @@ -33,6 +33,7 @@ import java.util.Optional; import java.util.Properties; import java.util.Set; +import java.util.UUID; import jakarta.annotation.PostConstruct; import jakarta.validation.Valid; @@ -48,6 +49,7 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionRegistry; @@ -1243,6 +1245,24 @@ void loadWhenBindingToConstructorParametersWithConversionToCustomListImplementat "b"); } + @Test + void loadPrototypeScopedProperties() { + load(PrototypeScopePropertiesConfiguration.class); + PrototypeScopeProperties p1 = this.context.getBean(PrototypeScopeProperties.class); + PrototypeScopeProperties p2 = this.context.getBean(PrototypeScopeProperties.class); + assertThat(p1.id).isNotNull(); + assertThat(p2.id).isNotNull(); + assertThat(p1.id).isNotEqualTo(p2.id); + } + + @Test + void loadProxyScopedProperties() { + load(ProxyScopePropertiesConfiguration.class, "name=test"); + ProxyScopeProperties p = this.context.getBean(ProxyScopeProperties.class); + assertThat(p.name).isEqualTo("test"); + assertThat(p.getName()).isEqualTo("test"); + } + @Test void loadWhenBindingToJavaBeanWithConversionToCustomListImplementation() { load(SetterBoundCustomListPropertiesConfiguration.class, "test.values=a,b"); @@ -1493,12 +1513,52 @@ static PropertySourcesPlaceholderConfigurer configurer2() { } + @EnableConfigurationProperties(PrototypeScopeProperties.class) + @Configuration(proxyBeanMethods = false) + static class PrototypeScopePropertiesConfiguration { + + } + + @ConfigurationProperties + @Scope(BeanDefinition.SCOPE_PROTOTYPE) + static class PrototypeScopeProperties { + + private final String id = UUID.randomUUID().toString(); + + String getId() { + return this.id; + } + + } + + @EnableConfigurationProperties(ProxyScopeProperties.class) + @Configuration(proxyBeanMethods = false) + static class ProxyScopePropertiesConfiguration { + + } + + @ConfigurationProperties + @Scope(BeanDefinition.SCOPE_PROTOTYPE) + static class ProxyScopeProperties { + + private String name; + + String getName() { + return this.name; + } + + void setName(String name) { + this.name = name; + } + + } + @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties static class PrototypePropertiesConfiguration { @Bean - @Scope("prototype") + @Scope(BeanDefinition.SCOPE_PROTOTYPE) @ConfigurationProperties("example") PrototypeBean prototypeBean() { return new PrototypeBean();