springboot jar是如何启动的

我们先来看一个项目的打完包后的MANIFEST.MF文件:

java 复制代码
Manifest‐Version: 1.0
Implementation‐Title: spring‐learn
Implementation‐Version: 0.0.1‐SNAPSHOT
Start‐Class: com.tulingxueyuan.Application
Spring‐Boot‐Classes: BOOT‐INF/classes/
Spring‐Boot‐Lib: BOOT‐INF/lib/
Build‐Jdk‐Spec: 1.8
Spring‐Boot‐Version: 2.1.5.RELEASE
Created‐By: Maven Archiver 3.4.0
Main‐Class: org.springframework.boot.loader.JarLauncher

java -jar执行时会默认寻找Main‐Class对应的类并执行对应的main方法,这里为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);
	}

}

当jar的方式启动时,走的是JarLauncher的main方法:

java 复制代码
protected void launch(String[] args) throws Exception {
    JarFile.registerUrlProtocolHandler();
    ClassLoader classLoader = createClassLoader(getClassPathArchives());
    launch(args, getMainClass(), classLoader);
}
JarFile#registerUrlProtocolHandler():
java 复制代码
private static final String PROTOCOL_HANDLER = "java.protocol.handler.pkgs";
private static final String HANDLERS_PACKAGE = "org.springframework.boot.loader";

public static void registerUrlProtocolHandler() {
    String handlers = System.getProperty(PROTOCOL_HANDLER, "");
    System.setProperty(PROTOCOL_HANDLER,
            ("".equals(handlers) ? HANDLERS_PACKAGE : handlers + "|" + HANDLERS_PACKAGE));
    resetCachedUrlHandlers();
}
private static void resetCachedUrlHandlers() {
    try {
        URL.setURLStreamHandlerFactory(null);
    }
    catch (Error ex) {
        // Ignore
    }
}

这里主要注册了java.protocol.handler.pkgs属性,默认为org.springframework.boot.loader

createClassLoader(getClassPathArchives())
java 复制代码
protected abstract List<Archive> getClassPathArchives() throws Exception;

protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
    List<URL> urls = new ArrayList<>(archives.size());
    for (Archive archive : archives) {
        urls.add(archive.getUrl());
    }
    return createClassLoader(urls.toArray(new URL[0]));
}
protected ClassLoader createClassLoader(URL[] urls) throws Exception {
    return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
}

archives参数由getClassPathArchives类型传入,默认有2个实现:

java 复制代码
protected List<Archive> getClassPathArchives() throws Exception {
    List<Archive> archives = new ArrayList<>(this.archive.getNestedArchives(this::isNestedArchive));
    postProcessClassPathArchives(archives);
    return archives;
}
protected abstract boolean isNestedArchive(Archive.Entry entry);
protected void postProcessClassPathArchives(List<Archive> archives) throws Exception {}

//JarLauncher实现
static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";
static final String BOOT_INF_LIB = "BOOT-INF/lib/";
protected boolean isNestedArchive(Archive.Entry entry) {
    if (entry.isDirectory()) {
        return entry.getName().equals(BOOT_INF_CLASSES);
    }
    return entry.getName().startsWith(BOOT_INF_LIB);
}


public boolean isNestedArchive(Archive.Entry entry) {
		if (entry.isDirectory()) {
			return entry.getName().equals(WEB_INF_CLASSES);
		}
		else {
			return entry.getName().startsWith(WEB_INF_LIB) || entry.getName().startsWith(WEB_INF_LIB_PROVIDED);
		}
	}
java 复制代码
protected List<Archive> getClassPathArchives() throws Exception {
    List<Archive> lib = new ArrayList<>();
    for (String path : this.paths) {
        for (Archive archive : getClassPathArchives(path)) {
            if (archive instanceof ExplodedArchive) {
                List<Archive> nested = new ArrayList<>(archive.getNestedArchives(new ArchiveEntryFilter()));
                nested.add(0, archive);
                lib.addAll(nested);
            }
            else {
                lib.add(archive);
            }
        }
    }
    addNestedEntries(lib);
    return lib;
}

private List<Archive> getClassPathArchives(String path) throws Exception {
    String root = cleanupPath(handleUrl(path));
    List<Archive> lib = new ArrayList<>();
    File file = new File(root);
    if (!"/".equals(root)) {
        if (!isAbsolutePath(root)) {
            file = new File(this.home, root);
        }
        if (file.isDirectory()) {
            debug("Adding classpath entries from " + file);
            Archive archive = new ExplodedArchive(file, false);
            lib.add(archive);
        }
    }
    Archive archive = getArchive(file);
    if (archive != null) {
        debug("Adding classpath entries from archive " + archive.getUrl() + root);
        lib.add(archive);
    }
    List<Archive> nestedArchives = getNestedArchives(root);
    if (nestedArchives != null) {
        debug("Adding classpath entries from nested " + root);
        lib.addAll(nestedArchives);
    }
    return lib;
}

这里主要分析下launch(args, getMainClass(), classLoader)

这里是mainClass的获取:

java 复制代码
protected abstract String getMainClass() throws Exception;

//默认用的是这个
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 String getMainClass() throws Exception {
    String mainClass = getProperty(MAIN, "Start-Class");
    if (mainClass == null) {
        throw new IllegalStateException("No '" + MAIN + "' or 'Start-Class' specified");
    }
    return mainClass;
}

来看launch方法:

java 复制代码
protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception {
    Thread.currentThread().setContextClassLoader(classLoader);
    createMainMethodRunner(mainClass, args, classLoader).run();
}

protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) {
    return new MainMethodRunner(mainClass, args);
}

public void run() throws Exception {
    Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName);
    Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
    mainMethod.invoke(null, new Object[] { this.args });
}

通过反射调用mainClass的main方法,项目由此启动起来。这里注意采用的是自定义的classLoader:LaunchedURLClassLoader

相关推荐
猩猩程序员1 分钟前
别乱用二分查找!这些前提条件你都搞清楚了吗?
后端
真实的菜2 分钟前
消息队列处理模式:流式与批处理的艺术
java
Livingbody15 分钟前
通过huggingface学习【自动语音识别(Automatic Speech Recognition, ASR)】
后端
盖世英雄酱5813617 分钟前
Java 内存管理技巧(新手必看集合篇)
java
~plus~19 分钟前
Harmony核心:动态方法修补与.NET游戏Mod开发
开发语言·jvm·经验分享·后端·程序人生·c#
码农小灰20 分钟前
Java 8 Stream API 入门到实践详解
java·java案例
步、步、为营25 分钟前
.NET 事件模式举例介绍
java·开发语言·.net
cui_hao_nan28 分钟前
设计模式——模板方法
java·设计模式
~plus~28 分钟前
WPF八大法则:告别模态窗口卡顿
开发语言·经验分享·后端·程序人生·c#
小吕学编程28 分钟前
HttpServletRequest常用方法
java·http