spring-boot-run 指令是怎么运行 Spring Boot 项…
2020-05-15 16:05:07来源:博客园 阅读 ()
spring-boot-run 指令是怎么运行 Spring Boot 项目的?
作者:沙湖王
https://segmentfault.com/a/1190000021687878
初学 Spring?Boot 的时候,按照官方文档,都是建立了一个项目之后,然后执行?mvn spring-boot:run
?就能把这个项目运行起来。
我就很好奇这个指令到底做了什么,以及为什么项目里包含了 main 方法的那个class,要加一个?@SpringBootApplication? 的注解呢?
为什么加了这个注解 @SpringBootApplication 之后,mvn spring-boot:run
?指令就能找到这个class并执行它的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插件的开发机制,看不太懂,于是去找了下?maven?的插件开发文档:
http://maven.apache.org/guides/plugin/guide-java-plugin-development.html
根据官方的文档,一个 maven 插件会有很多个目标,每个目标就是一个 Mojo 类,比如?mvn spring-boot:run
?这个指令,spring-boot这部分是一个maven插件,run这部分是一个maven的目标,或者指令。
根据maven插件的开发文档,定位到?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
?的区别,在于前者是起一个进程来运行当前项目,后者是起一个线程来运行当前项目。
我首先了解的是?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 就是解析到的环境变量,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文件的字节码并分析,找到含有main方法的类,然后再判断这个类有没有使用了?@SpringBootApplication?注解,有的话,就属于要执行的代码文件了。
如果项目里面有多个含有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;
????????}
????????@Override
????????public?void?run()?{
????????????this.runProcess.kill();
????????}
????}
}
相关库的路径获取,都是spring-boot-maven-plugin这个项目里面的私有方法,所以我这里直接在 bootsample 这个spring boot项目下执行?mvn spring-boot:run -X
, 输出classpath,把classpath复制过来即可。执行bootexp这个项目,即可运行起 bootsample 这个spring boot项目了。
所以为什么spring boot的项目,main方法所在的类都要加上注解 @SpringBootApplication 这个疑问也得到了解决。
综上,mvn spring-boot:run
?这个指令为什么能运行起一个spring boot项目就没有那么神秘了,这里主要的难点就两个,一个是maven插件的开发,获得项目的配置信息,执行起指令;一个是类加载机制,以及注解分析。
关注微信公众号:Java技术栈,在后台回复:boot,可以获取我整理的 N 篇 Spring Boot?教程,都是干货。
推荐去我的博客阅读更多:
1.Java JVM、集合、多线程、新特性系列教程
2.Spring MVC、Spring Boot、Spring Cloud 系列教程
3.Maven、Git、Eclipse、Intellij IDEA 系列工具教程
4.Java、后端、架构、阿里巴巴等大厂最新面试题
觉得不错,别忘了点赞+转发哦!
原文链接:https://www.cnblogs.com/javastack/p/12895286.html
如有疑问请与原作者联系
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
- 作为一个面试官,我想问问你Redis分布式锁怎么搞? 2020-06-10
- 怎么用Java 高效提取、替换、删除PDF文档中的图片 2020-06-09
- dockerMesos配置项是怎么解析的?案例详解 2020-06-02
- JVM 怎么判断对象已经死了? 2020-06-01
- 引入mybatis-plus报 Invalid bound statement错误怎么办,动 2020-05-28
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash