During the phone interview, the interviewer asked a question: Do you know what’s different about a Java-JAR launching Spring Boot project from a traditional JAR?

I didn’t know how to answer the question. After the interview, I knew that I probably failed the interview. I asked the interviewer how to consider this question.

I remember the interviewer saying… Does a custom classloader know? . (I didn’t hear the middle part)

I: originally from this respect to consider ah, thank the interviewer for giving directions!

After a quick study, also read the startup process source code, finally know he said the custom class loader, also know he asked the purpose of the question.

If you’ve ever worked on a Spring Boot project, you know that you can start a Spring Boot service using the java-jar XXX. Jar command. (If you haven’t already, you can read this in the future, and get a slight idea of how the Spring Boot project works.)

What does a rudimentary Java-jar do to launch our handwritten application (our project might be called XXXApplication.java)?

That’s the purpose of this article, to take a look at what Java-JAR does.

At least in the interview, I can talk, can say a couple of words, not like I can only oh oh oh…

Here’s a quick overview

Understand a technical point, directly into the source heap, the clouds in the fog, very uncomfortable, easy to let a person daunting.

At this time you can first from the overall or non-source point of view to understand its operating mechanism, the heart has a bottom, if interested, you can find some details, slowly break down, the effect may be better, more can let people stick to it.

This is also the way I prepare to learn the source code, just write it.

Although is also so persuade oneself, but still can’t understand, embarrassed, ha ha ha…

Let’s start with the java-jar XXX. Jar:

Spring Boot defines its own set of rules in the executable FAT JAR. For example, a third party depends on the JAR in the /lib directory. Jars of custom rules and the rules on the use of the URL path you need to use org. Springframework.. The boot loader. The jar. The Handler processor processing.

Fat jar of Main – Class use org. Springframework.. The boot loader. JarLauncher, meaning to execute Java jar XXX. The jar will trigger the execution of the Main method of JarLauncher first, Rather than let’s application of XXX. XXX. XXX. XXXApplication.

But don’t worry, JarLauncher#main will perform some logic, do some material preparation, and eventually trigger our xxxApplication #main to launch the application.

First look at a startup process overview, future research will not panic!

The main purpose of this diagram is to provide the calling relationship of the startup process.

Afraid of the timing diagram expression is not perfect, and then the brief code paste, ha ha…

Hint: The latter takes some patience.

Learn about some of the abstract concepts of Spring Boot

Spring Boot Loader to understand the abstract out of some concepts, to read the Spring Boot Loader source code some help

Launcher: The basic abstract class for various launchers, used to launch applications, in conjunction with Archive.

There are currently three implementations, namely

  1. JarLauncher
  2. WarLauncher
  3. PropertiesLauncher

The inheritance relationship is as follows

Archive: The underlying abstract class for the Archive file.

  1. JarFileArchive is an abstraction of a JAR package file.

    It provides methods such as getURL that will return the URL corresponding to this Archive. The GetManifest method gets the Manifest data, etc.

  2. Explodedarchive is an abstraction of a file directory.

JarFile: Encapsulation of JAR packages, one JarFile for each JarFileArchive. When JarFile is constructed, the internal structure is parsed to retrieve the various files or folders in the JAR package, which are wrapped in Entry and stored in JarFileArchive. If Entry is a JAR, it is resolved to JarFileArchive.

JarFile is a class that SpringBoot-Loader inherits from the JDK JarFile.

For example, a JarFileArchive URL would be:

The jar: file: C: \ Users \ Administrator \ Desktop \ demo \ demo \ target \ jarlauncher - 0.0.1 - the SNAPSHOT. The jar! /

Its corresponding JarFile is:

C: \ Users \ \ Administrator \ Desktop \ demo \ demo \ target \ jarlauncher - 0.0.1 - the SNAPSHOT. The jar

This JarFile has many entries, such as:

META-INF/ META-INF/MANIFEST.MF ...... The BOOT - INF/lib/spring - the BOOT - starter - 1.5.10. The jar BOOT - INF/lib/spring - the BOOT - 1.5.10. The jar...

Some of the internal dependencies of JarFileArchive are URLs corresponding to JARs

. (use SpringBoot org. Springframework. The boot loader. The jar. The Handler processor to handle these URL)

The jar: file: C: / Users/Administrator/Desktop/demo/demo/target/jarlauncher - 0.0.1 - the SNAPSHOT. The jar! / lib/spring - the boot - 1.5.10. The jar! / jar: file: C: / Users/Administrator/Desktop/demo/demo/target/jarlauncher - 0.0.1 - the SNAPSHOT. The jar! / lib/spring - the boot - 1.5.10. The jar! /org/springframework/boot/loader/JarLauncher.class

We saw that if a jar contains a jar, or if a jar contains a class file inside a jar, we would use! / separated, only this way org. Springframework.. The boot loader. Inside the jar. The Handler can handle, it is SpringBoot extend a URL protocol (actually this is very important, for the following custom loader, The extended URL protocol is the cornerstone).

The executable JAR directory structure

Note: Let’s use Spring Boot 1.5.10 for the analysis

I wanted to use Spring Boot 2.3.x as the debug environment directly, but after reading some web documents, I found that there are more concepts than 2.3.x and 1.x version, such as layered JarModel. I don’t know how to make it, so I gave it up first. You end up with the not-so-old version 1.5.10.

SpringBoot provides a plugin-spring-boot-maven-plugin for packaging programs into an executable JAR.

Add this plugin to your POM file:

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

Then we package the generated jarlauncher-0.0.1-snapshot.jar (we call it Fat jar) in the Terminal Execution Maven Package with the following structure:

├─ Exercises - Boot-inf │ ├─ Exercises - Application. Properties │ ├─ Com │ ├─ Exercises - Example │ ├─ Exercises - Exercises └ ─ JarlauncherApplication. Class │ └ ─ lib │ ├ ─ spring - the boot - 1.5.10. The jar │ ├ ─ spring - the boot loader - - 1.5.10. The jar │ ├ ─... └ ├─ Meta-Inf ├─ Manifest.MF ├─ Maven ├─ Com. Example ├─ Demo ├─ Pim.Properties │ ├─ Pim.xml ├─ Spring Framework └ ─ boot └ ─ loader ├ ─ ExecutableArchiveLauncher. Class ├ ─ JarLauncher. Class ├ ─ LaunchedURLClassLoader. Class ├ ─ the Launcher. The class ├ ─ MainMethodRunner. Class └ ─...

Inside the FAT JAR, there are three folders:

  1. The META-INF folder: Program entry, where MANIFEST.MF (Resource List) describes information about the JAR package
  2. The boot-inf directory: The place for our program code and the JAR packages that third parties depend on
  3. Spring Boot Loader related source code, we start the program on him

The contents of the MANIFEST.MF file:

Manifest-version: 1.0 implementation-title: demo implementation-version: 0.0.1-snapshot archiver-version: Plexus Archiver Built-By: Administrator Implementation-Vendor-Id: com.example Spring-Boot-Version: 1.5.10. Release implementation-vendor: Pivotal Software, Inc. Main-Class: org.springframework.boot.loader.JarLauncher Start-Class: com.example.jarlauncher.JarlauncherApplication Spring-Boot-Classes: BOOT-INF/classes/ Spring-Boot-Lib: Boot-inf /lib/ create-by: Apache Maven 3.5.2 build-jdk: 1.8.0_162 implementation-by: Apache Maven 3.5.2 build-jdk: 1.8.0_162 implementation-by: http://projects.spring.io/spring-boot/demo/

We see that its Main – Class is org. Springframework.. The boot loader. JarLauncher, when we use Java – jar jar package will call JarLauncher Main method, Rather than call we write com. Example. Jarlauncher. JarlauncherApplication.

Let’s take a look at the code to see how it actually works.

Jarlauncher implementation process

Hint: it may be better to combine the time sequence diagram in the overview with your day reading.

Jarlauncher’s main method:

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

JarLauncher is constructed when calling the superclass ExecutableArchiveLauncher constructor.

ExecutableArchiveLauncher inside the constructor to Archive structure, construct JarFileArchive here. There are many other things that are constructed during the construction of JarFileArchive, such as JarFile, Entry…

