Prohibit circular references by default

Closes gh-27652
This commit is contained in:
Andy Wilkinson
2021-07-15 11:51:24 +01:00
parent 228e4e3bc8
commit 01e741d703
8 changed files with 193 additions and 8 deletions

View File

@@ -26,6 +26,7 @@ import java.util.function.Supplier;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionCustomizer;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.context.annotation.Configurations;
@@ -199,6 +200,17 @@ public abstract class AbstractApplicationContextRunner<SELF extends AbstractAppl
return newInstance(this.runnerConfiguration.withAllowBeanDefinitionOverriding(allowBeanDefinitionOverriding));
}
/**
* Specify if circular references between beans should be allowed.
* @param allowCircularReferences if circular references between beans are allowed
* @return a new instance with the updated circular references policy
* @since 2.6.0
* @see AbstractAutowireCapableBeanFactory#setAllowCircularReferences(boolean)
*/
public SELF withAllowCircularReferences(boolean allowCircularReferences) {
return newInstance(this.runnerConfiguration.withAllowCircularReferences(allowCircularReferences));
}
/**
* Add an {@link ApplicationContextInitializer} to be called when the context is
* created.
@@ -427,9 +439,13 @@ public abstract class AbstractApplicationContextRunner<SELF extends AbstractAppl
private C createAndLoadContext() {
C context = this.runnerConfiguration.contextFactory.get();
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.runnerConfiguration.allowBeanDefinitionOverriding);
if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
((AbstractAutowireCapableBeanFactory) beanFactory)
.setAllowCircularReferences(this.runnerConfiguration.allowCircularReferences);
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.runnerConfiguration.allowBeanDefinitionOverriding);
}
}
try {
configureContext(context);
@@ -504,6 +520,8 @@ public abstract class AbstractApplicationContextRunner<SELF extends AbstractAppl
private boolean allowBeanDefinitionOverriding = false;
private boolean allowCircularReferences = false;
private List<ApplicationContextInitializer<? super C>> initializers = Collections.emptyList();
private TestPropertyValues environmentProperties = TestPropertyValues.empty();
@@ -525,6 +543,7 @@ public abstract class AbstractApplicationContextRunner<SELF extends AbstractAppl
private RunnerConfiguration(RunnerConfiguration<C> source) {
this.contextFactory = source.contextFactory;
this.allowBeanDefinitionOverriding = source.allowBeanDefinitionOverriding;
this.allowCircularReferences = source.allowCircularReferences;
this.initializers = source.initializers;
this.environmentProperties = source.environmentProperties;
this.systemProperties = source.systemProperties;
@@ -540,6 +559,12 @@ public abstract class AbstractApplicationContextRunner<SELF extends AbstractAppl
return config;
}
private RunnerConfiguration<C> withAllowCircularReferences(boolean allowCircularReferences) {
RunnerConfiguration<C> config = new RunnerConfiguration<>(this);
config.allowCircularReferences = allowCircularReferences;
return config;
}
private RunnerConfiguration<C> withInitializer(ApplicationContextInitializer<? super C> initializer) {
Assert.notNull(initializer, "Initializer must not be null");
RunnerConfiguration<C> config = new RunnerConfiguration<>(this);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,7 +23,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
import com.google.gson.Gson;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanCurrentlyInCreationException;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.annotation.UserConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.assertj.ApplicationContextAssertProvider;
@@ -181,6 +184,22 @@ abstract class AbstractApplicationContextRunnerTests<T extends AbstractApplicati
});
}
@Test
void runDisablesCircularReferencesByDefault() {
get().withUserConfiguration(ExampleConsumerConfiguration.class, ExampleProducerConfiguration.class)
.run((context) -> {
assertThat(context).hasFailed();
assertThat(context).getFailure().hasRootCauseInstanceOf(BeanCurrentlyInCreationException.class);
});
}
@Test
void circularReferencesCanBeAllowed() {
get().withAllowCircularReferences(true)
.withUserConfiguration(ExampleConsumerConfiguration.class, ExampleProducerConfiguration.class)
.run((context) -> assertThat(context).hasNotFailed());
}
@Test
void runWithUserBeanShouldBeRegisteredInOrder() {
get().withAllowBeanDefinitionOverriding(true).withBean(String.class, () -> "one")
@@ -250,4 +269,41 @@ abstract class AbstractApplicationContextRunnerTests<T extends AbstractApplicati
}
static class Example {
}
@FunctionalInterface
interface ExampleConfigurer {
void configure(Example example);
}
@Configuration(proxyBeanMethods = false)
static class ExampleProducerConfiguration {
@Bean
Example example(ObjectProvider<ExampleConfigurer> configurers) {
Example example = new Example();
configurers.orderedStream().forEach((configurer) -> configurer.configure(example));
return example;
}
}
@Configuration(proxyBeanMethods = false)
static class ExampleConsumerConfiguration {
@Autowired
Example example;
@Bean
ExampleConfigurer configurer() {
return (example) -> {
};
}
}
}