We all know that to start a spring boot project, you only need to run the main method of the main class. What is the starting mechanism behind this? Let's trace the source code and analyze it specifically:
Our startup class is as follows:
@SpringBootApplication public class DemoSpringbootApplication { public static void main(String[] args) { ApplicationContext context = SpringApplication.run(DemoSpringbootApplication.class, args); } }
In the SpringApplication.run method, an overloaded method is called here:
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) { //Call the following overloaded method return run(new Class[]{primarySource}, args); } public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return (new SpringApplication(primarySources)).run(args); }
We see that the final launch is divided into two steps:
- Create SpringApplication object
- Call the run method
In this article, we mainly analyze the creation of spring application.
Create SpringApplication
When creating a SpringApplication object, its construction method will be called. We enter it, and only part of the core code is reserved here. The comments for each step are as follows:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { ...//Omit some code // Put the incoming DemoSpringbootApplication startup class into primarySources, so that the application knows what the primary startup class is this.primarySources = new LinkedHashSet(Arrays.asList(primarySources)); //Judge the current application environment, none, servlet and reactive this.webApplicationType = WebApplicationType.deduceFromClasspath(); //Set initializer this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class)); //Set up a listener this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)); //Determine the main configuration class this.mainApplicationClass = this.deduceMainApplicationClass(); }
Let's analyze the following steps:
-
Judge the current application environment
The WebApplicationType.deduceFromClasspath() method judges which environment the current spring boot application should use to start from the classpath. The code is as follows:private static final String[] SERVLET_INDICATOR_CLASSES = new String[]{"javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext"}; private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet"; private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler"; private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer"; private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext"; private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext"; static WebApplicationType deduceFromClasspath() { if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null) && !ClassUtils.isPresent("org.glassfish.jersey.servlet.ServletContainer", (ClassLoader)null)) { return REACTIVE;// If there are active related classes in the classpath, return to the reactive environment } else { String[] var0 = SERVLET_INDICATOR_CLASSES; int var1 = var0.length; for(int var2 = 0; var2 < var1; ++var2) { String className = var0[var2]; if (!ClassUtils.isPresent(className, (ClassLoader)null)) { return NONE;// If there is no servlet related class in the classpath, return to the NONE environment } } return SERVLET;// If none of the above is true, the SERVLET environment will be returned } }
-
Set initializer
The setInitializers method places a set of initializers of type ApplicationContextInitializer into SpringApplication. This group of applicationcontextinitializers is obtained through the getSpringFactoriesInstances method. We enter the method:private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = this.getClassLoader(); //Loads the fully qualified class name of all configured components of the specified type Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); //Create instances of these components List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }
The main function of loadFactoryNames method is to read the META-INF/spring.factories file under all jar packages in the classpath, and then find all implementation classes of the specified type (in our case, ApplicationContextInitializer).
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { //Get from cache first MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader); if (result != null) { return result; } else { try { //Read META-INF/spring.factories under all jar packages in the classpath Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories"); LinkedMultiValueMap result = new LinkedMultiValueMap(); while(urls.hasMoreElements()) { URL url = (URL)urls.nextElement(); UrlResource resource = new UrlResource(url); //Each spring.factories configuration file is read into Properties Properties properties = PropertiesLoaderUtils.loadProperties(resource); Iterator var6 = properties.entrySet().iterator(); while(var6.hasNext()) { Entry<?, ?> entry = (Entry)var6.next(); String factoryTypeName = ((String)entry.getKey()).trim(); //There may be multiple values for each configuration item String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue()); int var10 = var9.length; for(int var11 = 0; var11 < var10; ++var11) { String factoryImplementationName = var9[var11]; result.add(factoryTypeName, factoryImplementationName.trim()); } } } //Put it into the cache so that the next read can be obtained directly from the cache cache.put(classLoader, result); return result; } catch (IOException var13) { throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13); } } }
Let's debug. The project initializes the following ApplicationContextInitializer:
The main function of the createSpringFactoriesInstances method is to create instances of these components through reflection according to the classes found in the previous step. The code is as follows:private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) { List<T> instances = new ArrayList(names.size()); Iterator var7 = names.iterator(); while(var7.hasNext()) { String name = (String)var7.next(); try { Class<?> instanceClass = ClassUtils.forName(name, classLoader); Assert.isAssignable(type, instanceClass); Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes); //instantiation T instance = BeanUtils.instantiateClass(constructor, args); instances.add(instance); } catch (Throwable var12) { throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, var12); } } return instances; }
-
Set up a listener
The setListeners method will put a set of initializers of type ApplicationListener into spring application, and its obtaining method is the same as that of setInitializers. We will not repeat this again. Through debug, we can see that some applicationlisteners are initialized as follows: -
Determine the main configuration class
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; }
The source code is very simple. This method is to find our main configuration class. Here is our main method's class DemoSpringbootApplication.
At this point, the process analysis of creating spring application is finished, and we will continue to analyze the run method in the next chapter.