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

相关推荐
羊小猪~~31 分钟前
MYSQL学习笔记(四):多表关系、多表查询(交叉连接、内连接、外连接、自连接)、七种JSONS、集合
数据库·笔记·后端·sql·学习·mysql·考研
大叔_爱编程38 分钟前
wx030基于springboot+vue+uniapp的养老院系统小程序
vue.js·spring boot·小程序·uni-app·毕业设计·源码·课程设计
ByteBlossom6663 小时前
MDX语言的语法糖
开发语言·后端·golang
程序研3 小时前
JAVA之外观模式
java·设计模式
计算机学姐3 小时前
基于微信小程序的驾校预约小程序
java·vue.js·spring boot·后端·spring·微信小程序·小程序
黄名富3 小时前
Kafka 日志存储 — 日志索引
java·分布式·微服务·kafka
m0_748255023 小时前
头歌答案--爬虫实战
java·前端·爬虫
小白的一叶扁舟4 小时前
深入剖析 JVM 内存模型
java·jvm·spring boot·架构
sjsjsbbsbsn4 小时前
基于注解实现去重表消息防止重复消费
java·spring boot·分布式·spring cloud·java-rocketmq·java-rabbitmq
苹果醋34 小时前
golang 编程规范 - Effective Go 中文
java·运维·spring boot·mysql·nginx