Why the jar of springboot can run independently

Posted by php.ajax.coder on Sun, 28 Jun 2020 04:08:50 +0200

Welcome to my GitHub

https://github.com/zq2599/blog_demos
Content: all original articles are classified, summarized and matched source code, including Java, Docker, Kubernetes, DevOPS, etc;

jar files that can run independently

When developing spring boot applications, it is a common way to start applications with Java jar commands. Today, let's learn about the technology behind this simple operation;

Develop demo

Develop a springboot application as the object of this study. The corresponding version information is as follows:

  • JDK: 1.8.0_211
  • springboot: 2.3.1.RELEASE
  • maven: 3.6.0

Next, develop the springboot application, which is very simple:

  1. The springboot application is named springbootstarterdemo, pom.xml Document content:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.bolingcavalry</groupId>
    <artifactId>springbootstarterdemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springbootstarterdemo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
  1. There is only one java class with an http interface:
package com.bolingcavalry.springbootstarterdemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;

@SpringBootApplication
@RestController
public class SpringbootstarterdemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootstarterdemoApplication.class, args);
    }
    @RequestMapping(value = "/hello")
    public String hello(){
        return "hello " + new Date();
    }
}
  1. Coding complete at pom.xml Execute command in directory
mvn clean package -U -DskipTests
  1. After the successful construction, get the file springbootstarterdemo-0.0.1 in the target directory- SNAPSHOT.jar
  2. This is the spring bootstarter demo-0.0.1- SNAPSHOT.jar At this time, execute java-jar springbootstarterdemo-0.0.1-SNAPSHOT.jar You can start the application, as shown in the following figure:

    Next, use this spring bootstarter demo-0.0.1- SNAPSHOT.jar To analyze the reason that jar files can be started independently;

What Java jar does

  • First, we need to find out what the Java jar command has done oracle official website A description of the command was found:

    If the -jar option is specified, its argument is the name of the JAR file containing class and resource files for the application. The startup class must be indicated by the Main-Class manifest header in its source code.

  • Show me my poor English translation again:

  1. When using the - jar parameter, the following parameter is the jar file name of (in this case, springbootstarterdemo-0.0.1-SNAPSHOT.jar);
  2. The jar file contains class and resource files;
  3. The main class is defined in the manifest file;
  4. The source code of main class specifies the startup class of the whole application; (in its source code)

To summarize:
Java jar will look for the manifest file in the jar, and find the real startup class there;

Exploring spring bootstarter demo-0.0.1- SNAPSHOT.jar

  1. springboot starterdemo-0.0.1- SNAPSHOT.jar It's the result of the previous spring boot project. It's a compressed package, which can be decompressed with common compression tools. My environment here is MacBook Pro, which can be decompressed with unzip;
  2. After decompression, there are many contents. First, we pay attention to the manifest. The following red box is the manifest file:
  3. Open the file in the red box above, as follows:
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Implementation-Title: springbootstarterdemo
Implementation-Version: 0.0.1-SNAPSHOT
Start-Class: com.bolingcavalry.springbootstarterdemo.Springbootstarter
 demoApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.3.1.RELEASE
Created-By: Maven Jar Plugin 3.2.0
Implementation-Vendor: Pivotal Software, Inc.
Main-Class: org.springframework.boot.loader.JarLauncher
  1. See the value of main class above org.springframework.boot.loader.JarLauncher , which corresponds to the previous java official document. It is the code of this jarlauncher class that specifies the real startup class;

Doubts appear

  1. stay MANIFEST.MF There is a line in the file:
Start-Class: com.bolingcavalry.springbootstarterdemo.Springbootstarter
 demoApplication
  1. In the previous official java document, only main class is mentioned, but start class is not mentioned;
  2. The value of start class is SpringbootstarterdemoApplication, which is the only class in our java code and only the real application startup class;
  3. So the question is: theoretically, the JarLauncher class will be executed when executing the Java jar command, but in fact, the SpringbootstarterdemoApplication will be executed. What happened?

guess

Before you start, I guess the reasons are as follows:

  1. The Java jar command will start JarLauncher;
  2. Start class is for JarLauncher;
  3. JarLauncher finds the springbootstarter demoapplication according to the start class and executes it;

Analyze JarLauncher

  1. First download the SpringBoot source code. I downloaded version 2.3.1. Address: https://github.com/spring-projects/spring-boot/releases/tag/v2.3.1.RELEASE
  2. The project of JarLauncher is spring boot loader. First, understand the inheritance relationship of JarLauncher, as shown in the following figure. It can be seen that JarLauncher inherits from ExecutableArchiveLauncher, while the parent class of ExecutableArchiveLauncher, Launcher, is at the top level, which is an abstract class:
  3. Java jar implements the main method of JarLauncher, as follows: instantiate a JarLauncher object, execute its launch method, and bring in all the input parameters:
public static void main(String[] args) throws Exception {
	new JarLauncher().launch(args);
}
  1. The above launch method is in the parent class Launcher:
protected void launch(String[] args) throws Exception {
    // The way to extract the jar and run it is called expanded mode
    // If it is expanded mode, jar loading through URL is not supported
    // If it is not expanded mode, you can load jar through URL
	if (!isExploded()) {
	    // If you allow the jar to be loaded through the URL, register the corresponding processing class here
		JarFile.registerUrlProtocolHandler();
	}
	// Create classLoader
	ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());
	// jarmode is a parameter used to create a docker image. It is used to generate an image with multiple layer information
	// I don't care about jarmode for the moment
	String jarMode = System.getProperty("jarmode");
	//If there is no jarmode parameter, the value of launchClass is returned from getMainClass()
	String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();
	launch(args, launchClass, classLoader);
}
  1. It can be seen that we should focus on the getMainClass() method. Before looking at this method, we first focus on the archive of an important member variable, which is the archive of the ExecutableArchiveLauncher, the parent class of JarLauncher. As can be seen below, this variable comes from the method createArchive:
public ExecutableArchiveLauncher() {
		try {
			this.archive = createArchive();
			this.classPathIndex = getClassPathIndex(this.archive);
		}
		catch (Exception ex) {
			throw new IllegalStateException(ex);
		}
	}
  1. Method from Launcher.createArchive , as shown below, it can be seen that the member variable archive is actually a JarFileArchive object:
protected final Archive createArchive() throws Exception {
		ProtectionDomain protectionDomain = getClass().getProtectionDomain();
		CodeSource codeSource = protectionDomain.getCodeSource();
		URI location = (codeSource != null) ? codeSource.getLocation().toURI() : null;
		String path = (location != null) ? location.getSchemeSpecificPart() : null;
		if (path == null) {
			throw new IllegalStateException("Unable to determine code source archive");
		}
		File root = new File(path);
		if (!root.exists()) {
			throw new IllegalStateException("Unable to determine code source archive from " + root);
		}
		return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root));
	}
  1. Now back to the getMainClass() method, you can see this.archive.getManifest Method returns META-INF/MANIFEST.MF The contents of the file, and then getValue(START_CLASS_ATTRIBUTE) method is actually from META-INF/MANIFEST.MF The start class attribute is obtained in:
@Override
	protected String getMainClass() throws Exception {
	    // The corresponding is JarFileArchive.getManifest method,
	    // After entering, I found that the corresponding JarFile.getManifest method,
	    // JarFile.getManifest Corresponding to META-INF/MANIFEST.MF Contents of the document
		Manifest manifest = this.archive.getManifest();
		String mainClass = null;
		if (manifest != null) {
		    // Corresponding to META-INF/MANIFEST.MF Properties of start class in file
			mainClass = manifest.getMainAttributes().getValue(START_CLASS_ATTRIBUTE);
		}
		if (mainClass == null) {
			throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);
		}
		return mainClass;
	}
  1. From the above analysis, we can see that the getMainClass() method returns META-INF/MANIFEST.MF Get start class attribute in com.bolingcavalry.springbootstarterdemo Spring bootstarter demoapplication, go back to the launch method again, and you can see that the final running code is launch(args, launchClass, classLoader), whose launchClass parameter is com.bolingcavalry.springbootstarterdemo.SpringbootstarterdemoApplication:
protected void launch(String[] args) throws Exception {
		if (!isExploded()) {
			JarFile.registerUrlProtocolHandler();
		}
		ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());
		String jarMode = System.getProperty("jarmode");
		// The launchClass here is equal to“ com.bolingcavalry.springbootstarterdemo.SpringbootstarterdemoApplication"
		String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();
		// This is where you start the spring bootstarter demo application
		launch(args, launchClass, classLoader);
	}
  1. Expand launch(args, launchClass, classLoader), and finally find the MainMethodRunner class:
public class MainMethodRunner {

	private final String mainClassName;

	private final String[] args;

	/**
	 * Create a new {@link MainMethodRunner} instance.
	 * @param mainClass the main class
	 * @param args incoming arguments
	 */
	public MainMethodRunner(String mainClass, String[] args) {
	    // mainClassName is assigned as“ com.bolingcavalry.springbootstarterdemo.SpringbootstarterdemoApplication"
		this.mainClassName = mainClass;
		this.args = (args != null) ? args.clone() : null;
	}

	public void run() throws Exception {
	    // Get the Class object of SpringbootstarterdemoApplication
		Class<?> mainClass = Class.forName(this.mainClassName, false, Thread.currentThread().getContextClassLoader());
		// Get the main method object of springbootstarter demoapplication
		Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
		mainMethod.setAccessible(true);
		// Execute main method by reflection
		mainMethod.invoke(null, new Object[] { this.args });
	}
}

Finally, the truth is revealed;

Summary

At last, make a brief summary as far as possible, first look at how jars are generated, as shown in the following figure. In the jar files generated by maven plug-in, there are common classes, jars, and java compliant ones MANIFEST.MF File, and, still MANIFEST.MF A configuration named start class is additionally generated in the file. Here is our application startup class, SpringbootstarterdemoApplication:

The startup class is JarLauncher. How does it relate to MANIFEST.MF What about the file association? As can be seen from the figure below, it is finally associated through the member variable manifestSupplier of JarFile class:

Take a look at the execution of the key code, as shown in the following figure:

So far, the basic principle of spring boot jar running independently has been clear. In the process of exploration, in addition to being familiar with the key code process, I have learned more about the files in jar. If you are learning spring boot, I hope this article can give you some references;

Official documents

  1. Last attached Official document of SpringBoot , you can see the start class description:
  2. The above documents explicitly mention that: start class defines the actual start class. At this time, you should be clear about everything and have such feelings;

Welcome to my official account: programmer Xin Chen.

Topics: Spring Java snapshot Maven