你真的了解SpringBoot应用的启动入口么?

Springboot可以说是Java程序员必备技能了,大家都知道Springboot最终可以通过maven打成jar包,然后直接使用java -jar命令运行一个Web工程(或其它)。这样就避免了原先基于tomcat的web工程的复杂操作。Springboot能够使Web服务的部署简单到如此程度是因为其内置了Jetty(或Tomcat)服务器,并且在容器启动过程中start该服务器,成功运行Web服务。

本篇并不是深究内置服务器的启动过程,而是追溯Springboot启动之前到底做了什么?它是如何与我们经常写的@SpringBootApplication注解注释的main方法类绑定起来的?

1、一切的开始

相信各位Springbooter一定不会陌生下面的代码,无论是初学Springboot的新同学,或是开始研究Springboot源码的新司机,这段代码几乎是我们的落脚点。我们如此熟悉它,以至于认为它就是Springboot这个魔法乐园的起点。但真的是这样吗?

@SpringBootApplication
public class Springboot01helloworldApplication {
    public static void main(String[] args) {
        SpringApplication.run(Springboot01helloworldApplication.class, args);
    }
}

我们都知道,一个Java工程打包过后,这个jar包的入口描述被写在了/META-INF/MANIFEST.MF文件下,下面让我们来看看这个文件内容:

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: MrXu
Start-Class: com.vivo.internet.nex.repeater.console.RepeaterConsoleApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Version: 1.5.19.RELEASE
Created-By: Apache Maven 3.8.1
Build-Jdk: 1.8.0_281
Main-Class: org.springframework.boot.loader.JarLauncher

文件入口的描述为Main-Class对应的value,即org.springframework.boot.loader.JarLauncher。那么,接下来我们需要看下这个类究竟做了什么?

// JarLauncher.java
public class JarLauncher extends ExecutableArchiveLauncher {
    static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";
    static final String BOOT_INF_LIB = "BOOT-INF/lib/";
    public JarLauncher() {
    }
    // ...省略无关代码
    public static void main(String[] args) throws Exception {
        (new JarLauncher()).launch(args);
    }
}

明显的main函数吸引了我们的注意,没错了,这就是入口,看看JarLauncher的空构造并没有任何代码,我们先往它的父类找找:

// ExecutableArchiveLauncher.java
public abstract class ExecutableArchiveLauncher extends Launcher {
    public ExecutableArchiveLauncher() {
        try {
            this.archive = this.createArchive();
        } catch (Exception var2) {
            throw new IllegalStateException(var2);
        }
    }
    // ...省略
}

// Launcher.java
public abstract class Launcher {
    public Launcher() {}
    // ...省略无关代码
}

从代码中可以看出,真正干了事情的父类是ExecutableArchiveLauncher,它在初始化时构造了archive实例,该实例封装了/META-INF/MANIFEST.MF文件的信息。后面我们也会用到它。

随后便是launch方法,我们只关系核心执行流程:

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

// ExecutableArchiveLauncher.java
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);
    } else {
        return mainClass;
    }
}

这里首先调用子类ExecutableArchiveLauncher的getMainClass方法,主要逻辑就是从/META-INF/MANIFEST.MF文件中获取Start-Class信息,对应上文就是com.vivo.internet.nex.repeater.console.RepeaterConsoleApplication字符串,这样就和我们写的启动类关联上了。

然后是launch方法的具体执行,launch()首先创建一个MainMethodRunner,将上文获取的Start-Class和透传的参数传递进去,然后调用MainMethodRunner的run方法。run方法的执行也非常简单,就是加载Start-Class对应的启动类,然后反射调用启动类的main方法。之后就是容器的初始化过程了。

// Launcher.java
protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception {
    Thread.currentThread().setContextClassLoader(classLoader);
    // 这里首先调用createMainMethodRunner创建一个MainMethodRunner实例,将mainClass和args参数传入。随后调用
    this.createMainMethodRunner(mainClass, args, classLoader).run();
}
protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) {
    return new MainMethodRunner(mainClass, args);
}

// MainMethodRunner.java
public MainMethodRunner(String mainClass, String[] args) {
    this.mainClassName = mainClass;
    this.args = args != null ? (String[])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((Object)null, this.args);
}

2、总结

综上所述,对于Springboot工程,启动类并不是真正的工程入口,他会被真正的入口反射调用其main方法实现Spring容器的启动。这也可能是Spring的开发人员为我们“营造的一种假象”,抽象出来的逻辑入口。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值