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.
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
ApplicationContextInitializerclasses. - 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