org.springframework.boot.builder.SpringApplicationBuilder Maven / Gradle / Ivy
/*
* 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.
* 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.builder;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Properties;
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.BootstrapRegistry;
import org.springframework.boot.BootstrapRegistryInitializer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.convert.ApplicationConversionService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.metrics.ApplicationStartup;
import org.springframework.util.StringUtils;
/**
* Builder for {@link SpringApplication} and {@link ApplicationContext} instances with
* convenient fluent API and context hierarchy support. Simple example of a context
* hierarchy:
*
*
* new SpringApplicationBuilder(ParentConfig.class).child(ChildConfig.class).run(args);
*
*
* Another common use case is setting active profiles and default properties to set up the
* environment for an application:
*
*
* new SpringApplicationBuilder(Application.class).profiles("server")
* .properties("transport=local").run(args);
*
*
*
* If your needs are simpler, consider using the static convenience methods in
* SpringApplication instead.
*
* @author Dave Syer
* @author Andy Wilkinson
* @since 1.0.0
* @see SpringApplication
*/
public class SpringApplicationBuilder {
private final SpringApplication application;
private ConfigurableApplicationContext context;
private SpringApplicationBuilder parent;
private final AtomicBoolean running = new AtomicBoolean();
private final Set> sources = new LinkedHashSet<>();
private final Map defaultProperties = new LinkedHashMap<>();
private ConfigurableEnvironment environment;
private Set additionalProfiles = new LinkedHashSet<>();
private boolean registerShutdownHookApplied;
private boolean configuredAsChild = false;
public SpringApplicationBuilder(Class>... sources) {
this.application = createSpringApplication(sources);
}
/**
* Creates a new {@link org.springframework.boot.SpringApplication} instances from the
* given sources. Subclasses may override in order to provide a custom subclass of
* {@link org.springframework.boot.SpringApplication}
* @param sources the sources
* @return the {@link org.springframework.boot.SpringApplication} instance
* @since 1.1.0
*/
protected SpringApplication createSpringApplication(Class>... sources) {
return new SpringApplication(sources);
}
/**
* Accessor for the current application context.
* @return the current application context (or null if not yet running)
*/
public ConfigurableApplicationContext context() {
return this.context;
}
/**
* Accessor for the current application.
* @return the current application (never null)
*/
public SpringApplication application() {
return this.application;
}
/**
* Create an application context (and its parent if specified) with the command line
* args provided. The parent is run first with the same arguments if has not yet been
* started.
* @param args the command line arguments
* @return an application context created from the current state
*/
public ConfigurableApplicationContext run(String... args) {
if (this.running.get()) {
// If already created we just return the existing context
return this.context;
}
configureAsChildIfNecessary(args);
if (this.running.compareAndSet(false, true)) {
synchronized (this.running) {
// If not already running copy the sources over and then run.
this.context = build().run(args);
}
}
return this.context;
}
private void configureAsChildIfNecessary(String... args) {
if (this.parent != null && !this.configuredAsChild) {
this.configuredAsChild = true;
if (!this.registerShutdownHookApplied) {
this.application.setRegisterShutdownHook(false);
}
initializers(new ParentContextApplicationContextInitializer(this.parent.run(args)));
}
}
/**
* Returns a fully configured {@link SpringApplication} that is ready to run.
* @return the fully configured {@link SpringApplication}.
*/
public SpringApplication build() {
return build(new String[0]);
}
/**
* Returns a fully configured {@link SpringApplication} that is ready to run. Any
* parent that has been configured will be run with the given {@code args}.
* @param args the parent's args
* @return the fully configured {@link SpringApplication}.
*/
public SpringApplication build(String... args) {
configureAsChildIfNecessary(args);
this.application.addPrimarySources(this.sources);
return this.application;
}
/**
* Create a child application with the provided sources. Default args and environment
* are copied down into the child, but everything else is a clean sheet.
* @param sources the sources for the application (Spring configuration)
* @return the child application builder
*/
public SpringApplicationBuilder child(Class>... sources) {
SpringApplicationBuilder child = new SpringApplicationBuilder();
child.sources(sources);
// Copy environment stuff from parent to child
child.properties(this.defaultProperties).environment(this.environment)
.additionalProfiles(this.additionalProfiles);
child.parent = this;
// It's not possible if embedded web server are enabled to support web contexts as
// parents because the servlets cannot be initialized at the right point in
// lifecycle.
web(WebApplicationType.NONE);
// Probably not interested in multiple banners
bannerMode(Banner.Mode.OFF);
// Make sure sources get copied over
this.application.addPrimarySources(this.sources);
return child;
}
/**
* Add a parent application with the provided sources. Default args and environment
* are copied up into the parent, but everything else is a clean sheet.
* @param sources the sources for the application (Spring configuration)
* @return the parent builder
*/
public SpringApplicationBuilder parent(Class>... sources) {
if (this.parent == null) {
this.parent = new SpringApplicationBuilder(sources).web(WebApplicationType.NONE)
.properties(this.defaultProperties).environment(this.environment);
}
else {
this.parent.sources(sources);
}
return this.parent;
}
private SpringApplicationBuilder runAndExtractParent(String... args) {
if (this.context == null) {
run(args);
}
if (this.parent != null) {
return this.parent;
}
throw new IllegalStateException(
"No parent defined yet (please use the other overloaded parent methods to set one)");
}
/**
* Add an already running parent context to an existing application.
* @param parent the parent context
* @return the current builder (not the parent)
*/
public SpringApplicationBuilder parent(ConfigurableApplicationContext parent) {
this.parent = new SpringApplicationBuilder();
this.parent.context = parent;
this.parent.running.set(true);
return this;
}
/**
* Create a sibling application (one with the same parent). A side effect of calling
* this method is that the current application (and its parent) are started without
* any arguments if they are not already running. To supply arguments when starting
* the current application and its parent use {@link #sibling(Class[], String...)}
* instead.
* @param sources the sources for the application (Spring configuration)
* @return the new sibling builder
*/
public SpringApplicationBuilder sibling(Class>... sources) {
return runAndExtractParent().child(sources);
}
/**
* Create a sibling application (one with the same parent). A side effect of calling
* this method is that the current application (and its parent) are started if they
* are not already running.
* @param sources the sources for the application (Spring configuration)
* @param args the command line arguments to use when starting the current app and its
* parent
* @return the new sibling builder
*/
public SpringApplicationBuilder sibling(Class>[] sources, String... args) {
return runAndExtractParent(args).child(sources);
}
/**
* Explicitly set the context class to be used.
* @param cls the context class to use
* @return the current builder
* @deprecated since 2.4.0 for removal in 2.6.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
* @return the current builder
*/
public SpringApplicationBuilder sources(Class>... sources) {
this.sources.addAll(new LinkedHashSet<>(Arrays.asList(sources)));
return this;
}
/**
* Flag to explicitly request a specific type of web application. Auto-detected based
* on the classpath if not set.
* @param webApplicationType the type of web application
* @return the current builder
* @since 2.0.0
*/
public SpringApplicationBuilder web(WebApplicationType webApplicationType) {
this.application.setWebApplicationType(webApplicationType);
return this;
}
/**
* Flag to indicate the startup information should be logged.
* @param logStartupInfo the flag to set. Default true.
* @return the current builder
*/
public SpringApplicationBuilder logStartupInfo(boolean logStartupInfo) {
this.application.setLogStartupInfo(logStartupInfo);
return this;
}
/**
* Sets the {@link Banner} instance which will be used to print the banner when no
* static banner file is provided.
* @param banner the banner to use
* @return the current builder
*/
public SpringApplicationBuilder banner(Banner banner) {
this.application.setBanner(banner);
return this;
}
public SpringApplicationBuilder bannerMode(Banner.Mode bannerMode) {
this.application.setBannerMode(bannerMode);
return this;
}
/**
* Sets if the application is headless and should not instantiate AWT. Defaults to
* {@code true} to prevent java icons appearing.
* @param headless if the application is headless
* @return the current builder
*/
public SpringApplicationBuilder headless(boolean headless) {
this.application.setHeadless(headless);
return this;
}
/**
* Sets if the created {@link ApplicationContext} should have a shutdown hook
* registered.
* @param registerShutdownHook if the shutdown hook should be registered
* @return the current builder
*/
public SpringApplicationBuilder registerShutdownHook(boolean registerShutdownHook) {
this.registerShutdownHookApplied = true;
this.application.setRegisterShutdownHook(registerShutdownHook);
return this;
}
/**
* Fixes the main application class that is used to anchor the startup messages.
* @param mainApplicationClass the class to use.
* @return the current builder
*/
public SpringApplicationBuilder main(Class> mainApplicationClass) {
this.application.setMainApplicationClass(mainApplicationClass);
return this;
}
/**
* Flag to indicate that command line arguments should be added to the environment.
* @param addCommandLineProperties the flag to set. Default true.
* @return the current builder
*/
public SpringApplicationBuilder addCommandLineProperties(boolean addCommandLineProperties) {
this.application.setAddCommandLineProperties(addCommandLineProperties);
return this;
}
/**
* Flag to indicate if the {@link ApplicationConversionService} should be added to the
* application context's {@link Environment}.
* @param addConversionService if the conversion service should be added.
* @return the current builder
* @since 2.1.0
*/
public SpringApplicationBuilder setAddConversionService(boolean addConversionService) {
this.application.setAddConversionService(addConversionService);
return this;
}
/**
* Adds a {@link org.springframework.boot.Bootstrapper} that can be used to initialize
* the {@link BootstrapRegistry}.
* @param bootstrapper the bootstraper
* @return the current builder
* @since 2.4.0
* @deprecated since 2.4.5 for removal in 2.6 in favor of
* {@link #addBootstrapRegistryInitializer(BootstrapRegistryInitializer)}
*/
@Deprecated
public SpringApplicationBuilder addBootstrapper(org.springframework.boot.Bootstrapper bootstrapper) {
this.application.addBootstrapper(bootstrapper);
return this;
}
/**
* Adds {@link BootstrapRegistryInitializer} instances that can be used to initialize
* the {@link BootstrapRegistry}.
* @param bootstrapRegistryInitializer the bootstrap registry initializer to add
* @return the current builder
* @since 2.4.5
*/
public SpringApplicationBuilder addBootstrapRegistryInitializer(
BootstrapRegistryInitializer bootstrapRegistryInitializer) {
this.application.addBootstrapRegistryInitializer(bootstrapRegistryInitializer);
return this;
}
/**
* Flag to control whether the application should be initialized lazily.
* @param lazyInitialization the flag to set. Defaults to false.
* @return the current builder
* @since 2.2
*/
public SpringApplicationBuilder lazyInitialization(boolean lazyInitialization) {
this.application.setLazyInitialization(lazyInitialization);
return this;
}
/**
* Default properties for the environment in the form {@code key=value} or
* {@code key:value}. Multiple calls to this method are cumulative and will not clear
* any previously set properties.
* @param defaultProperties the properties to set.
* @return the current builder
* @see SpringApplicationBuilder#properties(Properties)
* @see SpringApplicationBuilder#properties(Map)
*/
public SpringApplicationBuilder properties(String... defaultProperties) {
return properties(getMapFromKeyValuePairs(defaultProperties));
}
private Map getMapFromKeyValuePairs(String[] properties) {
Map map = new HashMap<>();
for (String property : properties) {
int index = lowestIndexOf(property, ":", "=");
String key = (index > 0) ? property.substring(0, index) : property;
String value = (index > 0) ? property.substring(index + 1) : "";
map.put(key, value);
}
return map;
}
private int lowestIndexOf(String property, String... candidates) {
int index = -1;
for (String candidate : candidates) {
int candidateIndex = property.indexOf(candidate);
if (candidateIndex > 0) {
index = (index != -1) ? Math.min(index, candidateIndex) : candidateIndex;
}
}
return index;
}
/**
* Default properties for the environment.Multiple calls to this method are cumulative
* and will not clear any previously set properties.
* @param defaultProperties the properties to set.
* @return the current builder
* @see SpringApplicationBuilder#properties(String...)
* @see SpringApplicationBuilder#properties(Map)
*/
public SpringApplicationBuilder properties(Properties defaultProperties) {
return properties(getMapFromProperties(defaultProperties));
}
private Map getMapFromProperties(Properties properties) {
Map map = new HashMap<>();
for (Object key : Collections.list(properties.propertyNames())) {
map.put((String) key, properties.get(key));
}
return map;
}
/**
* Default properties for the environment. Multiple calls to this method are
* cumulative and will not clear any previously set properties.
* @param defaults the default properties
* @return the current builder
* @see SpringApplicationBuilder#properties(String...)
* @see SpringApplicationBuilder#properties(Properties)
*/
public SpringApplicationBuilder properties(Map defaults) {
this.defaultProperties.putAll(defaults);
this.application.setDefaultProperties(this.defaultProperties);
if (this.parent != null) {
this.parent.properties(this.defaultProperties);
this.parent.environment(this.environment);
}
return this;
}
/**
* Add to the active Spring profiles for this app (and its parent and children).
* @param profiles the profiles to add.
* @return the current builder
*/
public SpringApplicationBuilder profiles(String... profiles) {
this.additionalProfiles.addAll(Arrays.asList(profiles));
this.application.setAdditionalProfiles(StringUtils.toStringArray(this.additionalProfiles));
return this;
}
private SpringApplicationBuilder additionalProfiles(Collection additionalProfiles) {
this.additionalProfiles = new LinkedHashSet<>(additionalProfiles);
this.application.setAdditionalProfiles(StringUtils.toStringArray(this.additionalProfiles));
return this;
}
/**
* Bean name generator for automatically generated bean names in the application
* context.
* @param beanNameGenerator the generator to set.
* @return the current builder
*/
public SpringApplicationBuilder beanNameGenerator(BeanNameGenerator beanNameGenerator) {
this.application.setBeanNameGenerator(beanNameGenerator);
return this;
}
/**
* Environment for the application context.
* @param environment the environment to set.
* @return the current builder
*/
public SpringApplicationBuilder environment(ConfigurableEnvironment environment) {
this.application.setEnvironment(environment);
this.environment = environment;
return this;
}
/**
* {@link ResourceLoader} for the application context. If a custom class loader is
* needed, this is where it would be added.
* @param resourceLoader the resource loader to set.
* @return the current builder
*/
public SpringApplicationBuilder resourceLoader(ResourceLoader resourceLoader) {
this.application.setResourceLoader(resourceLoader);
return this;
}
/**
* Add some initializers to the application (applied to the {@link ApplicationContext}
* before any bean definitions are loaded).
* @param initializers some initializers to add
* @return the current builder
*/
public SpringApplicationBuilder initializers(ApplicationContextInitializer>... initializers) {
this.application.addInitializers(initializers);
return this;
}
/**
* Add some listeners to the application (listening for SpringApplication events as
* well as regular Spring events once the context is running). Any listeners that are
* also {@link ApplicationContextInitializer} will be added to the
* {@link #initializers(ApplicationContextInitializer...) initializers} automatically.
* @param listeners some listeners to add
* @return the current builder
*/
public SpringApplicationBuilder listeners(ApplicationListener>... listeners) {
this.application.addListeners(listeners);
return this;
}
/**
* Configure the {@link ApplicationStartup} to be used with the
* {@link ApplicationContext} for collecting startup metrics.
* @param applicationStartup the application startup to use
* @return the current builder
* @since 2.4.0
*/
public SpringApplicationBuilder applicationStartup(ApplicationStartup applicationStartup) {
this.application.setApplicationStartup(applicationStartup);
return this;
}
}