Interpretation of Spring boot startup source code

catalogue

preface

Preparation before chasing source code

text

Explanation of notes

Start process source code analysis

Rough model diagram of Spring boot startup

summary

Course recommendation (free)

preface

In the last post, I introduced my friends to Spring boot and built a hello world project with Spring boot. I also raised the question why Spring boot can start the whole project without any configuration files. So this post explores how Spring boot can help you automatically select and configure from the bottom!

Introduction and use of Spring boothttps://blog.csdn.net/qq_43799161/article/details/122789131?spm=1001.2014.3001.5501

Preparation before chasing source code

1. Have a general understanding of the bottom layer of Spring boot and master the common annotations of Spring boot

2. Prepare the hello world project code (students who need hello world code can go to the post linked above to copy)

3. Be able to skillfully use the debug tools of idea or eclipse (idea is recommended)

4. When chasing Spring boot, you must have a certain understanding of the underlying layer of Spring and master the common annotations of Spring

5. Find the entrance of the debug program (personal suggestion: you'd better find it yourself first, find the headache, and then find the post or video learning)

The first three points are the universal formula for chasing the source code.

text

Explanatory notes

Before chasing the source code, think about how to design Spring boot instead of you?

As a scaffold, the original intention must be to simplify the configuration operation, so it must integrate the common environment configuration of back-end development, and open a specification to other uncommon environments for configuration. Then, after we write the code of automatic configuration, the project has been started and all configurations will take effect, which will take up a lot of space. Can we select those environments according to the users and generate only the configurations of those environments?

So how do we find the entrance?

The current entry is in the @ SpringBootApplication annotation of the main method. Let's catch up and have a look

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

The above four annotations are self-contained in the specification and have little to do with us. We see the following three annotations

@ComponentScan annotation students who have studied Spring should understand that when injected into the IOC container of Spring in the form of annotation, a Configuration class should be configured to scan the annotation and use it together with the @ Configuration annotation. Where is @ Configuration?

See @ SpringBootConfiguration, let's catch up first

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {

See @ Configuration annotation. However, we have chased two of the three important annotations and failed to catch up with what we want, so the entry must be in the @ EnableAutoConfiguration annotation, so we chased in.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

As an old rule, the above four are not important, so the important ones are the following two notes. See @ AutoConfigurationPackage first

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

Only @ Import(AutoConfigurationPackages.Registrar.class) is important

Go back to autoconfigurationpackages Registrar. In class

	static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
			register(registry, new PackageImport(metadata).getPackageName());
		}

		@Override
		public Set<Object> determineImports(AnnotationMetadata metadata) {
			return Collections.singleton(new PackageImport(metadata));
		}

	}

You don't have to think about it. Trace it to the register() method

In fact, if you catch up here, you don't need to go back. Students who have seen the IOC source code in Spring can understand it.

First of all, when we see the parameter beandefinitionregistry in the registerBeanDefinitions() method, we can understand that the data is parsed into BeanDefinition objects, and the other parameter is annotationmetadata. Here, the parameter stores some meta information of the annotation, that is, some information about where the annotation is used. See the register() method again, that is, add the data to the BeanDefinition and to the collection.

Blogger's interpretation of IOC source code in Springhttps://blog.csdn.net/qq_43799161/article/details/122371976?spm=1001.2014.3001.5501

What we are pursuing is the source code of automatic configuration. Here, the class using annotation is parsed into BeanDefinition and added to the collection for later injection into the IOC container. The result is different from what we want, so the entry is another annotation @ Import(AutoConfigurationImportSelector.class). We go back to the beginning and catch up.

@Role of Import annotation

Simply put, the class marked with this annotation uses the configuration class in the parentheses of the annotation.

When we come to the AutoConfigurationImportSelector class, we find that the class is very complex. At this time, how do we find the entry?

At this time, we need to think about it. The automatic configuration must be here. We haven't found any other intersections, so our starting point here is to go to the automatic configuration method.

At this point, we open all methods ctrl+F12. We can see that there is a getAutoConfigurationEntry() method, which is called by selectImports(). selectImports is a method that overrides the ImportSelector class. At this time, we need to understand the role of ImportSelector class. At this time, we find that ImportSelector is not implemented, but DeferredImportSelector class is implemented. Therefore, we need to understand the relationship between ImportSelector and DeferredImportSelector class

Both are interface implementations of @ Import annotation. That is, you can also implement the function of @ Import by implementing these two interfaces, but what is the difference between them?

DeferredImportSelector: executed after @ Configuration other logic

@ selectimporter: before executing other logic

So we know that the return value of the selectImports() method can be imported into Spring.

So let's look at the getAutoConfigurationEntry() method.

See getCandidateConfigurations().

	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				getBeanClassLoader());
		Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
				+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
	}

So continue to the loadFactoryNames() method.

	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
			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()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

We see facts_ RESOURCE_ By using the location constant, you can understand that the automatic configuration is being parsed here, and by checking whether spring exists in the META-INF folder Farms folder, and we see maven warehouse.

We are parsing these parameters, but we need to think about whether all of them will be added?

We just open the path under the automatic configuration package

You can see that the tag of @ Conditional series judges whether to add it or not, so it can be proved that not all automatic configurations will be added to Spring boot.

However, with the update iteration of the version, spring boot2.0 is being used instead Version 2.1 does not follow this process. Where else can we go if we don't go here?

At present, the only speculated code that has not been seen is the method in the main() method, and the code in the main() method is also the Spring boot startup code, so let's track down the synchronization of the Spring boot startup source code to see if it replaces these steps?

  

Start process source code analysis

Give the main() method a breakpoint, and then debug starts running the project.

We see the construction method of SpringApplication()

	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();
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

For the execution process, I will directly talk about the important code, not the reasoning process and some codes that are not important to us. Here I suggest that you can understand the general and strengthen other codes at the same time, because the existence is reasonable, and other codes may be some high extension points of Spring boot and Spring!

See setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); And come to the last breakpoint, jump to the breakpoint and continue to catch up.

	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();
		// Use names and ensure unique to protect against duplicates
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

Is this similar to the code explained above?

In this line, go up to the breakpoint and catch up with springfactoriesloader loadFactoryNames(type, classLoader)

Continue to this method, loadspring factories (classloader)

	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
			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()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

This is the same as the code of annotation, so it can be proved that it is parsed when Spring boot is started

META-INF/spring. Automatically configure the parameters of the class configured in factories and add them to the cache. So let's go back all the way and continue to see what Spring boot did in the subsequent startup.

// Here are some listening interfaces of Spring boot, which are highly extensible
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

// This is where to find the main method, because we can create a static internal write a main method in the startup class, which can also start the container
this.mainApplicationClass = deduceMainApplicationClass();

Just now, it's just some initialization steps of the SpringApplication constructor, and then we continue to pursue the run() method.

public ConfigurableApplicationContext run(String... args) {

       // Create a timer
		StopWatch stopWatch = new StopWatch();

       // Timer start
		stopWatch.start();

       // Create a Spring context object to add the data parsed by Spring boot to the IOC container
		ConfigurableApplicationContext context = null;

       // Exception of Spring boot
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		
       // Some variables in the project environment
        configureHeadlessProperty();

        //Get the event listener, and get the previously resolved event and start listening
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();


		try {
           // args is our startup parameter, so here is to encapsulate our startup parameters
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

           //Some preprocessing of Spring boot environment configuration, because it is a web project, it is servle environment
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

           // Configure system environment
			configureIgnoreBeanInfo(environment);

           // Printing of Spring logo on console
			Banner printedBanner = printBanner(environment);

           // Get the context container (details)
			context = createApplicationContext();
            
           // Exception handling of Spring boot
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);

           // The preprocessing of the context container is some preparation operations before refresh (to be explained in detail)
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);

           // refresh operation (details)
			refreshContext(context);

           // Some operations after refresh
			afterRefresh(context, applicationArguments);

           // How long does it take to finish the startup time and print the console
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}

           // Calling the started method of listening can be regarded as switching the listening state
			listeners.started(context);

           // Complete an ApplicationRunner or CommandLineRunner callback after the container is started
			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;
	}

The core codes are all here and have been annotated. Here, only the core and important codes will be selected to explain.

See context = createApplicationContext(); Give the previous breakpoint, adjust to the breakpoint on this line and enter.

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

You can see that the syntax of switch case is used to determine which ConfigurableApplicationContext instance object to use.

Because we are a web project, it must be servlet. At this time, we obtain the instance object of the servlet's ApplicationContext through reflection, so we see DEFAULT_SERVLET_WEB_CONTEXT_CLASS constant value

At this time, use the idea shortcut key and press shift twice to search the annotation configservletwebserver ApplicationContext globally

After we came in, we found that there were three construction methods. We all gave breakpoints to see which construction method was executed by default. We directly released the breakpoints and directly came to the parameterless construction method.

	public AnnotationConfigServletWebServerApplicationContext() {
		this.reader = new AnnotatedBeanDefinitionReader(this);
		this.scanner = new ClassPathBeanDefinitionScanner(this);
	}

Before moving on, I think I have to figure out the class diagram of annotationconfigservletwebserver ApplicationContext

The relationship is very complex, so it proves that the framework of Spring series is very delicate in interface design and has high extension points.

We know that as long as we inherit or implement the class, we can have all the parent class, so we just inherit it on the original basis and then extend it.

Let's take a look at his parent class servletwebserver ApplicationContext

We can see WebServer

In fact, we can understand that tomcat launched by our project is encapsulated here.

Look at the parent up again

You can see that the ServletContext context is managed here

Check the upper parent class again

It is found that BeanFactory objects are maintained here. With BeanFactory, you can do whatever you want?

Therefore, although the source code of Spring series has a headache to catch up and the inheritance relationship is very complex, its extension is really cow.

