Startup principle of SpringBoot

Posted by Shibby on Mon, 03 Jan 2022 07:52:05 +0100

To start a SpringBoot project, we only need to execute Java - jar XXX The jar command is OK. At this time, we start a web server container, which can access the corresponding interface through the interface address on the browser, but the whole startup process is transparent to us, so it is necessary to understand.

Start front

  • java -jar command: this command will find the manifest file in the jar, and then execute the main class configured therein;
  • MANIFEST file:
    The MANIFEST file in Spring Boot is / meta-inf / MANIFEST MF file, see its contents:
Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Implementation-Title: sdk-api-demo
Implementation-Version: 1.0.0
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Start-Class: com.xxx.xxx.XxxApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.6.1
Created-By: Maven Jar Plugin 3.2.0
Main-Class: org.springframework.boot.loader.JarLauncher

The main class we need is configured at the end, so when we execute the java -jar command, we actually execute org. Jar springframework. boot. loader. JarLauncher class. Looking up, there is also a start class parameter configuration. You can see that the value of this parameter configuration is the start class of our application project, so you can guess that there should be a call to XxxApplication class in JarLauncher class.

  • JarLauncher class: This is the entry class for the startup of the whole application. It will load all dependent jar packages under / BOOT-INF/lib /, and create a new thread to execute the main method. Look at its source code. Oh, by the way, if you want to see the source code, add dependencies first:
	<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-loader</artifactId>
        <scope>provided</scope>
    </dependency>

Source code:

public class JarLauncher extends ExecutableArchiveLauncher {

	static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";

	static final String BOOT_INF_LIB = "BOOT-INF/lib/";

	public JarLauncher() {
	}

	protected JarLauncher(Archive archive) {
		super(archive);
	}

	@Override
	protected boolean isNestedArchive(Archive.Entry entry) {
		if (entry.isDirectory()) {
			return entry.getName().equals(BOOT_INF_CLASSES);
		}
		return entry.getName().startsWith(BOOT_INF_LIB);
	}

	public static void main(String[] args) throws Exception {
		new JarLauncher().launch(args);
	}
}

In fact, this class has little content. We mainly look at the launch method. The launch method here calls the Launcher#launch method:

	protected void launch(String[] args) throws Exception {
		JarFile.registerUrlProtocolHandler();
		ClassLoader classLoader = createClassLoader(getClassPathArchives());
		// Look at the getMainClass method here
		launch(args, getMainClass(), classLoader);
	}

The getMainClass() method here will read the class path configured by the start class parameter above. ExecutableArchiveLauncher#getMainClass:

	@Override
	protected String getMainClass() throws Exception {
		// MANIFEST file
		Manifest manifest = this.archive.getManifest();
		String mainClass = null;
		if (manifest != null) {
			// Get the value of start class configuration
			mainClass = manifest.getMainAttributes().getValue("Start-Class");
		}
		if (mainClass == null) {
			throw new IllegalStateException(
					"No 'Start-Class' manifest entry specified in " + this);
		}
		return mainClass;
	}

After obtaining the value of the start class configuration, the MainMethodRunner#run method will be accessed through a series of call stacks:

	public void run() throws Exception {
		// mainClassName is the value of start class
		Class<?> mainClass = Thread.currentThread().getContextClassLoader()
				.loadClass(this.mainClassName);
		// Get main method
		Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
		// Use reflection to call the main method of the configuration class
		mainMethod.invoke(null, new Object[] { this.args });
	}

Here, the whole process comes to the familiar xxxapplication In the main (string [] args) method.

From xxxapplication The main (string [] args) method starts

Starting from the main method in the XxxApplication, it is the process of starting the spring container. Oh, it is also necessary to start the web container. From the main method, you can find the call of the SpringApplication#run method along the call stack.

  • SpringApplication#run:
	public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
			// Look here
			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);
		}
		return context;
	}

Ignore the others and look at the call of refreshContext(context), which is the method initialized by the spring container, and then go along the call stack to the SpringApplication#refresh method.

  • SpringApplication#refresh method:
	protected void refresh(ApplicationContext applicationContext) {
		Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
		((AbstractApplicationContext) applicationContext).refresh();
	}

You see, the AbstractApplicationContext#refresh method is called. You see that the ioc container initialization process is too familiar with this method, and the rest of the process is the content of spring. Oh, there is also the container startup. In the refresh method, there is a call to onRefresh method. This method is a hook method, which is not implemented in spring. The container startup is implemented in this method.

  • ReactiveWebServerApplicationContext#onRefresh
	@Override
	protected void onRefresh() {
		super.onRefresh();
		try {
			createWebServer();
		}
		catch (Throwable ex) {
			throw new ApplicationContextException("Unable to start reactive web server",
					ex);
		}
	}
  • ReactiveWebServerApplicationContext#createWebServer
	private void createWebServer() {
		WebServer localServer = this.webServer;
		// This is mainly for compatibility with external web container startup
		// If you already have a webServer, you won't start another webServer
		if (localServer == null) {
			this.webServer = getWebServerFactory().getWebServer(getHttpHandler());
		}
		initPropertySources();
	}

Look at the comments, and then look at the getWebServer method. We generally use tomcat, so let's look at the implementation of Tomcat.

  • TomcatReactiveWebServerFactory#getWebServer
	@Override
	public WebServer getWebServer(HttpHandler httpHandler) {
		Tomcat tomcat = new Tomcat();
		File baseDir = (this.baseDirectory != null ? this.baseDirectory
				: createTempDir("tomcat"));
		tomcat.setBaseDir(baseDir.getAbsolutePath());
		Connector connector = new Connector(this.protocol);
		tomcat.getService().addConnector(connector);
		customizeConnector(connector);
		tomcat.setConnector(connector);
		tomcat.getHost().setAutoDeploy(false);
		configureEngine(tomcat.getEngine());
		TomcatHttpHandlerAdapter servlet = new TomcatHttpHandlerAdapter(httpHandler);
		prepareContext(tomcat.getHost(), servlet);
		// Look here
		return new TomcatWebServer(tomcat, getPort() >= 0);
	}

This method mainly creates a Tomcat object and fills in some attributes. The main creation work is in the new TomcatWebServer. The new TomcatWebServer mainly calls the TomcatWebServer#initialize method. Look at this method directly.

  • TomcatWebServer#initialize:
	private void initialize() throws WebServerException {
		TomcatWebServer.logger
				.info("Tomcat initialized with port(s): " + getPortsDescription(false));
		synchronized (this.monitor) {
			try {
				addInstanceIdToEngineName();

				Context context = findContext();
				context.addLifecycleListener((event) -> {
					if (context.equals(event.getSource())
							&& Lifecycle.START_EVENT.equals(event.getType())) {
						// Remove service connectors so that protocol binding doesn't
						// happen when the service is started.
						removeServiceConnectors();
					}
				});

				// Look here, tomcat started
				this.tomcat.start();

				// We can re-throw failure exception directly in the main thread
				rethrowDeferredStartupExceptions();

				try {
				    // Binding class loader 
					ContextBindings.bindClassLoader(context, context.getNamingToken(),
							getClass().getClassLoader());
				}
				catch (NamingException ex) {
					// Naming is not enabled. Continue
				}

				// Start a background thread to run. If the main method runs, the tomcat thread will disappear.
				startDaemonAwaitThread();
			}
			catch (Exception ex) {
				stopSilently();
				throw new WebServerException("Unable to start embedded Tomcat", ex);
			}
		}
	}

This method is the method to start tomcat. Here, tomcat is also started.

Topics: Java Spring Spring Boot Spring Cloud