From: juejin. Cn/post / 684490…

Summary:

Some time ago, I saw an open source project in the SpringCloud community, focusing on enhancements to service discovery. While researching the project, I found the code to be concise, elegant and, most importantly, handy with the application of Spring IOC and AOP features. It is impossible to write such a good open source project without deep research into the source code. In addition, in the existing Springboot column, most of the posts are aimed at applications, integration of some middleware, etc., and the number of source code analysis blogs is limited. In view of the above two aspects, the series came into being.

This series is mainly the core source of Spring, but Springboot is very popular now, so we start with Springboot. The latest version is Springboot2.0.4, Spring5, so new features will be highlighted later in this series.

The whole series will be around the Springboot boot process for source analysis, in the whole process, will encounter some core classes or core processes, will focus on explaining, so the length may increase, be prepared.

Source code analysis

The first is the project startup class:

public static void main(String[] args) { SpringApplication.run(MarsApplication.class, args); } Duplicate codeCopy the code
public SpringApplication(Object... Initialize (sources) {// initialize(sources); } Duplicate codeCopy the code

When initialized, the meta-INF /spring.factories file is loaded:

	private void initialize(Object[] sources) {
		if(sources ! = null && sources.length > 0) { this.sources.addAll(Arrays.asList(sources)); } // Set the servlet environment this.webEnvironment = deduceWebEnvironment(); / / get ApplicationContextInitializer, began his first loaded here spring. Factories filessetInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); // Get the listener and load the spring.factories file for the second timesetListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); } Duplicate codeCopy the code

DeduceWebEnvironment () :

	private WebApplicationType deduceWebApplicationType() {
		if(ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null) && ! ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {return WebApplicationType.REACTIVE;
		}
		for (String className : WEB_ENVIRONMENT_CLASSES) {
			if(! ClassUtils.isPresent(className, null)) {returnWebApplicationType.NONE; }}returnWebApplicationType.SERVLET; } Duplicate codeCopy the code

This is done by checking whether the REACTIVE bytecode exists, and if not, the Web environment is a SERVLET type. The Web environment type is set here, and the corresponding environment is initialized later based on the type.

ApplicationContextInitializer is spring components spring – the context of an interface, mainly is the spring ioc container refresh before a callback interface, used in a custom logic. The implementation classes in the spring.Factories file:

Here there are 9 listeners:

# Application Listenersorg.springframework.context.ApplicationListener=\ org.springframework.boot.ClearCachesApplicationListener,\ org.springframework.boot.builder.ParentContextCloserApplicationListener,\ org.springframework.boot.context.FileEncodingApplicationListener,\ org.springframework.boot.context.config.AnsiOutputApplicationListener,\ org.springframework.boot.context.config.ConfigFileApplicationListener,\ org.springframework.boot.context.config.DelegatingApplicationListener,\ org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\ org.springframework.boot.context.logging.LoggingApplicationListener,\ Org. Springframework. Boot. Liquibase. LiquibaseServiceLocatorApplicationListener duplicate codeCopy the code

And one is: org. Springframework. Boot. Autoconfigure. BackgroundPreinitializer throughout the 10 listeners will springBoot the entire life cycle. More on that later.

So let’s continue the process here. Take a look at the run method:

public ConfigurableApplicationContext run(String... Args) {// StopWatch StopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); //java.awt.headless is a J2SE mode for system configuration in the absence of a display, keyboard, or mouse. Many monitoring tools such as JConsole require this value to be set totrue, the system variable defaults totrueconfigureHeadlessProperty(); // Get the listener variable in the Spring. factories with args as the specified parameter array and default as the current class SpringApplication. Get and start the listener SpringApplicationRunListeners listeners = getRunListeners (args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); // Create an environment ConfigurableEnvironment = prepareEnvironment(Listeners, applicationArguments); // Set the bean configureIgnoreBeanInfo(environment) to be ignored; // Print banner banner printedBanner =printBanner(environment); // Create container context = createApplicationContext(); // Step 4: Instantiation SpringBootExceptionReporter. Class, Used to support the report about the startup errors exceptionReporters = getSpringFactoriesInstances (SpringBootExceptionReporter. Class, new Class[] { ConfigurableApplicationContext.class }, context); PrepareContext (Context, environment, Listeners, applicationArguments, printedBanner); // Step 6: refresh the container refreshContext(context); AfterRefresh (Context, applicationArguments); stopWatch.stop();if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
			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);
		}
		returncontext; } Duplicate codeCopy the code
  • Step 1: Get and start the listener
  • Step 2: Construct the container environment
  • Step 3: Create the container
  • Step 4: instantiate SpringBootExceptionReporter. Class, used to support the report about the startup errors
  • Step 5: Prepare the container
  • Step 6: Refresh the container
  • Step 7: Refresh the extension interface after the container

The following is specific analysis.

One: Get and start the listener

1) Get the listener

