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
This commit is contained in:
Madhura Bhave 2019-11-05 21:54:16 -08:00
parent f9785d2bda
commit 471ca01ccf
3 changed files with 64 additions and 2 deletions

View File

@ -40,13 +40,17 @@ class ConfigurationPropertiesBeanDefinitionValidator implements BeanFactoryPostP
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
if (!(definition instanceof ConfigurationPropertiesValueObjectBeanDefinition)) {
if (!(beanFactory.containsSingleton(beanName) || isValueObjectBeanDefinition(beanFactory, beanName))) {
validate(beanFactory, beanName);
}
}
}
private boolean isValueObjectBeanDefinition(ConfigurableListableBeanFactory beanFactory, String beanName) {
BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
return (definition instanceof ConfigurationPropertiesValueObjectBeanDefinition);
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;

View File

@ -44,8 +44,11 @@ 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.ConfigurableListableBeanFactory;
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.RootBeanDefinition;
import org.springframework.boot.context.properties.bind.BindException;
import org.springframework.boot.context.properties.bind.DefaultValue;
import org.springframework.boot.context.properties.bind.validation.BindValidationException;
@ -895,6 +898,15 @@ class ConfigurationPropertiesTests {
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) {
return load(new Class<?>[] { configuration }, inlinedProperties);
}
@ -921,6 +933,12 @@ class ConfigurationPropertiesTests {
this.context = new AnnotationConfigApplicationContext();
}
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
static class TestConfiguration {
}
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(BasicProperties.class)
static class BasicConfiguration {

View File

@ -0,0 +1,40 @@
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 {
}
}