Avoid using reflection to create SpringApplication's context

Closes gh-22322
pull/22366/head
Andy Wilkinson 4 years ago
parent 0cd83007e9
commit 0c8e52e877

@ -110,7 +110,8 @@ public class SpringBootContextLoader extends AbstractContextLoader {
else if (config instanceof ReactiveWebMergedContextConfiguration) {
application.setWebApplicationType(WebApplicationType.REACTIVE);
if (!isEmbeddedWebEnvironment(config)) {
new ReactiveWebConfigurer().configure(application);
application.setApplicationContextFactory(
(webApplicationType) -> new GenericReactiveWebApplicationContext());
}
}
else {
@ -250,13 +251,11 @@ public class SpringBootContextLoader extends AbstractContextLoader {
*/
private static class WebConfigurer {
private static final Class<GenericWebApplicationContext> WEB_CONTEXT_CLASS = GenericWebApplicationContext.class;
void configure(MergedContextConfiguration configuration, SpringApplication application,
List<ApplicationContextInitializer<?>> initializers) {
WebMergedContextConfiguration webConfiguration = (WebMergedContextConfiguration) configuration;
addMockServletContext(initializers, webConfiguration);
application.setApplicationContextClass(WEB_CONTEXT_CLASS);
application.setApplicationContextFactory((webApplicationType) -> new GenericWebApplicationContext());
}
private void addMockServletContext(List<ApplicationContextInitializer<?>> initializers,
@ -268,19 +267,6 @@ public class SpringBootContextLoader extends AbstractContextLoader {
}
/**
* Inner class to configure {@link ReactiveWebMergedContextConfiguration}.
*/
private static class ReactiveWebConfigurer {
private static final Class<GenericReactiveWebApplicationContext> WEB_CONTEXT_CLASS = GenericReactiveWebApplicationContext.class;
void configure(SpringApplication application) {
application.setApplicationContextClass(WEB_CONTEXT_CLASS);
}
}
/**
* Adapts a {@link ContextCustomizer} to a {@link ApplicationContextInitializer} so
* that it can be triggered via {@link SpringApplication}.

@ -0,0 +1,53 @@
/*
* Copyright 2012-2020 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ConfigurableApplicationContext;
/**
* Strategy interface for creating the {@link ConfigurableApplicationContext} used by a
* {@link SpringApplication}. Created contexts should be returned in their default form,
* with the {@code SpringApplication} responsible for configuring and refreshing the
* context.
*
* @author Andy Wilkinson
* @since 2.4.0
*/
@FunctionalInterface
public interface ApplicationContextFactory {
/**
* Creates the {@link ConfigurableApplicationContext application context} for a
* {@link SpringApplication}, respecting the given {@code webApplicationType}.
* @param webApplicationType the web application type
* @return the newly created application context
*/
ConfigurableApplicationContext create(WebApplicationType webApplicationType);
/**
* Creates an {@code ApplicationContextFactory} that will create contexts by
* instantiating the given {@code contextClass} via its primary constructor.
* @param contextClass the context class
* @return the factory that will instantiate the context class
* @see BeanUtils#instantiateClass(Class)
*/
static ApplicationContextFactory forContextClass(Class<? extends ConfigurableApplicationContext> contextClass) {
return (webApplicationType) -> BeanUtils.instantiateClass(contextClass);
}
}

@ -47,7 +47,9 @@ import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.boot.convert.ApplicationConversionService;
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext;
import org.springframework.boot.web.reactive.context.StandardReactiveWebEnvironment;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ApplicationListener;
@ -163,21 +165,27 @@ public class SpringApplication {
/**
* The class name of application context that will be used by default for non-web
* environments.
* @deprecated since 2.4.0 in favour of using a {@link ApplicationContextFactory}
*/
@Deprecated
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
+ "annotation.AnnotationConfigApplicationContext";
/**
* The class name of application context that will be used by default for web
* environments.
* @deprecated since 2.4.0 in favour of using an {@link ApplicationContextFactory}
*/
@Deprecated
public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."
+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
/**
* The class name of application context that will be used by default for reactive web
* environments.
* @deprecated since 2.4.0 in favour of using an {@link ApplicationContextFactory}
*/
@Deprecated
public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
+ "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";
@ -217,8 +225,6 @@ public class SpringApplication {
private ConfigurableEnvironment environment;
private Class<? extends ConfigurableApplicationContext> applicationContextClass;
private WebApplicationType webApplicationType;
private boolean headless = true;
@ -239,6 +245,8 @@ public class SpringApplication {
private boolean lazyInitialization = false;
private ApplicationContextFactory applicationContextFactory = new DefaultApplicationContextFactory();
/**
* Create a new {@link SpringApplication} instance. The application context will load
* beans from the specified primary sources (see {@link SpringApplication class-level}
@ -560,32 +568,14 @@ public class SpringApplication {
/**
* Strategy method used to create the {@link ApplicationContext}. By default this
* method will respect any explicitly set application context or application context
* class before falling back to a suitable default.
* method will respect any explicitly set application context class or factory before
* falling back to a suitable default.
* @return the application context (not yet refreshed)
* @see #setApplicationContextClass(Class)
* @see #setApplicationContextFactory(ApplicationContextFactory)
*/
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
return this.applicationContextFactory.create(this.webApplicationType);
}
/**
@ -1154,10 +1144,27 @@ public class SpringApplication {
* applications or {@link AnnotationConfigApplicationContext} for non web based
* applications.
* @param applicationContextClass the context class to set
* @deprecated since 2.4.0 in favor of
* {@link #setApplicationContextFactory(ApplicationContextFactory)}
*/
@Deprecated
public void setApplicationContextClass(Class<? extends ConfigurableApplicationContext> applicationContextClass) {
this.applicationContextClass = applicationContextClass;
this.webApplicationType = WebApplicationType.deduceFromApplicationContext(applicationContextClass);
this.applicationContextFactory = ApplicationContextFactory.forContextClass(applicationContextClass);
}
/**
* Sets the factory that will be called to create the application context. If not set,
* defaults to a factory that will create
* {@link AnnotationConfigServletWebServerApplicationContext} for servlet web
* applications, {@link AnnotationConfigReactiveWebServerApplicationContext} for
* reactive web applications, and {@link AnnotationConfigApplicationContext} for
* non-web applications.
* @param applicationContextFactory the factory for the context
* @since 2.4.0
*/
public void setApplicationContextFactory(ApplicationContextFactory applicationContextFactory) {
this.applicationContextFactory = applicationContextFactory;
}
/**
@ -1302,4 +1309,26 @@ public class SpringApplication {
return new LinkedHashSet<>(list);
}
private static final class DefaultApplicationContextFactory implements ApplicationContextFactory {
@Override
public ConfigurableApplicationContext create(WebApplicationType webApplicationType) {
try {
switch (webApplicationType) {
case SERVLET:
return new AnnotationConfigServletWebServerApplicationContext();
case REACTIVE:
return new AnnotationConfigReactiveWebServerApplicationContext();
default:
return new AnnotationConfigApplicationContext();
}
}
catch (Exception ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, please specify an ApplicationContextFactory", ex);
}
}
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -28,6 +28,7 @@ import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.boot.ApplicationContextFactory;
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
@ -272,12 +273,26 @@ public class SpringApplicationBuilder {
* Explicitly set the context class to be used.
* @param cls the context class to use
* @return the current builder
* @deprecated since 2.4.0 in favor of
* {@link #contextFactory(ApplicationContextFactory)}
*/
@Deprecated
public SpringApplicationBuilder contextClass(Class<? extends ConfigurableApplicationContext> cls) {
this.application.setApplicationContextClass(cls);
return this;
}
/**
* Explicitly set the factory used to create the application context.
* @param factory the factory to use
* @return the current builder
* @since 2.4.0
*/
public SpringApplicationBuilder contextFactory(ApplicationContextFactory factory) {
this.application.setApplicationContextFactory(factory);
return this;
}
/**
* Add more sources (configuration classes and components) to this application.
* @param sources the sources to add

@ -134,7 +134,7 @@ public abstract class SpringBootServletInitializer implements WebApplicationInit
builder.initializers(new ParentContextApplicationContextInitializer(parent));
}
builder.initializers(new ServletContextApplicationContextInitializer(servletContext));
builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
builder.contextFactory((webApplicationType) -> new AnnotationConfigServletWebServerApplicationContext());
builder = configure(builder);
builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
SpringApplication application = builder.build();

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -42,7 +42,8 @@ class SpringApplicationNoWebTests {
@Test
void specificApplicationContextClass() {
SpringApplication application = new SpringApplication(ExampleConfig.class);
application.setApplicationContextClass(StaticApplicationContext.class);
application.setApplicationContextFactory(
ApplicationContextFactory.forContextClass(StaticApplicationContext.class));
ConfigurableApplicationContext context = application.run();
assertThat(context).isInstanceOf(StaticApplicationContext.class);
context.close();

@ -320,6 +320,7 @@ class SpringApplicationTests {
}
@Test
@SuppressWarnings("deprecation")
void specificApplicationContextClass() {
SpringApplication application = new SpringApplication(ExampleConfig.class);
application.setApplicationContextClass(StaticApplicationContext.class);
@ -328,6 +329,16 @@ class SpringApplicationTests {
}
@Test
void specificApplicationContextFactory() {
SpringApplication application = new SpringApplication(ExampleConfig.class);
application.setApplicationContextFactory(
ApplicationContextFactory.forContextClass(StaticApplicationContext.class));
this.context = application.run();
assertThat(this.context).isInstanceOf(StaticApplicationContext.class);
}
@Test
@SuppressWarnings("deprecation")
void specificWebApplicationContextClassDetectWebApplicationType() {
SpringApplication application = new SpringApplication(ExampleConfig.class);
application.setApplicationContextClass(AnnotationConfigServletWebApplicationContext.class);
@ -335,6 +346,7 @@ class SpringApplicationTests {
}
@Test
@SuppressWarnings("deprecation")
void specificReactiveApplicationContextClassDetectReactiveApplicationType() {
SpringApplication application = new SpringApplication(ExampleConfig.class);
application.setApplicationContextClass(AnnotationConfigReactiveWebApplicationContext.class);
@ -342,6 +354,7 @@ class SpringApplicationTests {
}
@Test
@SuppressWarnings("deprecation")
void nonWebNorReactiveApplicationContextClassDetectNoneApplicationType() {
SpringApplication application = new SpringApplication(ExampleConfig.class);
application.setApplicationContextClass(StaticApplicationContext.class);
@ -876,7 +889,8 @@ class SpringApplicationTests {
@Test
void registerShutdownHook() {
SpringApplication application = new SpringApplication(ExampleConfig.class);
application.setApplicationContextClass(SpyApplicationContext.class);
application
.setApplicationContextFactory(ApplicationContextFactory.forContextClass(SpyApplicationContext.class));
this.context = application.run();
SpyApplicationContext applicationContext = (SpyApplicationContext) this.context;
verify(applicationContext.getApplicationContext()).registerShutdownHook();
@ -885,7 +899,8 @@ class SpringApplicationTests {
@Test
void registerListener() {
SpringApplication application = new SpringApplication(ExampleConfig.class, ListenerConfig.class);
application.setApplicationContextClass(SpyApplicationContext.class);
application
.setApplicationContextFactory(ApplicationContextFactory.forContextClass(SpyApplicationContext.class));
Set<ApplicationEvent> events = new LinkedHashSet<>();
application.addListeners((ApplicationListener<ApplicationEvent>) events::add);
this.context = application.run();
@ -898,7 +913,8 @@ class SpringApplicationTests {
void registerListenerWithCustomMulticaster() {
SpringApplication application = new SpringApplication(ExampleConfig.class, ListenerConfig.class,
Multicaster.class);
application.setApplicationContextClass(SpyApplicationContext.class);
application
.setApplicationContextFactory(ApplicationContextFactory.forContextClass(SpyApplicationContext.class));
Set<ApplicationEvent> events = new LinkedHashSet<>();
application.addListeners((ApplicationListener<ApplicationEvent>) events::add);
this.context = application.run();
@ -978,7 +994,8 @@ class SpringApplicationTests {
@Test
void registerShutdownHookOff() {
SpringApplication application = new SpringApplication(ExampleConfig.class);
application.setApplicationContextClass(SpyApplicationContext.class);
application
.setApplicationContextFactory(ApplicationContextFactory.forContextClass(SpyApplicationContext.class));
application.setRegisterShutdownHook(false);
this.context = application.run();
SpyApplicationContext applicationContext = (SpyApplicationContext) this.context;

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -24,6 +24,7 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationContextFactory;
import org.springframework.boot.WebApplicationType;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
@ -68,7 +69,8 @@ class SpringApplicationBuilderTests {
@Test
void profileAndProperties() {
SpringApplicationBuilder application = new SpringApplicationBuilder().sources(ExampleConfig.class)
.contextClass(StaticApplicationContext.class).profiles("foo").properties("foo=bar");
.contextFactory(ApplicationContextFactory.forContextClass(StaticApplicationContext.class))
.profiles("foo").properties("foo=bar");
this.context = application.run();
assertThat(this.context).isInstanceOf(StaticApplicationContext.class);
assertThat(this.context.getEnvironment().getProperty("foo")).isEqualTo("bucket");
@ -78,7 +80,8 @@ class SpringApplicationBuilderTests {
@Test
void propertiesAsMap() {
SpringApplicationBuilder application = new SpringApplicationBuilder().sources(ExampleConfig.class)
.contextClass(StaticApplicationContext.class).properties(Collections.singletonMap("bar", "foo"));
.contextFactory(ApplicationContextFactory.forContextClass(StaticApplicationContext.class))
.properties(Collections.singletonMap("bar", "foo"));
this.context = application.run();
assertThat(this.context.getEnvironment().getProperty("bar")).isEqualTo("foo");
}
@ -86,7 +89,7 @@ class SpringApplicationBuilderTests {
@Test
void propertiesAsProperties() {
SpringApplicationBuilder application = new SpringApplicationBuilder().sources(ExampleConfig.class)
.contextClass(StaticApplicationContext.class)
.contextFactory(ApplicationContextFactory.forContextClass(StaticApplicationContext.class))
.properties(StringUtils.splitArrayElementsIntoProperties(new String[] { "bar=foo" }, "="));
this.context = application.run();
assertThat(this.context.getEnvironment().getProperty("bar")).isEqualTo("foo");
@ -95,7 +98,7 @@ class SpringApplicationBuilderTests {
@Test
void propertiesWithRepeatSeparator() {
SpringApplicationBuilder application = new SpringApplicationBuilder().sources(ExampleConfig.class)
.contextClass(StaticApplicationContext.class)
.contextFactory(ApplicationContextFactory.forContextClass(StaticApplicationContext.class))
.properties("one=c:\\logging.file.name", "two=a:b", "three:c:\\logging.file.name", "four:a:b");
this.context = application.run();
ConfigurableEnvironment environment = this.context.getEnvironment();
@ -106,6 +109,7 @@ class SpringApplicationBuilderTests {
}
@Test
@SuppressWarnings("deprecation")
void specificApplicationContextClass() {
SpringApplicationBuilder application = new SpringApplicationBuilder().sources(ExampleConfig.class)
.contextClass(StaticApplicationContext.class);
@ -113,10 +117,18 @@ class SpringApplicationBuilderTests {
assertThat(this.context).isInstanceOf(StaticApplicationContext.class);
}
@Test
void specificApplicationContextFactory() {
SpringApplicationBuilder application = new SpringApplicationBuilder().sources(ExampleConfig.class)
.contextFactory(ApplicationContextFactory.forContextClass(StaticApplicationContext.class));
this.context = application.run();
assertThat(this.context).isInstanceOf(StaticApplicationContext.class);
}
@Test
void parentContextCreationThatIsRunDirectly() {
SpringApplicationBuilder application = new SpringApplicationBuilder(ChildConfig.class)
.contextClass(SpyApplicationContext.class);
.contextFactory(ApplicationContextFactory.forContextClass(SpyApplicationContext.class));
application.parent(ExampleConfig.class);
this.context = application.run("foo.bar=baz");
verify(((SpyApplicationContext) this.context).getApplicationContext()).setParent(any(ApplicationContext.class));
@ -129,7 +141,7 @@ class SpringApplicationBuilderTests {
@Test
void parentContextCreationThatIsBuiltThenRun() {
SpringApplicationBuilder application = new SpringApplicationBuilder(ChildConfig.class)
.contextClass(SpyApplicationContext.class);
.contextFactory(ApplicationContextFactory.forContextClass(SpyApplicationContext.class));
application.parent(ExampleConfig.class);
this.context = application.build("a=alpha").run("b=bravo");
verify(((SpyApplicationContext) this.context).getApplicationContext()).setParent(any(ApplicationContext.class));
@ -141,7 +153,8 @@ class SpringApplicationBuilderTests {
@Test
void parentContextCreationWithChildShutdown() {
SpringApplicationBuilder application = new SpringApplicationBuilder(ChildConfig.class)
.contextClass(SpyApplicationContext.class).registerShutdownHook(true);
.contextFactory(ApplicationContextFactory.forContextClass(SpyApplicationContext.class))
.registerShutdownHook(true);
application.parent(ExampleConfig.class);
this.context = application.run();
verify(((SpyApplicationContext) this.context).getApplicationContext()).setParent(any(ApplicationContext.class));
@ -151,7 +164,7 @@ class SpringApplicationBuilderTests {
@Test
void contextWithClassLoader() {
SpringApplicationBuilder application = new SpringApplicationBuilder(ExampleConfig.class)
.contextClass(SpyApplicationContext.class);
.contextFactory(ApplicationContextFactory.forContextClass(SpyApplicationContext.class));
ClassLoader classLoader = new URLClassLoader(new URL[0], getClass().getClassLoader());
application.resourceLoader(new DefaultResourceLoader(classLoader));
this.context = application.run();
@ -161,7 +174,7 @@ class SpringApplicationBuilderTests {
@Test
void parentContextWithClassLoader() {
SpringApplicationBuilder application = new SpringApplicationBuilder(ChildConfig.class)
.contextClass(SpyApplicationContext.class);
.contextFactory(ApplicationContextFactory.forContextClass(SpyApplicationContext.class));
ClassLoader classLoader = new URLClassLoader(new URL[0], getClass().getClassLoader());
application.resourceLoader(new DefaultResourceLoader(classLoader));
application.parent(ExampleConfig.class);
@ -173,7 +186,7 @@ class SpringApplicationBuilderTests {
void parentFirstCreation() {
SpringApplicationBuilder application = new SpringApplicationBuilder(ExampleConfig.class)
.child(ChildConfig.class);
application.contextClass(SpyApplicationContext.class);
application.contextFactory(ApplicationContextFactory.forContextClass(SpyApplicationContext.class));
this.context = application.run();
verify(((SpyApplicationContext) this.context).getApplicationContext()).setParent(any(ApplicationContext.class));
assertThat(((SpyApplicationContext) this.context).getRegisteredShutdownHook()).isFalse();
@ -230,7 +243,7 @@ class SpringApplicationBuilderTests {
void parentContextIdentical() {
SpringApplicationBuilder application = new SpringApplicationBuilder(ExampleConfig.class);
application.parent(ExampleConfig.class);
application.contextClass(SpyApplicationContext.class);
application.contextFactory(ApplicationContextFactory.forContextClass(SpyApplicationContext.class));
this.context = application.run();
verify(((SpyApplicationContext) this.context).getApplicationContext()).setParent(any(ApplicationContext.class));
}

Loading…
Cancel
Save