boot - spring boot jar 启动过程

boot - spring boot jar 启动过程

目录结构

一般的,一个sprign boot package jar 包之后的 如下,这里以 boot-mvc-0.0.1-SNAPSHOT.jar 为例子

.
├── BOOT-INF
│   ├── classes    # 当前生成编译生成的 class
│   └── lib        # 依赖的第三方jar包 目录
├── META-INF
│   ├── MANIFEST.MF   # 打包的一些类信息
│   └── maven
└── org.springframework.boot.loader
    ├── archive
    ├── ClassPathIndexFile.class
    ├── data
    ├── ExecutableArchiveLauncher.class
    ├── jar
    ├── JarLauncher.class           # jar 包启动类
    ├── jarmode
    ├── LaunchedURLClassLoader$DefinePackageCallType.class
    ├── LaunchedURLClassLoader$UseFastConnectionExceptionsEnumeration.class
    ├── LaunchedURLClassLoader.class
    ├── Launcher.class
    ├── MainMethodRunner.class
    ├── PropertiesLauncher$1.class
    ├── PropertiesLauncher$ArchiveEntryFilter.class
    ├── PropertiesLauncher$ClassPathArchives.class
    ├── PropertiesLauncher$PrefixMatchingArchiveFilter.class
    ├── PropertiesLauncher.class
    ├── util
    └── WarLauncher.class         # war 包启动类

在这里插入图片描述

MANIFEST.MF

这个文件主要记录和描述了 Main-Class, Start-Class等等

Manifest-Version: 1.0
Created-By: Maven Jar Plugin 3.2.0
Build-Jdk-Spec: 15
Implementation-Title: boot-mvc
Implementation-Version: 0.0.1-SNAPSHOT
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.x.z.bootmvc.BootMvcApplication
Spring-Boot-Version: 2.4.0
Spring-Boot-Classes: BOOT-INF/classes/    # 当前工程编译生成 class 目录 
Spring-Boot-Lib: BOOT-INF/lib/            # 该jar 包依赖的第三方 jar
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Spring-Boot-Layers-Index: BOOT-INF/layers.idx

spring boot 是如何被加载启动的

当我们运行 java -jar boot-mvc-0.0.1-SNAPSHOT.jar 的时候就能启动服务,那这个过程是如何触发和加载的?

  • 启动的入口在哪里
  • 怎么加载到这些依赖的jar包

带着这些问题,我们继续看

JarLauncher.java

顾名思义,这个应该就是启动 boot-mvc.jar 的主类

public class JarLauncher extends ExecutableArchiveLauncher {
  private static final String DEFAULT_CLASSPATH_INDEX_LOCATION = "BOOT-INF/classpath.idx";
  
  static final Archive.EntryFilter NESTED_ARCHIVE_ENTRY_FILTER;
  
  static {
    NESTED_ARCHIVE_ENTRY_FILTER = (entry -> entry.isDirectory()
     ? entry.getName().equals("BOOT-INF/classes/") : entry.getName().startsWith("BOOT-INF/lib/"));
  }
  
  public JarLauncher() {}
  
  protected JarLauncher(Archive archive) {
    super(archive);
  }
  
  protected ClassPathIndexFile getClassPathIndex(Archive archive) throws IOException {
    if (archive instanceof org.springframework.boot.loader.archive.ExplodedArchive) {
      String location = getClassPathIndexFileLocation(archive);
      return ClassPathIndexFile.loadIfPossible(archive.getUrl(), location);
    } 
    return super.getClassPathIndex(archive);
  }
  
  private String getClassPathIndexFileLocation(Archive archive) throws IOException {
    Manifest manifest = archive.getManifest();
    Attributes attributes = (manifest != null) ? manifest.getMainAttributes() : null;
    String location = (attributes != null) ? attributes.getValue("Spring-Boot-Classpath-Index") : null;
    return (location != null) ? location : "BOOT-INF/classpath.idx";
  }
  
  protected boolean isPostProcessingClassPathArchives() {
    return false;
  }
  