SpringApplicationRunListeners listeners = getRunListeners(args); Methods of Tracking getRunListeners:

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

As you can see above, args itself is null by default, but in the method that gets the listener, GetSpringFactoriesInstances (SpringApplicationRunListener. Class, types, this, args) to the current object as a parameter, This method is used to get the listener corresponding to spring.Factories:

# Run Listenersorg.springframework.boot.SpringApplicationRunListener=\ Org. Springframework. Boot. Context. Event. EventPublishingRunListener duplicate codeCopy the code

The way to get factories across the springBoot framework is as follows:

	@SuppressWarnings("unchecked")
	private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<? >[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) { List<T> instances = new ArrayList<>(names.size());for(String name: names) {try {// Load the class file into memory class <? > instanceClass = ClassUtils.forName(name, classLoader); Assert.isAssignable(type, instanceClass); Constructor<? > constructor = instanceClass .getDeclaredConstructor(parameterTypes); / / mainly through reflection to create instances of T instance. = (T) BeanUtils instantiateClass (constructor, args); instances.add(instance); } catch (Throwable ex) { throw new IllegalArgumentException("Cannot instantiate " + type + ":"+ name, ex); }}returninstances; } Duplicate codeCopy the code

The above through reflection for instance will trigger EventPublishingRunListener constructor:

	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); }} Copy the codeCopy the code

Focus on the addApplicationListener method:

@Override public void addApplicationListener(ApplicationListener<? > listener) { synchronized (this.retrievalMutex) { // Explicitly remove targetfor a proxy, if registered already,
			// in order to avoid double invocations of the same listener.
			Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);
			if(singletonTarget instanceof ApplicationListener) { this.defaultRetriever.applicationListeners.remove(singletonTarget); } / / inner class object this. DefaultRetriever. ApplicationListeners. Add (the listener); this.retrieverCache.clear(); }} Copy the codeCopy the code

The methods defined in the SimpleApplicationEventMulticaster AbstractApplicationEventMulticaster superclass. The key code for this. DefaultRetriever. ApplicationListeners. Add (the listener); This is an inner class that holds all listeners. In this step, will spring. The listener to a SimpleApplicationEventMulticaster in factories. The inheritance relationship is as follows:

2) Start listener:

