springboot jar是如何启动的
我们先来看一个项目的打完包后的MANIFEST.MF
文件:
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:
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);}@Overrideprotected 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方法:
protected void launch(String[] args) throws Exception {JarFile.registerUrlProtocolHandler();ClassLoader classLoader = createClassLoader(getClassPathArchives());launch(args, getMainClass(), classLoader);
}
JarFile#registerUrlProtocolHandler():
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())
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个实现:
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);}}
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的获取:
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方法:
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