SpringBoot 2.1.7 Startup Process Source Analysis - Overview

Posted by jeremyphphaven on Mon, 23 Sep 2019 06:23:04 +0200

brief introduction

SpringBoot is widely used in today's development process. It simplifies the configuration of our development and makes it easy for us to complete a large number of complex development tasks. But most people just know it, but don't know why, so this article through the start-up process of SpringBoot to deeply divide how the SpringBoot underlying work.

startup code

For SpringBook startup classes, we're certainly familiar with them. The specific code is as follows:

    @SpringBootApplication
    public class DemoApplication {
    	public static void main(String[] args) {
    		SpringApplication.run(DemoApplication.class, args);
    	}
    }

Source code analysis

By launching the DemoApplication class (which we have always done), you can complete a series of operations such as Spring initialization, automatic assembly, and so on. This paper analyses the start-up process through two entry points: @SpringBootApplication and SpringApplication.run.

SpringApplication.run method

First, let's start with the run method to see how springboot's internal initialization works step by step.

(1) Entry method: After calling the static run method, through a series of calls we will eventually enter the following position in the Spring Application class:

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

As you can see, this method does two things, initializes the Spring Application class, and calls the internal public run method. Next let's look at what these two steps have accomplished.

(2) Spring Application initialization: The main code is as follows:

    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();       #1
        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));  #2
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)); #3
        this.mainApplicationClass = this.deduceMainApplicationClass(); #4
    }

Most of the initialization code above is initialization work, and the last four lines need our attention. Next, I will analyze what these four lines have done.

# 1: Its function is to determine the type of application by the resources of the classpath. By judging the code, it is not difficult to see that the judgment ideas are as follows:

     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;
        } 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;
                }
            }

            return SERVLET;
        }
    }

From the above code, it is not difficult to see that the judgment logic is as follows:

  1. Whether Dispatcher Handler exists in the class path and neither Dispatcher Servlet nor Servlet Container exists. Naturally, then, it belongs to the REACTIVE type of reference. If you are not satisfied, take the second step.
  2. If there are Servlet and Configurable Web Application Context in the classpath, then it is a Servlet application.
  3. Ordinary Java applications if they are not satisfied

#2: it mainly loads the registered ApplicationContextInitializer implementation class, and instantiates it and assigns it to initializers. The main code is as follows:
Loading Registered Classes

    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = this.getClassLoader();
        //Load the registered Application ContextInitializer implementation class
        Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); #1
        //Instantiate loaded classes
        List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); #2
        //Sort by priority
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }

Instantiate Registry Class: Instantiate according to the set of registry classes obtained in the previous step.

    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);
                T instance = BeanUtils.instantiateClass(constructor, args);
                instances.add(instance);
            } catch (Throwable var12) {
                throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, var12);
            }
        }

        return instances;
    }

# 2 # 3 Implementation

# 4: According to stack running information, the main running class is inferred.

    private Class<?> deduceMainApplicationClass() {
        try {
            //Get runtime stack information
            StackTraceElement[] stackTrace = (new RuntimeException()).getStackTrace();
            StackTraceElement[] var2 = stackTrace;
            int var3 = stackTrace.length;

            for(int var4 = 0; var4 < var3; ++var4) {
                StackTraceElement stackTraceElement = var2[var4];
                //Judge that if there is a main method, it represents running the main class, getting its class name and instantiating it.
                if ("main".equals(stackTraceElement.getMethodName())) {
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        } catch (ClassNotFoundException var6) {
            ;
        }

        return null;
    }

Summary: It's easy to see that the initialization of Spring Application has been completed here, and SpringBook has completed the initialization of some identifiers in this step. The Application Context Initializer and Application Listener are initialized based on the registration information, and the application type and the main running class are inferred. Next, let's look at how the specific run method completes the initialization of containers.

(3)run method analysis:
The run method is the main method for Spring initialization. Let's see how SpringBoot guides all this.

   public ConfigurableApplicationContext run(String... args) {
		
		#1. SpringBoot will open a monitor to monitor and calculate the application startup time, acting as a monitor.
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		# 2 Here is the initialization of some necessary variables.
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
		# Similar to the above initialization of Application Listener, Spring Application RunListener is instantiated by loading pre-configured classes of spring.factories
		SpringApplicationRunListeners listeners = getRunListeners(args);
		# 4 Open Initialized Listening Events
		listeners.starting();
		try {
		    #5 Encapsulate command line parameters
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
		    #6. Prepare parameter environment for detailed analysis
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			configureIgnoreBeanInfo(environment);
			#7 Print Output
			Banner printedBanner = printBanner(environment);
			#8 Create Spring Container for Spring Initialization Period will also be detailed in subsequent articles
			context = createApplicationContext();
			#9 Initialize SpringBook exception report parser
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			#10 Initialization container parameters
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			#11 stop monitoring
			stopWatch.stop();
			#12 to encapsulate startup logs
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			#13 Re-open an event to handle CommandLine Runner and Application Runner
			listeners.started(context);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
		    #14 After all the work has been done, a new Application ReadyEvent event is released to indicate that the Application is ready 
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

supplement

Implementation of Application Context Initializer initialization

# 1: Load the full path name of the implementation class of the registered ApplicationContextInitializer in spring.factories. The core code is as follows:

Main steps:

  1. You can see that, depending on the class loader passed in, you first query in the cache map whether it has been loaded, and if it already exists, return directly.
  2. If it does not exist, then first load META-INF/spring.factories under classpath and system path to get all spring.factories resources.
  3. Then, according to the parsed resources, the parsed class path name and its parent class mapping relationship are stored in LinkedMultiValueMap. Finally, classLoader is used as key, and classLoader and LinkedMultiValueMap are stored in Map.
  4. Finally, according to factoryClassName, get all the implementation class names of the required ApplicationContextInitializer
    public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }

     private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                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);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryClassName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;

                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryName = var9[var11];
                            result.add(factoryClassName, factoryName.trim());
                        }
                    }
                }

                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }

# 2 and # 3 are identical and are registered classes that retrieve all ApplicationListener s

Topics: Spring SpringBoot Java