All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.boot.builder.SpringApplicationBuilder Maven / Gradle / Ivy

There is a newer version: 3.3.3
Show newest version
/*
 * Copyright 2012-2016 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
 *
 *      http://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.Banner;
import org.springframework.boot.SpringApplication;
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.io.ResourceLoader;

/**
 * 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 default arguments, e.g. active Spring profiles, to * set up the environment for an application: * *
 * new SpringApplicationBuilder(Application.class).profiles("server")
 * 		.defaultArgs("--transport=local").run(args);
 * 
* *

* If your needs are simpler, consider using the static convenience methods in * SpringApplication instead. * * @author Dave Syer * @author Andy Wilkinson */ public class SpringApplicationBuilder { private final SpringApplication application; private ConfigurableApplicationContext context; private SpringApplicationBuilder parent; private final AtomicBoolean running = new AtomicBoolean(false); 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(Object... 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(Object... 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.setSources(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(Object... 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 containers are enabled to support web contexts as // parents because the servlets cannot be initialized at the right point in // lifecycle. web(false); // Probably not interested in multiple banners bannerMode(Banner.Mode.OFF); // Make sure sources get copied over this.application.setSources(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(Object... sources) { if (this.parent == null) { this.parent = new SpringApplicationBuilder(sources).web(false) .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. * @param sources the sources for the application (Spring configuration) * @return the new sibling builder */ public SpringApplicationBuilder sibling(Object... 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(Object[] 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 */ public SpringApplicationBuilder contextClass( Class cls) { this.application.setApplicationContextClass(cls); return this; } /** * Add more sources to use in this application. * @param sources the sources to add * @return the current builder */ public SpringApplicationBuilder sources(Object... sources) { this.sources.addAll(new LinkedHashSet(Arrays.asList(sources))); 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 web or non-web environment (auto detected based on * classpath if not set). * @param webEnvironment the flag to set * @return the current builder */ public SpringApplicationBuilder web(boolean webEnvironment) { this.application.setWebEnvironment(webEnvironment); 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; } /** * Default properties for the environment in the form {@code key=value} or * {@code key:value}. * @param defaultProperties the properties to set. * @return the current builder */ 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 = property.substring(0, index > 0 ? index : property.length()); 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 ? candidateIndex : Math.min(index, candidateIndex)); } } return index; } /** * Default properties for the environment in the form {@code key=value} or * {@code key:value}. * @param defaultProperties the properties to set. * @return the current builder */ public SpringApplicationBuilder properties(Properties defaultProperties) { return properties(getMapFromProperties(defaultProperties)); } private Map getMapFromProperties(Properties properties) { HashMap 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. * @param defaults the default properties * @return the current builder * @see SpringApplicationBuilder#properties(String...) */ 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(this.additionalProfiles .toArray(new String[this.additionalProfiles.size()])); return this; } private SpringApplicationBuilder additionalProfiles( Collection additionalProfiles) { this.additionalProfiles = new LinkedHashSet(additionalProfiles); this.application.setAdditionalProfiles(this.additionalProfiles .toArray(new String[this.additionalProfiles.size()])); 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; } }