listeners.starting(); , get the listener to EventPublishingRunListener, can be seen from the name is to start the event listener, is mainly used to release start events.

	@Override
	public void starting() {// key code, Here is to create the application start event ` ApplicationStartingEvent ` enclosing initialMulticaster. MulticastEvent (new ApplicationStartingEvent(this.application, this.args)); } Duplicate codeCopy the code

This is performed as a springBoot framework EventPublishingRunListener listener, started the carrying out of the listener () method, will continue to publish events, or events. This implementation is largely based on Spring’s event mechanism. Continue to follow up SimpleApplicationEventMulticaster, there is a core method:

	@Override
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		ResolvableType type= (eventType ! = null ? eventType : resolveDefaultEventType(event));for(final ApplicationListener<? > listener : getApplicationListeners(event,type)) {// Get the thread pool, if empty, synchronize. Here the thread pool is empty and has not been initialized. Executor executor = getTaskExecutor();if(executor ! Execute (() -> invokeListener(Listener, event)); }else{// Send invokeListener(listener, event); }}} copy the codeCopy the code

ApplicationStartingEvent gets the corresponding listener based on the event type. After the container is started, the response action is executed. There are four types of listeners:

This is the first place that the springBoot listener is executed, by type, during startup. Depending on the type of event to be published, select the listener from the above 10 for event publishing, of course, more than 10 if you inherit from springCloud or other frameworks. Here selected a springBoot log listener to explain, the core code is as follows:

@override public void onApplicationEvent(ApplicationEvent event) {// When SpringBoot is startedif(event instanceof ApplicationStartedEvent) { onApplicationStartedEvent((ApplicationStartedEvent) event); } // SpringBoot's Environment is readyelse if(event instanceof ApplicationEnvironmentPreparedEvent) { onApplicationEnvironmentPreparedEvent( (ApplicationEnvironmentPreparedEvent) event); } // After the springBoot container environment is set upelse if(event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent((ApplicationPreparedEvent) event); } // When the container is closedelse if(event instanceof ContextClosedEvent && ((ContextClosedEvent) event) .getApplicationContext().getParent() == null) { onContextClosedEvent(); } // The container fails to startelse if(event instanceof ApplicationFailedEvent) { onApplicationFailedEvent(); }} Copy the codeCopy the code

Because our event type for ApplicationEvent executes onApplicationStartedEvent ((ApplicationStartedEvent) event); . SpringBoot sends events at various stages of the run process to execute the corresponding methods for the listener. Similar, other listener execution process is not described here, will be explained separately later. Continue with the flow.

Ii. Environment Construction:

ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments); Follow the method:

private ConfigurableEnvironment prepareEnvironment( SpringApplicationRunListeners listeners, ApplicationArguments ApplicationArguments) {// Create and configure the environment ConfigurableEnvironment environment = getOrCreateEnvironment(); / / configuration configureEnvironment (environment, applicationArguments getSourceArgs ()); / / publishing environment is ready to event, this is the second time publish event listeners. EnvironmentPrepared (environment);bindToSpringApplication(environment);
		if (this.webApplicationType == WebApplicationType.NONE) {
			environment = new EnvironmentConverter(getClassLoader())
					.convertToStandardEnvironmentIfNecessary(environment);
		}
		ConfigurationPropertySources.attach(environment);
		returnenvironment; } Duplicate codeCopy the code

Look at getOrCreateEnvironment () method, as mentioned earlier, the environment has been set for the servlet type, so here is the environment created by object is StandardServletEnvironment.

	private ConfigurableEnvironment getOrCreateEnvironment() {
		if(this.environment ! = null) {return this.environment;
		}
		if (this.webApplicationType == WebApplicationType.SERVLET) {
			return new StandardServletEnvironment();
		}
		returnnew StandardEnvironment(); } Duplicate codeCopy the code

The enumeration class WebApplicationType is a new feature of springBoot2 and is designed to address the reactive features introduced in spring5. Enumeration types are as follows:

Public enum WebApplicationType {// Do not need to run in the web container, Servlet is a servlet based web project, and REACTIVE} is a new feature in spring5Copy the code

Environment interface provides four kinds of way, StandardEnvironment, StandardServletEnvironment and MockEnvironment, StandardReactiveWebEnvironment, They represent common programs, Web programs, test program environments, and responsive Web environments, which will be explained later. On his return to return here just need to know the new StandardServletEnvironment (); Object to add the system variables and environment variables of the running machine to the MutablePropertySources object defined by its AbstractEnvironment parent class. A set of properties is defined in the MutablePropertySources object:

private final List<PropertySource<? >> propertySourceList = new CopyOnWriteArrayList<PropertySource<? > > (); Copy the codeCopy the code

At this point, system and environment variables have been loaded into the collection of configuration files, and it’s time to parse the configuration files in your project.

Look at the listeners. EnvironmentPrepared (environment); As mentioned above, here is the second release event. What event? As the name implies, the system environment initializes the completed event. The process of publishing events has been described above and will not be repeated here. Take a look at the listeners obtained by event type:

It can be seen that several of the listeners obtained and those obtained by the first release start event are duplicated, which also verifies that the listeners can be obtained multiple times, depending on the event type to distinguish the specific processing logic. The log listener was mentioned above. Mainly consider ConfigFileApplicationListener, the listener is very core, mainly used for processing project configuration. The properties and YML files in the project are loaded by its inner classes. To be specific, first method execution entry:

>

postProcessors = loadPostProcessors(); The following four types of processing classes are obtained:

# Environment Post ProcessorsOrg. Springframework. Boot. The env. EnvironmentPostProcessor = / / a @ FunctionalInterface functional interface org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor, / / for springCloud extension class org. Springframework. Boot. The env. SpringApplicationJsonEnvironmentPostProcessor, / / support json environment variable org. Springframework. Boot. The env. SystemEnvironmentPropertySourceEnvironmentPostProcessor / / springBoo2 provide a wrapper class, Mainly to ` StandardServletEnvironment ` packaged into ` SystemEnvironmentPropertySourceEnvironmentPostProcessor ` object replication codeCopy the code

The execution of the above three listener process, ConfigFileApplicationListener performs the logic of the class itself. The configuration file under the path specified by its internal class Loader loading project:

private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/"; Copy the codeCopy the code

At this point, the variable configuration of the project has been loaded. Let’s take a look:

There are six configuration files in descending order. That is, the preceding configuration variable overrides the following one. Keep this in mind when configuring variables for projects