spring boot source parsing and startup process

First of all, spring boot is a simplified development framework with convention greater than configuration. If I look at the source code of spring boot, I think there are two directions. One is the process of spring boot based on spring container and some of its own startup. The other is the implementation code with convention greater than configuration.

Start from the start of the spring boot container.

spring boot is just a line of code, SpringApplication.run , but this is a static method, and the internal calls are as follows.

	/**
	 * Static helper that can be used to run a {@link SpringApplication} from the
	 * specified sources using default settings and user supplied arguments.
	 * @param primarySources the primary sources to load
	 * @param args the application arguments (usually passed from a Java main method)
	 * @return the running {@link ApplicationContext}
	 */
	public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
	}

Of course, it is recommended to use new SpringApplication directly to start spring boot, which has the advantage of setting some custom properties before executing the run method. Let's first look at the operations in the constructor.

/**
	 * Create a new instance of SpringApplication. The context will load the bean with the given primarySources parameter
	 * Create a new {@link SpringApplication} instance. The application context will load
	 * beans from the specified primary sources (see {@link SpringApplication class-level}
	 * documentation for details. The instance can be customized before calling
	 * {@link #run(String...)}.
	 * @param resourceLoader the resource loader to use
	 * @param primarySources the primary bean sources
	 * @see #run(Class, String[])
	 * @see #setSources(Set)
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {

		//resourceLoader can be passed in by itself, or it is currently empty by default
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        //There are three types of webapplicationtypes
		// NONE does not need an embedded web container, and does not start in a Web way
		// servlet needs the web container of the embedded servlet to start a servlet based web application
        // reactive requires an embedded reactive web container to start a reactive based web application reference weblux
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
        //From META-INF/spring.factories  All classes that implement ApplicationContextInitializer are found in the initializers
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		//From META-INF/spring.factories  All classes that implement ApplicationListener are set to listeners
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
         //Find the class of the current main method
		this.mainApplicationClass = deduceMainApplicationClass();
	}

The specific functions are written in the notes. The procedure from classpath returns the WebApplicationType method. The code is as follows. You can decide what WebApplicationType is by looking at it Class.forName Whether it can be loaded into the following classes

	static WebApplicationType deduceFromClasspath() {
		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
			return WebApplicationType.REACTIVE;
		}
		for (String className : SERVLET_INDICATOR_CLASSES) {
			if (!ClassUtils.isPresent(className, null)) {
				return WebApplicationType.NONE;
			}
		}
		return WebApplicationType.SERVLET;
	}

After spring application initialization, execute the run method.

/**
	 * Run the Spring application, creating and refreshing a new
	 * {@link ApplicationContext}.
	 * @param args the application arguments (usually passed from a Java main method)
	 * @return a running {@link ApplicationContext}
	 */
	public ConfigurableApplicationContext run(String... args) {
		//timer
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();

		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		//Configure headless without concern
		configureHeadlessProperty();
		//Get start event listener
		SpringApplicationRunListeners listeners = getRunListeners(args);
		//Propagation start event
		listeners.starting();
		try {
			//Initializing an application parameter class
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

			//Parsing environment variables, parameters, configuration, publishing events
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			//Create spring container class
			context = createApplicationContext();
			//Get the class inherited from SpringBootExceptionReporter, and the exception output
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
            
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			//Refresh container spring refresh()
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
			callRunners(context, applicationArguments);
		}

getRunListeners gets the list of implementation classes of SpringApplicationRunListener. In the spring boot project, there is a default configuration implementation, eventpublishingrunnlistener

	private SpringApplicationRunListeners getRunListeners(String[] args) {
		//From META-INF/spring.factories  All classes that implement SpringApplicationRunListener are found in
		Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
		return new SpringApplicationRunListeners(logger,
				getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
	}

During the initialization of the eventpublishingronlistener object construction method, a SimpleApplicationEventMulticaster is created and all the current event listeners are added.

	public EventPublishingRunListener(SpringApplication application, String[] args) {
		this.application = application;
		this.args = args;
		this.initialMulticaster = new SimpleApplicationEventMulticaster();
		for (ApplicationListener<?> listener : application.getListeners()) {
			this.initialMulticaster.addApplicationListener(listener);
		}
	}

In the next starting method, the starting method of eventpublishingrunnistener listener will be called, so an ApplicationStartingEvent event will be published to all current listeners

@Override
	public void starting() {
		this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
	}

Currently, there are four listeners that support the ApplicationStartingEvent event:

1,LoggingApplicationListener

2,BackgroundPreinitializer

3,DelegatingApplicationListener

4,LiquibaseServiceLocatorApplicationListener.

LoggingApplicationListener is used to initialize some configurations of logs,

BackgroundPreinitializer is used to initialize some time-consuming operations (some of the methods here are not assigned, nor are they stored in static variables. After reading it, they are confused and don't know what they have done). Other operations are not available.

Next, we will initialize the configuration parameters of the Environment variables. First, we will get an Environment. First, we can see whether there is an Environment in the current spring application. By default, it will definitely not exist. Then we will create a new Environment according to the type of webApplicationType and return a different Environment.

After getting the Environment, execute the configureEnvironment method, first create a ConversionService, then configure the PropertySources and Profiles

/**
	 * Template method delegating to
	 * {@link #configurePropertySources(ConfigurableEnvironment, String[])} and
	 * {@link #configureProfiles(ConfigurableEnvironment, String[])} in that order.
	 * Override this method for complete control over Environment customization, or one of
	 * the above for fine-grained control over property sources or profiles, respectively.
	 * @param environment this application's environment
	 * @param args arguments passed to the {@code run} method
	 * @see #configureProfiles(ConfigurableEnvironment, String[])
	 * @see #configurePropertySources(ConfigurableEnvironment, String[])
	 */
	protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
		// addConversionService default true
		if (this.addConversionService) {
			//Create a type converter service instance. During the creation process, all the default type converters have been registered. Single instance mode
			ConversionService conversionService = ApplicationConversionService.getSharedInstance();
			environment.setConversionService((ConfigurableConversionService) conversionService);
		}
		//Initialize PropertySources and Profiles
		configurePropertySources(environment, args);
		configureProfiles(environment, args);
	}

First, look at the method of configurePropertySources. First, look to see if there are defaultProperties at present. If there are any, they must not. Next, we will judge whether there are args variables at present. If there are some, we will create a SimpleCommandLinePropertySource.

Next is configureProfiles. This method is to combine the Profiles in the current additionalProfiles variable with the original ones in the Environment, and then put them into the Environment. The current additionalProfiles also does not have any.

Call configur after Environment loading is complete ationPropertySources.attach Method, create a ConfigurationPropertySourcesPropertySource and put it into the Environment.

Next, execute environmentPrepared to publish an ApplicationEnvironmentPreparedEvent event.

The current default event recipients are as follows:

1,ConfigFileApplicationListener

2,AnsiOutputApplicationListener

3,LoggingApplicationListener

4,BackgroundPreinitializer

5,ClasspathLoggingApplicationListener

6,DelegatingApplicationListener

7,FileEncodingApplicationListener

But it's not very important for the main line, so I'll skip it here.

Then call bindToSpringApplication,

In the createApplicationContext method, start to create spring containers. Create three kinds of containers according to different types. At present, look at the return of servlet, AnnotationConfigServletWebServerApplicationContext

	/**
	 * Strategy method used to create the {@link ApplicationContext}. By default this
	 * method will respect any explicitly set application context or application context
	 * class before falling back to a suitable default.
	 * @return the application context (not yet refreshed)
	 * @see #setApplicationContextClass(Class)
	 */
	protected ConfigurableApplicationContext createApplicationContext() {
		Class<?> contextClass = this.applicationContextClass;
		if (contextClass == null) {
			try {
				switch (this.webApplicationType) {
				case SERVLET:
					contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
					break;
				case REACTIVE:
					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
					break;
				default:
					contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
				}
			}
			catch (ClassNotFoundException ex) {
				throw new IllegalStateException(
						"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
			}
		}
		return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
	}

For the start-up process of spring, please refer to Spring source code analysis (I) IoC container , first look at the class diagram, and see that annotationconfigservletwebserver ApplicationContext inherits from GenericWebApplicationContext. On initialization, this container class has the same process as annotationconfidapplicationcontext

Next, go down to prepareContext, first set the current Environment to the spring container, then execute the postProcessApplicationContext method, and set the current resourceLoader and beanNameGenerator to the spring container, but they are not currently available, and then set the ConversionService

	/**
	 * Apply any relevant post processing the {@link ApplicationContext}. Subclasses can
	 * apply additional processing as required.
	 * @param context the application context
	 */
	protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
		if (this.beanNameGenerator != null) {
			context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
					this.beanNameGenerator);
		}
		if (this.resourceLoader != null) {
			if (context instanceof GenericApplicationContext) {
				((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);
			}
			if (context instanceof DefaultResourceLoader) {
				((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader());
			}
		}
		if (this.addConversionService) {
			context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance());
		}
	}

Then call contextPrepared to send the ApplicationContextInitializedEvent event,

There are two default recipients of this event

1,BackgroundPreinitializer

2,DelegatingApplicationListener

Then register the single application arguments and Banner to the container, and dynamically set the LazyInitializationBeanFactoryPostProcessor according to the lazyInitialization property of SpringApplication

Then execute the load method, which can refer to the register method of the spring container, and finally call contextLoaded. First, we find the listener inherited from ApplicationContextAware, injecting the context container object, and then publish ApplicationPreparedEvent event.

	@Override
	public void contextLoaded(ConfigurableApplicationContext context) {
		for (ApplicationListener<?> listener : this.application.getListeners()) {
			if (listener instanceof ApplicationContextAware) {
				((ApplicationContextAware) listener).setApplicationContext(context);
			}
			context.addApplicationListener(listener);
		}
		this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
	}

There are four default recipients of the current event:

1,ConfigFileApplicationListener

2,LoggingApplicationListener

3,BackgroundPreinitializer

4,DelegatingApplicationListener

A postprocessor, PropertySourceOrderingPostProcessor, has been added to ConfigFileApplicationListener. Others are still ignored.

Next, we will execute the refreshContext method, which calls the spring container's refresh method. Unlike the annotationconfigpplicationcontext blog, it inherits GenericWebApplicationContext, so some subclass implementations are different.

First, look at the initPropertySources method in the prepareRefresh method. This method needs to be overridden by a subclass override. This method is overridden in the GenericWebApplicationContext. However, because the current servletContext does not exist, the method will not call

/**
	 * {@inheritDoc}
	 * <p>Replace {@code Servlet}-related property sources.
	 */
	@Override
	protected void initPropertySources() {
		ConfigurableEnvironment env = getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(this.servletContext, null);
		}
	}

Next, postProcessBeanFactory, annotationconfigservletwebserverbapplicationcontext rewrites this method and registers a WebApplicationContextServletContextAwareProcessor with the container.

The most important is the override of the onRefresh method. The server will be created in the servlet web server application context.

	@Override
	protected void onRefresh() {
		super.onRefresh();
		try {
			createWebServer();
		}
		catch (Throwable ex) {
			throw new ApplicationContextException("Unable to start web server", ex);
		}
	}
private void createWebServer() {
		WebServer webServer = this.webServer;
		ServletContext servletContext = getServletContext();
		if (webServer == null && servletContext == null) {
			ServletWebServerFactory factory = getWebServerFactory();
			this.webServer = factory.getWebServer(getSelfInitializer());
		}
		else if (servletContext != null) {
			try {
				getSelfInitializer().onStartup(servletContext);
			}
			catch (ServletException ex) {
				throw new ApplicationContextException("Cannot initialize servlet context", ex);
			}
		}
		initPropertySources();
	}

First, the current webServer and servletContext must be empty by default, so the first Bean of ServletWebServerFactory will be obtained from the spring container, which is currently TomcatServletWebServerFactory

/**
	 * Returns the {@link ServletWebServerFactory} that should be used to create the
	 * embedded {@link WebServer}. By default this method searches for a suitable bean in
	 * the context itself.
	 * @return a {@link ServletWebServerFactory} (never {@code null})
	 */
	protected ServletWebServerFactory getWebServerFactory() {
		// Use bean names so that we don't consider the hierarchy
		String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
		if (beanNames.length == 0) {
			throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "
					+ "ServletWebServerFactory bean.");
		}
		if (beanNames.length > 1) {
			throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
					+ "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
		}
		return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
	}

