logo头像

优秀是一种习惯

springboot启动源码剖析

springboot启动源码剖析 author: Tank

前言

之前有写过springboot往往以运行main方法来启动应用,初始化 上下文 IOC…. 那main方法又是如何被执行的呢?被谁执行的呢。

java -jar application.jar 先说jar的启动。(war其实是一样的)

  1. ​ 先把一个springboot的jar解压 来看看打包后的结构 target

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    ├── [ 128]  BOOT-INF
    │   ├── [ 128] classes
    │   │   ├── [ 1] application.properties
    │   │   └── [ 96] com
    │   │   └── [ 96] example
    │   │   └── [ 96] demo
    │   │   └── [ 733] DemoApplication.class
    │   └── [1.2K] lib
    │   ├── [ 65K] classmate-1.4.0.jar
    .... 省略
    ├── [ 128] META-INF
    │   ├── [ 361] MANIFEST.MF
    │   └── [ 96] maven
    │   └── [ 96] com.example
    │   └── [ 128] demo
    │   ├── [ 59] pom.properties
    │   └── [1.4K] pom.xml

    //这个是一个springboot demo jar 解压后的结构 (org 源码目录去除了。。)
  1. 来看看非常重要的 MANIFEST.MF 文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    Manifest-Version: 1.0
    Implementation-Title: demo
    Implementation-Version: 0.0.1-SNAPSHOT
    Start-Class: com.example.demo.DemoApplication
    Spring-Boot-Classes: BOOT-INF/classes/
    Spring-Boot-Lib: BOOT-INF/lib/
    Build-Jdk-Spec: 1.8
    Spring-Boot-Version: 2.1.11.RELEASE
    Created-By: Maven Archiver 3.4.0
    Main-Class: org.springframework.boot.loader.JarLauncher

    # 先看看main-class 这个才是java -jar 命令调用的类 和 start-class 是应用的主类
  1. 来看看java 官方定义 传送门

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Description
    The java command starts a Java application. It does this by starting the Java Runtime Environment (JRE), loading the specified class, and calling that class's main() method. The method must be declared public and static, it must not return any value, and it must accept a String array as a parameter. The method declaration has the following form:

    public static void main(String[] args)
    The java command can be used to launch a JavaFX application by loading a class that either has a main() method or that extends javafx.application.Application. In the latter case, the launcher constructs an instance of the Application class, calls its init() method, and then calls the start(javafx.stage.Stage) method.

    By default, the first argument that is not an option of the java command is the fully qualified name of the class to be called. If the -jar option is specified, its argument is the name of the JAR file containing class and resource files for the application. The startup class must be indicated by the Main-Class manifest header in its source code.
    //如果指定了-jar选项,则其参数为包含应用程序类和资源文件的JAR文件的名称。 启动类必须由其源代码中的Main-//Class清单标头指示。
    The JRE searches for the startup class (and other classes used by the application) in three sets of locations: the bootstrap class path, the installed extensions, and the user's class path.

    Arguments after the class file name or the JAR file name are passed to the main() method.

    The javaw command is identical to java, except that with javaw there is no associated console window. Use javaw when you do not want a command prompt window to appear. The javaw launcher will, however, display a dialog box with error information if a launch fails
    也就是说java -jar jre会去读取清单获取main-class然后调用他的main方法。
  2. 来看看 JarLauncher

    1
    2
    3
    4
    5
    <!-- 这个包默认是 spring-boot-maven-plugin 加载的。看源码所以需要加一下这个包-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-loader</artifactId>
    </dependency>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    public class JarLauncher extends ExecutableArchiveLauncher { //Launcher 的子类
    static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";
    static final String BOOT_INF_LIB = "BOOT-INF/lib/";
    public JarLauncher() {}
    protected JarLauncher(Archive archive) {super(archive);}
    @Override
    protected boolean isNestedArchive(Archive.Entry entry) {
    if (entry.isDirectory()) {
    return entry.getName().equals(BOOT_INF_CLASSES);
    }
    return entry.getName().startsWith(BOOT_INF_LIB);
    }
    //java -jar 其实调用的是这个main方法 请参考第3条
    public static void main(String[] args) throws Exception {
    new JarLauncher().launch(args);
    }
    }

    ---
    public abstract class Launcher {

    protected void launch(String[] args) throws Exception {
    //这里是注册协议
    JarFile.registerUrlProtocolHandler();
    //创建类加载器
    ClassLoader classLoader = createClassLoader(getClassPathArchives());
    //执行前先获取 mainclass 看下面ExecutableArchiveLauncher 拿的就是 Start-Class
    launch(args, getMainClass(), classLoader);
    }
    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) {
    // 这里是直接用的new 并传入了Start-Class 也就是我们的应用的启动类,请看第2条 及main方法参数 。
    return new MainMethodRunner(mainClass, 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);
    //主方法运行器通过反射调用我们的业务应用的main方法
    mainMethod.invoke(null, new Object[] { this.args });
    }

    ---
    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;
    }
    整体流程就是这样的。但是里面细节需要单独一一列出。下一篇继续