  protected boolean isSearchCandidate(Archive.Entry entry) {
    return entry.getName().startsWith("BOOT-INF/");
  }
  
  protected boolean isNestedArchive(Archive.Entry entry) {
    return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry);
  }
  
  public static void main(String[] args) throws Exception {
    (new JarLauncher()).launch(args);
  }
}
ExecutableArchieveLauncher.java
public abstract class ExecutableArchiveLauncher extends Launcher {
  private static final String START_CLASS_ATTRIBUTE = "Start-Class";
  
  protected static final String BOOT_CLASSPATH_INDEX_ATTRIBUTE = "Spring-Boot-Classpath-Index";
  
  private final Archive archive;
  
  private final ClassPathIndexFile classPathIndex;
  
  public ExecutableArchiveLauncher() {
    try {
      this.archive = createArchive();
      this.classPathIndex = getClassPathIndex(this.archive);
    } catch (Exception ex) {
      throw new IllegalStateException(ex);
    } 
  }
  
  protected ExecutableArchiveLauncher(Archive archive) {
    try {
      this.archive = archive;
      this.classPathIndex = getClassPathIndex(this.archive);
    } catch (Exception ex) {
      throw new IllegalStateException(ex);
    } 
  }
  
  protected ClassPathIndexFile getClassPathIndex(Archive archive) throws IOException {
    return null;
  }
  
  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 ClassLoader createClassLoader(Iterator<Archive> archives) throws Exception {
    List<URL> urls = new ArrayList<>(guessClassPathSize());
    while (archives.hasNext())
      urls.add(((Archive)archives.next()).getUrl()); 
    if (this.classPathIndex != null)
      urls.addAll(this.classPathIndex.getUrls()); 
    return createClassLoader(urls.<URL>toArray(new URL[0]));
  }
  
  private int guessClassPathSize() {
    if (this.classPathIndex != null)
      return this.classPathIndex.size() + 10; 
    return 50;
  }
  
  protected Iterator<Archive> getClassPathArchivesIterator() throws Exception {
    Archive.EntryFilter searchFilter = this::isSearchCandidate;
    Iterator<Archive> archives = this.archive.getNestedArchives(searchFilter, entry -> 
        (isNestedArchive(entry) && !isEntryIndexed(entry)));
    if (isPostProcessingClassPathArchives())
      archives = applyClassPathArchivePostProcessing(archives); 
    return archives;
  }
  
  private boolean isEntryIndexed(Archive.Entry entry) {
    if (this.classPathIndex != null)
      return this.classPathIndex.containsEntry(entry.getName()); 
    return false;
  }
  
  private Iterator<Archive> applyClassPathArchivePostProcessing
  					(Iterator<Archive> archives) throws Exception {
    List<Archive> list = new ArrayList<>();
    while (archives.hasNext())
      list.add(archives.next()); 
    postProcessClassPathArchives(list);
    return list.iterator();
  }
  
  protected boolean isSearchCandidate(Archive.Entry entry) {
    return true;
  }
  
  protected boolean isPostProcessingClassPathArchives() {
    return true;
  }
  
  protected void postProcessClassPathArchives(List<Archive> archives) throws Exception {}
  
  protected boolean isExploded() {
    return this.archive.isExploded();
  }
  
  protected final Archive getArchive() {
    return this.archive;
  }
  
  protected abstract boolean isNestedArchive(Archive.Entry paramEntry);
}
Lanucher.java
public abstract class Launcher {
  private static final String JAR_MODE_LAUNCHER
  				 = "org.springframework.boot.loader.jarmode.JarModeLauncher";
  