Let's go back to the run method in our spring application class, which is a very core method to break the refreshContext().

	private void refreshContext(ConfigurableApplicationContext context) {
		refresh(context);
		if (this.registerShutdownHook) {
			try {
				context.registerShutdownHook();
			}
			catch (AccessControlException ex) {
				// Not allowed in some environments.
			}
		}
	}

Continue to the refresh() method

	protected void refresh(ApplicationContext applicationContext) {
		Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
		((AbstractApplicationContext) applicationContext).refresh();
	}

ApplicationContext is the annotation configservletwebserver ApplicationContext we obtained before reflection.

Continue to chase the refresh() method

	@Override
	public final void refresh() throws BeansException, IllegalStateException {
		try {
			super.refresh();
		}
		catch (RuntimeException ex) {
			stopAndReleaseWebServer();
			throw ex;
		}
	}

Notice super Refresh () method, super keyword, we all know is to call the method of the parent class. Let's catch up.

	@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				initMessageSource();

				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				onRefresh();

				// Check for listener beans and register them.
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				finishRefresh();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}
		}
	}

Small partners who have seen the Spring source code should be no longer familiar with this method. Here are some callback methods, and the specific implementation of some methods is based on the implementation in the implementation class.

At this time, we have come to the core class of Spring, which is no longer the class of Spring boot, so here is to add the parsed content of Spring boot to the IOC container through the refresh() method.

We see that the onRefresh() method gives the previous breakpoint, because the ServletWebServerApplicationContext class overrides this method and initializes some content through this method. We release it directly to onRefresh()

	@Override
	protected void onRefresh() {
		super.onRefresh();
		try {
			createWebServer();
		}
		catch (Throwable ex) {
			throw new ApplicationContextException("Unable to start web server", ex);
		}
	}

super.onRefresh() has nothing to do with us. We noticed that we caught up with the createWebServer() method.

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

At this time, our WebServer is empty, and the ServletContext is also empty. It is not initialized, so we enter the first if code block.

ServletWebServerFactory factory = getWebServerFactory(); Is to inject our tomcat encapsulated object into the IOC container, and then go down to factory getWebServer(getSelfInitializer()); Method

	@Override
	public WebServer getWebServer(ServletContextInitializer... initializers) {
		if (this.disableMBeanRegistry) {
			Registry.disableRegistry();
		}
		Tomcat tomcat = new Tomcat();
		File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
		tomcat.setBaseDir(baseDir.getAbsolutePath());
		Connector connector = new Connector(this.protocol);
		connector.setThrowOnFailure(true);
		tomcat.getService().addConnector(connector);
		customizeConnector(connector);
		tomcat.setConnector(connector);
		tomcat.getHost().setAutoDeploy(false);
		configureEngine(tomcat.getEngine());
		for (Connector additionalConnector : this.additionalTomcatConnectors) {
			tomcat.getService().addConnector(additionalConnector);
		}
		prepareContext(tomcat.getHost(), initializers);
		return getTomcatWebServer(tomcat);
	}

Yes, it is to initialize the tomcat object. We enter the getTomcatWebServer() method.

Initialize and start the tomcat container for one of them.

We can see that our control has output a tomcat startup log in response.

Then return to finishBeanFactoryInitialization(beanFactory) in the refresh() method; This is to inject the BeanDefinition object in the beanDefinitionNames collection into the IOC container, create the Bean and add the code to the container. I won't say much here. You can see the blogger's Spring IOC container to start source code analysis

Analysis of startup source code of Spring ioc containerhttps://blog.csdn.net/qq_43799161/article/details/122371976?spm=1001.2014.3001.5502

Then go down. After the refresh() method is fully executed, you will return to the refreshcontext () context in the SpringApplication class registerShutdownHook(); It is basically a hook function. It is triggered at the end of the project. The class content is to create a destruction thread, and the content is to close a resource. We then return.

Back to the run() method in spring application, the next important thing is to process the start time and output it on the console, and then the event listener switches to the started state and then to the running state.

Then judge whether to reproduce the ApplicationRunner and CommandLineRunner interfaces, which are also hook functions. The execution of the method here is also the last method of the run() method, so it is a callback function after startup and a high extension point.

Rough model diagram of Spring boot startup

 

summary

In fact, before chasing the source code, you can guess what the general steps are. It's much easier to go in and chase the source code.

The following section describes the handwritten configuration to add the Spring boot scan to the IOC container.

Course recommendation (free)

Because it is impossible to unify ideas among people, and it is difficult to describe the implementation of some processes in documents, I recommend video courses for you (free, absolute conscience recommended)

The enlightenment thought of Spring boot, but it is time-consuming and time-consuming. It is highly recommended by my friendshttps://www.bilibili.com/video/BV1P541147pr?spm_id_from=333.999.0.0Very high quality and high efficiencyhttps://www.bilibili.com/video/BV19K4y1L7MT?p=8

Keywords: Java IntelliJ IDEA Spring Spring Boot Back-end

Added by sfarid on Sun, 06 Feb 2022 21:06:34 +0200