在上述内容可见Main-Class的值org.springframework.boot.loader.JarLauncher,这个和前面的java官方文档对应上了,正是这个JarLauncher类的代码中指定了真正的启动类;
疑惑出现在MANIFEST.MF文件中有这么一行内容:
Start-Class: com.bolingcavalry.springbootstarterdemo.Springbootstarter demoApplication前面的java官方文档中,只提到过Main-Class ,并没有提到Start-Class;
Start-Class的值是SpringbootstarterdemoApplication,这是我们的java代码中的唯一类,也只真正的应用启动类;
所以问题就来了:理论上看,执行java -jar命令时JarLauncher类会被执行,但实际上是SpringbootstarterdemoApplication被执行了,这其中发生了什么呢?
猜测动手之前先猜一下,个人觉得原因应该如下:
java -jar命令会启动JarLauncher;
Start-Class是给JarLauncher用的;
JarLauncher根据Start-Class找到了SpringbootstarterdemoApplication,然后执行它;
分析JarLauncher
先下载SpringBoot源码,我下载的是2.3.1版本,地址:https://github.com/spring-projects/spring-boot/releases/tag/v2.3.1.RELEASE
JarLauncher所在的工程是spring-boot-loader,先弄明白JarLauncher的继承关系,如下图,可见JarLauncher继承自ExecutableArchiveLauncher,而ExecutableArchiveLauncher的父类Launcher位于最顶层,是个抽象类:
java -jar执行的是JarLauncher的main方法,如下,会实例化一个JarLauncher对象,然后执行其launch方法,并且将所有入参都带入:
public static void main(String[] args) throws Exception { new JarLauncher().launch(args); }上面的launch方法在父类Launcher中:
protected void launch(String[] args) throws Exception { // 将jar解压后运行的方式叫做exploded mode // 如果是exploded mode,就不能支持通过URL加载jar // 如果不是exploded mode,就可以通过URL加载jar if (!isExploded()) { // 如果允许通过URL加载jar,就在此注册对应的处理类 JarFile.registerUrlProtocolHandler(); } // 创建classLoader ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator()); // jarmode是创建docker镜像时用到的参数,使用该参数是为了生成带有多个layer信息的镜像 // 这里暂时不关注jarmode String jarMode = System.getProperty("jarmode"); //如果没有jarmode参数,launchClass的值就来自getMainClass()返回 String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass(); launch(args, launchClass, classLoader); }可见要重点关注的是getMainClass()方法,在看这个方法之前,我们先去关注一个重要的成员变量archive,是JarLauncher的父类ExecutableArchiveLauncher的archive,如下可见,该变量又来自方法createArchive:
public ExecutableArchiveLauncher() { try { this.archive = createArchive(); this.classPathIndex = getClassPathIndex(this.archive); } catch (Exception ex) { throw new IllegalStateException(ex); } }方法来自Launcher.createArchive,如下所示,可见成员变量archive实际上是个JarFileArchive对象:
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() ? new ExplodedArchive(root) : new JarFileArchive(root)); }现在回到getMainClass()方法,可见his.archive.getManifest方法返回的是META-INF/MANIFEST.MF文件的内容,然后getValue(START_CLASS_ATTRIBUTE)方法实际上就是从META-INF/MANIFEST.MF中取得了Start-Class的属性:
@Override protected String getMainClass() throws Exception { // 对应的是JarFileArchive.getManifest方法, // 进去后发现对应的就是JarFile.getManifest方法, // JarFile.getManifest对应的就是META-INF/MANIFEST.MF文件的内容 Manifest manifest = this.archive.getManifest(); String mainClass = null; if (manifest != null) { // 对应的是META-INF/MANIFEST.MF文件中的Start-Class的属性 mainClass = manifest.getMainAttributes().getValue(START_CLASS_ATTRIBUTE); } if (mainClass == null) { throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this); } return mainClass; }