Spring Boot 如何启动—JarLauncher 实现原理

https://blog.csdn.net/qq_38423105/article/details/89433050

已经了解了Spring Boot 启动时依靠于 META-INF/MANIFEST.MF 中的 Main-Class 启动的。

为了方便学习源码添加jar包如下

 <!-- spring-boot-loader-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-loader</artifactId>
        <scope>provided</scope>
    </dependency>
下面我们深入了解下 JarLauncher 是如何实现启动 main(String[] args) 的。

首先看下 Launcher 的类图

 我们可以发现 Launcher 有一个抽象类 ExecutableArchiveLauncher ,JarLauncher 和 WarLauncher 都继承了 ExecutableArchiveLauncher

进入 JarLauncher 类中,执行main方法

public class JarLauncher extends ExecutableArchiveLauncher {
  ...
  public static void main(String[] args) throws Exception {
   new JarLauncher().launch(args);
  }
}
launch() 方法是 抽象类 Launcher 实现的方法

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

注册URL协议并清除应用缓存

设置类加载路径

执行main方法

1 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
      }
    }
}
2 createClassLoader(getClassPathArchives())
创建 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 或 WarLauncher 实现,以 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 ,只要符合以上路径即可,故 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);
    }
  }
  ...
}
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));
    }
}
3 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

public class MainMethodRunner {
    ...
  public MainMethodRunner(String mainClass, String[] args) {
   this.mainClassName = mainClass;
   this.args = (args != null ? args.clone() : null);
  }
  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 来自 getMainClass()

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;
    }
}
类名称来自于 /META-INF/MANIFEST.MF 资源中的 Start-Class 属性,其子类没有覆盖此方法实现,故无论是JAR还是WAR,读取 Spring Boot 启动类均来自于此属性。获取mainClass 之后会执行MainMethodRunner#run()方法去读取mainClass 类中的main(String[] args)方法

// 方法一开始就展示了,为了更加清楚哪里调用,再写一遍
public abstract class Launcher {
  protected void launch(String[] args, String mainClass, ClassLoader classLoader)
    throws Exception {
   Thread.currentThread().setContextClassLoader(classLoader);
   createMainMethodRunner(mainClass, args, classLoader).run();
  }
}
因此 JarLuncher 实际上是同进程内调用 Start-Class 类的 main(String[] args) 方法,并在启动前准备好了 Class Path. WarLuncher 就不展开了,大部分实现逻辑如上
 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值