For source code learning, I think it would be better for us to look at the problem together.

What is the startup principle of Springboot?

Without further ado, we will download a demo from [start.spring. IO]. The SpringBoot version will be selected as 2.1.4, and then we will learn step by step how SpringBoot works.

Our project catalog is as follows:

Everything will start with our demoApplication.java file. The code is as follows:

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

【 Tip 1 】

I often see friends implement the ApplicationContextAware interface in the DemoApplication class and then get the ApplicationContext object, as in the following code:

@SpringBootApplication public class DemoApplication implements ApplicationContextAware { private static ApplicationContext applicationContext = null; public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); / / get a bean System. Out.println (applicationContext. GetBean ("xxxx"));
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { DemoApplication.applicationContext = applicationContext; }}Copy the code

The springApplication. run method returns the Spring context. The springApplication. run method returns the Spring context.

@SpringBootApplication public class DemoApplication { public static void main(String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(DemoApplication.class, args); / / get a bean System. Out.println (applicationContext. GetBean ("xxxx")); }}Copy the code

The code jumps to Line 263 of the SpringApplication class

@SuppressWarnings({ "unchecked"."rawtypes"}) public SpringApplication(ResourceLoader resourceLoader, Class<? >... PrimarySources) {// 1, initialize a class loader this.resourceLoader = resourceLoader; Assert.notNull(primarySources,"PrimarySources must not be null"); This.primarysources = new LinkedHashSet<>(arrays.asList (primarySources)); / / 3, the current application type, there are three: NONE, SERVLET, REACTIVE enclosing webApplicationType = webApplicationType. DeduceFromClasspath (); // 4. Initialize InitializersetInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); // Process 2setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); / / 6, initialize the entrance this. MainApplicationClass = deduceMainApplicationClass (); }Copy the code

Step 1-3 nothing to speak of, some logo and a list of what is is initialized, focus on the first 4 and 5 points, 4, and 5) to help us to load all the ApplicationListener dependence and ApplicationContextInitializer configuration items, Look at line 132 of the SpringFactoriesLoader and see that springBoot loads the meta-INF/Spring. factories file in each jar with the ClassLoader key. A Map cache is made for all configurations.

Private static Map<String, List<String>> loadSpringFactories(@nullable ClassLoader ClassLoader) { The default is thread.currentThread ().getContextClassLoader(); MultiValueMap<String, String> result = cache.get(classLoader); MultiValueMap<String, String> result = cache.get(classLoader);if(result ! = null) {returnresult; } try { Enumeration<URL> urls = (classLoader ! = null ? // The value of FACTORIES_RESOURCE_LOCATION is meta-INF/spring.Factories 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 factoryClassName = ((String) entry.getKey()).trim();for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
    				result.add(factoryClassName, factoryName.trim());
    			}
    		}
    	}
    	cache.put(classLoader, result);
    	return result;
    }
    catch (IOException ex) {
    	throw new IllegalArgumentException("Unable to load factories from location [" +
    			FACTORIES_RESOURCE_LOCATION + "]", ex); }}Copy the code

Spring-boot-autoconfigure-2.1.4.release.jar < spring-boots-autoconfigure-2.1.4.release.jar >

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

Configure automatic configuration (see below)
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
......
Copy the code

【 Tip 2 】

Now let’s look at step 6, where we can learn a little trick. How do we get the class information of an intermediate method in the current method call chain? Let’s look at the source code:

private Class<? >deduceMainApplicationClass() { try { StackTraceElement[] stackTrace = new RuntimeException().getStackTrace(); // Get the runtime method stackfor(StackTraceElement StackTraceElement: stackTrace) {// Find the class name by nameif ("main".equals(stackTraceElement.getMethodName())) {
    			return Class.forName(stackTraceElement.getClassName());
    		}
    	}
    }
    catch (ClassNotFoundException ex) {
    	// Swallow and continue
    }
    return null;
}
Copy the code

So far we have just initialized the SpringApplication class. We have all the listener and initializer names configured in the meta-INF/Spring. factories directory and instantiated them. And finally stored in the SpringApplication class.

The code moves to line 295 of SpringApplication.java and reads as follows

public ConfigurableApplicationContext run(String... StopWatch = new StopWatch(); StopWatch = new StopWatch(); stopWatch.start(); / / 2, create an initialization context variable ConfigurableApplicationContext context = null; / / 3, this is like the spring report, not understanding the Collection < SpringBootExceptionReporter > exceptionReporters = new ArrayList < > (); configureHeadlessProperty(); / / 4, obtain configuration SpringApplicationRunListener types of listeners, and start it SpringApplicationRunListeners listeners = getRunListeners (args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); // Listeners can set the environment to workableEnvironment = workEnvironment (Listeners, applicationArguments); configureIgnoreBeanInfo(environment); // 6, print banner banner printedBanner =printBanner(environment); Context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); // 8, prepare the context component, environment, listeners prepareContext(context, environment, listeners, applicationArguments, printedBanner); // refreshContext refreshContext(context); afterRefresh(context, applicationArguments); Stopwatch.stop ();if(this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } listeners.started(context); Runners(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;
}
Copy the code

【 Tip 3 】

In step 1, we used the timer StopWatch tool, which can also be used directly. Usually, we count the execution time of a piece of code or a method. We will use System.CurrentTimemillis to achieve this, and we can also use StopWatch instead. The power of StopWatch lies in that it can count the time consumption ratio of each time period, which is roughly as follows:

