Original articles, reprinted please indicate the source: Spring Basic Series - Container Startup Process (2)
I. overview
The last article described the container in a SSM-based web project Startup process In this article, let's look at the container startup process in the SpringBook project.
II. Start-up process
The Springboot project needs to be started directly from the main method.
Source 1 - From DemoApplicaiton (Application Customized)
@SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
The first call is the run method in Spring Application.
Source 2 - From: Spring Application
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) { return run(new Class<?>[] { primarySource }, args); } public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); }
The first run method calls the second run method.
There are two steps in the second run method:
- Step 1: Create an instance of Spring Application CAITON
- Step 2: Call the run method
First, we create an instance of Spring Application:
Source 3 - From: Spring Application
public SpringApplication(Class<?>... primarySources) { this(null, primarySources); } public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); //Judging the type of application this.webApplicationType = deduceWebApplicationType(); //Adding initializers setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); //Adding listeners setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); //Locate the main method class and record it this.mainApplicationClass = deduceMainApplicationClass(); }
There are three types of SpringBook applications:
Source 4 - From: Web Application Type
public enum WebApplicationType { NONE,SERVLET,REACTIVE }
Analysis:
- NONE: An application is not a web application, so you don't have to start a built-in server program at startup time
- SERVLET: This is a Servlet-based web application that needs to open a built-in Servlet container (web server)
- REACTIVE: This is a reactive web application that needs to start a built-in reactive web server
The three criteria are as follows.
Source 5 - From: Spring Application
private WebApplicationType deduceWebApplicationType() { if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null) && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null) && !ClassUtils.isPresent(JERSEY_WEB_ENVIRONMENT_CLASS, null)) { return WebApplicationType.REACTIVE; } for (String className : WEB_ENVIRONMENT_CLASSES) { if (!ClassUtils.isPresent(className, null)) { return WebApplicationType.NONE; } } return WebApplicationType.SERVLET; }
Four constants are involved in the above method:
Source 6 - From: Spring Application
private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework." + "web.reactive.DispatcherHandler"; private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework." + "web.servlet.DispatcherServlet"; private static final String JERSEY_WEB_ENVIRONMENT_CLASS = "org.glassfish.jersey.server.ResourceConfig"; private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext" };
The code is simple.
Then set up the initializer, combined with the previous description in the SSM framework of web applications also have initializers, these two represent the same concept, different from the previous way of processing, here is just the initializer found and set to initializers properties.
Let's see how it gets these initializers:
Source 7 - From: Spring Application, Spring Factories Loader
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) { return getSpringFactoriesInstances(type, new Class<?>[] {}); } private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // Use names and ensure unique to protect against duplicates //Gets a collection of class names of the specified type using the specified class loader Set<String> names = new LinkedHashSet<>( SpringFactoriesLoader.loadFactoryNames(type, classLoader)); //Create examples List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); //sort AnnotationAwareOrderComparator.sort(instances); return instances; } // Here's from: Spring Factories Loader public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); } private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { //First try to get it from the cache MultiValueMap<String, String> result = cache.get(classLoader); if (result != null) { return result; } try //Get the configuration from the META-INF/spring.factories file Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); result = new LinkedMultiValueMap<>(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { List<String> factoryClassNames = Arrays.asList( StringUtils.commaDelimitedListToStringArray((String) entry.getValue())); result.addAll((String) entry.getKey(), factoryClassNames); } } //Add to Cache Standby cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } }
Thus, the initializer is obtained from the META-INF/spring.factories file, so we can build the directory file to add, provided that we need a custom initializer.
In addition, only when the first call needs to be read from the file, the second can be retrieved from the cache, even if not the content of the initializer, because the first time all the configuration content will be retrieved and parsed to save to the cache standby. This saves a lot of time for the next step of setting up listeners.
The last step is to record the main method class.
Source 8 - From: Spring Application
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 method call stack is acquired by creating a new runtime exception, and the class where the method named main is located is searched in the stack.
If we want to get the call stack of a method, we can also use this method, using exceptions to get the call stack.
After the Spring Application instance is created, the run method is directly invoked to execute the application startup.
Source 9 - From: Spring Application
public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); //Configure the system property headless, which defaults to true configureHeadlessProperty(); //Get all runtime listeners SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting();//lsnrctl start try { //Encapsulating custom parameters ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); //Configure environment according to parameters //If it's a web project, it creates a Standard Servlet Environment, otherwise it's a Standard Environment. //Then configure the properties and profile ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); //Set whether BeanInfo is ignored by default to true configureIgnoreBeanInfo(environment); //Print banner Banner printedBanner = printBanner(environment); //Create Application Context //If it is a Servlet web project, create Annotation ConfigEmbedded Web Application Context, if it is Reactive web //The project creates Annotation Config Reactive Web Server Application Context, otherwise Annotation Config Application Context context = createApplicationContext(); //Loading exception reports exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); //Preparatory context environment, i.e. initialization prepareContext(context, environment, listeners, applicationArguments, printedBanner); //Refresh Context refreshContext(context); //After refresh operation, these are two hole methods, which can be customized to achieve logic. afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } listeners.started(context);//After the container and application are enabled, call Runner is executed before //Callback to runner after the container is started, including: Application Runner and CommandLineRunner callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context; }
This is all the logical entry for the entire container to start, which can be understood by reference to the annotations. We focus on the prepareContext method, which is a pre-initialization operation before the container refreshes:
Source 10 - From: Spring Application
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { //Setting environment to context context.setEnvironment(environment); //Setting up BeanName Generator //Setting up a resource loader or class loader postProcessApplicationContext(context); //Execution initializer applyInitializers(context); listeners.contextPrepared(context);//Execute the contextPrepared operation of the listener //Configuration log information if (this.logStartupInfo) { logStartupInfo(context.getParent() == null); logStartupProfileInfo(context); } // Add boot specific singleton beans //Spring Application Arguments (Spring Boot Banner) context.getBeanFactory().registerSingleton("springApplicationArguments", applicationArguments); if (printedBanner != null) { context.getBeanFactory().registerSingleton("springBootBanner", printedBanner); } // Load the sources Set<Object> sources = getAllSources(); Assert.notEmpty(sources, "Sources must not be empty"); //Load beans into containers load(context, sources.toArray(new Object[0])); //Execute the contextLoaded operation of the listener listeners.contextLoaded(context); }
Focusing on the load method, his goal is to load the Bean definition:
Source 11 - From: Spring Application
//To prepare for the load operation, create a resource reader scanner protected void load(ApplicationContext context, Object[] sources) { if (logger.isDebugEnabled()) { logger.debug( "Loading source " + StringUtils.arrayToCommaDelimitedString(sources)); } //Create Bean Definition Reader, Scanner (Annotated Bean Definition Reader, XmlBean Definition Reader, //GroovyBeanDefinitionReader,ClassPathBeanDefinitionScanner) BeanDefinitionLoader loader = createBeanDefinitionLoader( getBeanDefinitionRegistry(context), sources); //Configure BeanName generator, resource loader, environment parameters to reader and scanner if (this.beanNameGenerator != null) { loader.setBeanNameGenerator(this.beanNameGenerator); } if (this.resourceLoader != null) { loader.setResourceLoader(this.resourceLoader); } if (this.environment != null) { loader.setEnvironment(this.environment); } //Execute Bean Loading loader.load(); } //Statistics on the number of loaded beans public int load() { int count = 0; for (Object source : this.sources) { count += load(source); } return count; } //Loading bean resources for different situations private int load(Object source) { Assert.notNull(source, "Source must not be null"); // if (source instanceof Class<?>) { return load((Class<?>) source); } //Resource resources may be beans defined by XML or beans defined by groovy if (source instanceof Resource) { return load((Resource) source); } //Packet resources are typically used for annotation-defined Bean resources, requiring scanners to scan packages if (source instanceof Package) { return load((Package) source); } // if (source instanceof CharSequence) { return load((CharSequence) source); } throw new IllegalArgumentException("Invalid source type " + source.getClass()); }
Load different resources.
Let's go back to the key operation refreshContext(context) in the run method:
Source 12 - From: Spring Application
private void refreshContext(ConfigurableApplicationContext context) { refresh(context); if (this.registerShutdownHook) { try { context.registerShutdownHook(); } catch (AccessControlException ex) { // Not allowed in some environments. } } } protected void refresh(ApplicationContext applicationContext) { Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext); ((AbstractApplicationContext) applicationContext).refresh(); }
Now it's time to execute the refresh() method, so let's take a look at this refresh.