Skip to content

Commit 23d76a0

Browse files
committed
Polish "Add consistent scope support for ConfigurationProperties beans"
See gh-42073
1 parent 2f8fc5c commit 23d76a0

File tree

6 files changed

+127
-135
lines changed

6 files changed

+127
-135
lines changed

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

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import org.springframework.beans.factory.config.BeanDefinitionHolder;
2424
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
2525
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
26-
import org.springframework.beans.factory.support.RootBeanDefinition;
26+
import org.springframework.beans.factory.support.GenericBeanDefinition;
2727
import org.springframework.boot.context.properties.bind.BindMethod;
2828
import org.springframework.context.annotation.AnnotationScopeMetadataResolver;
2929
import org.springframework.context.annotation.ScopeMetadata;
@@ -88,21 +88,26 @@ private void registerBeanDefinition(String beanName, Class<?> type,
8888
}
8989

9090
private BeanDefinitionHolder createBeanDefinition(String beanName, Class<?> type) {
91+
GenericBeanDefinition definition = new AnnotatedGenericBeanDefinition(type);
9192
BindMethod bindMethod = ConfigurationPropertiesBean.deduceBindMethod(type);
92-
RootBeanDefinition definition = new RootBeanDefinition(type);
9393
BindMethodAttribute.set(definition, bindMethod);
9494
if (bindMethod == BindMethod.VALUE_OBJECT) {
9595
definition.setInstanceSupplier(() -> ConstructorBound.from(this.beanFactory, beanName, type));
9696
}
97-
ScopeMetadata metadata = scopeMetadataResolver.resolveScopeMetadata(new AnnotatedGenericBeanDefinition(type));
97+
ScopeMetadata metadata = scopeMetadataResolver.resolveScopeMetadata(definition);
9898
definition.setScope(metadata.getScopeName());
9999
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(definition, beanName);
100+
return applyScopedProxyMode(metadata, definitionHolder, this.registry);
101+
}
102+
103+
static BeanDefinitionHolder applyScopedProxyMode(ScopeMetadata metadata, BeanDefinitionHolder definition,
104+
BeanDefinitionRegistry registry) {
100105
ScopedProxyMode scopedProxyMode = metadata.getScopedProxyMode();
101106
if (scopedProxyMode.equals(ScopedProxyMode.NO)) {
102-
return definitionHolder;
107+
return definition;
103108
}
104109
boolean proxyTargetClass = scopedProxyMode.equals(ScopedProxyMode.TARGET_CLASS);
105-
return ScopedProxyUtils.createScopedProxy(definitionHolder, this.registry, proxyTargetClass);
110+
return ScopedProxyUtils.createScopedProxy(definition, registry, proxyTargetClass);
106111
}
107112

