1. Jar包目录分析
1.1 spring boot 2.0.0 项目打包成 jar后目录结构
├── META-INF
│ ├── MANIFEST.MF (Jar.Properties?)
├── BOOT-INF
│ ├── classes 项目源代码
│ ├── lib 项目jar包
└── org
└── springframework
└── boot
└── loader
├── ExecutableArchiveLauncher.class
├── JarLauncher.class
├── JavaAgentDetector.class
├── LaunchedURLClassLoader.class
├── Launcher.class
├── MainMethodRunner.class
├── ...
1.2 MANIFEST.MF 文件
Manifest-Version: 1.0
Implementation-Title: TeamManager
Implementation-Version: 0.0.1-SNAPSHOT
Built-By: MRZhao
Implementation-Vendor-Id: com.zhao
Spring-Boot-Version: 2.0.0.RELEASE
//以jar包形式启动的主函数 主启动器
Main-Class: org.springframework.boot.loader.JarLauncher
//自己项目 Main函数入口
Start-Class: com.zhao.Application
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Created-By: Apache Maven 3.3.3
Build-Jdk: 1.8.0_31
Implementation-URL: https://projects.spring.io/spring-boot/#/spring-bo
ot-starter-parent/TeamManager
2.Archive的概念
1.Archive即归档文件,这个概念在linux下比较常见
2.Archive 略等价于 jar包或者文档目录的抽象
package org.springframework.boot.loader.archive;
public abstract interface Archive extends Iterable<Entry>{
//返回此jar包的url(定位符)
public abstract URL getUrl()
//获取lib下/jar包列表
public abstract Manifest getManifest()
public abstract List<Archive> getNestedArchives(EntryFilter paramEntryFilter)
public static abstract interface EntryFilter {
public abstract boolean matches(Archive.Entry paramEntry);
}
public static abstract interface Entry {
public abstract boolean isDirectory();
public abstract String getName();
}
}
3.JarLauncher
从MANIFEST.MF可以看到Main函数是JarLauncher,下面来分析它的工作流程。JarLauncher类的继承结构是:
class JarLauncher
extends ExecutableArchiveLauncher
class ExecutableArchiveLauncher
extends Launcher
以demo-0.0.1-SNAPSHOT.jar创建一个Archive:
JarLauncher先找到自己所在的jar,即demo-0.0.1-SNAPSHOT.jar的路径,然后创建了一个Archive。
下面的代码展示了如何从一个类找到它的加载的位置的技巧:
protected final Archive createArchive() throws 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() ? new ExplodedArchive(root)
: new JarFileArchive(root));
}
获取lib/下面的jar,并创建一个LaunchedURLClassLoader
JarLauncher创建好Archive之后,通过getNestedArchives函数来获取到demo-0.0.1-SNAPSHOT.jar/lib下面的所有jar文件,并创建为List。
注意上面提到,Archive都是有自己的URL的。
获取到这些Archive的URL之后,也就获得了一个URL[]数组,用这个来构造一个自定义的ClassLoader:LaunchedURLClassLoader。
创建好ClassLoader之后,再从MANIFEST.MF里读取到Start-Class,即com.example.SpringBootDemoApplication,然后创建一个新的线程来启动应用的Main函数。
/**
* Launch the application given the archive file and a fully configured classloader.
*/
protected void launch(String[] args, String mainClass, ClassLoader classLoader)
throws Exception {
Runnable runner = createMainMethodRunner(mainClass, args, classLoader);
Thread runnerThread = new Thread(runner);
runnerThread.setContextClassLoader(classLoader);
runnerThread.setName(Thread.currentThread().getName());
runnerThread.start();
}
/**
* Create the {@code MainMethodRunner} used to launch the application.
*/
protected Runnable createMainMethodRunner(String mainClass, String[] args,
ClassLoader classLoader) throws Exception {
Class<?> runnerClass = classLoader.loadClass(RUNNER_CLASS);
Constructor<?> constructor = runnerClass.getConstructor(String.class,
String[].class);
return (Runnable) constructor.newInstance(mainClass, args);
}
LaunchedURLClassLoader
LaunchedURLClassLoader和普通的URLClassLoader的不同之处是,它提供了从Archive里加载.class的能力。
结合Archive提供的getEntries函数,就可以获取到Archive里的Resource。当然里面的细节还是很多的,下面再描述。
spring boot应用启动流程总结
看到这里,可以总结下Spring Boot应用的启动流程:
- spring boot应用打包之后,生成一个fat jar,里面包含了应用依赖的jar包,还有Spring boot loader相关的类
- Fat jar的启动Main函数是JarLauncher,它负责创建一个LaunchedURLClassLoader来加载/lib下面的jar,并以一个新线程启动应用的Main函数。