Why can Spring Boot package run directly?

Author: Format http://fangjian0423.github.io/2017/05/31/springboot-executable-jar

Spring Boot provides a plug-in spring-boot-maven-plugin It is used to package the program into an executable jar package.

Add this plug-in to the pom file:

<build>
 <plugins>
 <plugin>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-maven-plugin</artifactId>
 </plugin>
 </plugins>
</build>

Package the generated executable-jar-1.0-SNAPSHOT.jar The internal structure is as follows:

├── META-INF
│   ├── MANIFEST.MF
│   └── maven
│       └── spring.study
│           └── executable-jar
│               ├── pom.properties
│               └── pom.xml
├── lib
│   ├── aopalliance-1.0.jar
│   ├── classmate-1.1.0.jar
│   ├── spring-boot-1.3.5.RELEASE.jar
│   ├── spring-boot-autoconfigure-1.3.5.RELEASE.jar
│   ├── ...
├── org
│   └── springframework
│       └── boot
│           └── loader
│               ├── ExecutableArchiveLauncher$1.class
│               ├── ...
└── spring
    └── study
        └── executablejar
            └── ExecutableJarApplication.class

Then you can directly execute the jar package to start the program:

java -jar executable-jar-1.0-SNAPSHOT.jar

See this article for several ways to run the Spring Boot application: Three ways to run Spring Boot applications.

There are four file types in the packed fat jar:

  1. META-INF folder: program entry, where MANIFEST.MF Information used to describe the jar package

  2. lib Directory: place the third-party dependent jar packages, such as some of springboot's jar packages

  3. Code related to spring boot loader

  4. Module's own code

MANIFEST.MF Contents of the document:

Manifest-Version: 1.0
Implementation-Title: executable-jar
Implementation-Version: 1.0-SNAPSHOT
Archiver-Version: Plexus Archiver
Built-By: Format
Start-Class: spring.study.executablejar.ExecutableJarApplication
Implementation-Vendor-Id: spring.study
Spring-Boot-Version: 1.3.5.RELEASE
Created-By: Apache Maven 3.2.3
Build-Jdk: 1.8.0_20
Implementation-Vendor: Pivotal Software, Inc.
Main-Class: org.springframework.boot.loader.JarLauncher

As we can see, its main class is org.springframework.boot.loader.JarLauncher , when we use Java jar to execute the jar package, we will call the main method of jarlauncher instead of the spring application we wrote.

So what's the role of JarLauncher?

It is a tool class for executing Application class provided by Spring Boot Loader, a tool provided by spring boot (the code related to spring loader is used in fat jar). It is equivalent to that Spring Boot Loader provides a set of standard jars for executing spring boot package.

WeChat official account: Java technology stack, back in the background: boot, can get my N Spring boot tutorial, all dry cargo.

Some abstract classes of Spring Boot Loader

Abstract class Launcher: the basic abstract class of various launchers, which is used to launch applications; used in conjunction with Archive; currently there are three implementations, namely JarLauncher, WarLauncher and PropertiesLauncher.

Archive: the basic abstract class of archive file. JarFileArchive is the abstraction of jar package files. It provides methods such as getUrl will return the URL corresponding to the archive; getManifest method will get the Manifest data, etc. ExplodedArchive is an abstraction of a file directory

JarFile: the encapsulation of jar packages. Each JarFileArchive corresponds to a JarFile. When the JarFile is constructed, the internal structure will be parsed to obtain the files or folders in the jar package. These files or folders will be encapsulated in the Entry and stored in the JarFileArchive. If the Entry is a jar, it will be resolved to JarFileArchive.

For example, the URL of a JarFileArchive is:

jar:file:/Users/format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/

Its corresponding JarFile is:

/Users/format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar

This JarFile has many entries, such as:

META-INF/
META-INF/MANIFEST.MF
spring/
spring/study/
....
spring/study/executablejar/ExecutableJarApplication.class
lib/spring-boot-starter-1.3.5.RELEASE.jar
lib/spring-boot-1.3.5.RELEASE.jar
...

Some URLs inside JarFileArchive that depend on jars (used by springboot org.springframework.boot.loader.jar.Handler Processor to handle these URLs):

jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-starter-web-1.3.5.RELEASE.jar!/

jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-loader-1.3.5.RELEASE.jar!/org/springframework/boot/loader/JarLauncher.class

We see that if jar package contains jar, or jar package contains class file in jar package, it will be separated by! / in this way org.springframework.boot.loader.jar.Handler Can handle, it's Spring Boot An internally extended URL protocol.

The execution process of JarLauncher

main method of JarLauncher:

public static void main(String\[\] args) {
 //Construct JarLauncher and call its launch method. Parameters are passed by the console
 new JarLauncher().launch(args);
}

When JarLauncher is constructed, the constructor of the parent class ExecutableArchiveLauncher will be called.

The construction method of ExecutableArchiveLauncher will construct the Archive internally, where JarFileArchive is constructed. In the process of constructing JarFileArchive, many things will be constructed, such as JarFile, Entry

JarLauncher's launch method:

protected void launch(String\[\] args) {
 try {
 //Set the registered custom URL handler in the system properties: org.springframework.boot.loader.jar.Handler .  If no handler is specified in the URL, it will be queried in the system properties
 JarFile.registerUrlProtocolHandler();
 //The getClassPathArchives method will find the corresponding third party under the lib directory to rely on JarFileArchive, and at the same time, it will also project its own JarFileArchive
 //Create ClassLoader according to the JarFileArchive collection from getClassPathArchives. A launchedrurlclassloader class loader will be constructed here. This class loader inherits URLClassLoader and uses the URL s of these JarFileArchive collections to construct URLClassPath
 //The parent loader of the launchedrurlclassloader class loader is the class loader of the current execution class JarLauncher
 ClassLoader classLoader = createClassLoader(getClassPathArchives());
 //The getMainClass method will go to the Manifest in the Archive of the project to find the class whose key is start class
 //Call overload method launch
 launch(args, getMainClass(), classLoader);
 }
 catch (Exception ex) {
 ex.printStackTrace();
 System.exit(1);
 }
}

//getMainClass method of Archive
//We'll find out spring.study.executablejar.ExecutableJarApplication
public String getMainClass() throws Exception {
 Manifest manifest = getManifest();
 String mainClass = null;
 if (manifest != null) {
 mainClass = manifest.getMainAttributes().getValue("Start-Class");
 }
 if (mainClass == null) {
 throw new IllegalStateException(
 "No 'Start-Class' manifest entry specified in " \+ this);
 }
 return mainClass;
}

//launch overload method
protected void launch(String\[\] args, String mainClass, ClassLoader classLoader)
 throws Exception {
 //Create a MainMethodRunner and pass args and start class to it
 Runnable runner = createMainMethodRunner(mainClass, args, classLoader);
 //Construct new thread
 Thread runnerThread = new Thread(runner);
 //Thread set class loader and name, and then start
 runnerThread.setContextClassLoader(classLoader);
 runnerThread.setName(Thread.currentThread().getName());
 runnerThread.start();
}

MainMethodRunner's run method:

@Override
public void run() {
 try {
 //Instantiate according to start class
 Class<?> mainClass = Thread.currentThread().getContextClassLoader()
 .loadClass(this.mainClassName);
 //Find the main method
 Method mainMethod = mainClass.getDeclaredMethod("main", String\[\].class);
 //Throw an exception if the main method does not exist
 if (mainMethod == null) {
 throw new IllegalStateException(
 this.mainClassName + " does not have a main method");
 }
 //Call
 mainMethod.invoke(null, new Object\[\] { this.args });
 }
 catch (Exception ex) {
 UncaughtExceptionHandler handler = Thread.currentThread()
 .getUncaughtExceptionHandler();
 if (handler != null) {
 handler.uncaughtException(Thread.currentThread(), ex);
 }
 throw new RuntimeException(ex);
 }
}

After the main method of start class is called, the Spring container will be constructed internally to start the built-in Servlet container and other processes. We have analyzed all these processes. Introduction to Spring Boo main class and directory structure , take a look at this one.

About the custom classloader LaunchedURLClassLoader

LaunchedURLClassLoader rewrites the loadClass method, that is, it modifies the default class loading method (see whether the class has been loaded or not first, and then the rules for actually loading classes are changed, instead of directly loading from the parent class loader). LaunchedURLClassLoader defines its own class loading rules:

