From 228e4e3bc83afac0db237a8c12c82597de6e8999 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 17 Aug 2021 12:15:30 +0100 Subject: [PATCH] Make it easier to add new config options to ApplicationContextRunners Closes gh-27690 --- .../AbstractApplicationContextRunner.java | 269 +++++++++++++----- .../runner/ApplicationContextRunner.java | 10 +- .../ReactiveWebApplicationContextRunner.java | 11 +- .../runner/WebApplicationContextRunner.java | 10 +- 4 files changed, 217 insertions(+), 83 deletions(-) diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/AbstractApplicationContextRunner.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/AbstractApplicationContextRunner.java index 003b6d2e2e..5d88fc7c0a 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/AbstractApplicationContextRunner.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/AbstractApplicationContextRunner.java @@ -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. @@ -105,31 +105,49 @@ import org.springframework.util.Assert; */ public abstract class AbstractApplicationContextRunner, C extends ConfigurableApplicationContext, A extends ApplicationContextAssertProvider> { - private final Supplier contextFactory; + private final RunnerConfiguration runnerConfiguration; - private final boolean allowBeanDefinitionOverriding; - - private final List> initializers; - - private final TestPropertyValues environmentProperties; - - private final TestPropertyValues systemProperties; - - private final ClassLoader classLoader; - - private final ApplicationContext parent; - - private final List> beanRegistrations; - - private final List configurations; + private final Function, SELF> instanceFactory; /** * Create a new {@link AbstractApplicationContextRunner} instance. * @param contextFactory the factory used to create the actual context + * @deprecated since 2.6.0 for removal in 2.8.0 in favor of + * {@link #AbstractApplicationContextRunner(Supplier, Function)} */ + @Deprecated protected AbstractApplicationContextRunner(Supplier contextFactory) { - this(contextFactory, false, Collections.emptyList(), TestPropertyValues.empty(), TestPropertyValues.empty(), - null, null, Collections.emptyList(), Collections.emptyList()); + Assert.notNull(contextFactory, "ContextFactory must not be null"); + this.runnerConfiguration = new RunnerConfiguration<>(contextFactory); + this.instanceFactory = this::legacyNewInstance; + } + + /** + * Create a new {@link AbstractApplicationContextRunner} instance. + * @param contextFactory the factory used to create the actual context + * @param instanceFactory the factory used to create new instance of the runner + * @since 2.6.0 + */ + protected AbstractApplicationContextRunner(Supplier contextFactory, + Function, SELF> instanceFactory) { + Assert.notNull(contextFactory, "ContextFactory must not be null"); + Assert.notNull(contextFactory, "RunnerConfiguration must not be null"); + this.runnerConfiguration = new RunnerConfiguration<>(contextFactory); + this.instanceFactory = instanceFactory; + } + + /** + * Create a new {@link AbstractApplicationContextRunner} instance. + * @param configuration the configuration for the runner to use + * @param instanceFactory the factory used to create new instance of the runner + * @since 2.6.0 + */ + protected AbstractApplicationContextRunner(RunnerConfiguration configuration, + Function, SELF> instanceFactory) { + Assert.notNull(configuration, "RunnerConfiguration must not be null"); + Assert.notNull(instanceFactory, "instanceFactory must not be null"); + this.runnerConfiguration = configuration; + this.instanceFactory = instanceFactory; } /** @@ -143,7 +161,10 @@ public abstract class AbstractApplicationContextRunner contextFactory, boolean allowBeanDefinitionOverriding, List> initializers, TestPropertyValues environmentProperties, TestPropertyValues systemProperties, ClassLoader classLoader, ApplicationContext parent, @@ -153,15 +174,17 @@ public abstract class AbstractApplicationContextRunner configuration = new RunnerConfiguration<>(contextFactory); + configuration.allowBeanDefinitionOverriding = allowBeanDefinitionOverriding; + configuration.initializers = Collections.unmodifiableList(initializers); + configuration.environmentProperties = environmentProperties; + configuration.systemProperties = systemProperties; + configuration.classLoader = classLoader; + configuration.parent = parent; + configuration.beanRegistrations = Collections.unmodifiableList(beanRegistrations); + configuration.configurations = Collections.unmodifiableList(configurations); + this.runnerConfiguration = configuration; + this.instanceFactory = this::legacyNewInstance; } /** @@ -173,9 +196,7 @@ public abstract class AbstractApplicationContextRunner initializer) { Assert.notNull(initializer, "Initializer must not be null"); - return newInstance(this.contextFactory, this.allowBeanDefinitionOverriding, add(this.initializers, initializer), - this.environmentProperties, this.systemProperties, this.classLoader, this.parent, - this.beanRegistrations, this.configurations); + return newInstance(this.runnerConfiguration.withInitializer(initializer)); } /** @@ -202,9 +221,7 @@ public abstract class AbstractApplicationContextRunner SELF withBean(String name, Class type, Object... constructorArgs) { - return newInstance(this.contextFactory, this.allowBeanDefinitionOverriding, this.initializers, - this.environmentProperties, this.systemProperties, this.classLoader, this.parent, - add(this.beanRegistrations, new BeanRegistration<>(name, type, constructorArgs)), this.configurations); + return newInstance(this.runnerConfiguration.withBean(name, type, constructorArgs)); } /** @@ -320,10 +329,7 @@ public abstract class AbstractApplicationContextRunner SELF withBean(String name, Class type, Supplier supplier, BeanDefinitionCustomizer... customizers) { - return newInstance(this.contextFactory, this.allowBeanDefinitionOverriding, this.initializers, - this.environmentProperties, this.systemProperties, this.classLoader, this.parent, - add(this.beanRegistrations, new BeanRegistration<>(name, type, supplier, customizers)), - this.configurations); + return newInstance(this.runnerConfiguration.withBean(name, type, supplier, customizers)); } /** @@ -343,9 +349,7 @@ public abstract class AbstractApplicationContextRunner List add(List list, T element) { - List result = new ArrayList<>(list); - result.add(element); - return result; + @Deprecated + private SELF legacyNewInstance(RunnerConfiguration configuration) { + return newInstance(configuration.contextFactory, configuration.allowBeanDefinitionOverriding, + configuration.initializers, configuration.environmentProperties, configuration.systemProperties, + configuration.classLoader, configuration.parent, configuration.beanRegistrations, + configuration.configurations); } + @Deprecated protected abstract SELF newInstance(Supplier contextFactory, boolean allowBeanDefinitionOverriding, List> initializers, TestPropertyValues environmentProperties, TestPropertyValues systemProperties, ClassLoader classLoader, ApplicationContext parent, List> beanRegistrations, List configurations); + private SELF newInstance(RunnerConfiguration runnerConfiguration) { + return this.instanceFactory.apply(runnerConfiguration); + } + /** * Create and refresh a new {@link ApplicationContext} based on the current state of * this loader. The context is consumed by the specified {@code consumer} and closed @@ -378,12 +389,13 @@ public abstract class AbstractApplicationContextRunner consumer) { - withContextClassLoader(this.classLoader, () -> this.systemProperties.applyToSystemProperties(() -> { - try (A context = createAssertableContext()) { - accept(consumer, context); - } - return null; - })); + withContextClassLoader(this.runnerConfiguration.classLoader, + () -> this.runnerConfiguration.systemProperties.applyToSystemProperties(() -> { + try (A context = createAssertableContext()) { + accept(consumer, context); + } + return null; + })); return (SELF) this; } @@ -413,11 +425,11 @@ public abstract class AbstractApplicationContextRunner[] classes = Configurations.getClasses(this.configurations); + this.runnerConfiguration.environmentProperties.applyTo(context); + Class[] classes = Configurations.getClasses(this.runnerConfiguration.configurations); if (classes.length > 0) { ((AnnotationConfigRegistry) context).register(classes); } - this.beanRegistrations.forEach((registration) -> registration.apply(context)); - this.initializers.forEach((initializer) -> initializer.initialize(context)); + this.runnerConfiguration.beanRegistrations.forEach((registration) -> registration.apply(context)); + this.runnerConfiguration.initializers.forEach((initializer) -> initializer.initialize(context)); context.refresh(); } @@ -466,7 +478,7 @@ public abstract class AbstractApplicationContextRunner the bean type */ - protected final class BeanRegistration { + protected static final class BeanRegistration { Consumer registrar; @@ -486,4 +498,107 @@ public abstract class AbstractApplicationContextRunner { + + private final Supplier contextFactory; + + private boolean allowBeanDefinitionOverriding = false; + + private List> initializers = Collections.emptyList(); + + private TestPropertyValues environmentProperties = TestPropertyValues.empty(); + + private TestPropertyValues systemProperties = TestPropertyValues.empty(); + + private ClassLoader classLoader; + + private ApplicationContext parent; + + private List> beanRegistrations = Collections.emptyList(); + + private List configurations = Collections.emptyList(); + + private RunnerConfiguration(Supplier contextFactory) { + this.contextFactory = contextFactory; + } + + private RunnerConfiguration(RunnerConfiguration source) { + this.contextFactory = source.contextFactory; + this.allowBeanDefinitionOverriding = source.allowBeanDefinitionOverriding; + this.initializers = source.initializers; + this.environmentProperties = source.environmentProperties; + this.systemProperties = source.systemProperties; + this.classLoader = source.classLoader; + this.parent = source.parent; + this.beanRegistrations = source.beanRegistrations; + this.configurations = source.configurations; + } + + private RunnerConfiguration withAllowBeanDefinitionOverriding(boolean allowBeanDefinitionOverriding) { + RunnerConfiguration config = new RunnerConfiguration<>(this); + config.allowBeanDefinitionOverriding = allowBeanDefinitionOverriding; + return config; + } + + private RunnerConfiguration withInitializer(ApplicationContextInitializer initializer) { + Assert.notNull(initializer, "Initializer must not be null"); + RunnerConfiguration config = new RunnerConfiguration<>(this); + config.initializers = add(config.initializers, initializer); + return config; + } + + private RunnerConfiguration withPropertyValues(String... pairs) { + RunnerConfiguration config = new RunnerConfiguration<>(this); + config.environmentProperties = config.environmentProperties.and(pairs); + return config; + } + + private RunnerConfiguration withSystemProperties(String... pairs) { + RunnerConfiguration config = new RunnerConfiguration<>(this); + config.systemProperties = config.systemProperties.and(pairs); + return config; + } + + private RunnerConfiguration withClassLoader(ClassLoader classLoader) { + RunnerConfiguration config = new RunnerConfiguration<>(this); + config.classLoader = classLoader; + return config; + } + + private RunnerConfiguration withParent(ApplicationContext parent) { + RunnerConfiguration config = new RunnerConfiguration<>(this); + config.parent = parent; + return config; + } + + private RunnerConfiguration withBean(String name, Class type, Object... constructorArgs) { + RunnerConfiguration config = new RunnerConfiguration<>(this); + config.beanRegistrations = add(config.beanRegistrations, + new BeanRegistration<>(name, type, constructorArgs)); + return config; + } + + private RunnerConfiguration withBean(String name, Class type, Supplier supplier, + BeanDefinitionCustomizer... customizers) { + RunnerConfiguration config = new RunnerConfiguration<>(this); + config.beanRegistrations = add(config.beanRegistrations, + new BeanRegistration<>(name, type, supplier, customizers)); + return config; + } + + private RunnerConfiguration withConfiguration(Configurations configurations) { + Assert.notNull(configurations, "Configurations must not be null"); + RunnerConfiguration config = new RunnerConfiguration<>(this); + config.configurations = add(config.configurations, configurations); + return config; + } + + private static List add(List list, T element) { + List result = new ArrayList<>(list); + result.add(element); + return result; + } + + } + } diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/ApplicationContextRunner.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/ApplicationContextRunner.java index feb9416b6c..7b791e98e0 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/ApplicationContextRunner.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/ApplicationContextRunner.java @@ -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. @@ -55,9 +55,14 @@ public class ApplicationContextRunner extends * @param contextFactory a supplier that returns a new instance on each call */ public ApplicationContextRunner(Supplier contextFactory) { - super(contextFactory); + super(contextFactory, ApplicationContextRunner::new); } + private ApplicationContextRunner(RunnerConfiguration runnerConfiguration) { + super(runnerConfiguration, ApplicationContextRunner::new); + } + + @Deprecated private ApplicationContextRunner(Supplier contextFactory, boolean allowBeanDefinitionOverriding, List> initializers, @@ -69,6 +74,7 @@ public class ApplicationContextRunner extends } @Override + @Deprecated protected ApplicationContextRunner newInstance(Supplier contextFactory, boolean allowBeanDefinitionOverriding, List> initializers, diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/ReactiveWebApplicationContextRunner.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/ReactiveWebApplicationContextRunner.java index 932e27ff85..a9f077d26f 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/ReactiveWebApplicationContextRunner.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/ReactiveWebApplicationContextRunner.java @@ -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. @@ -55,9 +55,15 @@ public final class ReactiveWebApplicationContextRunner extends * @param contextFactory a supplier that returns a new instance on each call */ public ReactiveWebApplicationContextRunner(Supplier contextFactory) { - super(contextFactory); + super(contextFactory, ReactiveWebApplicationContextRunner::new); } + private ReactiveWebApplicationContextRunner( + RunnerConfiguration configuration) { + super(configuration, ReactiveWebApplicationContextRunner::new); + } + + @Deprecated private ReactiveWebApplicationContextRunner(Supplier contextFactory, boolean allowBeanDefinitionOverriding, List> initializers, @@ -69,6 +75,7 @@ public final class ReactiveWebApplicationContextRunner extends } @Override + @Deprecated protected ReactiveWebApplicationContextRunner newInstance( Supplier contextFactory, boolean allowBeanDefinitionOverriding, List> initializers, diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/WebApplicationContextRunner.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/WebApplicationContextRunner.java index 8f9c671cd0..2566a52558 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/WebApplicationContextRunner.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/WebApplicationContextRunner.java @@ -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. @@ -59,9 +59,14 @@ public final class WebApplicationContextRunner extends * @param contextFactory a supplier that returns a new instance on each call */ public WebApplicationContextRunner(Supplier contextFactory) { - super(contextFactory); + super(contextFactory, WebApplicationContextRunner::new); } + private WebApplicationContextRunner(RunnerConfiguration configuration) { + super(configuration, WebApplicationContextRunner::new); + } + + @Deprecated private WebApplicationContextRunner(Supplier contextFactory, boolean allowBeanDefinitionOverriding, List> initializers, @@ -73,6 +78,7 @@ public final class WebApplicationContextRunner extends } @Override + @Deprecated protected WebApplicationContextRunner newInstance(Supplier contextFactory, boolean allowBeanDefinitionOverriding, List> initializers,