Add properties class to bind to in SpringApplication

Closes gh-40592
This commit is contained in:
Moritz Halbritter 2024-08-09 15:00:05 +02:00
parent 16347a7e31
commit 5141045f6f
4 changed files with 203 additions and 65 deletions

View File

@ -0,0 +1,152 @@
/*
* Copyright 2012-2024 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 java.util.LinkedHashSet;
import java.util.Set;
import org.springframework.boot.Banner.Mode;
/**
* Spring application properties.
*
* @author Moritz Halbritter
*/
class ApplicationProperties {
/**
* Whether bean definition overriding, by registering a definition with the same name
* as an existing definition, is allowed.
*/
private boolean allowBeanDefinitionOverriding;
/**
* Whether to allow circular references between beans and automatically try to resolve
* them.
*/
private boolean allowCircularReferences;
/**
* Mode used to display the banner when the application runs.
*/
private Banner.Mode bannerMode = Banner.Mode.CONSOLE;
/**
* Whether to keep the application alive even if there are no more non-daemon threads.
*/
private boolean keepAlive;
/**
* Whether initialization should be performed lazily.
*/
private boolean lazyInitialization = false;
/**
* Whether to log information about the application when it starts.
*/
private boolean logStartupInfo = true;
/**
* Whether the application should have a shutdown hook registered.
*/
private boolean registerShutdownHook = true;
/**
* Sources (class names, package names, or XML resource locations) to include in the
* ApplicationContext.
*/
private Set<String> sources = new LinkedHashSet<>();
/**
* Flag to explicitly request a specific type of web application. If not set,
* auto-detected based on the classpath.
*/
private WebApplicationType webApplicationType;
boolean isAllowBeanDefinitionOverriding() {
return this.allowBeanDefinitionOverriding;
}
void setAllowBeanDefinitionOverriding(boolean allowBeanDefinitionOverriding) {
this.allowBeanDefinitionOverriding = allowBeanDefinitionOverriding;
}
boolean isAllowCircularReferences() {
return this.allowCircularReferences;
}
void setAllowCircularReferences(boolean allowCircularReferences) {
this.allowCircularReferences = allowCircularReferences;
}
Mode getBannerMode() {
return this.bannerMode;
}
void setBannerMode(Mode bannerMode) {
this.bannerMode = bannerMode;
}
boolean isKeepAlive() {
return this.keepAlive;
}
void setKeepAlive(boolean keepAlive) {
this.keepAlive = keepAlive;
}
boolean isLazyInitialization() {
return this.lazyInitialization;
}
void setLazyInitialization(boolean lazyInitialization) {
this.lazyInitialization = lazyInitialization;
}
boolean isLogStartupInfo() {
return this.logStartupInfo;
}
void setLogStartupInfo(boolean logStartupInfo) {
this.logStartupInfo = logStartupInfo;
}
boolean isRegisterShutdownHook() {
return this.registerShutdownHook;
}
void setRegisterShutdownHook(boolean registerShutdownHook) {
this.registerShutdownHook = registerShutdownHook;
}
Set<String> getSources() {
return this.sources;
}
void setSources(Set<String> sources) {
this.sources = new LinkedHashSet<>(sources);
}
WebApplicationType getWebApplicationType() {
return this.webApplicationType;
}
void setWebApplicationType(WebApplicationType webApplicationType) {
this.webApplicationType = webApplicationType;
}
}

View File