Next, tomcat will be started. This process will be skipped, but it should be noted that a callback is registered here and will be called after tomcat is started. The following code is the method reference of jdk8.

	/**
	 * Returns the {@link ServletContextInitializer} that will be used to complete the
	 * setup of this {@link WebApplicationContext}.
	 * @return the self initializer
	 * @see #prepareWebApplicationContext(ServletContext)
	 */
	private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
		return this::selfInitialize;
	}

The call is as follows. First, the content of prepareWebApplicationContext is very simple. It prints several lines of logs, sets servletContext to the current container, and registerApplicationScope sets servletContext to the scope of application, Registerenvironmentbeans register contextParameters and contextAttributes into the container, and then onStartup method adds some default filters and so on.

	private void selfInitialize(ServletContext servletContext) throws ServletException {
		prepareWebApplicationContext(servletContext);
		registerApplicationScope(servletContext);
		WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
		for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
			beans.onStartup(servletContext);
		}
	}

Finally, call the initPropertySources method again. After the spring container starts, it returns and registers a callback hook, which will be called when the jvm stops

public void registerShutdownHook() {
		if (this.shutdownHook == null) {
			// No shutdown hook registered yet.
			this.shutdownHook = new Thread(SHUTDOWN_HOOK_THREAD_NAME) {
				@Override
				public void run() {
					synchronized (startupShutdownMonitor) {
						doClose();
					}
				}
			};
			Runtime.getRuntime().addShutdownHook(this.shutdownHook);
		}
	}

After that, the afterRefresh method is called, which is not currently implemented

Then call listeners.started , publish the ApplicationStartedEvent event

There are two default recipients of the current event:

1,BackgroundPreinitializer

2,DelegatingApplicationListener

Then call the callRunners method, which is to find out and implement the ApplicationRunner and CommandLineRunner, and call the run method

Last call listeners.running , publish ApplicationReadyEvent event

There are three default recipients of the current event:

1,SpringApplicationAdminMXBeanRegistrar

2,BackgroundPreinitializer

3,DelegatingApplicationListener

spring boot is complete.

 

Keywords: Spring Java Web Server Tomcat

Added by PHPLRNR on Tue, 23 Jun 2020 08:49:04 +0300