Commit 471ca01c authored by Madhura Bhave's avatar Madhura Bhave

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
parent f9785d2b
...@@ -40,13 +40,17 @@ class ConfigurationPropertiesBeanDefinitionValidator implements BeanFactoryPostP ...@@ -40,13 +40,17 @@ class ConfigurationPropertiesBeanDefinitionValidator implements BeanFactoryPostP
@Override @Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
for (String beanName : beanFactory.getBeanDefinitionNames()) { for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition definition = beanFactory.getBeanDefinition(beanName); if (!(beanFactory.containsSingleton(beanName) || isValueObjectBeanDefinition(beanFactory, beanName))) {
if (!(definition instanceof ConfigurationPropertiesValueObjectBeanDefinition)) {
validate(beanFactory, beanName); validate(beanFactory, beanName);
} }
} }
} }
private boolean isValueObjectBeanDefinition(ConfigurableListableBeanFactory beanFactory, String beanName) {
BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
return (definition instanceof ConfigurationPropertiesValueObjectBeanDefinition);
}
@Override @Override
public int getOrder() { public int getOrder() {
return Ordered.LOWEST_PRECEDENCE; return Ordered.LOWEST_PRECEDENCE;
......
...@@ -44,8 +44,11 @@ import org.springframework.beans.factory.InitializingBean; ...@@ -44,8 +44,11 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.context.properties.bind.BindException; import org.springframework.boot.context.properties.bind.BindException;
import org.springframework.boot.context.properties.bind.DefaultValue; import org.springframework.boot.context.properties.bind.DefaultValue;
import org.springframework.boot.context.properties.bind.validation.BindValidationException; import org.springframework.boot.context.properties.bind.validation.BindValidationException;
...@@ -895,6 +898,15 @@ class ConfigurationPropertiesTests { ...@@ -895,6 +898,15 @@ class ConfigurationPropertiesTests {
assertThat(bean.getNested().getAge()).isEqualTo(10); assertThat(bean.getNested().getAge()).isEqualTo(10);
} }
@Test // gh-18652
void loadWhenBeanFactoryContainsSingletonForConstructorBindingTypeShouldNotFail() {
ConfigurableListableBeanFactory beanFactory = this.context.getBeanFactory();
((BeanDefinitionRegistry) beanFactory).registerBeanDefinition("test",
new RootBeanDefinition(ConstructorParameterProperties.class));
beanFactory.registerSingleton("test", new ConstructorParameterProperties("bar", 5));
load(TestConfiguration.class);
}
private AnnotationConfigApplicationContext load(Class<?> configuration, String... inlinedProperties) { private AnnotationConfigApplicationContext load(Class<?> configuration, String... inlinedProperties) {
return load(new Class<?>[] { configuration }, inlinedProperties); return load(new Class<?>[] { configuration }, inlinedProperties);
} }
...@@ -921,6 +933,12 @@ class ConfigurationPropertiesTests { ...@@ -921,6 +933,12 @@ class ConfigurationPropertiesTests {
this.context = new AnnotationConfigApplicationContext(); this.context = new AnnotationConfigApplicationContext();
} }
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
static class TestConfiguration {
}
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(BasicProperties.class) @EnableConfigurationProperties(BasicProperties.class)
static class BasicConfiguration { static class BasicConfiguration {
......
package org.springframework.boot.context.properties
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.support.BeanDefinitionRegistry
import org.springframework.beans.factory.support.RootBeanDefinition
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.Configuration
/**
* Tests for {@link ConfigurationProperties @ConfigurationProperties}-annotated beans.
*
* @author Madhura Bhave
*/
class KotlinConfigurationPropertiesTests {
private var context = AnnotationConfigApplicationContext()
@Test //gh-18652
fun `type with constructor binding and existing singleton should not fail`() {
val beanFactory = this.context.beanFactory
(beanFactory as BeanDefinitionRegistry).registerBeanDefinition("foo",
RootBeanDefinition(BingProperties::class.java))
beanFactory.registerSingleton("foo", BingProperties(""))
this.context.register(TestConfig::class.java)
this.context.refresh();
}
@ConfigurationProperties(prefix = "foo")
@ConstructorBinding
class BingProperties(@Suppress("UNUSED_PARAMETER") bar: String) {
}
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
internal open class TestConfig {
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment