Add consistent scope support for ConfigurationProperties beans
See gh-42073
This commit is contained in:
committed by
Stéphane Nicoll
parent
be47355a9f
commit
2f8fc5cb05
@@ -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;
|
||||
*/
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
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 {
|
||||
|
||||
}
|
||||
|
||||
@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 {
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
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 {
|
||||
"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 {
|
||||
|
||||
}
|
||||
|
||||
@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();
|
||||
|
||||
Reference in New Issue
Block a user