The jar package generated by SpringBoot
The executable jar package of Spring Boot is also called "fat jar", which is a jar that contains all three-party dependencies. The biggest difference between it and the traditional jar package is that it contains a lib directory and embedded web container.
Directory structure of executable jar package
After packaging through the maven command, there will be two jar packages, one is application-name.version-SNAPSHOT.jar and the other is application-name.version-SNAPSHOT.jar.original. The latter only contains local resources after application compilation, while the former introduces related third-party dependencies.
The directory structure after decompressing the former is as follows:
This directory is more complicated than the traditional jar command packaging structure, and the meaning of the directory is as follows:
- BOOT-INF/classes: The directory stores the compiled class files of the application.
- BOOT-INF/lib: The directory stores third-party JAR package files that the application depends on.
- META-INF: The directory stores application packaging information (Maven coordinates, pom files) and MANIFEST.MF files.
- org: The directory stores SpringBoot related class files.
Configuration file: MANIFEST.MF
The MANIFEST.MF file is located in the META-INF folder of the jar package, and its contents are as follows:
Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Archiver-Version: Plexus Archiver
Built-By: cindy
Start-Class: com.shinemo.wangge.web.MainApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Version: 2.3.3.RELEASE
Created-By: Apache Maven 3.6.3
Build-Jdk: 1.8.0_212
Main-Class: org.springframework.boot.loader.JarLauncher
The main method of the class corresponding to the Main-Class attribute is used as the program entry to start the application.
The Start-Class attribute defines the startup class of our project.
Executable jar package launcher: JarLauncher
When using the java -jar command to execute the executable jar file of the Spring Boot application, the command guides the standard executable jar file and reads the Main-Class attribute value of the META-INF/MANIFEST.MF file in the jar. This value represents The application execution entry class is the class containing the main method.
From the content of the MANIFEST.MF file, you can see that the Main-Class attribute defines org.springframework.boot.loader.JarLauncher, and JarLauncher is the launcher corresponding to the Jar file. And the startup class MainApplication of our project is defined in the Start-Class attribute,
JarLauncher will add the class files under BOOT-INF/classes and the dependent jars under BOOT-INF/lib to the classpath, and then call the Start-Class attribute of the META-INF/MANIFEST.MF file to complete the startup of the application.
The inheritance relationship of Launcher is as follows:
Starter realization principle
Startup class: JarLauncher
//JarLauncher.java
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);
}
}
The default constructor of JarLauncher is empty. Its parent class ExecutiveArchiveLauncher will call the createArchive method of the parent class Launcher to load the jar package. After the jar package is loaded, we can get all the resources inside.
//JarLauncher.java
//JarLauncher默认构造函数
public JarLauncher() {
}
//ExecutableArchiveLauncher.java
public ExecutableArchiveLauncher() {
try {
//开始加载jar包
this.archive = createArchive();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
//Launcher.java
protected final Archive createArchive() throws Exception {
//通过获取当前Class类的信息,查找到当前归档文件的路径
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);
}
//如果是目录,则创建ExplodedArchive,否则创建JarFileArchive
return (root.isDirectory() ? new ExplodedArchive(root)
: new JarFileArchive(root));
}
Core method: launch(String[] args)
The launch method actually calls the launch method of the parent class Launcher
// Launcher.java
protected void launch(String[] args) throws Exception {
//注册 Spring Boot 自定义的 URLStreamHandler 实现类,用于 jar 包的加载读取, 可读取到内嵌的jar包
JarFile.registerUrlProtocolHandler();
//创建自定义的 ClassLoader 实现类,用于从 jar 包中加载类。
ClassLoader classLoader = createClassLoader(getClassPathArchives());
//执行我们声明的 Spring Boot 启动类,进行 Spring Boot 应用的启动。
launch(args, getMainClass(), classLoader);
}
Simply put, it can create a reading jar
loader package of classes to ensure BOOT-INF/lib
classes and directory BOOT-classes
embedded jar
in the class can be loaded into a normal, after the execution start Spring Boot application.
Method one: registerUrlProtocolHandler
JarFile.registerUrlProtocolHandler();
// JarFile.java
public static void registerUrlProtocolHandler() {
// 获得 URLStreamHandler 的路径
String handlers = System.getProperty(PROTOCOL_HANDLER, "");
// 将 Spring Boot 自定义的 HANDLERS_PACKAGE(org.springframework.boot.loader) 补充上去
System.setProperty(PROTOCOL_HANDLER, ("".equals(handlers) ? HANDLERS_PACKAGE
: handlers + "|" + HANDLERS_PACKAGE));
// 重置已缓存的 URLStreamHandler 处理器们
resetCachedUrlHandlers();
}
The purpose of this method is through the org.springframework.boot.loader
package to set the "java.protocol.handler.pkgs"
environment variable, thus to use a custom implementation class URLStreamHandler Handler, treatment jar:
protocol URL.
利用java url协议实现扩展原理,自定义jar协议
将org.springframework.boot.loader包 追加到java系统 属性java.protocol.handler.pkgs中,实现自定义jar协议
java会在java.protocol.handler.pkgs系统属性指定的包中查找与协议同名的子包和名为Handler的类,
即负责处理当前协议的URLStreamHandler实现类必须在 <包名>.<协议名定义的包> 中,并且类名称必须为Handler
例如:
org.springframework.boot.loader.jar.Handler这个类 将用于处理jar协议
这个jar协议实现作用:
默认情况下,JDK提供的ClassLoader只能识别jar中的class文件以及加载classpath下的其他jar包中的class文件。
对于jar包中的jar包是无法加载的
所以spring boot 自己定义了一套URLStreamHandler实现类和JarURLConnection实现类,用来加载jar包中的jar包的class类文件
for example:
jar:file:C:/Users/Administrator/Desktop/demo/demo/target/jarlauncher-0.0.1-SNAPSHOT.jar!/lib/spring-boot-1.5.10.RELEASE.jar!/
jar:file:C:/Users/Administrator/Desktop/demo/demo/target/jarlauncher-0.0.1-SNAPSHOT.jar!/lib/spring-boot-1.5.10.RELEASE.jar!/org/springframework/boot/loader/JarLauncher.class
We see that if there is a jar package that contains a jar, or a jar package contains a class file in the jar package, it will be separated by !/. This method can only be processed by org.springframework.boot.loader.jar.Handler. It is a URL protocol extended internally by SpringBoot .
Usually, the resource separator in the jar is !/. The JarFile URL provided in the JDK only supports one layer of "!/", but Spring Boot extends this protocol to support multiple layers of "!/".
Method two: createClassLoader
ClassLoader classLoader = createClassLoader(getClassPathArchives());
getClassPathArchives()
// ExecutableArchiveLauncher.java
@Override
protected List<Archive> getClassPathArchives() throws Exception {
// <1> 获得所有 Archive
List<Archive> archives = new ArrayList<>(
this.archive.getNestedArchives(this::isNestedArchive));
// <2> 后续处理:是个空方法
postProcessClassPathArchives(archives);
return archives;
}
<1>
, The this::isNestedArchive
code segment to create an anonymous EntryFilter implementation class for filtering jar
packets unwanted directory. The purpose is to get filtered, BOOT-INF/classes/
directory classes, as well as BOOT-INF/lib/
embedded jar
package.
// JarLauncher.java
static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";
static final String BOOT_INF_LIB = "BOOT-INF/lib/";
@Override
protected boolean isNestedArchive(Archive.Entry entry) {
// 如果是目录的情况,只要 BOOT-INF/classes/ 目录
if (entry.isDirectory()) {
return entry.getName().equals(BOOT_INF_CLASSES);
}
// 如果是文件的情况,只要 BOOT-INF/lib/ 目录下的 `jar` 包
return entry.getName().startsWith(BOOT_INF_LIB);
}
<1> getNestedArchives() method implementation
//JarFileArchive.java
@Override
public List<Archive> getNestedArchives(EntryFilter filter) throws IOException {
List<Archive> nestedArchives = new ArrayList<>();
for (Entry entry : this) {
if (filter.matches(entry)) {
nestedArchives.add(getNestedArchive(entry));
}
}
return Collections.unmodifiableList(nestedArchives);
}
createClassLoader(List archives)
// ExecutableArchiveLauncher.java
protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
// 获得所有 Archive 的 URL 地址
List<URL> urls = new ArrayList<>(archives.size());
for (Archive archive : archives) {
urls.add(archive.getUrl());
}
// 创建加载这些 URL 的 ClassLoader
return createClassLoader(urls.toArray(new URL[0]));
}
protected ClassLoader createClassLoader(URL[] urls) throws Exception {
return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
}
Archive obtained based on an array, create a custom ClassLoader implementation class LaunchedURLClassLoader, through which to load the BOOT-INF/classes
class directory, and the BOOT-INF/lib
directory jar
class package.
方法三:launch(String[] args, String mainClass, ClassLoader classLoader)
launch(args, getMainClass(), classLoader);
protected void launch(String[] args, String mainClass, ClassLoader classLoader)
throws Exception {
// <1> 设置 LaunchedURLClassLoader 作为类加载器
Thread.currentThread().setContextClassLoader(classLoader);
// <2> 创建 MainMethodRunner 对象,并执行 run 方法,启动 Spring Boot 应用
createMainMethodRunner(mainClass, args, classLoader).run();
}
<1>
At: Set LaunchedURLClassLoader class loader as to guarantee that the jar
loaded package to the appropriate class.
getMainClass()
// ExecutableArchiveLauncher.java
@Override
protected String getMainClass() throws Exception {
// 获得启动的类的全名
Manifest manifest = this.archive.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;
}
From the jar
package MANIFEST.MF
file Start-Class
configuration item ,, we get Spring Boot set the main startup class.
createMainMethodRunner
protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args,
ClassLoader classLoader) {
return new MainMethodRunner(mainClass, args);
}
run()
public void run() throws Exception {
// <1> 加载 Spring Boot
Class<?> mainClass = Thread.currentThread().getContextClassLoader()
.loadClass(this.mainClassName);
// <2> 反射调用 main 方法
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
mainMethod.invoke(null, new Object[] {
this.args });
}
This method is responsible for the real startup of the final Spring Boot application .
SpringBoot custom class loader: LaunchedURLClassLoader
LaunchedURLClassLoader is spring-boot-loader
custom items class loader to realize the jar
package META-INF/classes
directory in the class and META-INF/lib
inline jar
package type of load .
The ClassLoader inherits from UrlClassLoader. UrlClassLoader loads the class by relying on the Url array passed in by the initial parameter, and tries to load the Class file from the resource pointed to by Url
//LaunchedURLClassLoader.java
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
Handler.setUseFastConnectionExceptions(true);
try {
try {
//尝试根据类名去定义类所在的包,即java.lang.Package,确保jar in jar里匹配的manifest能够和关联的package关联起来
definePackageIfNecessary(name);
}
catch (IllegalArgumentException ex) {
// Tolerate race condition due to being parallel capable
if (getPackage(name) == null) {
// This should never happen as the IllegalArgumentException indicates
// that the package has already been defined and, therefore,
// getPackage(name) should not return null.
//这里异常表明,definePackageIfNecessary方法的作用实际上是预先过滤掉查找不到的包
throw new AssertionError("Package " + name + " has already been "
+ "defined but it could not be found");
}
}
return super.loadClass(name, resolve);
}
finally {
Handler.setUseFastConnectionExceptions(false);
}
}
The method super.loadClass(name, resolve) will actually return to java.lang.ClassLoader#loadClass(java.lang.String, boolean), follow the parent delegation mechanism to find the class, but Bootstrap ClassLoader and Extension ClassLoader will not be found The classes that fat jar depends on will eventually come to Application ClassLoader and call java.net.URLClassLoader#findClass
Why introduce a custom class loader
Because SpringBoot
the nesting of Jar packages is realized, one Jar package can complete the operation of the entire program.
The introduction of custom class loader is to solve the problem of jar package nesting jar package, the AppClassLoarder that comes with the system does not support reading nested jar package
Why does SpringBoot copy all the files under the Loader class?
Because the program must have a startup entry after all, this entry has to be loaded by the application class loader, first load the SpringBoot Class Loader into memory, and then create a thread context loader through some subsequent operations to load third-party jars.
If SpringBoot Class Loader
you put it under the lib file, it cannot be loaded at all, because it does not conform to a standard specification of the jar file.