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

相关推荐
用户67570498850212 小时前
你知道 Go 结构体和结构体指针调用的区别吗?一文带你彻底搞懂!
后端·go
程序员cxuan12 小时前
读懂 Claude Code 架构分析系列,第一篇,开始!
人工智能·后端·架构
用户67570498850212 小时前
面试官问“装饰器模式”,这样回答薪资多要 3000!
后端
tntxia12 小时前
Geo Scene域名修改引起的一些问题
后端
用户2986985301412 小时前
Java 实现 Word 文档加密与权限解除
java·后端
vanuan13 小时前
给你的A2A-Agent加把锁-认证鉴权实战指南
后端
Yeats_Liao13 小时前
14:Servlet中的页面跳转-Java Web
java·后端·架构
未秃头的程序猿13 小时前
告别"if-else地狱"!Java 21模式匹配,代码优雅了10倍
java·后端·面试
武子康13 小时前
调查研究-201 Rust 里的 dev build 和 release build:为什么同一份代码性能差这么多?
后端·架构·rust