Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Spring Boot Application Startup Lifecycle

Tech 1

SpringApplication Initialization

When launching a Spring Boot application, the typical entry point is:

SpringApplication.run(MainApplication.class, args);

The run method is a static method that delegatees to another overloaded version:

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    return (new SpringApplication(primarySources)).run(args);
}

This method performs two key operations:

  1. Instantiating a SpringApplication object
  2. Invoking the run method on that instance

Constructor Breakdown

The SpringApplication constructor performs the following initialization steps:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    this.bootstrapRegistryInitializers = new ArrayList<>(
        getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

Step-by-step explanation:

  1. this.resourceLoader = resourceLoader: Sets the resource loader for loading configurations and class files. When null, Spring Boot's auto-configuration handles resource loading.

  2. this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)): Stores the source classes containing @SpringBootApplication or @Configuration annotations. These classes define the bean definitions for the Spring context.

  3. this.webApplicationType = WebApplicationType.deduceFromClasspath(): Determines the web application type by inspecting classpath dependencies—SERVLET, REACTIVE, or NONE.

  4. this.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class)): Loads all BootstrapRegistryInitializer implementations from META-INF/spring.factories. These initializers prepare the bootstrap registry for sharing objects before the Spring container exists.

  5. setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)): Loads ApplicationContextInitializer implementations to extend the context after creation but before refresh.

  6. setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)): Loads ApplicationListener implementations for event handling during the application lifecycle.

  7. this.mainApplicationClass = deduceMainApplicationClass(): Identifies the class containing the main method.

Note that the Spring container is not created during constructoin—creation happens during the run phase.

Practical Demonstration

Bean Definition Sources

The primarySources parameter represents the initial source of bean definitions, typically the main application class:

public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(DemoApplication.class);
        
        ConfigurableApplicationContext ctx = app.run(args);
        
        for (String beanName : ctx.getBeanDefinitionNames()) {
            System.out.println("Bean: " + beanName + " - Source: " + 
                ctx.getBeanFactory().getBeanDefinition(beanName).getResourceDescription());
        }
        ctx.close();
    }

    static class ServiceA {}
    static class ServiceB {}
    static class ServiceC {}

    @Bean
    public ServiceB serviceB() {
        return new ServiceB();
    }

    @Bean
    public TomcatServletWebServerFactory webServerFactory() {
        return new TomcatServletWebServerFactory();
    }
}

Running this produces output showing bean definitions with their sources. Internal Spring beans show null as their source, while user-defined beans show their originating class.

Additional sources can be added programmatically:

app.setSources(new HashSet<String>() {{ add("classpath:beans.xml"); }});

Web Application Type Detection

The WebApplicationType.deduceFromClasspath() method determines the application type:

static WebApplicationType deduceFromClasspath() {
    if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && 
        !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null) &&
        !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
        return WebApplicationType.REACTIVE;
    }

    for (String className : SERVLET_INDICATOR_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }

    return WebApplicationType.SERVLET;
}

Detection logic:

  1. If reactive classes exist without servlet classes → REACTIVE
  2. If neither javax.servlet.Servlet nor ConfigurableWebApplicationContext exist → NONE
  3. Otherwise → SERVLET

ApplicationContext Initializers

Initializers allow extending the context before refresh:

app.addInitializers(applicationContext -> {
    if (applicationContext instanceof GenericApplicationContext) {
        GenericApplicationContext gac = (GenericApplicationContext) applicationContext;
        gac.registerBean("customService", CustomService.class);
    }
});

Event Listeners

The ApplicationListener interface handles application lifecycle events:

app.addListeners(event -> System.out.println("Event: " + event.getClass()));

Key events include:

  • ApplicationStartingEvent
  • ApplicationEnvironmentPreparedEvent
  • ApplicationContextInitializedEvent
  • ApplicationPreparedEvent
  • ContextRefreshedEvent
  • ApplicationStartedEvent
  • ApplicationReadyEvent
  • ApplicationFailedEvent

Main Application Class Detection

Method method = SpringApplication.class.getDeclaredMethod("deduceMainApplicationClass");
method.setAccessible(true);
Class<?> mainClass = (Class<?>) method.invoke(app);

The run Method

