Skip to content

Commit 471ca01

Browse files
committed
Do not validate value object bean definion when singleton present
Prior to this commit constructor bound configuration properties could not be mocked because it would fail validation from ConfigurationPropertiesBeanDefinitionValidator. The MockitoPostProcessor registers the mocked bean as a singleton and validation can be skipped if a singleton for the type is found in the bean factory. Fixes gh-18652
1 parent f9785d2 commit 471ca01

File tree

3 files changed

+64
-2
lines changed

3 files changed

+64
-2
lines changed

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,17 @@ class ConfigurationPropertiesBeanDefinitionValidator implements BeanFactoryPostP
4040
@Override
4141
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
4242
for (String beanName : beanFactory.getBeanDefinitionNames()) {
43-
BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
44-
if (!(definition instanceof ConfigurationPropertiesValueObjectBeanDefinition)) {
43+
if (!(beanFactory.containsSingleton(beanName) || isValueObjectBeanDefinition(beanFactory, beanName))) {
4544
validate(beanFactory, beanName);
4645
}
4746
}
4847
}
4948

49+
private boolean isValueObjectBeanDefinition(ConfigurableListableBeanFactory beanFactory, String beanName) {
50+
BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
51+
return (definition instanceof ConfigurationPropertiesValueObjectBeanDefinition);
52+
}
53+
5054
@Override
5155
public int getOrder() {
5256
return Ordered.LOWEST_PRECEDENCE;

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,11 @@
4444
import org.springframework.beans.factory.ObjectProvider;
4545
import org.springframework.beans.factory.annotation.Autowired;
4646
import org.springframework.beans.factory.annotation.Value;
47+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
4748
import org.springframework.beans.factory.support.AbstractBeanDefinition;
49+
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
4850
import org.springframework.beans.factory.support.GenericBeanDefinition;
51+
import org.springframework.beans.factory.support.RootBeanDefinition;
4952
import org.springframework.boot.context.properties.bind.BindException;
5053
import org.springframework.boot.context.properties.bind.DefaultValue;
5154
import org.springframework.boot.context.properties.bind.validation.BindValidationException;
@@ -895,6 +898,15 @@ void loadWhenBindingToJavaBeanWithoutExplicitConstructorBindingOnNestedShouldUse
895898
assertThat(bean.getNested().getAge()).isEqualTo(10);
896899
}
897900

901+
@Test // gh-18652
902+
void loadWhenBeanFactoryContainsSingletonForConstructorBindingTypeShouldNotFail() {
903+
ConfigurableListableBeanFactory beanFactory = this.context.getBeanFactory();
904+
((BeanDefinitionRegistry) beanFactory).registerBeanDefinition("test",
905+
new RootBeanDefinition(ConstructorParameterProperties.class));
906+
beanFactory.registerSingleton("test", new ConstructorParameterProperties("bar", 5));
907+
load(TestConfiguration.class);
908+
}
909+
898910
private AnnotationConfigApplicationContext load(Class<?> configuration, String... inlinedProperties) {
899911
return load(new Class<?>[] { configuration }, inlinedProperties);
900912
}
@@ -921,6 +933,12 @@ private void resetContext() {
921933
this.context = new AnnotationConfigApplicationContext();
922934
}
923935

936+
@Configuration(proxyBeanMethods = false)
937+
@EnableConfigurationProperties
938+
static class TestConfiguration {
939+
940+
}
941+
924942
@Configuration(proxyBeanMethods = false)
925943
@EnableConfigurationProperties(BasicProperties.class)
926944
static class BasicConfiguration {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package org.springframework.boot.context.properties
2+
3+
import org.junit.jupiter.api.Test
4+
import org.springframework.beans.factory.support.BeanDefinitionRegistry
5+
import org.springframework.beans.factory.support.RootBeanDefinition
6+
import org.springframework.context.annotation.AnnotationConfigApplicationContext
7+
import org.springframework.context.annotation.Configuration
8+
9+
/**
10+
* Tests for {@link ConfigurationProperties @ConfigurationProperties}-annotated beans.
11+
*
12+
* @author Madhura Bhave
13+
*/
14+
class KotlinConfigurationPropertiesTests {
15+
16+
private var context = AnnotationConfigApplicationContext()
17+
18+
@Test //gh-18652
19+
fun `type with constructor binding and existing singleton should not fail`() {
20+
val beanFactory = this.context.beanFactory
21+
(beanFactory as BeanDefinitionRegistry).registerBeanDefinition("foo",
22+
RootBeanDefinition(BingProperties::class.java))
23+
beanFactory.registerSingleton("foo", BingProperties(""))
24+
this.context.register(TestConfig::class.java)
25+
this.context.refresh();
26+
}
27+
28+
@ConfigurationProperties(prefix = "foo")
29+
@ConstructorBinding
30+
class BingProperties(@Suppress("UNUSED_PARAMETER") bar: String) {
31+
32+
}
33+
34+
@Configuration(proxyBeanMethods = false)
35+
@EnableConfigurationProperties
36+
internal open class TestConfig {
37+
38+
}
39+
40+
}

0 commit comments

Comments
 (0)