@SpringBootApplication public class DemoApplication { public static void main(String[] args) { StopWatch stopWatch = new  StopWatch(); stopWatch.start("startContext");
    	ConfigurableApplicationContext applicationContext = SpringApplication.run(DemoApplication.class, args);
    	stopWatch.stop();
    	stopWatch.start("printBean"); / / get a bean System. Out. Println (applicationContext. GetBean (DemoApplication. Class)); stopWatch.stop(); System.err.println(stopWatch.prettyPrint()); }}Copy the code

Step 4 Proceed to Line 413 of the SpringApplication with the following code:

private SpringApplicationRunListeners getRunListeners(String[] args) { Class<? >[] types = new Class<? >[] { SpringApplication.class, String[].class };return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
		SpringApplicationRunListener.class, types, this, args));
}
Copy the code

It can be seen that springboot remains to the meta-inf/spring. Factories find SpringApplicationRunListener configuration class, and startup.

Step 5 default create Spring Environment module StandardServletEnvironment in the Environment.

Step 7 is created by default context type AnnotationConfigServletWebServerApplicationContext, it can be seen that this is the Spring context annotation-based Servlet context, therefore, The annotation @SpringBootApplication declared in our original DemoApplication. Java class will be scanned and parsed.

Step 9 Refresh context is the most core, have seen the spring source code, this refresh() method is very classic, specific can refer to the small series of other articles in the spring container IOC initialization process

Step 11 executes the following code for all beans implementing ApplicationRunner and CommandLineRunner in the entire context:

private void callRunners(ApplicationContext context, ApplicationArguments args) { List<Object> runners = new ArrayList<>(); runners.addAll(context.getBeansOfType(ApplicationRunner.class).values()); runners.addAll(context.getBeansOfType(CommandLineRunner.class).values()); / / prioritize and perform all runners AnnotationAwareOrderComparator. Sort (runners);for (Object runner : new LinkedHashSet<>(runners)) {
    	if (runner instanceof ApplicationRunner) {
    		callRunner((ApplicationRunner) runner, args);
    	}
    	if(runner instanceof CommandLineRunner) { callRunner((CommandLineRunner) runner, args); }}}Copy the code

In normal development, we may want to perform some operations after the Spring container has been started. For example, if we need to perform a scheduled task after the Spring container has been started, we will probably see the following code in the light of the source code of step 11 above. This code is called in the SpringApplication class.

@Component
public class MyRunner implements ApplicationRunner {

	@Override
	public void run(ApplicationArguments args) throws Exception {
		System.err.println("Run ApplicationRunner~"); }}Copy the code
@Component
public class MyCommandRunner implements CommandLineRunner {

	@Override
	public void run(String... args) throws Exception {
		System.out.println("Commandrunner has been executed."); }}Copy the code

Note:

  • CommandLineRunner and ApplicationRunner are executed after the Spring container has been started
  • 2. Execute the entire container life cycle only once

What is the function of @enableAutoConfiguration?

In general, beans declared in jar files introduced by Java are not scanned by Spring, so how do our various starter initialize their own beans? The answer is in the meta-inf/spring. Factories declared in org. Springframework. Boot. Autoconfigure. EnableAutoConfiguration = XXX, For example, the starter spring-cloud-Netflix-Zuul declares the following:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.zuul.ZuulServerAutoConfiguration,\
org.springframework.cloud.netflix.zuul.ZuulProxyAutoConfiguration
Copy the code

What does this statement mean? Will say springboot startup process, org. Springframework. Boot. Autoconfigure. EnableAutoConfiguration = XXX as bean class instance declaration, and registered to the container, the following is the test case:

We declare a bean in myDemo as follows:

@Service
public class MyUser {

}
Copy the code

In the demo, print the MyUser bean as follows:

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'css.demo.user.MyUser' available
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:343)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:335)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1123)
	at com.example.demo.DemoApplication.main(DemoApplication.java:15)
Copy the code

We add this configuration to myDemo project:

The demo project is printed as follows:

The 19:31:34 2019-08-02. 21984-814 the INFO [main] O.S.B.W.E mbedded. Tomcat. TomcatWebServer: tomcat started on the port (s) : 8080 (http) with context path' 'The 19:31:34 2019-08-02. 21984-818 the INFO [main] com. Example. Demo. DemoApplication: Started DemoApplicationin2.734 seconds (JVM is runningfor3.254) ran ApplicationRunner~ commandrunner css.demo.user.MyUser@589b028eCopy the code

Why is it ok to configure it? During springboot start in AutoConfigurationImportSelector# getAutoConfigurationEntry to call getCandidateConfigurations method, this method is the source code is as follows:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames( // Here to call EnableAutoConfiguration annotation 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;
}
Copy the code

GetSpringFactoriesLoaderFactoryClass method source code is as follows:

protected Class<? >getSpringFactoriesLoaderFactoryClass() {
	return EnableAutoConfiguration.class;
}
Copy the code

This is essentially using the configuration in the meta-INF/Spring. factories file with the Springboot Factories mechanism.

Third, summary

This paper analyzes general direction springboot roughly the boot process, unquestioning, in some places did not do in-depth research, but we learn the source code in order to absorb its essence and encoding to write better code, secondly, understand the relevant principle, convenient and more rapid orientation to solve the problem, if you have write wrong place, please correct me, welcome comments area the message communication, Thank you!