org.springframework.boot.SpringApplication.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.boot.SpringApplication.java

Source

/*
 * Copyright 2012-2015 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;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Constructor;
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.env.CommandLinePropertySource;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.SimpleCommandLinePropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StopWatch;
import org.springframework.util.StringUtils;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.StandardServletEnvironment;

/**
 * Classes that can be used to bootstrap and launch a Spring application from a Java main
 * method. By default class will perform the following steps to bootstrap your
 * application:
 *
 * <ul>
 * <li>Create an appropriate {@link ApplicationContext} instance (depending on your
 * classpath)</li>
 * <li>Register a {@link CommandLinePropertySource} to expose command line arguments as
 * Spring properties</li>
 * <li>Refresh the application context, loading all singleton beans</li>
 * <li>Trigger any {@link CommandLineRunner} beans</li>
 * </ul>
 *
 * In most circumstances the static {@link #run(Object, String[])} method can be called
 * directly from your {@literal main} method to bootstrap your application:
 *
 * <pre class="code">
 * &#064;Configuration
 * &#064;EnableAutoConfiguration
 * public class MyApplication  {
 *
 * // ... Bean definitions
 *
 * public static void main(String[] args) throws Exception {
 *   SpringApplication.run(MyApplication.class, args);
 * }
 * </pre>
 *
 * <p>
 * For more advanced configuration a {@link SpringApplication} instance can be created and
 * customized before being run:
 *
 * <pre class="code">
 * public static void main(String[] args) throws Exception {
 *   SpringApplication app = new SpringApplication(MyApplication.class);
 *   // ... customize app settings here
 *   app.run(args)
 * }
 * </pre>
 *
 * {@link SpringApplication}s can read beans from a variety of different sources. It is
 * generally recommended that a single {@code @Configuration} class is used to bootstrap
 * your application, however, any of the following sources can also be used:
 *
 * <ul>
 * <li>{@link Class} - A Java class to be loaded by {@link AnnotatedBeanDefinitionReader}
 * </li>
 * <li>{@link Resource} - An XML resource to be loaded by {@link XmlBeanDefinitionReader},
 * or a groovy script to be loaded by {@link GroovyBeanDefinitionReader}</li>
 * <li>{@link Package} - A Java package to be scanned by
 * {@link ClassPathBeanDefinitionScanner}</li>
 * <li>{@link CharSequence} - A class name, resource handle or package name to loaded as
 * appropriate. If the {@link CharSequence} cannot be resolved to class and does not
 * resolve to a {@link Resource} that exists it will be considered a {@link Package}.</li>
 * </ul>
 *
 * @author Phillip Webb
 * @author Dave Syer
 * @author Andy Wilkinson
 * @author Christian Dupuis
 * @author Stephane Nicoll
 * @author Jeremy Rickard
 * @see #run(Object, String[])
 * @see #run(Object[], String[])
 * @see #SpringApplication(Object...)
 */
public class SpringApplication {

    /**
     * The class name of application context that will be used by default for non-web
     * environments.
     */
    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.
     */
    public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."
            + "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";

