Spring Boot 启动原理

461 阅读2分钟

Spring Boot 启动时依靠于 META-INF/MANIFEST.MF 中的 Main-Class 启动的; 若是JAR, META-INF/MANIFEST.MF 中的 Main-Class 显示的是org.springframework.boot.loader.JarLauncher;

那具体是怎么启动的呢,那我们就来调试下; 调试方法为

如果是Idea的话,点击设置Path to JAR的路径

调试的最开始的位置也就是JarLauncher的main方法

public class JarLauncher extends ExecutableArchiveLauncher {
    ...
    public static void main(String[] args) throws Exception {
    		new JarLauncher().launch(args);
    	}
}

再下一步就进入

public abstract class Launcher {
	protected void launch(String[] args) throws Exception {
		JarFile.registerUrlProtocolHandler();
		ClassLoader classLoader = createClassLoader(getClassPathArchives());
		launch(args, getMainClass(), classLoader);
	}
	...
}

然后就从三个步骤接着分析

步骤一: JarFile.registerUrlProtocolHandler() 该方法利用了 java.net.URLStreamHandler 扩展机制,其实现由 URL#getURLStreamHandler(String) 提供。

public class JarFile extends java.util.jar.JarFile {
    ...
    // 注册URL协议
    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
      }
    }
}

步骤二

创建 ClassLoader ,getClassPathArchives() 由子类 ExecutableArchiveLauncher 实现

public abstract class ExecutableArchiveLauncher extends Launcher {
  ...
  protected List<Archive> getClassPathArchives() throws Exception {
   List<Archive> archives = new ArrayList<>(
     this.archive.getNestedArchives(this::isNestedArchive));
   postProcessClassPathArchives(archives);
   return archives;
  }
}

isNestedArchive(Archive.Entry entry) 需要子类 JarLauncher实现:

public class JarLauncher extends ExecutableArchiveLauncher {
	...
	@Override
	protected boolean isNestedArchive(Archive.Entry entry) {
		if (entry.isDirectory()) {
			return entry.getName().equals(BOOT_INF_CLASSES);
		}
		return entry.getName().startsWith(BOOT_INF_LIB);
	}
}

该方法主要是为了过滤掉 Archive.Entry 实例是否匹配 BOOT-INF/lib 或 BOOT-INF/classes ,只要符合以上路径即可,而postProcessClassPathArchives方法是一个空实现,根据解释是为了后续可以添加或删除条目,故 getClassPathArchives() 的返回值还是取决于archives 属性对象的内容,

public abstract class ExecutableArchiveLauncher extends Launcher {
  private final Archive archive;
  public ExecutableArchiveLauncher() {
    try {
      this.archive = createArchive();
    }
    catch (Exception ex) {
     throw new IllegalStateException(ex);
    }
  }
  ...
  protected void postProcessClassPathArchives(List<Archive> archives) throws Exception {
	}
}

createArchive() 该方法来自于父类 Launcher ,该方法主要判断文件路径和归档文件是否正确

public abstract class Launcher {
    ...
	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));
	}
}

步骤三 launch(args, getMainClass(), classLoader),而主要的是 createMainMethodRunner()

public abstract class Launcher {
  ...
  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);
	}
}

MainMethodRunner 对象关联 mainClass 及 main 方法参数 args。 从launch(args, getMainClass(), classLoader);得知 mainClass 来自 getMainClass(), 也就赋予了mainClass一个值即/META-INF/MANIFEST.MF资源中的 Start-Class 属性

public abstract class ExecutableArchiveLauncher extends Launcher {
	...
	@Override
	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;
	}
}

获取mainClass 之后会执行MainMethodRunner#run()方法去读取mainClass类中的main(String[] args)方法

public class MainMethodRunner {
	...
  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 });
	}
}

因此 JarLuncher 实际上是同进程内调用 /META-INF/MANIFEST.MF资源中的 Start-Class 属性 所指向的类的main(String[] args)方法,并在启动前准备好了Class Path.