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;@SpringBootApplicationpublic 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 應用的部署和執行流程。