Alias interprets the source code of SpringBoot 2.6.0: start the application context refresh post-processing of process analysis (start completion event, Runner runner, ready event)

Posted by piet123 on Wed, 05 Jan 2022 12:41:56 +0100

1, Background

   we use his three articles to interpret the refresh of application context and have a general outline of the whole refresh process. This article mainly interprets some operations after the refresh of application context. As usual, we still review the whole process started, so that we can not get lost.

1.1. Overall process of run method

  the specific path of the class where the next few methods are located: org springframework. boot. SpringApplication

	public ConfigurableApplicationContext run(String... args) {
		// 1. Record the start time of startup in nanoseconds
		long startTime = System.nanoTime();
		
		// 2. Initialize startup context, initialize application context
		DefaultBootstrapContext bootstrapContext = createBootstrapContext();
		ConfigurableApplicationContext context = null;

		// 3. Set the headless attribute: "java.awt.headless". The default value is: true (there is no graphical interface)
		configureHeadlessProperty();

		// 4. Get all Spring run listeners
		SpringApplicationRunListeners listeners = getRunListeners(args);
		// Publish app start event
		listeners.starting(bootstrapContext, this.mainApplicationClass);
		try {
			// 5. Initialize default application parameter class (command line parameter)
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

			// 6. Prepare the Spring environment according to the running listener and application parameters
			ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
			// Configure ignore bean information
			configureIgnoreBeanInfo(environment);

			// 7. Create a Banner and print
			Banner printedBanner = printBanner(environment);

			// 8. Create application context
			context = createApplicationContext();
			// Set applicationStartup
			context.setApplicationStartup(this.applicationStartup);

			// 9. Prepare application context
			prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);

			// 10. Refresh application context (core)
			refreshContext(context);

			// 11. Post processing of application context refresh
			afterRefresh(context, applicationArguments);

			// 13. Time information, output log record and execution main class name
			Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
			}

			// 14. Publish application context startup completion event
			listeners.started(context, timeTakenToStartup);

			// 15. Execute all Runner
			callRunners(context, applicationArguments);
		} catch (Throwable ex) {
			// Run error handling
			handleRunFailure(context, ex, listeners);
			throw new IllegalStateException(ex);
		}
		try {
			// 16. Publish application context ready event (available)
			Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
			listeners.ready(context, timeTakenToReady);
		} catch (Throwable ex) {
			// Run error handling
			handleRunFailure(context, ex, null);
			throw new IllegalStateException(ex);
		}
		// 17. Return application context
		return context;
	}

1.2 scope of interpretation

  the scope of this article is as follows:

	// 11. Post processing of application context refresh
	afterRefresh(context, applicationArguments);

	// 13. Time information, output log record and execution main class name
	Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
	if (this.logStartupInfo) {
		new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
	}

	// 14. Publish application context startup completion event
	listeners.started(context, timeTakenToStartup);

	// 15. Execute all Runner
	callRunners(context, applicationArguments);

	try {
		// 16. Publish application context ready event (available)
		Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
		listeners.ready(context, timeTakenToReady);
	} catch (Throwable ex) {
		// Run error handling
		handleRunFailure(context, ex, null);
		throw new IllegalStateException(ex);
	}
	// 17. Return application context
	return context;

2, Post processing of application context refresh

  the specific path of the class where this method is located: org springframework. boot. SpringApplication

	protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
	}

  the post refresh in this is an empty implementation.

3, Time information, output log record and execution main class name

	// Calculate the time from start to refresh
	Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
	if (this.logStartupInfo) {
		new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
	}

  let's first look at the logStarted method. The specific path of the class where this method is located: org springframework. boot. StartupInfoLogger

class StartupInfoLogger {
	private final Class<?> sourceClass;

	StartupInfoLogger(Class<?> sourceClass) {
		this.sourceClass = sourceClass;
	}

	void logStarted(Log applicationLog, Duration timeTakenToStartup) {
		if (applicationLog.isInfoEnabled()) {
			// Get the startup information and output the log
			applicationLog.info(getStartedMessage(timeTakenToStartup));
		}
	}
	