108113
}

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

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,7 +25,6 @@
2525
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
2626
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
2727
import org.springframework.beans.factory.support.GenericBeanDefinition;
28-
import org.springframework.beans.factory.support.RootBeanDefinition;
2928
import org.springframework.boot.context.properties.bind.BindMethod;
3029
import org.springframework.context.annotation.Scope;
3130
import org.springframework.context.annotation.ScopedProxyMode;
@@ -47,38 +46,12 @@ class ConfigurationPropertiesBeanRegistrarTests {
4746
private final ConfigurationPropertiesBeanRegistrar registrar = new ConfigurationPropertiesBeanRegistrar(
4847
this.registry);
4948

50-
@Test
51-
void registerScopedBeanDefinition() {
52-
String beanName = "scopedbeancp-" + ScopedBeanConfigurationProperties.class.getName();
53-
this.registrar.register(ScopedBeanConfigurationProperties.class);
54-
BeanDefinition beanDefinition = this.registry.getBeanDefinition(beanName);
55-
assertThat(beanDefinition).isNotNull();
56-
assertThat(beanDefinition.getBeanClassName()).isEqualTo(ScopedBeanConfigurationProperties.class.getName());
57-
assertThat(beanDefinition.getScope()).isEqualTo(BeanDefinition.SCOPE_PROTOTYPE);
58-
}
59-
60-
@Test
61-
void registerScopedBeanDefinitionWithProxyMode() {
62-
String beanName = "scopedbeancp-" + ProxyScopedBeanConfigurationProperties.class.getName();
63-
this.registrar.register(ProxyScopedBeanConfigurationProperties.class);
64-
BeanDefinition proxiedBeanDefinition = this.registry.getBeanDefinition(beanName);
65-
assertThat(proxiedBeanDefinition).isNotNull();
66-
assertThat(proxiedBeanDefinition.getBeanClassName()).isEqualTo(ScopedProxyFactoryBean.class.getName());
67-
String targetBeanName = (String) proxiedBeanDefinition.getPropertyValues().get("targetBeanName");
68-
assertThat(targetBeanName).isNotNull();
69-
BeanDefinition beanDefinition = this.registry.getBeanDefinition(targetBeanName);
70-
assertThat(beanDefinition).isNotNull();
71-
assertThat(beanDefinition.getBeanClassName()).isEqualTo(ProxyScopedBeanConfigurationProperties.class.getName());
72-
assertThat(beanDefinition.getScope()).isEqualTo(BeanDefinition.SCOPE_PROTOTYPE);
73-
}
74-
7549
@Test
7650
void registerWhenNotAlreadyRegisteredAddBeanDefinition() {
7751
String beanName = "beancp-" + BeanConfigurationProperties.class.getName();
7852
this.registrar.register(BeanConfigurationProperties.class);
7953
BeanDefinition definition = this.registry.getBeanDefinition(beanName);
8054
assertThat(definition).isNotNull();
81-
assertThat(definition.getScope()).isEqualTo(BeanDefinition.SCOPE_SINGLETON);
8255
assertThat(definition.getBeanClassName()).isEqualTo(BeanConfigurationProperties.class.getName());
8356
}
8457

@@ -104,20 +77,53 @@ void registerWhenValueObjectRegistersValueObjectBeanDefinition() {
10477
String beanName = "valuecp-" + ValueObjectConfigurationProperties.class.getName();
10578
this.registrar.register(ValueObjectConfigurationProperties.class);
10679
BeanDefinition definition = this.registry.getBeanDefinition(beanName);
107-
assertThat(definition).satisfies(configurationPropertiesBeanDefinition(BindMethod.VALUE_OBJECT));
80+
assertThat(definition).satisfies(hasBindMethodAttribute(BindMethod.VALUE_OBJECT));
10881
}
10982

11083
@Test
11184
void registerWhenNotValueObjectRegistersRootBeanDefinitionWithJavaBeanBindMethod() {
11285
String beanName = MultiConstructorBeanConfigurationProperties.class.getName();
11386
this.registrar.register(MultiConstructorBeanConfigurationProperties.class);
11487
BeanDefinition definition = this.registry.getBeanDefinition(beanName);
115-
assertThat(definition).satisfies(configurationPropertiesBeanDefinition(BindMethod.JAVA_BEAN));
88+
assertThat(definition).satisfies(hasBindMethodAttribute(BindMethod.JAVA_BEAN));
11689
}
11790

118-
private Consumer<BeanDefinition> configurationPropertiesBeanDefinition(BindMethod bindMethod) {
91+
@Test
92+
void registerWhenNoScopeUsesSingleton() {
93+
String beanName = "beancp-" + BeanConfigurationProperties.class.getName();
94+
this.registrar.register(BeanConfigurationProperties.class);
95+
BeanDefinition definition = this.registry.getBeanDefinition(beanName);
96+
assertThat(definition).isNotNull();
97+
assertThat(definition.getScope()).isEqualTo(BeanDefinition.SCOPE_SINGLETON);
98+
}
99+
100+
@Test
101+
void registerScopedBeanDefinition() {
102+
String beanName = "beancp-" + ScopedBeanConfigurationProperties.class.getName();
103+
this.registrar.register(ScopedBeanConfigurationProperties.class);
104+
BeanDefinition beanDefinition = this.registry.getBeanDefinition(beanName);
105+
assertThat(beanDefinition).isNotNull();
106+
assertThat(beanDefinition.getBeanClassName()).isEqualTo(ScopedBeanConfigurationProperties.class.getName());
107+
assertThat(beanDefinition.getScope()).isEqualTo(BeanDefinition.SCOPE_PROTOTYPE);
108+
}
109+
110+
@Test
111+
void registerScopedBeanDefinitionWithProxyMode() {
112+
String beanName = "beancp-" + ProxyScopedBeanConfigurationProperties.class.getName();
113+
this.registrar.register(ProxyScopedBeanConfigurationProperties.class);
114+
BeanDefinition proxiedBeanDefinition = this.registry.getBeanDefinition(beanName);
115+
assertThat(proxiedBeanDefinition).isNotNull();
116+
assertThat(proxiedBeanDefinition.getBeanClassName()).isEqualTo(ScopedProxyFactoryBean.class.getName());
117+
String targetBeanName = (String) proxiedBeanDefinition.getPropertyValues().get("targetBeanName");
118+
assertThat(targetBeanName).isNotNull();
119+
BeanDefinition beanDefinition = this.registry.getBeanDefinition(targetBeanName);
120+
assertThat(beanDefinition).isNotNull();
121+
assertThat(beanDefinition.getBeanClassName()).isEqualTo(ProxyScopedBeanConfigurationProperties.class.getName());
122+
assertThat(beanDefinition.getScope()).isEqualTo(BeanDefinition.SCOPE_PROTOTYPE);
123+
}
124+
125+
private Consumer<BeanDefinition> hasBindMethodAttribute(BindMethod bindMethod) {
119126
return (definition) -> {
120-
assertThat(definition).isExactlyInstanceOf(RootBeanDefinition.class);
121127
assertThat(definition.hasAttribute(BindMethod.class.getName())).isTrue();
122128
assertThat(definition.getAttribute(BindMethod.class.getName())).isEqualTo(bindMethod);
123129
};
@@ -128,14 +134,14 @@ static class BeanConfigurationProperties {
128134

129135
}
130136

131-
@ConfigurationProperties(prefix = "scopedbeancp")
137+
@ConfigurationProperties(prefix = "beancp")
132138
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
133139
static class ScopedBeanConfigurationProperties {
134140

135141
}
136142

137-
@ConfigurationProperties(prefix = "scopedbeancp")
138-
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS, value = BeanDefinition.SCOPE_PROTOTYPE)
143+
@ConfigurationProperties(prefix = "beancp")
144+
@Scope(scopeName = BeanDefinition.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
139145
static class ProxyScopedBeanConfigurationProperties {
140146

141147
}

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

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,13 +17,13 @@
1717
package org.springframework.boot.context.properties;
1818

1919
import java.io.IOException;
20+
import java.util.Locale;
2021
import java.util.function.Consumer;
2122

2223
import org.junit.jupiter.api.Test;
2324

2425
import org.springframework.beans.factory.config.BeanDefinition;
2526
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
26-
import org.springframework.beans.factory.support.RootBeanDefinition;
2727
import org.springframework.boot.context.properties.bind.BindMethod;
2828
import org.springframework.boot.context.properties.scan.combined.c.CombinedConfiguration;
2929
import org.springframework.boot.context.properties.scan.combined.d.OtherCombinedConfiguration;
@@ -56,9 +56,9 @@ void registerBeanDefinitionsShouldScanForConfigurationProperties() throws IOExce
5656
"foo-org.springframework.boot.context.properties.scan.valid.ConfigurationPropertiesScanConfiguration$FooProperties");
5757
BeanDefinition barDefinition = this.beanFactory.getBeanDefinition(
5858
"bar-org.springframework.boot.context.properties.scan.valid.ConfigurationPropertiesScanConfiguration$BarProperties");
59-
assertThat(bingDefinition).satisfies(configurationPropertiesBeanDefinition(BindMethod.JAVA_BEAN));
60-
assertThat(fooDefinition).satisfies(configurationPropertiesBeanDefinition(BindMethod.JAVA_BEAN));
61-
assertThat(barDefinition).satisfies(configurationPropertiesBeanDefinition(BindMethod.VALUE_OBJECT));
59+
assertThat(bingDefinition).satisfies(hasBindMethod(BindMethod.JAVA_BEAN));
60+
assertThat(fooDefinition).satisfies(hasBindMethod(BindMethod.JAVA_BEAN));
61+
assertThat(barDefinition).satisfies(hasBindMethod(BindMethod.VALUE_OBJECT));
6262
}
6363

6464
@Test
@@ -67,9 +67,12 @@ void scanWhenBeanDefinitionExistsShouldSkip() throws IOException {
6767
beanFactory.setAllowBeanDefinitionOverriding(false);
6868
this.registrar.registerBeanDefinitions(
6969
getAnnotationMetadata(ConfigurationPropertiesScanConfiguration.TestConfiguration.class), beanFactory);
70-
BeanDefinition fooDefinition = beanFactory.getBeanDefinition(
71-
"foo-org.springframework.boot.context.properties.scan.valid.ConfigurationPropertiesScanConfiguration$FooProperties");
72-
assertThat(fooDefinition).isExactlyInstanceOf(RootBeanDefinition.class);
70+
assertThat(beanFactory.containsBeanDefinition(
71+
"foo-org.springframework.boot.context.properties.scan.valid.ConfigurationPropertiesScanConfiguration$FooProperties"))
72+
.isTrue();
73+
assertThat(beanFactory.getBeanDefinitionNames())
74+
.filteredOn((name) -> name.toLowerCase(Locale.ENGLISH).contains("fooproperties"))
75+
.hasSize(1);
7376
}
7477

7578
@Test
@@ -88,11 +91,11 @@ void scanWhenBasePackagesAndBasePackageClassesProvidedShouldUseThat() throws IOE
8891
"b.first-org.springframework.boot.context.properties.scan.valid.b.BScanConfiguration$BFirstProperties");
8992
BeanDefinition bSecondDefinition = beanFactory.getBeanDefinition(
9093
"b.second-org.springframework.boot.context.properties.scan.valid.b.BScanConfiguration$BSecondProperties");
91-
assertThat(aDefinition).satisfies(configurationPropertiesBeanDefinition(BindMethod.JAVA_BEAN));
94+
assertThat(aDefinition).satisfies(hasBindMethod(BindMethod.JAVA_BEAN));
9295
// Constructor injection
93-
assertThat(bFirstDefinition).satisfies(configurationPropertiesBeanDefinition(BindMethod.VALUE_OBJECT));
96+
assertThat(bFirstDefinition).satisfies(hasBindMethod(BindMethod.VALUE_OBJECT));
9497
// Post-processing injection
95-
assertThat(bSecondDefinition).satisfies(configurationPropertiesBeanDefinition(BindMethod.JAVA_BEAN));
98+
assertThat(bSecondDefinition).satisfies(hasBindMethod(BindMethod.JAVA_BEAN));
9699
}
97100

