1 静态 main 方法与 JVM 进程的关系
在讲解 SpringBoot 项目的启动和运行之前,首先分析一下基于静态 main 方法启动运行 Java 类的相关知识,以便读者能够更好理解基于 SpringBoot 框架搭建的项目的启动和运行方式。
1.1 基于静态 main 方法执行 Java 类
Java 类的静态 main 方法是每一个 Java 开发人员都再熟悉不过的一个方法,在进行 Java 应用程序开发,特别是 Java SE 应用程序开发时,一般都会在 Java 类定义一个静态 main 方法来作为该类的执行入口。
具体为在运行该 Java 类时,main 方法会被 JVM 调用,然后执行 main 方法内部的每一行代码,执行完毕之后,程序退出。如下为静态 main 方法的定义:
public class MainDemo {
// 类执行入口
public static void main(String[] args) {
// 自定义需要执行的代码
}
}
拓展知识:
Java 语言由于底层是基于 c 语言实现的,故在类的运行方面也仿照了 c 语言的习惯,使用一个名为 main 的方法来作为类的执行入口。
不过 Java 语言在具体设计方面结合了 Java 语言的相关特性,即 main 方法需要使用 static 关键字修饰来成为 Java 的类方法,从而实现了无需创建类的对象实例即可调用该 main 方法。
1.2 静态 main 方法的定义
该静态 main 方法的返回类型为 void,参数为 String 字符串数组 args,该数组代表该类的多个运行时参数,具体可以通过以下方式来传递:
java DemoMain 1 2 3
其中 java 为 Java 语言提供的执行类的命令,DemoMain 是以上类的类名,注意该类对应的 class 文件需要在类路径下,1,2,3 就是以上的 args 字符串数组的内容,即 args[0] = 1,args[1] = 2,args[2] = 3。
拓展知识:
注意 main 方法作为类的执行入口的以上两个条件,即方法返回类型为 void,方法参数为 String 字符串数组是必须的,否则 Java 类在运行时无法将该方法作为执行入口。例如,如果返回类型为 int,或者方法参数为空,则该 main 方法只是一个普通的静态方法,而不是该类的执行入口。
以上两个条件都是 Java 语言自身的规范,只有符合规范才能被 JVM 识别和执行。
除此之外, main 方法需要使用 static 关键字修饰来成为静态方法的原因是:静态方法也称为类方法,即 main 方法是类对象的方法,而不是通过 new 关键字创建的类的对象实例的成员方法。
由 JVM 类加载的相关知识可知,JVM 在运行过程中,当需要使用某个类的时候,如访问该类的静态属性时(或者类的静态方法,或者创建该类的对象实例),需要将该类的 class 文件的二进制数据加载到 JVM 的运行时数据区的方法区中,并且会创建对应的类型为 java.lang.Class 的类对象。之后就可以使用该类对象来访问该类的相关信息,如类的静态属性等。
所以当通过 java 命令来执行某个类时,首先会创建一个 JVM 进程,然后加载该类的二进制数据到 JVM 的方法区并创建类型为 java.lang.Class 的类对象。接着在该 JVM 进程的主线程中会通过该类对象来调用该类的 main 方法,从而完成类的执行。当 执行完 main 方法的所有代码,则方法调用结束,JVM 进程退出。
tips:不过如果在 main 方法内,存在让主线程阻塞等待的方法调用的话,则 JVM 进程会继续保持运行状态而不会退出,这也是 SpringBoot 框架基于 main 方法来启动 JVM 进程并保持该进程运行而不退出的实现原理。
2 SpringBoot项目的启动与运行
SpringBoot 框架的一个核心设计就是基于 main 方法来独立启动一个 JVM 进程来运行应用,而不需要将应用打 包为 war 包并部署到 Tomcat 去运行。这种设计简化了基于 SpringBoot 搭建的项目的部署和运行流程,使得 Java 企业级应用回归了最原始、最简单、但又是最高效的运行模式。
所以基于 SpringBoot 框架搭建项目时,会在应用代码目录 java 的顶层包路径下包含一个名称类似于 XXXApplication 的项目启动类,其中 XXX 是项目名称。如下是在上一小节介绍的 demo 项目中,由 SpringBoot 框架自动创建的的项目启动类 DemoApplication 的源码:
package com.yzxie.study.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
由以上源码实现可知,在 DemoApplication 类的执行入口方法 main 中只包含了一行代码,即调用由 SpringBoot 框架提供的 SpringApplication 类的静态方法 run 来完成 SpringBoot 框架的启动与运行。
拓展知识:
SpringApplication 的静态 run 方法的核心工作包括:在内部完成 Spring IOC 容器的加载和相关 bean 对象的创建,完成 Servlet 引擎的加载与在指定端口监听客户端的连接请求,使得该 JVM 进程的主线程阻塞不退出,保持运行状态,从而实现与传统的、将项目打 war 包部署到 Tomcat 去运行一样的效果。关于 SpringApplication 类的相关用法与设计原理的更多知识在后续章节详细分析。
由以上 1.1 部分的分析可知,当需要启动和运行项目时,可以使用 java 命令来启动和运行项目启动类,如 demo 项目的 DemoApplicatiion 。
不过与运行单个 Java 类不一样,在命令行使用 java 命令启动项目时,首先需要将整个项目打成一个 jar 包,然后将该 jar 包添加到系统的类路径,或者在 java 命令中指定该 jar 包所在的类路径,最后在 java 命令中指定需要执行的 Java 类名,如 DemoApplication,完成启动和执行该类。
tips:Java 命令执行 Java 类的工作原理为首先创建一个 JVM 进程,然后在主线程中调用 main 方法来完成类的执行。其中将项目打成 jar 包并使用 java 命令来在命令行启动和运行项目是 Java 企业级项目在 Linux 服务器部署的常用方式。
3 总结
在本小节我们首先介绍了 Java 语言最原始的基于静态 main 方法来执行 Java 类的相关用法和工作原理,以及分析了 main 方法定义的相关规范。具体为 main 方法要符合是静态方法,方法返回值为 void,方法参数为 String 数组这三个条件,只有这样才能被 JVM 进程识别为执行入口方法并进行调用。
其次我们分析了 SpringBoot 应用的启动运行方式,即基于 SpringBoot 搭建的项目回归了基于 main 方法启动运行的方式,从而实现了以独立 JVM 进程来启动和运行项目,而不需要依赖额外部署的 Tomcat,这极大简化了 Spring 应用的部署和运行流程。