Merge branch '2.4.x'

Closes gh-24569
pull/24582/head
Phillip Webb 4 years ago
commit 8a67ab0c2d

@ -21,6 +21,7 @@ import java.util.function.Supplier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
/**
* A simple object registry that is available during startup and {@link Environment}
@ -47,7 +48,8 @@ public interface BootstrapRegistry {
/**
* Register a specific type with the registry. If the specified type has already been
* registered, but not get obtained, it will be replaced.
* registered and has not been obtained as a {@link Scope#SINGLETON singleton}, it
* will be replaced.
* @param <T> the instance type
* @param type the instance type
* @param instanceSupplier the instance supplier
@ -87,11 +89,13 @@ public interface BootstrapRegistry {
void addCloseListener(ApplicationListener<BootstrapContextClosedEvent> listener);
/**
* Supplier used to provide the actual instance the first time it is accessed.
* Supplier used to provide the actual instance when needed.
*
* @param <T> the instance type
* @see Scope
*/
public interface InstanceSupplier<T> {
@FunctionalInterface
interface InstanceSupplier<T> {
/**
* Factory method used to create the instance when needed.
@ -101,6 +105,39 @@ public interface BootstrapRegistry {
*/
T get(BootstrapContext context);
/**
* Return the scope of the supplied instance.
* @return the scope
* @since 2.4.2
*/
default Scope getScope() {
return Scope.SINGLETON;
}
/**
* Return a new {@link InstanceSupplier} with an updated {@link Scope}.
* @param scope the new scope
* @return a new {@link InstanceSupplier} instance with the new scope
* @since 2.4.2
*/
default InstanceSupplier<T> withScope(Scope scope) {
Assert.notNull(scope, "Scope must not be null");
InstanceSupplier<T> parent = this;
return new InstanceSupplier<T>() {
@Override
public T get(BootstrapContext context) {
return parent.get(context);
}
@Override
public Scope getScope() {
return scope;
}
};
}
/**
* Factory method that can be used to create a {@link InstanceSupplier} for a
* given instance.
@ -125,4 +162,24 @@ public interface BootstrapRegistry {
}
/**
* The scope of a instance.
* @since 2.4.2
*/
enum Scope {
/**
* A singleton instance. The {@link InstanceSupplier} will be called only once and
* the same instance will be returned each time.
*/
SINGLETON,
/**
* A prototype instance. The {@link InstanceSupplier} will be called whenver an
* instance is needed.
*/
PROTOTYPE
}
}

@ -117,8 +117,10 @@ public class DefaultBootstrapContext implements ConfigurableBootstrapContext {
T instance = (T) this.instances.get(type);
if (instance == null) {
instance = (T) instanceSupplier.get(this);
if (instanceSupplier.getScope() == Scope.SINGLETON) {
this.instances.put(type, instance);
}
}
return instance;
}

@ -22,10 +22,12 @@ import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import org.apache.commons.logging.Log;
import org.springframework.boot.BootstrapRegistry.InstanceSupplier;
import org.springframework.boot.BootstrapRegistry.Scope;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.DefaultPropertiesPropertySource;
import org.springframework.boot.context.config.ConfigDataEnvironmentContributors.BinderOption;
@ -220,11 +222,10 @@ class ConfigDataEnvironment {
void processAndApply() {
ConfigDataImporter importer = new ConfigDataImporter(this.logFactory, this.notFoundAction, this.resolvers,
this.loaders);
this.bootstrapContext.register(Binder.class, InstanceSupplier
.from(() -> this.contributors.getBinder(null, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE)));
registerBootstrapBinder(() -> this.contributors.getBinder(null, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE));
ConfigDataEnvironmentContributors contributors = processInitial(this.contributors, importer);
Binder initialBinder = contributors.getBinder(null, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE);
this.bootstrapContext.register(Binder.class, InstanceSupplier.of(initialBinder));
registerBootstrapBinder(() -> initialBinder);
ConfigDataActivationContext activationContext = createActivationContext(initialBinder);
contributors = processWithoutProfiles(contributors, importer, activationContext);
activationContext = withProfiles(contributors, activationContext);
@ -232,6 +233,10 @@ class ConfigDataEnvironment {
applyToEnvironment(contributors, activationContext);
}
private void registerBootstrapBinder(Supplier<Binder> supplier) {
this.bootstrapContext.register(Binder.class, InstanceSupplier.from(supplier).withScope(Scope.PROTOTYPE));
}
private ConfigDataEnvironmentContributors processInitial(ConfigDataEnvironmentContributors contributors,
ConfigDataImporter importer) {
this.logger.trace("Processing initial config data environment contributors without activation context");

@ -24,6 +24,7 @@ import org.assertj.core.api.AssertProvider;
import org.junit.jupiter.api.Test;
import org.springframework.boot.BootstrapRegistry.InstanceSupplier;
import org.springframework.boot.BootstrapRegistry.Scope;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
@ -74,6 +75,24 @@ class DefaultBootstrapContextTests {
assertThat(this.context.get(Integer.class)).isEqualTo(100);
}
@Test
void registerWhenSingletonAlreadyCreatedThrowsException() {
this.context.register(Integer.class, InstanceSupplier.from(this.counter::getAndIncrement));
this.context.get(Integer.class);
assertThatIllegalStateException()
.isThrownBy(() -> this.context.register(Integer.class, InstanceSupplier.of(100)))
.withMessage("java.lang.Integer has already been created");
}
@Test
void registerWhenPrototypeAlreadyCreatedReplacesInstance() {
this.context.register(Integer.class,
InstanceSupplier.from(this.counter::getAndIncrement).withScope(Scope.PROTOTYPE));
this.context.get(Integer.class);
this.context.register(Integer.class, InstanceSupplier.of(100));
assertThat(this.context.get(Integer.class)).isEqualTo(100);
}
@Test
void registerWhenAlreadyCreatedThrowsException() {
this.context.register(Integer.class, InstanceSupplier.from(this.counter::getAndIncrement));
@ -146,12 +165,25 @@ class DefaultBootstrapContextTests {
}
@Test
void getCreatesOnlyOneInstance() {
void getWhenSingletonCreatesOnlyOneInstance() {
this.context.register(Integer.class, InstanceSupplier.from(this.counter::getAndIncrement));
assertThat(this.context.get(Integer.class)).isEqualTo(0);
assertThat(this.context.get(Integer.class)).isEqualTo(0);
}
@Test
void getWhenPrototypeCreatesOnlyNewInstances() {
this.context.register(Integer.class,
InstanceSupplier.from(this.counter::getAndIncrement).withScope(Scope.PROTOTYPE));
assertThat(this.context.get(Integer.class)).isEqualTo(0);
assertThat(this.context.get(Integer.class)).isEqualTo(1);
}
@Test
void testName() {
}
@Test
void getOrElseWhenNoRegistrationReturnsOther() {
this.context.register(Number.class, InstanceSupplier.of(1));
@ -228,6 +260,20 @@ class DefaultBootstrapContextTests {
assertThat(listener).wasCalledOnlyOnce();
}
@Test
void instanceSupplierGetScopeWhenNotConfiguredReturnsSingleton() {
InstanceSupplier<String> supplier = InstanceSupplier.of("test");
assertThat(supplier.getScope()).isEqualTo(Scope.SINGLETON);
assertThat(supplier.get(null)).isEqualTo("test");
}
@Test
void instanceSupplierWithScopeChangesScope() {
InstanceSupplier<String> supplier = InstanceSupplier.of("test").withScope(Scope.PROTOTYPE);
assertThat(supplier.getScope()).isEqualTo(Scope.PROTOTYPE);
assertThat(supplier.get(null)).isEqualTo("test");
}
private static class TestCloseListener
implements ApplicationListener<BootstrapContextClosedEvent>, AssertProvider<CloseListenerAssert> {

@ -41,6 +41,7 @@ class TestConfigDataBootstrap {
@Override
public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location) {
context.getBootstrapContext().get(Binder.class); // gh-24559
return location.hasPrefix("testbootstrap:");
}

Loading…
Cancel
Save