98101
@Test
@@ -112,9 +115,8 @@ void scanWhenOtherComponentAnnotationPresentShouldSkipType() throws IOException
112115
assertThat(beanFactory.getBeanDefinitionCount()).isZero();
113116
}
114117

115-
private Consumer<BeanDefinition> configurationPropertiesBeanDefinition(BindMethod bindMethod) {
118+
private Consumer<BeanDefinition> hasBindMethod(BindMethod bindMethod) {
116119
return (definition) -> {
117-
assertThat(definition).isExactlyInstanceOf(RootBeanDefinition.class);
118120
assertThat(definition.hasAttribute(BindMethod.class.getName())).isTrue();
119121
assertThat(definition.getAttribute(BindMethod.class.getName())).isEqualTo(bindMethod);
120122
};

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

Lines changed: 36 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
import java.util.Optional;
3434
import java.util.Properties;
3535
import java.util.Set;
36-
import java.util.UUID;
3736

3837
import jakarta.annotation.PostConstruct;
3938
import jakarta.validation.Valid;
@@ -586,13 +585,21 @@ void loadWhenHasUnboundElementsFromSystemEnvironmentShouldNotThrowException() {
586585
}
587586

588587
@Test
589-
void loadShouldSupportRebindableConfigurationProperties() {
590-
// gh-9160
588+
void loadShouldSupportRebindableConfigurationPropertiesRegisteredAsBean() {
589+
testRebindableConfigurationProperties(PrototypePropertiesBeanConfiguration.class);
590+
}
591+
592+
@Test
593+
void loadShouldSupportRebindableConfigurationPropertiesRegisteredUsingRegistrar() {
594+
testRebindableConfigurationProperties(PrototypePropertiesRegistrarConfiguration.class);
595+
}
596+
597+
void testRebindableConfigurationProperties(Class<?> configurationClass) {
591598
MutablePropertySources sources = this.context.getEnvironment().getPropertySources();
592599
Map<String, Object> source = new LinkedHashMap<>();
593600
source.put("example.one", "foo");
594601
sources.addFirst(new MapPropertySource("test-source", source));
595-
this.context.register(PrototypePropertiesConfiguration.class);
602+
this.context.register(configurationClass);
596603
this.context.refresh();
597604
PrototypeBean first = this.context.getBean(PrototypeBean.class);
598605
assertThat(first.getOne()).isEqualTo("foo");
@@ -604,12 +611,24 @@ void loadShouldSupportRebindableConfigurationProperties() {
604611
}
605612

606613
@Test
607-
void loadWhenHasPropertySourcesPlaceholderConfigurerShouldSupportRebindableConfigurationProperties() {
614+
void loadWhenHasPropertySourcesPlaceholderConfigurerShouldSupportRebindableConfigurationPropertiesRegisteredAsBean() {
615+
testPropertySourcesPlaceholderConfigurerShouldSupportRebindableConfigurationProperties(
616+
PrototypePropertiesBeanConfiguration.class);
617+
}
618+
619+
@Test
620+
void loadWhenHasPropertySourcesPlaceholderConfigurerShouldSupportRebindableConfigurationPropertiesRegisteredUsingRegistrar() {
621+
testPropertySourcesPlaceholderConfigurerShouldSupportRebindableConfigurationProperties(
622+
PrototypePropertiesRegistrarConfiguration.class);
623+
}
624+
625+
void testPropertySourcesPlaceholderConfigurerShouldSupportRebindableConfigurationProperties(
626+
Class<?> configurationClass) {
608627
MutablePropertySources sources = this.context.getEnvironment().getPropertySources();
609628
Map<String, Object> source = new LinkedHashMap<>();
610629
source.put("example.one", "foo");
611630
sources.addFirst(new MapPropertySource("test-source", source));
612-
this.context.register(PrototypePropertiesConfiguration.class);
631+
this.context.register(configurationClass);
613632
this.context.register(PropertySourcesPlaceholderConfigurer.class);
614633
this.context.refresh();
615634
PrototypeBean first = this.context.getBean(PrototypeBean.class);
@@ -1245,24 +1264,6 @@ void loadWhenBindingToConstructorParametersWithConversionToCustomListImplementat
12451264
"b");
12461265
}
12471266

1248-
@Test
1249-
void loadPrototypeScopedProperties() {
1250-
load(PrototypeScopePropertiesConfiguration.class);
1251-
PrototypeScopeProperties p1 = this.context.getBean(PrototypeScopeProperties.class);
1252-
PrototypeScopeProperties p2 = this.context.getBean(PrototypeScopeProperties.class);
1253-
assertThat(p1.id).isNotNull();
1254-
assertThat(p2.id).isNotNull();
1255-
assertThat(p1.id).isNotEqualTo(p2.id);
1256-
}
1257-
1258-
@Test
1259-
void loadProxyScopedProperties() {
1260-
load(ProxyScopePropertiesConfiguration.class, "name=test");
1261-
ProxyScopeProperties p = this.context.getBean(ProxyScopeProperties.class);
1262-
assertThat(p.name).isEqualTo("test");
1263-
assertThat(p.getName()).isEqualTo("test");
1264-
}
1265-
12661267
@Test
12671268
void loadWhenBindingToJavaBeanWithConversionToCustomListImplementation() {
12681269
load(SetterBoundCustomListPropertiesConfiguration.class, "test.values=a,b");
@@ -1513,56 +1514,28 @@ static PropertySourcesPlaceholderConfigurer configurer2() {
15131514

15141515
}
15151516

1516-
@EnableConfigurationProperties(PrototypeScopeProperties.class)
15171517
@Configuration(proxyBeanMethods = false)
1518-
static class PrototypeScopePropertiesConfiguration {
1519-
1520-
}
1521-
1522-
@ConfigurationProperties
1523-
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
1524-
static class PrototypeScopeProperties {
1525-
1526-
private final String id = UUID.randomUUID().toString();
1518+
@EnableConfigurationProperties
1519+
static class PrototypePropertiesBeanConfiguration {
15271520

1528-
String getId() {
1529-
return this.id;
1521+
@Bean
1522+
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
1523+
@ConfigurationProperties("example")
1524+
PrototypeBean prototypeBean() {
1525+
return new PrototypeBean();
15301526
}
15311527

15321528
}
15331529

1534-
@EnableConfigurationProperties(ProxyScopeProperties.class)
15351530
@Configuration(proxyBeanMethods = false)
1536-
static class ProxyScopePropertiesConfiguration {
1531+
@EnableConfigurationProperties(PrototypeBeanProperties.class)
1532+
static class PrototypePropertiesRegistrarConfiguration {
15371533

15381534
}
15391535

1540-
@ConfigurationProperties
1536+
@ConfigurationProperties("example")
15411537
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
1542-
static class ProxyScopeProperties {
1543-
1544-
private String name;
1545-
1546-
String getName() {
1547-
return this.name;
1548-
}
1549-
1550-
void setName(String name) {
1551-
this.name = name;
1552-
}
1553-
1554-
}
1555-
1556-
@Configuration(proxyBeanMethods = false)
1557-
@EnableConfigurationProperties
1558-
static class PrototypePropertiesConfiguration {
1559-
1560-
@Bean
1561-
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
1562-
@ConfigurationProperties("example")
1563-
PrototypeBean prototypeBean() {
1564-
return new PrototypeBean();
1565-
}
1538+
static class PrototypeBeanProperties extends PrototypeBean {
15661539

15671540
}
15681541

0 commit comments

Comments
 (0)