    private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
            "org.springframework.web.context.ConfigurableWebApplicationContext" };

    private static final String CONFIGURABLE_WEB_ENVIRONMENT_CLASS = "org.springframework.web.context.ConfigurableWebEnvironment";

    private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";

    private static final Banner DEFAULT_BANNER = new SpringBootBanner();

    private static final Set<String> SERVLET_ENVIRONMENT_SOURCE_NAMES;

    static {
        Set<String> names = new HashSet<String>();
        names.add(StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME);
        names.add(StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME);
        names.add(StandardServletEnvironment.JNDI_PROPERTY_SOURCE_NAME);
        SERVLET_ENVIRONMENT_SOURCE_NAMES = Collections.unmodifiableSet(names);
    }

    private final Log log = LogFactory.getLog(getClass());

    private final Set<Object> sources = new LinkedHashSet<Object>();

    private Class<?> mainApplicationClass;

    private Banner.Mode bannerMode = Banner.Mode.CONSOLE;

    private boolean logStartupInfo = true;

    private boolean addCommandLineProperties = true;

    private Banner banner;

    private ResourceLoader resourceLoader;

    private BeanNameGenerator beanNameGenerator;

    private ConfigurableEnvironment environment;

    private Class<? extends ConfigurableApplicationContext> applicationContextClass;

    private boolean webEnvironment;

    private boolean headless = true;

    private boolean registerShutdownHook = true;

    private List<ApplicationContextInitializer<?>> initializers;

    private List<ApplicationListener<?>> listeners;

    private Map<String, Object> defaultProperties;

    private Set<String> additionalProfiles = new HashSet<String>();

    /**
     * Create a new {@link SpringApplication} instance. The application context will load
     * beans from the specified sources (see {@link SpringApplication class-level}
     * documentation for details. The instance can be customized before calling
     * {@link #run(String...)}.
     * @param sources the bean sources
     * @see #run(Object, String[])
     * @see #SpringApplication(ResourceLoader, Object...)
     */
    public SpringApplication(Object... sources) {
        initialize(sources);
    }

    /**
     * Create a new {@link SpringApplication} instance. The application context will load
     * beans from the specified sources (see {@link SpringApplication class-level}
     * documentation for details. The instance can be customized before calling
     * {@link #run(String...)}.
     * @param resourceLoader the resource loader to use
     * @param sources the bean sources
     * @see #run(Object, String[])
     * @see #SpringApplication(ResourceLoader, Object...)
     */
    public SpringApplication(ResourceLoader resourceLoader, Object... sources) {
        this.resourceLoader = resourceLoader;
        initialize(sources);
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    private void initialize(Object[] sources) {
        if (sources != null && sources.length > 0) {
            this.sources.addAll(Arrays.asList(sources));
        }
        this.webEnvironment = deduceWebEnvironment();
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
    }

    private boolean deduceWebEnvironment() {
        for (String className : WEB_ENVIRONMENT_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return false;
            }
        }
        return true;
    }

    private Class<?> deduceMainApplicationClass() {
        try {
            StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
            for (StackTraceElement stackTraceElement : stackTrace) {
                if ("main".equals(stackTraceElement.getMethodName())) {
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        } catch (ClassNotFoundException ex) {
            // Swallow and continue
        }
        return null;
    }

    /**
     * Run the Spring application, creating and refreshing a new
     * {@link ApplicationContext}.
     * @param args the application arguments (usually passed from a Java main method)
     * @return a running {@link ApplicationContext}
     */
    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.started();
        try {
            context = doRun(listeners, args);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            return context;
        } catch (Throwable ex) {
            try {
                listeners.finished(context, ex);
                this.log.error("Application startup failed", ex);
            } finally {
                if (context != null) {
                    context.close();
                }
            }
            ReflectionUtils.rethrowRuntimeException(ex);
            return context;
        }
    }

    private ConfigurableApplicationContext doRun(SpringApplicationRunListeners listeners, String... args) {
        ConfigurableApplicationContext context;
        // Create and configure the environment
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        configureEnvironment(environment, args);
        listeners.environmentPrepared(environment);
        if (isWebEnvironment(environment) && !this.webEnvironment) {
            environment = convertToStandardEnvironment(environment);
        }

        if (this.bannerMode != Banner.Mode.OFF) {
            printBanner(environment);
        }

        // Create, load, refresh and run the ApplicationContext
        context = createApplicationContext();
        context.setEnvironment(environment);
        postProcessApplicationContext(context);
        applyInitializers(context);
        listeners.contextPrepared(context);
        if (this.logStartupInfo) {
            logStartupInfo(context.getParent() == null);
            logStartupProfileInfo(context);
        }

        // Add boot specific singleton beans
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        context.getBeanFactory().registerSingleton("springApplicationArguments", applicationArguments);

        // Load the sources
        Set<Object> sources = getSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        load(context, sources.toArray(new Object[sources.size()]));
        listeners.contextLoaded(context);

        // Refresh the context
        refresh(context);
        if (this.registerShutdownHook) {
            try {
                context.registerShutdownHook();
            } catch (AccessControlException ex) {
                // Not allowed in some environments.
            }
        }
        afterRefresh(context, applicationArguments);
        listeners.finished(context, null);
        return context;
    }

    private void configureHeadlessProperty() {
        System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
                System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
    }

    private SpringApplicationRunListeners getRunListeners(String[] args) {
        Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
        return new SpringApplicationRunListeners(this.log,
                getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
    }

    private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) {
        return getSpringFactoriesInstances(type, new Class<?>[] {});
    }

    @SuppressWarnings("unchecked")
    private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
            Object... args) {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

        // Use names and ensure unique to protect against duplicates
        Set<String> names = new LinkedHashSet<String>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        List<T> instances = new ArrayList<T>(names.size());

        // Create instances from the names
        for (String name : names) {
            try {
                Class<?> instanceClass = ClassUtils.forName(name, classLoader);
                Assert.isAssignable(type, instanceClass);
                Constructor<?> constructor = instanceClass.getConstructor(parameterTypes);
                T instance = (T) constructor.newInstance(args);
                instances.add(instance);
            } catch (Throwable ex) {
                throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
            }
        }

        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }

    private ConfigurableEnvironment getOrCreateEnvironment() {
        if (this.environment != null) {
            return this.environment;
        }
        if (this.webEnvironment) {
            return new StandardServletEnvironment();
        }
        return new StandardEnvironment();

    }

    /**
     * Template method delegating to
     * {@link #configurePropertySources(ConfigurableEnvironment, String[])} and
     * {@link #configureProfiles(ConfigurableEnvironment, String[])} in that order.
     * Override this method for complete control over Environment customization, or one of
     * the above for fine-grained control over property sources or profiles, respectively.
     * @param environment this application's environment
     * @param args arguments passed to the {@code run} method
     * @see #configureProfiles(ConfigurableEnvironment, String[])
     * @see #configurePropertySources(ConfigurableEnvironment, String[])
     */
    protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
        configurePropertySources(environment, args);
        configureProfiles(environment, args);
    }

    private boolean isWebEnvironment(ConfigurableEnvironment environment) {
        try {
            Class<?> webEnvironmentClass = ClassUtils.forName(CONFIGURABLE_WEB_ENVIRONMENT_CLASS, getClassLoader());
            return (webEnvironmentClass.isInstance(environment));
        } catch (Throwable ex) {
            return false;
        }
    }

    private ConfigurableEnvironment convertToStandardEnvironment(ConfigurableEnvironment environment) {
        StandardEnvironment result = new StandardEnvironment();
        removeAllPropertySources(result.getPropertySources());
        result.setActiveProfiles(environment.getActiveProfiles());
        for (PropertySource<?> propertySource : environment.getPropertySources()) {
            if (!SERVLET_ENVIRONMENT_SOURCE_NAMES.contains(propertySource.getName())) {
                result.getPropertySources().addLast(propertySource);
            }
        }
        return result;
    }

    private void removeAllPropertySources(MutablePropertySources propertySources) {
        Set<String> names = new HashSet<String>();
        for (PropertySource<?> propertySource : propertySources) {
            names.add(propertySource.getName());
        }
        for (String name : names) {
            propertySources.remove(name);
        }
    }

    /**
     * Add, remove or re-order any {@link PropertySource}s in this application's
     * environment.
     * @param environment this application's environment
     * @param args arguments passed to the {@code run} method
     * @see #configureEnvironment(ConfigurableEnvironment, String[])
     */
    protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
        MutablePropertySources sources = environment.getPropertySources();
        if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
            sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
        }
        if (this.addCommandLineProperties && args.length > 0) {
            String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
            if (sources.contains(name)) {
                PropertySource<?> source = sources.get(name);
                CompositePropertySource composite = new CompositePropertySource(name);
                composite
                        .addPropertySource(new SimpleCommandLinePropertySource(name + "-" + args.hashCode(), args));
                composite.addPropertySource(source);
                sources.replace(name, composite);
            } else {
                sources.addFirst(new SimpleCommandLinePropertySource(args));
            }
        }
    }

    /**
     * Configure which profiles are active (or active by default) for this application
     * environment. Additional profiles may be activated during configuration file
     * processing via the {@code spring.profiles.active} property.
     * @param environment this application's environment
     * @param args arguments passed to the {@code run} method
     * @see #configureEnvironment(ConfigurableEnvironment, String[])
     * @see org.springframework.boot.context.config.ConfigFileEnvironmentPostProcessor
     */
    protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
        environment.getActiveProfiles(); // ensure they are initialized
        // But these ones should go first (last wins in a property key clash)
        Set<String> profiles = new LinkedHashSet<String>(this.additionalProfiles);
        profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
        environment.setActiveProfiles(profiles.toArray(new String[profiles.size()]));
    }

    /**
     * Print a custom banner message to the console, optionally extracting its location or
     * content from the Environment (banner.location and banner.charset). The defaults are
     * banner.location=classpath:banner.txt, banner.charset=UTF-8. If the banner file does
     * not exist or cannot be printed, a simple default is created.
     * @param environment the environment
     * @see #setBannerMode
     */
    protected void printBanner(Environment environment) {

        Banner selectedBanner = selectBanner(environment);

        if (this.bannerMode == Banner.Mode.LOG) {
            try {
                this.log.info(createStringFromBanner(selectedBanner, environment));
            } catch (UnsupportedEncodingException ex) {
                this.log.warn("Failed to create String for banner", ex);
            }
        } else {
            selectedBanner.printBanner(environment, this.mainApplicationClass, System.out);
        }
    }

    private Banner selectBanner(Environment environment) {
        String location = environment.getProperty("banner.location", "banner.txt");
        ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader
                : new DefaultResourceLoader(getClassLoader());
        Resource resource = resourceLoader.getResource(location);

        if (resource.exists()) {
            return new ResourceBanner(resource);
        }
        if (this.banner != null) {
            return this.banner;
        }
        return DEFAULT_BANNER;
    }

    private String createStringFromBanner(Banner banner, Environment environment)
            throws UnsupportedEncodingException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        banner.printBanner(environment, this.mainApplicationClass, new PrintStream(baos));
        String charset = environment.getProperty("banner.charset", "UTF-8");
        return baos.toString(charset);
    }

    /**
     * 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.
     * @return the application context (not yet refreshed)
     * @see #setApplicationContextClass(Class)
     */
    protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                contextClass = Class
                        .forName(this.webEnvironment ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
            } catch (ClassNotFoundException ex) {
                throw new IllegalStateException("Unable create a default ApplicationContext, "
                        + "please specify an ApplicationContextClass", ex);
            }
        }
        return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
    }

    /**
     * Apply any relevant post processing the {@link ApplicationContext}. Subclasses can
     * apply additional processing as required.
     * @param context the application context
     */
    protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
        if (this.webEnvironment) {
            if (context instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext configurableContext = (ConfigurableWebApplicationContext) context;
                if (this.beanNameGenerator != null) {
                    configurableContext.getBeanFactory().registerSingleton(
                            AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, this.beanNameGenerator);
                }
            }
        }

        if (this.resourceLoader != null) {
            if (context instanceof GenericApplicationContext) {
                ((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);
            }
            if (context instanceof DefaultResourceLoader) {
                ((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader());
            }
        }
    }

    /**
     * Apply any {@link ApplicationContextInitializer}s to the context before it is
     * refreshed.
     * @param context the configured ApplicationContext (not refreshed yet)
     * @see ConfigurableApplicationContext#refresh()
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    protected void applyInitializers(ConfigurableApplicationContext context) {
        for (ApplicationContextInitializer initializer : getInitializers()) {
            Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
                    ApplicationContextInitializer.class);
            Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
            initializer.initialize(context);
        }
    }

    /**
     * Called to log startup information, subclasses may override to add additional
     * logging.
     * @param isRoot true if this application is the root of a context hierarchy
     */
    protected void logStartupInfo(boolean isRoot) {
        if (isRoot) {
            new StartupInfoLogger(this.mainApplicationClass).logStarting(getApplicationLog());
        }
    }

    /**
     * Called to log active profile information.
     * @param context the application context
     */
    protected void logStartupProfileInfo(ConfigurableApplicationContext context) {
        Log log = getApplicationLog();
        if (log.isInfoEnabled()) {
            String[] activeProfiles = context.getEnvironment().getActiveProfiles();
            if (ObjectUtils.isEmpty(activeProfiles)) {
                log.info("No profiles are active");
            } else {
                log.info("The following profiles are active: "
                        + StringUtils.arrayToCommaDelimitedString(activeProfiles));
            }
        }
    }

    /**
     * Returns the {@link Log} for the application. By default will be deduced.
     * @return the application log
     */
    protected Log getApplicationLog() {
        if (this.mainApplicationClass == null) {
            return this.log;
        }
        return LogFactory.getLog(this.mainApplicationClass);
    }

    /**
     * Load beans into the application context.
     * @param context the context to load beans into
     * @param sources the sources to load
     */
    protected void load(ApplicationContext context, Object[] sources) {
        if (this.log.isDebugEnabled()) {
            this.log.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
        }
        BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
        if (this.beanNameGenerator != null) {
            loader.setBeanNameGenerator(this.beanNameGenerator);
        }
        if (this.resourceLoader != null) {
            loader.setResourceLoader(this.resourceLoader);
        }
        if (this.environment != null) {
            loader.setEnvironment(this.environment);
        }
        loader.load();
    }

    /**
     * The ResourceLoader that will be used in the ApplicationContext.
     * @return the resourceLoader the resource loader that will be used in the
     * ApplicationContext (or null if the default)
     */
    public ResourceLoader getResourceLoader() {
        return this.resourceLoader;
    }

    /**
     * Either the ClassLoader that will be used in the ApplicationContext (if
     * {@link #setResourceLoader(ResourceLoader) resourceLoader} is set, or the context
     * class loader (if not null), or the loader of the Spring {@link ClassUtils} class.
     * @return a ClassLoader (never null)
     */
    public ClassLoader getClassLoader() {
        if (this.resourceLoader != null) {
            return this.resourceLoader.getClassLoader();
        }
        return ClassUtils.getDefaultClassLoader();
    }

    /**
     * Get the bean definition registry.
     * @param context the application context
     * @return the BeanDefinitionRegistry if it can be determined
     */
    private BeanDefinitionRegistry getBeanDefinitionRegistry(ApplicationContext context) {
        if (context instanceof BeanDefinitionRegistry) {
            return (BeanDefinitionRegistry) context;
        }
        if (context instanceof AbstractApplicationContext) {
            return (BeanDefinitionRegistry) ((AbstractApplicationContext) context).getBeanFactory();
        }
        throw new IllegalStateException("Could not locate BeanDefinitionRegistry");
    }

    /**
     * Factory method used to create the {@link BeanDefinitionLoader}.
     * @param registry the bean definition registry
     * @param sources the sources to load
     * @return the {@link BeanDefinitionLoader} that will be used to load beans
     */
    protected BeanDefinitionLoader createBeanDefinitionLoader(BeanDefinitionRegistry registry, Object[] sources) {
        return new BeanDefinitionLoader(registry, sources);
    }

    /**
     * Refresh the underlying {@link ApplicationContext}.
     * @param applicationContext the application context to refresh
     */
    protected void refresh(ApplicationContext applicationContext) {
        Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
        ((AbstractApplicationContext) applicationContext).refresh();
    }

    /**
     * Called after the context has been refreshed.
     * @param context the application context
     * @param args the application argumments
     */
    protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
        afterRefresh(context, args.getSourceArgs());
        callRunners(context, args);
    }

    private void callRunners(ApplicationContext context, ApplicationArguments args) {
        List<Object> runners = new ArrayList<Object>();
        runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
        runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
        AnnotationAwareOrderComparator.sort(runners);
        for (Object runner : new LinkedHashSet<Object>(runners)) {
            if (runner instanceof ApplicationRunner) {
                callRunner((ApplicationRunner) runner, args);
            }
            if (runner instanceof CommandLineRunner) {
                callRunner((CommandLineRunner) runner, args);
            }
        }
    }

    private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
        try {
            (runner).run(args);
        } catch (Exception ex) {
            throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
        }
    }

    private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
        try {
            (runner).run(args.getSourceArgs());
        } catch (Exception ex) {
            throw new IllegalStateException("Failed to execute CommandLineRunner", ex);
        }
    }

    /**
     * Called after the context has been refreshed.
     * @param context the application context
     * @param args the application argumments
     * @deprecated in 1.3 in favor of
     * {@link #afterRefresh(ConfigurableApplicationContext, ApplicationArguments)}
     */
    @Deprecated
    protected void afterRefresh(ConfigurableApplicationContext context, String[] args) {
    }

    /**
     * Set a specific main application class that will be used as a log source and to
     * obtain version information. By default the main application class will be deduced.
     * Can be set to {@code null} if there is no explicit application class.
     * @param mainApplicationClass the mainApplicationClass to set or {@code null}
     */
    public void setMainApplicationClass(Class<?> mainApplicationClass) {
        this.mainApplicationClass = mainApplicationClass;
    }

    /**
     * Sets if this application is running within a web environment. If not specified will
     * attempt to deduce the environment based on the classpath.
     * @param webEnvironment if the application is running in a web environment
     */
    public void setWebEnvironment(boolean webEnvironment) {
        this.webEnvironment = webEnvironment;
    }

    /**
     * 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
     */
    public void setHeadless(boolean headless) {
        this.headless = headless;
    }

    /**
     * Sets if the created {@link ApplicationContext} should have a shutdown hook
     * registered. Defaults to {@code true} to ensure that JVM shutdowns are handled
     * gracefully.
     * @param registerShutdownHook if the shutdown hook should be registered
     */
    public void setRegisterShutdownHook(boolean registerShutdownHook) {
        this.registerShutdownHook = registerShutdownHook;
    }

    /**
     * Sets the {@link Banner} instance which will be used to print the banner when no
     * static banner file is provided.
     * @param banner The Banner instance to use
     */
    public void setBanner(Banner banner) {
        this.banner = banner;
    }

    /**
     * Sets if the Spring banner should be displayed when the application runs. Defaults
     * to {@code true}.
     * @param showBanner if the banner should be shown
     * @deprecated since 1.3.0 in favor of {@link #setBannerMode}
     */
    @Deprecated
    public void setShowBanner(boolean showBanner) {
        setBannerMode(showBanner ? Banner.Mode.CONSOLE : Banner.Mode.OFF);
    }

    /**
     * Sets the mode used to display the banner when the application runs. Defaults to
     * {@code Banner.Mode.CONSOLE}.
     * @param bannerMode the mode used to display the banner
     */
    public void setBannerMode(Banner.Mode bannerMode) {
        this.bannerMode = bannerMode;
    }

    /**
     * Sets if the application information should be logged when the application starts.
     * Defaults to {@code true}.
     * @param logStartupInfo if startup info should be logged.
     */
    public void setLogStartupInfo(boolean logStartupInfo) {
        this.logStartupInfo = logStartupInfo;
    }

    /**
     * Sets if a {@link CommandLinePropertySource} should be added to the application
     * context in order to expose arguments. Defaults to {@code true}.
     * @param addCommandLineProperties if command line arguments should be exposed
     */
    public void setAddCommandLineProperties(boolean addCommandLineProperties) {
        this.addCommandLineProperties = addCommandLineProperties;
    }

    /**
     * Set default environment properties which will be used in addition to those in the
     * existing {@link Environment}.
     * @param defaultProperties the additional properties to set
     */
    public void setDefaultProperties(Map<String, Object> defaultProperties) {
        this.defaultProperties = defaultProperties;
    }

    /**
     * Convenient alternative to {@link #setDefaultProperties(Map)}.
     * @param defaultProperties some {@link Properties}
     */
    public void setDefaultProperties(Properties defaultProperties) {
        this.defaultProperties = new HashMap<String, Object>();
        for (Object key : Collections.list(defaultProperties.propertyNames())) {
            this.defaultProperties.put((String) key, defaultProperties.get(key));
        }
    }

    /**
     * Set additional profile values to use (on top of those set in system or command line
     * properties).
     * @param profiles the additional profiles to set
     */
    public void setAdditionalProfiles(String... profiles) {
        this.additionalProfiles = new LinkedHashSet<String>(Arrays.asList(profiles));
    }

    /**
     * Sets the bean name generator that should be used when generating bean names.
     * @param beanNameGenerator the bean name generator
     */
    public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) {
        this.beanNameGenerator = beanNameGenerator;
    }

    /**
     * Sets the underlying environment that should be used with the created application
     * context.
     * @param environment the environment
     */
    public void setEnvironment(ConfigurableEnvironment environment) {
        this.environment = environment;
    }

    /**
     * Returns a mutable set of the sources that will be added to an ApplicationContext
     * when {@link #run(String...)} is called.
     * @return the sources the application sources.
     * @see #SpringApplication(Object...)
     */
    public Set<Object> getSources() {
        return this.sources;
    }

    /**
     * The sources that will be used to create an ApplicationContext. A valid source is
     * one of: a class, class name, package, package name, or an XML resource location.
     * Can also be set using constructors and static convenience methods (e.g.
     * {@link #run(Object[], String[])}).
     * <p>
     * NOTE: sources defined here will be used in addition to any sources specified on
     * construction.
     * @param sources the sources to set
     * @see #SpringApplication(Object...)
     */
    public void setSources(Set<Object> sources) {
        Assert.notNull(sources, "Sources must not be null");
        this.sources.addAll(sources);
    }

    /**
     * Sets the {@link ResourceLoader} that should be used when loading resources.
     * @param resourceLoader the resource loader
     */
    public void setResourceLoader(ResourceLoader resourceLoader) {
        Assert.notNull(resourceLoader, "ResourceLoader must not be null");
        this.resourceLoader = resourceLoader;
    }

    /**
     * Sets the type of Spring {@link ApplicationContext} that will be created. If not
     * specified defaults to {@link #DEFAULT_WEB_CONTEXT_CLASS} for web based applications
     * or {@link AnnotationConfigApplicationContext} for non web based applications.
     * @param applicationContextClass the context class to set
     */
    public void setApplicationContextClass(
            Class<? extends ConfigurableApplicationContext> applicationContextClass) {
        this.applicationContextClass = applicationContextClass;
        if (!isWebApplicationContext(applicationContextClass)) {
            this.webEnvironment = false;
        }
    }

    private boolean isWebApplicationContext(Class<?> applicationContextClass) {
        try {
            return WebApplicationContext.class.isAssignableFrom(applicationContextClass);
        } catch (NoClassDefFoundError ex) {
            return false;
        }
    }

    /**
     * Sets the {@link ApplicationContextInitializer} that will be applied to the Spring
     * {@link ApplicationContext}.
     * @param initializers the initializers to set
     */
    public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
        this.initializers = new ArrayList<ApplicationContextInitializer<?>>();
        this.initializers.addAll(initializers);
    }

    /**
     * Add {@link ApplicationContextInitializer}s to be applied to the Spring
     * {@link ApplicationContext}.
     * @param initializers the initializers to add
     */
    public void addInitializers(ApplicationContextInitializer<?>... initializers) {
        this.initializers.addAll(Arrays.asList(initializers));
    }

    /**
     * Returns read-only ordered Set of the {@link ApplicationContextInitializer}s that
     * will be applied to the Spring {@link ApplicationContext}.
     * @return the initializers
     */
    public Set<ApplicationContextInitializer<?>> getInitializers() {
        return asUnmodifiableOrderedSet(this.initializers);
    }

    /**
     * Sets the {@link ApplicationListener}s that will be applied to the SpringApplication
     * and registered with the {@link ApplicationContext}.
     * @param listeners the listeners to set
     */
    public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
        this.listeners = new ArrayList<ApplicationListener<?>>();
        this.listeners.addAll(listeners);
    }

    /**
     * Add {@link ApplicationListener}s to be applied to the SpringApplication and
     * registered with the {@link ApplicationContext}.
     * @param listeners the listeners to add
     */
    public void addListeners(ApplicationListener<?>... listeners) {
        this.listeners.addAll(Arrays.asList(listeners));
    }

    /**
     * Returns read-only ordered Set of the {@link ApplicationListener}s that will be
     * applied to the SpringApplication and registered with the {@link ApplicationContext}
     * .
     * @return the listeners
     */
    public Set<ApplicationListener<?>> getListeners() {
        return asUnmodifiableOrderedSet(this.listeners);
    }

    /**
     * Static helper that can be used to run a {@link SpringApplication} from the
     * specified source using default settings.
     * @param source the source to load
     * @param args the application arguments (usually passed from a Java main method)
     * @return the running {@link ApplicationContext}
     */
    public static ConfigurableApplicationContext run(Object source, String... args) {
        return run(new Object[] { source }, args);
    }

    /**
     * Static helper that can be used to run a {@link SpringApplication} from the
     * specified sources using default settings and user supplied arguments.
     * @param sources the sources to load
     * @param args the application arguments (usually passed from a Java main method)
     * @return the running {@link ApplicationContext}
     */
    public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
        return new SpringApplication(sources).run(args);
    }

    /**
     * A basic main that can be used to launch an application. This method is useful when
     * application sources are defined via a {@literal --spring.main.sources} command line
     * argument.
     * <p>
     * Most developers will want to define their own main method can call the
     * {@link #run(Object, String...) run} method instead.
     * @param args command line arguments
     * @throws Exception if the application cannot be started
     * @see SpringApplication#run(Object[], String[])
     * @see SpringApplication#run(Object, String...)
     */
    public static void main(String[] args) throws Exception {
        SpringApplication.run(new Object[0], args);
    }

    /**
     * Static helper that can be used to exit a {@link SpringApplication} and obtain a
     * code indicating success (0) or otherwise. Does not throw exceptions but should
     * print stack traces of any encountered. Applies the specified
     * {@link ExitCodeGenerator} in addition to any Spring beans that implement
     * {@link ExitCodeGenerator}. In the case of multiple exit codes the highest value
     * will be used (or if all values are negative, the lowest value will be used)
     * @param context the context to close if possible
     * @param exitCodeGenerators exist code generators
     * @return the outcome (0 if successful)
     */
    public static int exit(ApplicationContext context, ExitCodeGenerator... exitCodeGenerators) {
        int exitCode = 0;
        try {
            try {
                List<ExitCodeGenerator> generators = new ArrayList<ExitCodeGenerator>();
                generators.addAll(Arrays.asList(exitCodeGenerators));
                generators.addAll(context.getBeansOfType(ExitCodeGenerator.class).values());
                exitCode = getExitCode(generators);
            } finally {
                close(context);
            }

        } catch (Exception ex) {
            ex.printStackTrace();
            exitCode = (exitCode == 0 ? 1 : exitCode);
        }
        return exitCode;
    }

    private static int getExitCode(List<ExitCodeGenerator> exitCodeGenerators) {
        int exitCode = 0;
        for (ExitCodeGenerator exitCodeGenerator : exitCodeGenerators) {
            try {
                int value = exitCodeGenerator.getExitCode();
                if (value > 0 && value > exitCode || value < 0 && value < exitCode) {
                    exitCode = value;
                }
            } catch (Exception ex) {
                exitCode = (exitCode == 0 ? 1 : exitCode);
                ex.printStackTrace();
            }
        }
        return exitCode;
    }

    private static void close(ApplicationContext context) {
        if (context instanceof ConfigurableApplicationContext) {
            ConfigurableApplicationContext closable = (ConfigurableApplicationContext) context;
            closable.close();
        }
    }

    private static <E> Set<E> asUnmodifiableOrderedSet(Collection<E> elements) {
        List<E> list = new ArrayList<E>();
        list.addAll(elements);
        Collections.sort(list, AnnotationAwareOrderComparator.INSTANCE);
        return new LinkedHashSet<E>(list);
    }

}