	private CharSequence getStartedMessage(Duration timeTakenToStartup) {
		StringBuilder message = new StringBuilder();
		message.append("Started ");
		// Splice subclass information
		appendApplicationName(message);
		message.append(" in ");
		// Time to start the main class
		message.append(timeTakenToStartup.toMillis() / 1000.0);
		message.append(" seconds");
		try {
			// JVM runtime
			double uptime = ManagementFactory.getRuntimeMXBean().getUptime() / 1000.0;
			message.append(" (JVM running for ").append(uptime).append(")");
		} catch (Throwable ex) {
			// No JVM time available
		}
		return message;
	}

	private void appendApplicationName(StringBuilder message) {
		// Get main class name
		String name = (this.sourceClass != null) ? ClassUtils.getShortName(this.sourceClass) : "application";
		message.append(name);
	}
}

  the actual result is equivalent to

Started SpringbootApplication in 1.3 seconds (JVM running for 4.738)

4, Publish application context startup completion event

  the specific path of the class where this method is located: org springframework. boot. SpringApplicationRunListeners

class SpringApplicationRunListeners {

	private final Log log;

	private final List<SpringApplicationRunListener> listeners;
	
	void started(ConfigurableApplicationContext context, Duration timeTaken) {
		doWithListeners("spring.boot.application.started", (listener) -> listener.started(context, timeTaken));
	}
	
	private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction) {
		doWithListeners(stepName, listenerAction, null);
	}

	private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction,
			Consumer<StartupStep> stepAction) {
		StartupStep step = this.applicationStartup.start(stepName);
		this.listeners.forEach(listenerAction);
		if (stepAction != null) {
			stepAction.accept(step);
		}
		step.end();
	}
}

  the specific path of the class where this method is located: org springframework. boot. context. event. EventPublishingRunListener

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
	@Override
	public void started(ConfigurableApplicationContext context, Duration timeTaken) {
		// The context here is the annotation configservletwebserver ApplicationContext we obtained
		// Initialize ApplicationStartedEvent
		// Publishing events through application context ApplicationStartedEvent
		context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context, timeTaken));
		// Publish events through AvailabilityChangeEvent
		AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
	}
}

4.1,ApplicationStartedEvent

When it comes to publishing this event, let's first look at a class diagram. We know that the context at this time is annotationconfigservletwebserver ApplicationContext

  when the publishEvent method is called, it is also the parent class of the execution: org springframework. context. support. publishEvent method of abstractapplicationcontext

	@Override
	public void publishEvent(ApplicationEvent event) {
		publishEvent(event, null);
	}
	
	protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
		Assert.notNull(event, "Event must not be null");
		ApplicationEvent applicationEvent;
		// Determine whether event is an instance of ApplicationEvent
		if (event instanceof ApplicationEvent) {
			// Yes, turn to ApplicationEvent
			applicationEvent = (ApplicationEvent) event;
		} else {
			// No, it is decorated as applicationEvent through PayloadApplicationEvent 
			applicationEvent = new PayloadApplicationEvent<>(this, event);
			if (eventType == null) {
				eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
			}
		}
		// At this time, earlyApplicationEvents is null
		if (this.earlyApplicationEvents != null) {
			// Legacy events are not null
			this.earlyApplicationEvents.add(applicationEvent);
		} else {
			// If it is not null, broadcast the event through simpleapplicationeventmulticast
			getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
		}

		// Parent context is not empty
		if (this.parent != null) {
			// The parent context is an instance of AbstractApplicationContext
			if (this.parent instanceof AbstractApplicationContext) {
				// Convert to AbstractApplicationContext and publish the event
				((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
			} else {
				// Publish events directly using the parent context
				this.parent.publishEvent(event);
			}
		}
	}

  what getapplicationeventmulticast() actually gets is simpleapplicationeventmulticast, which publishes ApplicationStartedEvent events. I believe those who read my article will be very familiar with the calling process. For details, please refer to: Alias interpretation of SpringBoot 2.6.0 source code (2): listener analysis of starting process analysis

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {

	@Override
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		// The executor defaults to null
		Executor executor = getTaskExecutor();
		// 1. Get listener list based on event and event type
		// 2. Then traverse the listener list and call the listener methods respectively
		for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			if (executor != null) {
				executor.execute(() -> invokeListener(listener, event));
			} else {
				// call
				invokeListener(listener, event);
			}
		}
	}
	
}

  finally, getapplicationlisters (event, type) obtains two listeners:

  • BackgroundPreinitializer
  • DelegatingApplicationListener

  in fact, these two listeners do nothing