Core Workflow

  1. Create SpringApplicationRunListeners for event publishing
  2. Publish ApplicationStartingEvent
  3. Prepare environment with command-line arguments
  4. Publish ApplicationEnvironmentPreparedEvent
  5. Execute EnvironmentPostProcessor plugins
  6. Bind spring.main.* properties to SpringApplication
  7. Print banner
  8. Create ApplicationContext
  9. Prepare context (call initializers)
  10. Publish ApplicationContextInitializedEvent
  11. Load bean definitions
  12. Publish ApplicationPreparedEvent
  13. Refresh context
  14. Publish ApplicationStartedEvent
  15. Execute runners
  16. Publish ApplicationReadyEvent

Event Publishing Implementation

Spring Boot uses EventPublishingRunListener to broadcast lifecycle events:

public class EventPublisherDemo {
    public static void main(String[] args) throws Exception {
        SpringApplication app = new SpringApplication();
        app.addListeners(event -> System.out.println("Event: " + event.getClass()));

        List<String> listenerClasses = SpringFactoriesLoader
            .loadFactoryNames(SpringApplicationRunListener.class, 
                EventPublisherDemo.class.getClassLoader());

        for (String className : listenerClasses) {
            Class<?> clazz = Class.forName(className);
            Constructor<?> ctor = clazz.getConstructor(SpringApplication.class, String[].class);
            SpringApplicationRunListener publisher = 
                (SpringApplicationRunListener) ctor.newInstance(app, args);

            DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
            
            publisher.starting(bootstrapContext);
            publisher.environmentPrepared(bootstrapContext, new StandardEnvironment());
            
            GenericApplicationContext context = new GenericApplicationContext();
            publisher.contextPrepared(context);
            publisher.contextLoaded(context);
            context.refresh();
            publisher.started(context);
            publisher.running(context);
            publisher.failed(context, new RuntimeException("Failed"));
        }
    }
}

ApplicationContext Creation

The context type depends on webApplicationType:

private static GenericApplicationContext createApplicationContext(WebApplicationType type) {
    return switch (type) {
        case SERVLET -> new AnnotationConfigServletWebServerApplicationContext();
        case REACTIVE -> new AnnotationConfigReactiveWebServerApplicationContext();
        case NONE -> new AnnotationConfigApplicationContext();
    };
}

Bean Definition Loading

Multiple strategies load bean definitions:

DefaultListableBeanFactory factory = context.getDefaultListableBeanFactory();

// Annotation-based
AnnotatedBeanDefinitionReader reader = new AnnotatedBeanDefinitionReader(factory);
reader.register(Config.class);

// XML-based
XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(factory);
xmlReader.loadBeanDefinitions(new ClassPathResource("beans.xml"));

// Classpath scanning
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(factory);
scanner.scan("com.example.beans");

context.refresh();

CommandLineRunner and ApplicationRunner

These interfaces execute code after the context refreshes:

@Bean
public CommandLineRunner commandLineRunner() {
    return args -> System.out.println("Args: " + Arrays.toString(args));
}

@Bean
public ApplicationRunner applicationRunner() {
    return args -> {
        System.out.println("Source Args: " + Arrays.toString(args.getSourceArgs()));
        System.out.println("Options: " + args.getOptionNames());
        System.out.println("Non-options: " + args.getNonOptionArgs());
    };
}

Environment Configuration

ApplicationEnvironment manages property sources with a priority order:

ApplicationEnvironment env = new ApplicationEnvironment();

// Command-line properties have highest priority
env.getPropertySources().addFirst(new SimpleCommandLinePropertySource(args));

// Custom properties added to end
env.getPropertySources().addLast(
    new ResourcePropertySource("custom", new ClassPathResource("config.properties")));

for (PropertySource<?> ps : env.getPropertySources()) {
    System.out.println(ps.getName());
}

EnvironmentPostProcessor

This interface modifies the environment before the application starts:

public class CustomEnvironmentPostProcessor implements EnvironmentPostProcessor {
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment env, SpringApplication app) {
        // Add custom property sources based on environment variables
    }
}

Configuration in META-INF/spring.factories:

org.springframework.boot.env.EnvironmentPostProcessor=com.example.CustomEnvironmentPostProcessor

Property Binding

Use Binder to bind configuration properties to objects:

SpringApplication app = new SpringApplication();
ApplicationEnvironment env = new ApplicationEnvironment();
env.getPropertySources().addLast(
    new ResourcePropertySource("config", new ClassPathResource("app.properties")));

// Bind properties with prefix
Binder.get(env).bind("spring.main", Bindable.ofInstance(app));

The Binder clas handles conversion from property sources to typed objects, supporting nested properties and collection types.

Tags: Spring Boot

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.