boot - spring boot jar 启动过程
目录结构
一般的,一个sprign boot package jar 包之后的 如下,这里以 boot-mvc-0.0.1-SNAPSHOT.jar
为例子
.
├── BOOT-INF
│ ├── classes # 当前生成编译生成的 class
│ └── lib # 依赖的第三方jar包 目录
├── META-INF
│ ├── MANIFEST.MF # 打包的一些类信息
│ └── maven
└── org.springframework.boot.loader
├── archive
├── ClassPathIndexFile.class
├── data
├── ExecutableArchiveLauncher.class
├── jar
├── JarLauncher.class # jar 包启动类
├── jarmode
├── LaunchedURLClassLoader$DefinePackageCallType.class
├── LaunchedURLClassLoader$UseFastConnectionExceptionsEnumeration.class
├── LaunchedURLClassLoader.class
├── Launcher.class
├── MainMethodRunner.class
├── PropertiesLauncher$1.class
├── PropertiesLauncher$ArchiveEntryFilter.class
├── PropertiesLauncher$ClassPathArchives.class
├── PropertiesLauncher$PrefixMatchingArchiveFilter.class
├── PropertiesLauncher.class
├── util
└── WarLauncher.class # war 包启动类
MANIFEST.MF
这个文件主要记录和描述了 Main-Class
, Start-Class
等等
Manifest-Version: 1.0
Created-By: Maven Jar Plugin 3.2.0
Build-Jdk-Spec: 15
Implementation-Title: boot-mvc
Implementation-Version: 0.0.1-SNAPSHOT
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.x.z.bootmvc.BootMvcApplication
Spring-Boot-Version: 2.4.0
Spring-Boot-Classes: BOOT-INF/classes/ # 当前工程编译生成 class 目录
Spring-Boot-Lib: BOOT-INF/lib/ # 该jar 包依赖的第三方 jar
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
spring boot 是如何被加载启动的
当我们运行 java -jar boot-mvc-0.0.1-SNAPSHOT.jar
的时候就能启动服务,那这个过程是如何触发和加载的?
- 启动的入口在哪里
- 怎么加载到这些依赖的jar包
带着这些问题,我们继续看
JarLauncher.java
顾名思义,这个应该就是启动 boot-mvc.jar 的主类
public class JarLauncher extends ExecutableArchiveLauncher {
private static final String DEFAULT_CLASSPATH_INDEX_LOCATION = "BOOT-INF/classpath.idx";
static final Archive.EntryFilter NESTED_ARCHIVE_ENTRY_FILTER;
static {
NESTED_ARCHIVE_ENTRY_FILTER = (entry -> entry.isDirectory()
? entry.getName().equals("BOOT-INF/classes/") : entry.getName().startsWith("BOOT-INF/lib/"));
}
public JarLauncher() {}
protected JarLauncher(Archive archive) {
super(archive);
}
protected ClassPathIndexFile getClassPathIndex(Archive archive) throws IOException {
if (archive instanceof org.springframework.boot.loader.archive.ExplodedArchive) {
String location = getClassPathIndexFileLocation(archive);
return ClassPathIndexFile.loadIfPossible(archive.getUrl(), location);
}
return super.getClassPathIndex(archive);
}
private String getClassPathIndexFileLocation(Archive archive) throws IOException {
Manifest manifest = archive.getManifest();
Attributes attributes = (manifest != null) ? manifest.getMainAttributes() : null;
String location = (attributes != null) ? attributes.getValue("Spring-Boot-Classpath-Index") : null;
return (location != null) ? location : "BOOT-INF/classpath.idx";
}
protected boolean isPostProcessingClassPathArchives() {
return false;
}
protected boolean isSearchCandidate(Archive.Entry entry) {
return entry.getName().startsWith("BOOT-INF/");
}
protected boolean isNestedArchive(Archive.Entry entry) {
return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry);
}
public static void main(String[] args) throws Exception {
(new JarLauncher()).launch(args);
}
}
ExecutableArchieveLauncher.java
public abstract class ExecutableArchiveLauncher extends Launcher {
private static final String START_CLASS_ATTRIBUTE = "Start-Class";
protected static final String BOOT_CLASSPATH_INDEX_ATTRIBUTE = "Spring-Boot-Classpath-Index";
private final Archive archive;
private final ClassPathIndexFile classPathIndex;
public ExecutableArchiveLauncher() {
try {
this.archive = createArchive();
this.classPathIndex = getClassPathIndex(this.archive);
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
protected ExecutableArchiveLauncher(Archive archive) {
try {
this.archive = archive;
this.classPathIndex = getClassPathIndex(this.archive);
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
protected ClassPathIndexFile getClassPathIndex(Archive archive) throws IOException {
return null;
}
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;
}
protected ClassLoader createClassLoader(Iterator<Archive> archives) throws Exception {
List<URL> urls = new ArrayList<>(guessClassPathSize());
while (archives.hasNext())
urls.add(((Archive)archives.next()).getUrl());
if (this.classPathIndex != null)
urls.addAll(this.classPathIndex.getUrls());
return createClassLoader(urls.<URL>toArray(new URL[0]));
}
private int guessClassPathSize() {
if (this.classPathIndex != null)
return this.classPathIndex.size() + 10;
return 50;
}
protected Iterator<Archive> getClassPathArchivesIterator() throws Exception {
Archive.EntryFilter searchFilter = this::isSearchCandidate;
Iterator<Archive> archives = this.archive.getNestedArchives(searchFilter, entry ->
(isNestedArchive(entry) && !isEntryIndexed(entry)));
if (isPostProcessingClassPathArchives())
archives = applyClassPathArchivePostProcessing(archives);
return archives;
}
private boolean isEntryIndexed(Archive.Entry entry) {
if (this.classPathIndex != null)
return this.classPathIndex.containsEntry(entry.getName());
return false;
}
private Iterator<Archive> applyClassPathArchivePostProcessing
(Iterator<Archive> archives) throws Exception {
List<Archive> list = new ArrayList<>();
while (archives.hasNext())
list.add(archives.next());
postProcessClassPathArchives(list);
return list.iterator();
}
protected boolean isSearchCandidate(Archive.Entry entry) {
return true;
}
protected boolean isPostProcessingClassPathArchives() {
return true;
}
protected void postProcessClassPathArchives(List<Archive> archives) throws Exception {}
protected boolean isExploded() {
return this.archive.isExploded();
}
protected final Archive getArchive() {
return this.archive;
}
protected abstract boolean isNestedArchive(Archive.Entry paramEntry);
}
Lanucher.java
public abstract class Launcher {
private static final String JAR_MODE_LAUNCHER
= "org.springframework.boot.loader.jarmode.JarModeLauncher";
protected void launch(String[] args) throws Exception {
if (!isExploded())
JarFile.registerUrlProtocolHandler();
ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());
String jarMode = System.getProperty("jarmode");
String launchClass = (jarMode != null && !jarMode.isEmpty()) ?
"org.springframework.boot.loader.jarmode.JarModeLauncher" : getMainClass();
launch(args, launchClass, classLoader);
}
@Deprecated
protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
return createClassLoader(archives.iterator());
}
protected ClassLoader createClassLoader(Iterator<Archive> archives) throws Exception {
List<URL> urls = new ArrayList<>(50);
while (archives.hasNext())
urls.add(((Archive)archives.next()).getUrl());
return createClassLoader(urls.<URL>toArray(new URL[0]));
}
protected ClassLoader createClassLoader(URL[] urls) throws Exception {
return new LaunchedURLClassLoader(isExploded(), getArchive(), urls, getClass().getClassLoader());
}
protected void launch(String[] args, String launchClass,
ClassLoader classLoader) throws Exception {
Thread.currentThread().setContextClassLoader(classLoader);
createMainMethodRunner(launchClass, args, classLoader).run();
}
protected MainMethodRunner createMainMethodRunner(String mainClass,
String[] args, ClassLoader classLoader) {
return new MainMethodRunner(mainClass, args);
}
protected abstract String getMainClass() throws Exception;
protected Iterator<Archive> getClassPathArchivesIterator() throws Exception {
return getClassPathArchives().iterator();
}
@Deprecated
protected List<Archive> getClassPathArchives() throws Exception {
throw new IllegalStateException("Unexpected call to getClassPathArchives()");
}
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() ?
(Archive)new ExplodedArchive(root) : (Archive)new JarFileArchive(root);
}
protected boolean isExploded() {
return false;
}
protected Archive getArchive() {
return null;
}
}
MainMethodRunner
public class MainMethodRunner {
private final String mainClassName;
private final String[] args;
public MainMethodRunner(String mainClass, String[] args) {
this.mainClassName = mainClass;
this.args = (args != null) ? (String[])args.clone() : null;
}
public void run() throws Exception {
Class<?> mainClass = Class.forName(this.mainClassName, false,
Thread.currentThread().getContextClassLoader());
Method mainMethod = mainClass.getDeclaredMethod("main", new Class[] { String[].class });
mainMethod.setAccessible(true);
mainMethod.invoke(null, new Object[] { this.args });
}
}
启动流程
-
根据 MANIFEST.MF 文件找到 /BOOT-INF/classes 和 /BOOT-INF/lib 目录,并加载
-
创建 ClassLoader,此时为 LaunchedURLClassLoader
-
根据 MANIFEST.MF 文件找到 Start-Class 属性的 主类,这里是
com.x.z.bootmvc.BootMvcApplication
-
设置当前线程上下文加载器为 ClassLoader
-
从当前线程获取ClassLoader,即 LaunchedURLClassLoader, 加载 mainClass(即 start-class)
-
利用反射执行 mainClass 中的 main 方法, 即执行
om.x.z.bootmvc.BootMvcApplication
中的 main 方法
WarLauncher
public class WarLauncher extends ExecutableArchiveLauncher {
public WarLauncher() {}
protected WarLauncher(Archive archive) {
super(archive);
}
protected boolean isPostProcessingClassPathArchives() {
return false;
}
protected boolean isSearchCandidate(Archive.Entry entry) {
return entry.getName().startsWith("WEB-INF/");
}
public boolean isNestedArchive(Archive.Entry entry) {
if (entry.isDirectory())
return entry.getName().equals("WEB-INF/classes/");
return (entry.getName().startsWith("WEB-INF/lib/") ||
entry.getName().startsWith("WEB-INF/lib-provided/"));
}
public static void main(String[] args) throws Exception {
(new WarLauncher()).launch(args);
}
}
加载 war 包的过程大同小异,只是把加载目录换成 WEB-INF/classes 和 WEB-INF/lib/ 目录