好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

快速掌握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这个魔法乐园的起点。但真的是这样吗?

?

1

2

3

4

5

6

@SpringBootApplication

public class Springboot01helloworldApplication {

     public static void main(String[] args) {

         SpringApplication.run(Springboot01helloworldApplication. class , args);

     }

}

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

?

1

2

3

4

5

6

7

8

9

10

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 。那么,接下来我们需要看下这个类究竟做了什么?

?

1

2

3

4

5

6

7

8

9

10

11

// 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的空构造并没有任何代码,我们先往它的父类找找:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

// 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方法,我们只关系核心执行流程:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

// 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方法。之后就是容器的初始化过程了。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

// 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的开发人员为我们[营造的一种假象],抽象出来的逻辑入口。

到此这篇关于快速掌握SpringBoot应用的启动入口的文章就介绍到这了,更多相关SpringBoot启动入口内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

原文链接:https://juejin.cn/post/7100386958580563982

查看更多关于快速掌握SpringBoot应用的启动入口的详细内容...

  阅读:12次