NOTE

Spring Boot Startup Principle

1. Start Spring Boot. 2. Process of creating SpringApplication: determine web environment, load ApplicationContextInitializer and ApplicationListener instances, save the main configuration class, and run the application.

JavaCreated Updated 1 min readhistorical

This is a historical learning note and may contain outdated or incomplete understanding.

1. Start Spring Boot

We start directly from this line of code that starts Spring Boot.

SpringApplication.run(Application.class, args)

Ignoring simple nested calls, the code that is eventually invoked is as follows:

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
    //(Application.class, args)
    return new SpringApplication(sources).run(args);
}

First, the main configuration class Application.class is used to create a SpringApplication, and then the startup arguments args are passed in to start it.

2. Process of Creating SpringApplication

First, let’s see how SpringApplication is created.

public SpringApplication(Object... sources) {
    // Initialize when it is created
    initialize(sources);
}

private void initialize(Object[] sources) {
    // Save configuration classes
    if (sources != null && sources.length > 0) {
        this.sources.addAll(Arrays.asList(sources));
    }
    // Determine whether this is a web environment and save the result
    this.webEnvironment = deduceWebEnvironment();
    // Find all ApplicationContextInitializer classes under META-INF/spring.factories on the classpath and save them
    setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));
    // Find all ApplicationListener classes under META-INF/spring.factories on the classpath and save them
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // Determine the main configuration class (the one with the main method) and save it
    this.mainApplicationClass = deduceMainApplicationClass();
}

Initialization is mainly divided into five steps.

2.1. Determine Whether It Is a Web Environment

private boolean deduceWebEnvironment() {
    //private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
    //        "org.springframework.web.context.ConfigurableWebApplicationContext" }
    // If any of the above classes can be loaded, it is a web environment
    for (String className : WEB_ENVIRONMENT_CLASSES) {
        // Return false if loading fails
        if (!ClassUtils.isPresent(className, null)) {
            return false;
        }
    }
    return true;
}

2.2. Load All ApplicationContextInitializer Instances

2.2.1. First Find All ApplicationContextInitializer Classes, in Three Steps

  • Find the names of all ApplicationContextInitializer classes.
  • Instantiate them.
  • Sort them.
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    // Find the names of all ApplicationContextInitializer classes
    Set<String> names = new LinkedHashSet<String>(
            SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // Instantiate them
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
            classLoader, args, names);
    // Sort them
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}
2.2.1.1. So How Are the Names Found?
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    try {
        //public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
        // Use the current thread's class loader to load all META-INF/spring.factories files under the resources directories on the classpath
        Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        List<String> result = new ArrayList<String>();
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            // Parse each file into a Properties instance
            Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
            // Find the value corresponding to the property named org.springframework.context.ApplicationContextInitializer
            String propertyValue = properties.getProperty(factoryClassName);
            // Split it, save it into a list, and return it
            for (String factoryName : StringUtils.commaDelimitedListToStringArray(propertyValue)) {
                result.add(factoryName.trim());
            }
        }
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}
2.2.1.2. Creating the Instances Is Actually Just Calling Constructors Through Reflection
private <T> List<T> createSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
        Set<String> names) {
    List<T> instances = new ArrayList<T>(names.size());
    for (String name : names) {
        try {
            // Create a Class instance from the class name and class loader
            Class<?> instanceClass = ClassUtils.forName(name, classLoader);
            Assert.isAssignable(type, instanceClass);
            // Get the class constructor
            Constructor<?> constructor = instanceClass
                    .getDeclaredConstructor(parameterTypes);
            // Instantiate it using the constructor and arguments
            T instance = (T) BeanUtils.instantiateClass(constructor, args);
            instances.add(instance);
        }
        catch (Throwable ex) {
            throw new IllegalArgumentException(
                    "Cannot instantiate " + type + " : " + name, ex);
        }
    }
    return instances;
}

Then, in step 2.2, save them into initializers.

public void setInitializers(
        Collection<? extends ApplicationContextInitializer<?>> initializers) {
    this.initializers = new ArrayList<ApplicationContextInitializer<?>>();
    this.initializers.addAll(initializers);
}

2.3. Load All ApplicationListener Instances

The logic is the same as in section 2.

2.4. Save the Main Configuration Class

private Class<?> deduceMainApplicationClass() {
    try {
        // Traverse the method names in the call stack. The one with main is the main configuration class
        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;
}

2.5. Run the Spring Boot Application

public ConfigurableApplicationContext run(String... args) {
    // Create and start a timer
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    FailureAnalyzers analyzers = null;
    configureHeadlessProperty();
    // Load all META-INF/spring.factories files under resources directories on the classpath
    // Create beans named org.springframework.boot.SpringApplicationRunListener and save them into listeners
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // Call the starting method of all SpringApplicationRunListeners
    // This actually broadcasts an event through the broadcaster and finally calls back listener.onApplicationEvent
    listeners.starting();
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
        // Create the environment bean
        // For web, create StandardServletEnvironment; otherwise create StandardEnvironment
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        // Print the banner
        // If it is disabled, do not print it
        // If log mode is configured, use logger to print it
        // The default is console mode, using System.out to print to the console
            // Load image: banner.image.location property; if absent, use banner.gif and similar files under resources
            // Load text: banner.location property
            // If none of the above exists, use the default SpringBootBanner
        // Call the banner's printBanner method
            // Print the string
            // Print version information
        Banner printedBanner = printBanner(environment);
        // Create application context
        // For web, create AnnotationConfigEmbeddedWebApplicationContext
        // Otherwise create AnnotationConfigApplicationContext
        context = createApplicationContext();
        // Create FailureAnalyzers
        // Load all META-INF/spring.factories files under resources directories on the classpath
        // Create all beans named org.springframework.boot.diagnostics.FailureAnalyzer and save them into analyzers
        // If one is of BeanFactoryAware type, inject BeanFactory
        analyzers = new FailureAnalyzers(context);
        // Prepare the container
        // Call initialize on all ApplicationContextInitializers
        // Call contextPrepared on all SpringApplicationRunListeners
        // Print startup information
        // Print profile information
        // Register the BeanDefinition of the main configuration class
        // Call contextLoaded on all SpringApplicationRunListeners
        prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);
        // Refresh the container: refresh
        refreshContext(context);
        // After the container refresh is completed:
        // Call the run methods of ApplicationRunner and CommandRunnner
        // Call the finished methods of SpringApplicationRunListeners
        afterRefresh(context, applicationArguments);
        listeners.finished(context, null);
        // Stop the timer
        stopWatch.stop();
        // Print startup duration
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }
        return context;
    }
    catch (Throwable ex) {
        handleRunFailure(context, listeners, analyzers, ex);
        throw new IllegalStateException(ex);
    }
}

It can be seen that Spring’s refresh method is called. See 1. Creating the Spring Container.

Discussion

Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub