Spring boot source code parsing - startup process

Keywords: Spring

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.

Published 2 original articles, won praise 1, visited 1683
Private letter follow

Posted by Txtlocal on Mon, 09 Mar 2020 00:52:28 -0700