javascript
spring-boot:run 是怎么运行 Spring Boot 项目的?
初學 Spring?Boot 的時候,按照官方文檔,都是建立了一個項目之后,然后執(zhí)行?mvn spring-boot:run?就能把這個項目運行起來。
我就很好奇這個指令到底做了什么,以及為什么項目里包含了 main 方法的那個class,要加一個?@SpringBootApplication?的注解呢?
為什么加了這個注解@SpringBootApplication之后,mvn spring-boot:run?指令就能找到這個class并執(zhí)行它的main方法呢?
首先我注意到,用maven新建的spring boot項目,pom.xml 里面有這么一條配置:
<build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins> </build>????看來mvn spring-boot:run?指令應該就是這個插件提供的。
由于不懂maven插件的開發(fā)機制,看不太懂,于是去找了下?maven?的插件開發(fā)文檔:
http://maven.apache.org/guides/plugin/guide-java-plugin-development.html
根據官方的文檔,一個 maven 插件會有很多個目標,每個目標就是一個 Mojo 類,比如?mvn spring-boot:run?這個指令,spring-boot這部分是一個maven插件,run這部分是一個maven的目標,或者指令。
根據maven插件的開發(fā)文檔,定位到?spring-boot-maven-plugin?項目里的RunMojo.java,就是mvn spring-boot:run?這個指令所運行的java代碼。
關鍵方法有兩個,一個是?runWithForkedJvm,一個是runWithMavenJvm,如果pom.xml是如上述配置,則運行的是?runWithForkedJvm,如果pom.xml里的配置如下,則運行runWithMavenJvm:
<build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><fork>false</fork></configuration></plugin></plugins> </build>????runWithForkedJvm?與?runWithMavenJvm?的區(qū)別,在于前者是起一個進程來運行當前項目,后者是起一個線程來運行當前項目。
我首先了解的是?runWithForkedJvm:
private?int?forkJvm(File?workingDirectory,?List<String\\>?args,?Map<String,?String\\>?environmentVariables)??throws?MojoExecutionException?{??try?{??RunProcess?runProcess?=?new?RunProcess(workingDirectory,?new?JavaExecutable().toString());??Runtime.getRuntime().addShutdownHook(new?Thread(new?RunProcessKiller(runProcess)));??return?runProcess.run(true,?args,?environmentVariables);??}??catch?(Exception?ex)?{??throw?new?MojoExecutionException("Could?not?exec?java",?ex);??}?? }根據這段代碼,RunProcess是由spring-boot-loader-tools 這個項目提供的,需要提供的workingDirectory 就是項目編譯后的 *.class 文件所在的目錄,environmentVariables 就是解析到的環(huán)境變量,args里,對于spring-boot的那些sample項目,主要是main方法所在的類名,以及引用的相關類庫的路徑。
workingDirectory 可以由maven的 ${project} 變量快速獲得,因此這里的關鍵就是main方法所在的類是怎么找到的,以及引用的相關類庫的路徑是如何獲得的。
找main方法所在的類的實現是在?AbstractRunMojo.java?里面:
mainClass?=?MainClassFinder.findSingleMainClass(this.classesDirectory,?SPRING\_BOOT\_APPLICATION\_CLASS\_NAME);MainClassFinder.java?是由spring-boot-loader-tools提供的,找到main方法所在的類主要是如下的代碼:
static?<T>?T?doWithMainClasses(File?rootFolder,?MainClassCallback<T>?callback)?throws?IOException?{if?(!rootFolder.exists())?{return?null;?//?nothing?to?do}if?(!rootFolder.isDirectory())?{throw?new?IllegalArgumentException("Invalid?root?folder?'"?+?rootFolder?+?"'");}String?prefix?=?rootFolder.getAbsolutePath()?+?"/";Deque<File>?stack?=?new?ArrayDeque<>();stack.push(rootFolder);while?(!stack.isEmpty())?{File?file?=?stack.pop();if?(file.isFile())?{try?(InputStream?inputStream?=?new?FileInputStream(file))?{ClassDescriptor?classDescriptor?=?createClassDescriptor(inputStream);if?(classDescriptor?!=?null?&&?classDescriptor.isMainMethodFound())?{String?className?=?convertToClassName(file.getAbsolutePath(),?prefix);T?result?=?callback.doWith(new?MainClass(className,?classDescriptor.getAnnotationNames()));if?(result?!=?null)?{return?result;}}}}if?(file.isDirectory())?{pushAllSorted(stack,?file.listFiles(PACKAGE_FOLDER_FILTER));pushAllSorted(stack,?file.listFiles(CLASS_FILE_FILTER));}}return?null; }這里的核心就是利用spring的asm框架,讀取class文件的字節(jié)碼并分析,找到含有main方法的類,然后再判斷這個類有沒有使用了?@SpringBootApplication?注解,有的話,就屬于要執(zhí)行的代碼文件了。
如果項目里面有多個含有main方法且被@SpringBootApplication?注解的類的話,我看代碼應該是直接選擇找到的第一個開運行。
讀取依賴的庫路徑,在spring-boot-maven-plugin里有大量的代碼來實現,還是利用maven本身的特性實現的。
根據了解到的這些信息,我新建了一個普通的java項目bootexp,用一段簡單的代碼來運行起一個spring boot項目:
package?com.shahuwang.bootexp;import?java.io.File; import?java.io.IOException; import?java.util.ArrayList; import?java.util.HashMap; import?java.util.List; import?java.util.Map;import?org.springframework.boot.loader.tools.JavaExecutable; import?org.springframework.boot.loader.tools.MainClassFinder; import?org.springframework.boot.loader.tools.RunProcess;public?class?Runner {public?static?void?main(?String[]?args?)?throws?IOException?{String?SPRING_BOOT_APPLICATION_CLASS_NAME?=?"org.springframework.boot.autoconfigure.SpringBootApplication";File?classesDirectory?=?new?File("C:\\share\\bootsample\\target\\classes");String?mainClass?=?MainClassFinder.findSingleMainClass(classesDirectory,?SPRING_BOOT_APPLICATION_CLASS_NAME);RunProcess?runProcess?=?new?RunProcess(classesDirectory,?new?JavaExecutable().toString());Runtime.getRuntime().addShutdownHook(new?Thread(new?RunProcessKiller(runProcess)));List<String>?params?=?new?ArrayList<>();params.add("-cp");params.add("相關庫路徑")params.add(mainClass);Map<String,?String>?environmentVariables?=?new?HashMap<>();runProcess.run(true,?params,?environmentVariables);}private?static?final?class?RunProcessKiller?implements?Runnable?{private?final?RunProcess?runProcess;private?RunProcessKiller(RunProcess?runProcess)?{this.runProcess?=?runProcess;}@Overridepublic?void?run()?{this.runProcess.kill();}} }相關庫的路徑獲取,都是spring-boot-maven-plugin這個項目里面的私有方法,所以我這里直接在 bootsample 這個spring boot項目下執(zhí)行?mvn spring-boot:run -X, 輸出classpath,把classpath復制過來即可。執(zhí)行bootexp這個項目,即可運行起 bootsample 這個spring boot項目了。
所以為什么spring boot的項目,main方法所在的類都要加上注解 @SpringBootApplication 這個疑問也得到了解決。
綜上,mvn spring-boot:run?這個指令為什么能運行起一個spring boot項目就沒有那么神秘了,這里主要的難點就兩個,一個是maven插件的開發(fā),獲得項目的配置信息,執(zhí)行起指令;一個是類加載機制,以及注解分析。
?
作者:沙湖王
https://segmentfault.com/a/1190000021687878
總結
以上是生活随笔為你收集整理的spring-boot:run 是怎么运行 Spring Boot 项目的?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 用费曼技巧自学编程,香不香?
- 下一篇: Spring IOC 知识点总结,写得太