private Class<?> doLoadClass(String name) throws ClassNotFoundException {

 // 1) Try the root class loader
 try {
 if (this.rootClassLoader != null) {
 return this.rootClassLoader.loadClass(name);
 }
 }
 catch (Exception ex) {
 // Ignore and continue
 }

 // 2) Try to find locally
 try {
 findPackage(name);
 Class<?> cls = findClass(name);
 return cls;
 }
 catch (Exception ex) {
 // Ignore and continue
 }

 // 3) Use standard loading
 return super.loadClass(name, false);
}

Load rule:

  1. If the root loader exists, call its load method. This is the root class loading. This is the ExtClassLoader

  2. Call the findClass method of launchdurlclassloader itself, that is, the findClass method of URLClassLoader

  3. Call the loadClass method of the parent class, that is, execute the default class loading order (starting from bootstrappclassloader and looking down from the bottom)

LaunchedURLClassLoader's own findClass method:

protected Class<?> findClass(final String name)
 throws ClassNotFoundException
{
 try {
 return AccessController.doPrivileged(
 new PrivilegedExceptionAction<Class<?>>() {
 public Class<?> run() throws ClassNotFoundException {
 //Resolve the class name to a path and add the. Class suffix
 String path = name.replace('.', '/').concat(".class");
 //Based on the previous third-party jar package dependency and its own jar package, get the URL array, and traverse to find the corresponding class name resources
 //For example, path is org/springframework/boot/loader/JarLauncher.class , it's in jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-loader-1.3.5.RELEASE.jar ! / found in
 //Then the URL of the found resource is jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-loader-1.3.5.RELEASE.jar!/org/springframework/boot/loader/JarLauncher.class
 Resource res = ucp.getResource(path, false);
 if (res != null) { //Resources found
 try {
 return defineClass(name, res);
 } catch (IOException e) {
 throw new ClassNotFoundException(name, e);
 }
 } else { //Throw ClassNotFoundException directly if the resource cannot be found
 throw new ClassNotFoundException(name);
 }
 }
 }, acc);
 } catch (java.security.PrivilegedActionException pae) {
 throw (ClassNotFoundException) pae.getException();
 }
}

Here is a test of LaunchedURLClassLoader:

//Registration org.springframework.boot.loader.jar.Handler URL protocol processor
JarFile.registerUrlProtocolHandler();
//Construct the LaunchedURLClassLoader class loader, where two URL s are used, respectively corresponding to the dependent packages spring boot loader and spring boot in the jar package. Separate them with "! /" org.springframework.boot.loader.jar.Handler Processor processing
LaunchedURLClassLoader classLoader = new LaunchedURLClassLoader(
 new URL\[\] {
 new URL("jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-loader-1.3.5.RELEASE.jar!/")
 , new URL("jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-1.3.5.RELEASE.jar!/")
 },
 LaunchedURLClassLoaderTest.class.getClassLoader());

//Load class
//These two classes will be found in the second step of local search (findClass method of URLClassLoader)
classLoader.loadClass("org.springframework.boot.loader.JarLauncher");
classLoader.loadClass("org.springframework.boot.SpringApplication");
//In the third step, use the default loading order to find out in the application class loader
classLoader.loadClass("org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration");

The role of Spring Boot Loader

Spring Boot A set of rules is defined in the executable jar package. For example, the third party depends on the jar package in the / lib directory. The URL path of the jar package uses the custom rule, which needs to be used org.springframework.boot.loader.jar.Handler Processor processing.

Its main class uses JarLauncher. If it is a war package, it uses WarLauncher to execute. Within these launchers, another thread will start the custom SpringApplication class.

These features are packaged through the spring boot Maven plugin plug-in.

Recommend to my blog to read more:

1.Java JVM, collection, multithreading, new features series

2.Spring MVC, Spring Boot, Spring Cloud series tutorials

3.Maven, Git, Eclipse, Intellij IDEA series tools tutorial

4.Latest interview questions of Java, backend, architecture, Alibaba and other large factories

Feel good, don't forget to like + forward!

Tags: Spring SpringBoot snapshot Maven

Posted on Tue, 19 May 2020 23:17:52 -0700 by Melville