public abstract class ExecutableArchiveLauncher extends Launcher { private final Archive archive; / / the constructor initialization for a fat jar Archive public ExecutableArchiveLauncher () {enclosing Archive = createArchive (); } / / protected by a superclass the Launcher implementation final Archive createArchive () throws the Exception {ProtectionDomain ProtectionDomain = getClass().getProtectionDomain(); CodeSource codeSource = protectionDomain.getCodeSource(); URI location = (codeSource == null ? null : codeSource.getLocation().toURI()); String path = (location == null ? null : location.getSchemeSpecificPart()); 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());} return (root.isDirectory());} return (root.isDirectory()? new ExplodedArchive(root) : new JarFileArchive(root)); } @Override protected List<Archive> getClassPathArchives() throws Exception { List<Archive> archives = new ArrayList < Archive > (/ / for all internal some Arichive enclosing Archive. GetNestedArchives (new EntryFilter () {@ Override public Boolean matches(Entry entry) { return isNestedArchive(entry); }})); / / short, useless postProcessClassPathArchives (archives); return archives; }}

Jarlauncher’s launch method:

Protected void launch (String [] args) {try {/ / registered in the system properties custom URL protocol Handler: org. Springframework. Boot. Loader. The jar. The Handler. / / initializes the URL, if not specified processor in the URL, will go to in the system properties query JarFile. RegisterUrlProtocolHandler (); // The getClassPathArchives method looks for the corresponding third party dependency in the lib directory. You will also find the JarFileArchive of the project itself and create a ClassLoader based on the JarFileArchive collection from GetClassPathArchives. Here we will construct a launchedUrlClassLoader class loader, which inherits the URLClassLoader and uses the URLs of these JarFileArchive collections to construct URLClassPath // To add two more words, // 1. URLClasspath is an important attribute to create a custom ClassLoader. FindClass is based on this attribute. // 2. You can pay attention to the archive. getURL method when building the LaunchedUrlClassLoader, which involves the custom URL protocol handler, JarFile, etc. After all, it's up to them to implement Jar in Jar. ClassLoader classLoader = createClassLoader(getClassPathArchives()); // The getMainClass method finds the Manifest in the project's own Archive for the Class with the key start-class. // Call the overloaded method launch(args, getMainClass(), classLoader); } catch (Exception ex) { ex.printStackTrace(); System.exit(1); }} // Archive's getMainClass method, But by ExecutableArchiveLauncher implementation / / here will find out the Start - Class identification of com. Example. Jarlauncher. JarlauncherApplication this Class 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 (String[] args, String mainClass, String mainClass, String mainClass) ClassLoader) throws Exception {// Set the launchedUrlClassLoader to the thread context loader Thread.currentThread().setContextClassLoader(classLoader); // Create a MainMethodRunner and run CreateMainMethodRunner (mainClass, args, classLoader).run(); }

Mainmethodrunner’s run method:

Public void run() throws Exception {// Class<? > mainClass = Thread.currentThread().getContextClassLoader() .loadClass(this.mainClassName); Good, the start process is finished. Happy! Can be associated with some of the interviewers Method mainMethod = mainClass. GetDeclaredMethod (" main ", String [] class); mainMethod.invoke(null, new Object[] { this.args }); }

After the main method of the start-class is called, the internal Spring container is constructed, and the built-in Servlet container is started. (The rest of this article is not the focus of this article, and I will not go into details 😂)

All right, so far we have the whole Java-Jar startup process to understand again, happy!

About custom classloaders

See the legendary LaunchedURLClassLoader what magic

The launchedUrlClassLoader overrides the loadClass method, so walk through it

protected Class<? > loadClass(String name, boolean resolve) throws ClassNotFoundException { Handler.setUseFastConnectionExceptions(true); // define the package before calling findClass, make sure the nested JAR list is associated with the package definepackageifNecessary (name); } catch (IllegalArgumentException ex) { if (getPackage(name) == null) { throw new AssertionError("Package " + name + " has already been " + "defined but it could not be found"); }} // call super.loadClass(name, resolve) return super.loadClass(name, resolve); } finally { Handler.setUseFastConnectionExceptions(false); }}

Just look at the 1.5.10 version of LoadClass implementation above, which is pretty much a normal parent delegate process.

And the launchedURLClassLoader uses findClass that is inherited from the parent URLClassLoader.

Eventually the LoadClass will go to the parent class of the LaunchedUrlClassLoader, UrlClassLoader# findClass

protected Class<? > findClass(final String name) throws ClassNotFoundException { final Class<? > result; try { result = AccessController.doPrivileged( new PrivilegedExceptionAction<Class<? >>() { public Class<? > run() throws ClassNotFoundException {String path = name.replace('.', '/').concat(".class"); / / based on prior to the third party jar package dependence and their jars get URL array, to traverse to find the corresponding class name of the resource / / such as path org/springframework/boot loader/JarLauncher. Class, It's in the jar: file: / Users/Format/Develop/gitrepository springboot - analysis/springboot - the executable jar/target/the executable jar - 1.0 - S NAPSHOT.jar! / lib/spring - the boot loader - 1.3.5. The jar! Is found in // Then find out the resources of the corresponding URL for the jar: file: / Users/Format/Develop/gitrepository springboot - analysis/springboot - the executable jar/target/executab Le - jar - 1.0 - the SNAPSHOT jar! / lib/spring - the boot loader - 1.3.5. The jar! / org/springframework/boot loader/JarLauncher. The class / / loaded fatjar a key part of the class!!!!!! Resource res = ucp.getResource(path, false); if (res ! = null) {try {return defineClass(name, res); } catch (IOException e) { throw new ClassNotFoundException(name, e); } } else { throw new ClassNotFoundException(name); } } }, acc); } catch (java.security.PrivilegedActionException pae) { throw (ClassNotFoundException) pae.getException(); } if (result == null) { throw new ClassNotFoundException(name); } return result; }

Resource res = ucp.getResource(path, false); It’s done here.

UCP stands for sun.misc.urlclasspath provided by JDK

Another diagram is drawn to see the underlying component support involved in URLClassPath# GetResource.

Can use the URL, URLStreamHandler, org. Springframework.. The boot loader. The jar. The Handler, eventually get to the Resource, to complete the class load.

So,

Personal conclusion: LaunchedURLClassLoader with the help of others key also was extension of Spring Boot URL jar protocol Archeive, abstraction of JarFile

LaunchedUrlClassLoader loads tests

Let’s manually simulate the loading process of JarLauncher, create LaunchedurlClassLoader, and then load a class to see if it works.

public class LaunchedURLClassLoaderTest { public static void main(String[] args) throws Exception { // Registered org. Springframework. Boot. Loader. The jar. The Handler URL protocol Handler JarFile.. registerUrlProtocolHandler (); // Construct the launchedUrlClassLoader class loader, using 1 URL, Corresponding jar package depends on package spring - the boot loader / / will use org. Springframework.. The boot loader. The jar. The Handler processor LaunchedURLClassLoader this  = new LaunchedURLClassLoader( new URL[]{ new URL (" jar: file: C: / Users/Administrator/Desktop/demo/demo/target/jarlauncher - 0.0.1 - the SNAPSHOT. The jar! / BOOT - INF/lib/spring - the BOOT loader - 1.5.10. The jar! /") }, DemoApplication.class.getClassLoader()); / / load this loadClass (" org. Springframework. Boot. Loader. JarLauncher "); }}

After running through this case, is the JarLauncher launch process OK?

A complimented IDEA Debug FAT JAR boot environment

How to debug the Spring Boot Loader directly?

So let’s do this. It’s very simple. It’s going to take a few minutes.

The code for

Initialize a SpringBoot application directly in start.spring.io (version 1.5.10).

Let me give you a Git code template, click to clone

Note that Maven will add Spring-Boot-Loader dependencies to the JAR.

<! -- Spring Boot loader --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-loader</artifactId> </dependency>

The MVN Package then packages the application as an executable JAR.

The IDEA of configuration

1. Configure to launch as a JAR application

2. Configure the JAR path and Apply

3. Find JarLauncher class, press the breakpoint, and start with debug mode

References

Spring Boot application startup theory (2) extends the URLClassLoader to implement the nested JAR load

SpringBoot can execute the JAR package

End, scatter flowers.

Did you get it?