Browse Source

Add consistent scope support for ConfigurationProperties beans

See gh-42073
pull/42277/head
Dmytro Nosan 1 year ago committed by Stéphane Nicoll
parent
commit
2f8fc5cb05
  1. 25
      spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrar.java
  2. 41
      spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrarTests.java
  3. 62
      spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java

25
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrar.java

@ -16,12 +16,19 @@ @@ -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 @@ import org.springframework.util.StringUtils; @@ -38,6 +45,8 @@ import org.springframework.util.StringUtils;
*/
final class ConfigurationPropertiesBeanRegistrar {
private static final ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver();
private final BeanDefinitionRegistry registry;
private final BeanFactory beanFactory;
@ -75,17 +84,25 @@ final class ConfigurationPropertiesBeanRegistrar { @@ -75,17 +84,25 @@ final class ConfigurationPropertiesBeanRegistrar {
MergedAnnotation<ConfigurationProperties> 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);
}
}

41
spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrarTests.java

@ -20,12 +20,15 @@ import java.util.function.Consumer; @@ -20,12 +20,15 @@ import java.util.function.Consumer;
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 { @@ -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 @@ class ConfigurationPropertiesBeanRegistrarTests { @@ -99,6 +128,18 @@ class ConfigurationPropertiesBeanRegistrarTests {
}
@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 {
}

62
spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java

@ -33,6 +33,7 @@ import java.util.Map; @@ -33,6 +33,7 @@ import java.util.Map;
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.InitializingBean; @@ -48,6 +49,7 @@ import org.springframework.beans.factory.InitializingBean;
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 @@ class ConfigurationPropertiesTests { @@ -1243,6 +1245,24 @@ class ConfigurationPropertiesTests {
"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 @@ class ConfigurationPropertiesTests { @@ -1493,12 +1513,52 @@ class ConfigurationPropertiesTests {
}
@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();

Loading…
Cancel
Save