SpringBoot打包部署解析:Launcher实现原理

Launcher实现原理在上节内容中 , 我们得知 jar 包 Main-Class 指定入口程序为 Spring Boot 提供的 L auncher(JarL auncher) , 并不是我们在 Spring Boot 项目中所写的入口类 。 那么 , Launcher 类又是如何实现项目的启动呢?本节带大家了解其相关原理 。
Launcher 类的具体实现类有 3 个: JarL auncher、Warl _auncher 和 PropertiesLauncher,我们这里主要讲解 JarLauncher 和 WarLauncher 。 首先 , 以 JarL auncher 为例来解析说明Spring Boot 基于 Launcher 来实现的启动过呈 。
JarLauncher在了解 JarL .auncher 的实现原理之前 , 先来看看 JarL auncher 的源码 。
public class JarLauncher extends ExecutableArchiveLauncherstatic final String BOOT_ INF_ CLASSES = "BOOT- INF/classes/";static final String BOOT_ INF_ LIB = "B0OOT-INF/lib/";//省略构造方法@Overrideprotected boolean isNestedArchive(Archive. Entry entry) {if (entry. isDirectory())return entry. getName() . equals(B0OT_ _INF_ CLASSES);return entry . getName() . startsWith(BOOT_ INF_ LIB);public static void main(String[] args) throws Exception {new JarLauncher(). launch(args);}}JarLauncher 类结构非常简单 , 它继承了抽象类 ExecutableArchiveLauncher , 而抽象类又继承了抽象类 Launcher 。
JarLauncher 中定义了两个常量: BOOT_ INF_ _CLASSES 和 BOOT_ _INF_ LIB,它们分别定义了业务代码存放在 jar 包中的位置( BOOT-INF/classes/)和依赖 jar 包所在的位置(BOOT-INF/ib/)。
JarLauncher 中提供了一-个 main 方法 , 即入口程序的功能 , 在该方法中首先创建了 JarLauncher 对象 , 然后调用其 launch 方法 。 大家都知道 , 当创建子类对象时 , 会先调用父类的构造方法 。 因此 , 父类 ExecutableArchiveL auncher 的构造方法被调用 。
public abstract class ExecutableArchiveL auncher extends L auncher {private final Archive archive;public ExecutableArchiveLauncher() {try {this.archive = createArchive();} catch (Exception ex) {throw new IllegalStateException(ex);}}}在 ExecutableArchiveLauncher 的构造方法中仅实现了父类 Launcher 的 createArchive 方法的调用和异常的抛出 。 Launcher 类中 createArchive 方法源代码如下 。
protected final Archive createArchive() throws Exception {//通过获得当前 Class 类的信息 , 查找到当前归档文件的路径ProtectionDomain protectionDomain = getClass() . getProtectionDomain();CodeSource codeSource = protectionDomain. getCodeSource();URI location = (codeSource != nu1l) ? codeSource . getLocation() . toURI()null;String path = (location != null) ? location. getSchemeSpecificPart() : nul1;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);//如果是目录 , 则创建 ExplodedArchive, 否则创建 JarF ileArchivereturn (root. isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root));}在 createArchive 方法中 , 根据当前类信息获得当前归档文件的路径(即打包后生成的可执行的 spring-learn-0.0.1-SNAPSHOT.jar), 并检查文件路径是否存在 。 如果存在且是文件夹 , 则创建 ExplodedArchive 的对象 ,否则创建 JarFileArchive 的对象 。
关于 Archive , 它在 Spring Boot 中是一个抽象的概念 ,Archive 可以是一 个jar (JarFileArchive), 也可以是一个文件目录(ExplodedArchive), 上面的代码已经进行了很好地证明 。 你可以理解为它是一个抽象出来的统一 -访问资源的层 。 Archive 接口的具体定义如下 。
public interface Archive extends Iterable {//获取该归档的 urlURL getUrl() throws MalformedURL Exception;// 获取 jar!/META- INF/MANIFEST.MF 或[ArchiveDir]/META- INF/MANIFEST.MFManifest getManifest() throws IOException;//获取 jar!/B0OT- INF/lib/*. jar 或[ArchiveDir]/BOOT- INF/Lib/*. jarList getNestedArchives(EntryFilter filter) throws IOException;}通过 Archive 接口中定义的方法可以看出 , Archive 不仅提供了获得归档自身 URL 的方法 , 也提供了获得该归档内部 jar 文件列表的方法 , 而 jar 内部的 jar 文件依旧会被 Spring Boot认为是一个 Archive 。
通常 , jar 里的资源分隔符是!/ , 在 JDK 提供的 JarFile URL 只支持一层“!"” , 而 Spring Boot扩展了该协议 , 可支持多层"!/” 。因此 , 在 Spring Boot 中也就可以表示 jar in jar、jar indirectory、fat jar 类型的资源了 。
我们再回到 JarL auncher 的入口程序 , 当创建 JarLauncher 对象 , 获得了当前归档文件的Archive , 下一步便是调用 launch 方法 , 该方法由 Launcher 类实现 。 Launcher 中的这个launch 方法就是启动应用程序的入口 , 而该方法的定义是为了让子类的静态 main 方法调用的 。