  protected void launch(String[] args) throws Exception {
    if (!isExploded())
      JarFile.registerUrlProtocolHandler(); 
    ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());
    String jarMode = System.getProperty("jarmode");
    String launchClass = (jarMode != null && !jarMode.isEmpty()) ? 
    			"org.springframework.boot.loader.jarmode.JarModeLauncher" : getMainClass();
    launch(args, launchClass, classLoader);
  }
  
  @Deprecated
  protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
    return createClassLoader(archives.iterator());
  }
  
  protected ClassLoader createClassLoader(Iterator<Archive> archives) throws Exception {
    List<URL> urls = new ArrayList<>(50);
    while (archives.hasNext())
      urls.add(((Archive)archives.next()).getUrl()); 
    return createClassLoader(urls.<URL>toArray(new URL[0]));
  }
  
  protected ClassLoader createClassLoader(URL[] urls) throws Exception {
    return new LaunchedURLClassLoader(isExploded(), getArchive(), urls, getClass().getClassLoader());
  }
  
  protected void launch(String[] args, String launchClass,
  							 ClassLoader classLoader) throws Exception {
    Thread.currentThread().setContextClassLoader(classLoader);
    createMainMethodRunner(launchClass, args, classLoader).run();
  }
  
  protected MainMethodRunner createMainMethodRunner(String mainClass,
  								 String[] args, ClassLoader classLoader) {
    return new MainMethodRunner(mainClass, args);
  }
  
  protected abstract String getMainClass() throws Exception;
  
  protected Iterator<Archive> getClassPathArchivesIterator() throws Exception {
    return getClassPathArchives().iterator();
  }
  
  @Deprecated
  protected List<Archive> getClassPathArchives() throws Exception {
    throw new IllegalStateException("Unexpected call to getClassPathArchives()");
  }
  
  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() ? 
    		(Archive)new ExplodedArchive(root) : (Archive)new JarFileArchive(root);
  }
  
  protected boolean isExploded() {
    return false;
  }
  
  protected Archive getArchive() {
    return null;
  }
}
MainMethodRunner

public class MainMethodRunner {
  private final String mainClassName;
  
  private final String[] args;
  
  public MainMethodRunner(String mainClass, String[] args) {
    this.mainClassName = mainClass;
    this.args = (args != null) ? (String[])args.clone() : null;
  }
  
  public void run() throws Exception {
    Class<?> mainClass = Class.forName(this.mainClassName, false, 
    							Thread.currentThread().getContextClassLoader());
    Method mainMethod = mainClass.getDeclaredMethod("main", new Class[] { String[].class });
    mainMethod.setAccessible(true);
    mainMethod.invoke(null, new Object[] { this.args });
  }
}
启动流程
  • 根据 MANIFEST.MF 文件找到 /BOOT-INF/classes 和 /BOOT-INF/lib 目录,并加载

  • 创建 ClassLoader,此时为 LaunchedURLClassLoader

  • 根据 MANIFEST.MF 文件找到 Start-Class 属性的 主类,这里是 com.x.z.bootmvc.BootMvcApplication

  • 设置当前线程上下文加载器为 ClassLoader

  • 从当前线程获取ClassLoader,即 LaunchedURLClassLoader, 加载 mainClass(即 start-class)

  • 利用反射执行 mainClass 中的 main 方法, 即执行 om.x.z.bootmvc.BootMvcApplication 中的 main 方法

WarLauncher


public class WarLauncher extends ExecutableArchiveLauncher {
  public WarLauncher() {}
  
  protected WarLauncher(Archive archive) {
    super(archive);
  }
  
  protected boolean isPostProcessingClassPathArchives() {
    return false;
  }
  
  protected boolean isSearchCandidate(Archive.Entry entry) {
    return entry.getName().startsWith("WEB-INF/");
  }
  
  public boolean isNestedArchive(Archive.Entry entry) {
    if (entry.isDirectory())
      return entry.getName().equals("WEB-INF/classes/"); 
    return (entry.getName().startsWith("WEB-INF/lib/") ||
    			 entry.getName().startsWith("WEB-INF/lib-provided/"));
  }
  
  public static void main(String[] args) throws Exception {
    (new WarLauncher()).launch(args);
  }
}

加载 war 包的过程大同小异,只是把加载目录换成 WEB-INF/classes 和 WEB-INF/lib/ 目录

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值