@ -208,14 +208,8 @@ public class SpringApplication {
private final Set<Class<?>> primarySources;
private Set<String> sources = new LinkedHashSet<>();
private Class<?> mainApplicationClass;
private Banner.Mode bannerMode = Banner.Mode.CONSOLE;
private boolean logStartupInfo = true;
private boolean addCommandLineProperties = true;
private boolean addConversionService = true;
@ -228,12 +222,8 @@ public class SpringApplication {
private ConfigurableEnvironment environment;
private WebApplicationType webApplicationType;
private boolean headless = true;
private boolean registerShutdownHook = true;
private List<ApplicationContextInitializer<?>> initializers;
private List<ApplicationListener<?>> listeners;
@ -244,21 +234,15 @@ public class SpringApplication {
private Set<String> additionalProfiles = Collections.emptySet();
private boolean allowBeanDefinitionOverriding;
private boolean allowCircularReferences;
private boolean isCustomEnvironment = false;
private boolean lazyInitialization = false;
private String environmentPrefix;
private ApplicationContextFactory applicationContextFactory = ApplicationContextFactory.DEFAULT;
private ApplicationStartup applicationStartup = ApplicationStartup.DEFAULT;
private boolean keepAlive;
final ApplicationProperties properties = new ApplicationProperties();
/**
* Create a new {@link SpringApplication} instance. The application context will load
@ -289,7 +273,7 @@ public class SpringApplication {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.properties.setWebApplicationType(WebApplicationType.deduceFromClasspath());
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
@ -317,7 +301,7 @@ public class SpringApplication {
*/
public ConfigurableApplicationContext run(String... args) {
Startup startup = Startup.create();
if (this.registerShutdownHook) {
if (this.properties.isRegisterShutdownHook()) {
SpringApplication.shutdownHook.enableShutdownHookAddition();
}
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
@ -335,7 +319,7 @@ public class SpringApplication {
refreshContext(context);
afterRefresh(context, applicationArguments);
startup.started();
if (this.logStartupInfo) {
if (this.properties.isLogStartupInfo()) {
new StartupInfoLogger(this.mainApplicationClass, environment).logStarted(getApplicationLog(), startup);
}
listeners.started(context, startup.timeTakenToStarted());
@ -383,9 +367,10 @@ public class SpringApplication {
private Class<? extends ConfigurableEnvironment> deduceEnvironmentClass() {
Class<? extends ConfigurableEnvironment> environmentType = this.applicationContextFactory
.getEnvironmentType(this.webApplicationType);
.getEnvironmentType(this.properties.getWebApplicationType());
if (environmentType == null && this.applicationContextFactory != ApplicationContextFactory.DEFAULT) {
environmentType = ApplicationContextFactory.DEFAULT.getEnvironmentType(this.webApplicationType);
environmentType = ApplicationContextFactory.DEFAULT
.getEnvironmentType(this.properties.getWebApplicationType());
}
if (environmentType == null) {
return ApplicationEnvironment.class;
@ -402,7 +387,7 @@ public class SpringApplication {
applyInitializers(context);
listeners.contextPrepared(context);
bootstrapContext.close(context);
if (this.logStartupInfo) {
if (this.properties.isLogStartupInfo()) {
logStartupInfo(context.getParent() == null);
logStartupInfo(context);
logStartupProfileInfo(context);
@ -414,15 +399,15 @@ public class SpringApplication {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof AbstractAutowireCapableBeanFactory autowireCapableBeanFactory) {
autowireCapableBeanFactory.setAllowCircularReferences(this.allowCircularReferences);
autowireCapableBeanFactory.setAllowCircularReferences(this.properties.isAllowCircularReferences());
if (beanFactory instanceof DefaultListableBeanFactory listableBeanFactory) {
listableBeanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
listableBeanFactory.setAllowBeanDefinitionOverriding(this.properties.isAllowBeanDefinitionOverriding());
}
}
if (this.lazyInitialization) {
if (this.properties.isLazyInitialization()) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
if (this.keepAlive) {
if (this.properties.isKeepAlive()) {
context.addApplicationListener(new KeepAlive());
}
context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
@ -452,7 +437,7 @@ public class SpringApplication {
}
private void refreshContext(ConfigurableApplicationContext context) {
if (this.registerShutdownHook) {
if (this.properties.isRegisterShutdownHook()) {
shutdownHook.registerApplicationContext(context);
}
refresh(context);
@ -489,9 +474,10 @@ public class SpringApplication {
if (this.environment != null) {
return this.environment;
}
ConfigurableEnvironment environment = this.applicationContextFactory.createEnvironment(this.webApplicationType);
ConfigurableEnvironment environment = this.applicationContextFactory
.createEnvironment(this.properties.getWebApplicationType());
if (environment == null && this.applicationContextFactory != ApplicationContextFactory.DEFAULT) {
environment = ApplicationContextFactory.DEFAULT.createEnvironment(this.webApplicationType);
environment = ApplicationContextFactory.DEFAULT.createEnvironment(this.properties.getWebApplicationType());
}
return (environment != null) ? environment : new ApplicationEnvironment();
}
@ -556,12 +542,12 @@ public class SpringApplication {
}
/**
* Bind the environment to the {@link SpringApplication}.
* Bind the environment to the {@link ApplicationProperties}.
* @param environment the environment to bind
*/
protected void bindToSpringApplication(ConfigurableEnvironment environment) {
try {
Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
Binder.get(environment).bind("spring.main", Bindable.ofInstance(this.properties));
}
catch (Exception ex) {
throw new IllegalStateException("Cannot bind to SpringApplication", ex);
@ -569,13 +555,13 @@ public class SpringApplication {
}
private Banner printBanner(ConfigurableEnvironment environment) {
if (this.bannerMode == Banner.Mode.OFF) {
if (this.properties.getBannerMode() == Banner.Mode.OFF) {
return null;
}
ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
: new DefaultResourceLoader(null);
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
if (this.bannerMode == Mode.LOG) {
if (this.properties.getBannerMode() == Mode.LOG) {
return bannerPrinter.print(environment, this.mainApplicationClass, logger);
}
return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
@ -589,7 +575,7 @@ public class SpringApplication {
* @see #setApplicationContextFactory(ApplicationContextFactory)
*/
protected ConfigurableApplicationContext createApplicationContext() {
return this.applicationContextFactory.create(this.webApplicationType);
return this.applicationContextFactory.create(this.properties.getWebApplicationType());
}
/**
@ -967,7 +953,7 @@ public class SpringApplication {
* @since 2.0.0
*/
public WebApplicationType getWebApplicationType() {
return this.webApplicationType;
return this.properties.getWebApplicationType();
}
/**
@ -978,7 +964,7 @@ public class SpringApplication {
*/
public void setWebApplicationType(WebApplicationType webApplicationType) {
Assert.notNull(webApplicationType, "WebApplicationType must not be null");
this.webApplicationType = webApplicationType;
this.properties.setWebApplicationType(webApplicationType);
}
/**
@ -989,7 +975,7 @@ public class SpringApplication {
* @see DefaultListableBeanFactory#setAllowBeanDefinitionOverriding(boolean)
*/
public void setAllowBeanDefinitionOverriding(boolean allowBeanDefinitionOverriding) {
this.allowBeanDefinitionOverriding = allowBeanDefinitionOverriding;
this.properties.setAllowBeanDefinitionOverriding(allowBeanDefinitionOverriding);
}
/**
@ -1000,7 +986,7 @@ public class SpringApplication {
* @see AbstractAutowireCapableBeanFactory#setAllowCircularReferences(boolean)
*/
public void setAllowCircularReferences(boolean allowCircularReferences) {
this.allowCircularReferences = allowCircularReferences;
this.properties.setAllowCircularReferences(allowCircularReferences);
}
/**
@ -1010,7 +996,7 @@ public class SpringApplication {
* @see BeanDefinition#setLazyInit(boolean)
*/
public void setLazyInitialization(boolean lazyInitialization) {
this.lazyInitialization = lazyInitialization;
this.properties.setLazyInitialization(lazyInitialization);
}
/**
@ -1030,7 +1016,7 @@ public class SpringApplication {
* @see #getShutdownHandlers()
*/
public void setRegisterShutdownHook(boolean registerShutdownHook) {
this.registerShutdownHook = registerShutdownHook;
this.properties.setRegisterShutdownHook(registerShutdownHook);
}
/**
@ -1048,7 +1034,7 @@ public class SpringApplication {
* @param bannerMode the mode used to display the banner
*/
public void setBannerMode(Banner.Mode bannerMode) {
this.bannerMode = bannerMode;
this.properties.setBannerMode(bannerMode);
}
/**
@ -1057,7 +1043,7 @@ public class SpringApplication {
* @param logStartupInfo if startup info should be logged.
*/
public void setLogStartupInfo(boolean logStartupInfo) {
this.logStartupInfo = logStartupInfo;
this.properties.setLogStartupInfo(logStartupInfo);
}
/**
@ -1173,7 +1159,7 @@ public class SpringApplication {
* @see #getAllSources()
*/
public Set<String> getSources() {
return this.sources;
return this.properties.getSources();
}
/**
@ -1188,7 +1174,7 @@ public class SpringApplication {
*/
public void setSources(Set<String> sources) {
Assert.notNull(sources, "Sources must not be null");
this.sources = new LinkedHashSet<>(sources);
this.properties.setSources(sources);
}
/**
@ -1203,8 +1189,8 @@ public class SpringApplication {
if (!CollectionUtils.isEmpty(this.primarySources)) {
allSources.addAll(this.primarySources);
}
if (!CollectionUtils.isEmpty(this.sources)) {
allSources.addAll(this.sources);
if (!CollectionUtils.isEmpty(this.properties.getSources())) {
allSources.addAll(this.properties.getSources());
}
return Collections.unmodifiableSet(allSources);
}
@ -1333,7 +1319,7 @@ public class SpringApplication {
* @since 3.2.0
*/
public boolean isKeepAlive() {
return this.keepAlive;
return this.properties.isKeepAlive();
}
/**
@ -1344,7 +1330,7 @@ public class SpringApplication {
* @since 3.2.0
*/
public void setKeepAlive(boolean keepAlive) {
this.keepAlive = keepAlive;
this.properties.setKeepAlive(keepAlive);
}
/**

View File

@ -56,6 +56,7 @@ import org.springframework.beans.factory.support.BeanDefinitionOverrideException
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.DefaultBeanNameGenerator;
import org.springframework.boot.Banner.Mode;
import org.springframework.boot.BootstrapRegistry.InstanceSupplier;
import org.springframework.boot.SpringApplication.SpringApplicationRuntimeHints;
import org.springframework.boot.availability.AvailabilityChangeEvent;
@ -119,6 +120,7 @@ import org.springframework.core.metrics.StartupStep;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.test.context.support.TestPropertySourceUtils;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
@ -140,7 +142,6 @@ import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.BDDMockito.willThrow;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockingDetails;
import static org.mockito.Mockito.never;
@ -284,7 +285,7 @@ class SpringApplicationTests {
SpringApplication application = spy(new SpringApplication(ExampleConfig.class));
application.setWebApplicationType(WebApplicationType.NONE);
this.context = application.run("--spring.main.banner-mode=log");
then(application).should(atLeastOnce()).setBannerMode(Banner.Mode.LOG);
assertThatBannerModeIs(application, Banner.Mode.LOG);
assertThat(output).contains("o.s.b.SpringApplication");
}
@ -293,7 +294,7 @@ class SpringApplicationTests {
SpringApplication application = new SpringApplication(ExampleConfig.class);
application.setWebApplicationType(WebApplicationType.NONE);
this.context = application.run("--spring.config.name=bindtoapplication");
assertThat(application).hasFieldOrPropertyWithValue("bannerMode", Banner.Mode.OFF);
assertThatBannerModeIs(application, Mode.OFF);
}
@Test
@ -302,7 +303,7 @@ class SpringApplicationTests {
SpringApplication application = new SpringApplication(ExampleConfig.class);
application.setWebApplicationType(WebApplicationType.NONE);
this.context = application.run();
assertThat(application).hasFieldOrPropertyWithValue("bannerMode", Banner.Mode.OFF);
assertThatBannerModeIs(application, Mode.OFF);
}
@Test
@ -311,7 +312,7 @@ class SpringApplicationTests {
application.setDefaultProperties(Collections.singletonMap("spring.main.banner-mode", false));
application.setWebApplicationType(WebApplicationType.NONE);
this.context = application.run();
assertThat(application).hasFieldOrPropertyWithValue("bannerMode", Banner.Mode.OFF);
assertThatBannerModeIs(application, Mode.OFF);
}
@Test
@ -319,7 +320,7 @@ class SpringApplicationTests {
SpringApplication application = new SpringApplication(ExampleConfig.class);
application.setWebApplicationType(WebApplicationType.NONE);
this.context = application.run("--spring.main.banner-mode=false");
assertThat(application).hasFieldOrPropertyWithValue("bannerMode", Banner.Mode.OFF);
assertThatBannerModeIs(application, Mode.OFF);
}
@Test
@ -1554,6 +1555,11 @@ class SpringApplicationTests {
return Thread.getAllStackTraces().keySet();
}
private void assertThatBannerModeIs(SpringApplication application, Mode mode) {
Object properties = ReflectionTestUtils.getField(application, "properties");
assertThat(properties).hasFieldOrPropertyWithValue("bannerMode", mode);
}
static class TestEventListener<E extends ApplicationEvent> implements SmartApplicationListener {
private final Class<E> eventType;
@ -1613,8 +1619,6 @@ class SpringApplicationTests {
private boolean useMockLoader;
private Banner.Mode bannerMode;
TestSpringApplication(Class<?>... primarySources) {
super(primarySources);
}
@ -1642,14 +1646,8 @@ class SpringApplicationTests {
return this.loader;
}
@Override
public void setBannerMode(Banner.Mode bannerMode) {
super.setBannerMode(bannerMode);
this.bannerMode = bannerMode;
}
Banner.Mode getBannerMode() {
return this.bannerMode;
return this.properties.getBannerMode();
}
}

View File

@ -52,6 +52,7 @@ import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.env.PropertySource;
import org.springframework.mock.web.MockServletContext;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.StandardServletEnvironment;
@ -119,7 +120,8 @@ class SpringBootServletInitializerTests {
new WithConfigurationAnnotation().onStartup(this.servletContext);
assertThat(this.servletContext.getAttribute(LoggingApplicationListener.REGISTER_SHUTDOWN_HOOK_PROPERTY))
.isEqualTo(false);
assertThat(this.application).hasFieldOrPropertyWithValue("registerShutdownHook", false);
Object properties = ReflectionTestUtils.getField(this.application, "properties");
assertThat(properties).hasFieldOrPropertyWithValue("registerShutdownHook", false);
}
@Test