4.2,AvailabilityChangeEvent

public class AvailabilityChangeEvent<S extends AvailabilityState> extends PayloadApplicationEvent<S> {
	public static <S extends AvailabilityState> void publish(ApplicationContext context, S state) {
		Assert.notNull(context, "Context must not be null");
		publish(context, context, state);
	}
	public static <S extends AvailabilityState> void publish(ApplicationEventPublisher publisher, Object source,
			S state) {
		Assert.notNull(publisher, "Publisher must not be null");
		publisher.publishEvent(new AvailabilityChangeEvent<>(source, state));
	}
}

   after the actual implementation, let's look at a simple class diagram:

   in fact, when ApplicationEventPublisher calls the publishEvent method, it is AbstractApplicationContext to execute publishEvent. The process is the same as that in the above context. See the previous chapter for details. The listener results obtained here are as follows:

  • DelegatingApplicationListener
  • ApplicationAvailabilityBean

  DelegatingApplicationListener does nothing here, and ApplicationAvailabilityBean maps:
Map<Class<? extends AvailabilityState>, AvailabilityChangeEvent<?>> events = new HashMap<>(); Added a pair of mappings
The key is org springframework. boot. availability. Livenessstate, value is org springframework. boot. availability. AvailabilityChangeEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext]

5, Execute all Runner

  the specific path of the class where this method is located: org springframework. boot. SpringApplication

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

  actually, I haven't implemented anything here

6, Publishing ApplicationReadyEvent events

  the specific path of the class where this method is located: org springframework. boot. SpringApplicationRunListeners

class SpringApplicationRunListeners {

	private final Log log;

	private final List<SpringApplicationRunListener> listeners;
	
	void ready(ConfigurableApplicationContext context, Duration timeTaken) {
		doWithListeners("spring.boot.application.ready", (listener) -> listener.ready(context, timeTaken));
	}
	
	private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction) {
		doWithListeners(stepName, listenerAction, null);
	}

	private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction,
			Consumer<StartupStep> stepAction) {
		StartupStep step = this.applicationStartup.start(stepName);
		this.listeners.forEach(listenerAction);
		if (stepAction != null) {
			stepAction.accept(step);
		}
		step.end();
	}
}

  the specific path of the class where this method is located: org springframework. boot. context. event. EventPublishingRunListener

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
	@Override
	public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
		// The context here is the annotation configservletwebserver ApplicationContext we obtained
		// Initialize ApplicationReadyEvent
		// Publish event ApplicationReadyEvent through application context
		context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context, timeTaken));
		// Publish events through AvailabilityChangeEvent
		AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);
	}
}

   in fact, the calling process here is the same as the execution process of launching and completing the event before publishing the application context in this article. I won't say more. When publishing the event by the application context, the results obtained by the listener are as follows:

monitorexplain
SpringApplicationAdminMXBeanRegistrarThe ready traversal is set to true
BackgroundPreinitializerWait for the pre initialization task to complete (when preparing the environment), and reduce the pre initialization complete to 0
DelegatingApplicationListenerNothing was done

   publish the event through AvailabilityChangeEvent, and the results obtained from the listener are as follows:

monitorexplain
DelegatingApplicationListenerNothing was done
ApplicationAvailabilityBeanRefer to the results of chapter 4.2 in this paper

epilogue

    basically, the whole springboot startup process has been basically finished. We still have no explanation on the call of post processor, the execution of getBean method and the startup principle of main class. We look forward to your attention.