初学 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;}@Overridepublic 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插件的开发,获得项目的配置信息,执行起指令;一个是类加载机制,以及注解分析。

作者:沙湖王

https://segmentfault.com/a/1190000021687878

spring-boot:run 是怎么运行 Spring Boot 项目的?相关推荐

  1. 运行iOS开源项目的坑

    carthage使用 https://www.jianshu.com/p/7a0634e14332 多xocde版本共存 xcode12加入了applesilicon的支持,有平台问题 https:/ ...

  2. Spring Boot系列(一) Spring Boot介绍和基础POM文件

    2019独角兽企业重金招聘Python工程师标准>>> Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框 ...

  3. eclipse中run运行不了_Springboot专辑:运行 Spring Boot 应用的 3 种方式!

    一:在 IDE 中运行 在 Eclipse.IDEA 中直接运行,又有以下两种方式. jar 包方式 Spring Boot 默认采用 jar 包内嵌 Tomcat.Jetty 等 Server 的方 ...

  4. 在Docker中运行Spring Boot的高级功能测试

    来源:SpringForAll社区 想要学习更多有关Spring Boot项目的功能测试吗?阅读这篇博客可以让您掌握如何利用Docker容器进行测试. 概览 本文重点介绍如何使用Spring Boot ...

  5. Spring Boot 参考指南(运行你的应用程序)

    19. 运行你的应用程序 将你的应用程序打包为jar并使用嵌入式HTTP服务器的最大优点之一是,你可以像对待其他应用程序一样运行应用程序,调试Spring Boot应用程序也很简单,你不需要任何特殊的 ...

  6. 8s 使用本地打包镜像_在Docker环境构建、打包和运行Spring Boot应用

    为何考虑采用Docker? Docker是提供用户构建镜像的一种容器化技术,所构建的镜像包含了主要的应用程序和运行应用所需的所有依赖项.该镜像可在任何虚拟机或物理机器上的Docker容器上运行.它的强 ...

  7. Spring Boot应用的后台运行配置

    酱油一篇,整理一下关于Spring Boot后台运行的一些配置方式.在介绍后台运行配置之前,我们先回顾一下Spring Boot应用的几种运行方式: 运行Spring Boot的应用主类 使用Mave ...

  8. docker build -t_在Docker环境构建、打包和运行Spring Boot应用

    为何考虑采用Docker? Docker是提供用户构建镜像的一种容器化技术,所构建的镜像包含了主要的应用程序和运行应用所需的所有依赖项.该镜像可在任何虚拟机或物理机器上的Docker容器上运行.它的强 ...

  9. apache目录 vscode_VsCode搭建Java开发环境(Spring Boot项目创建、运行、调试)

    VsCode搭建Java开发环境(Spring Boot项目创建.运行.调试) 安装如下两个主要扩展即可,这两个扩展已关联java项目开发主要使用的maven.springboot等所需要的扩展. 开 ...

最新文章

  1. systemd下supervisord服务开机自启动以及注意事项
  2. 如何用JS获取页面上的所有标签
  3. 反射型XSS漏洞详解
  4. 利用unison+inotify 实现数据双向实时同步
  5. C++多线程强制终止
  6. 双十一快递被暴力分拣,快递员踩踏包裹随意扔,网友们却表示很理解...
  7. SQL SERVER 锁定的实例
  8. 20190109每日一句
  9. ssr提示服务器名无效_联想服务器在UEFI HII界面中配置阵列的基本操作
  10. 解决ERROR: text file '***' contains disallowed UTF-8 whitespace character(s)
  11. 平面设计常见的配色方案及色标
  12. IDEA 如何 buil dpath
  13. 递归的算法求1,1,2,3,5,8.......的第30位数是多少,然后求这些数的和.
  14. Keras深度学习实战——信用预测
  15. 【算法】算法之美—Crashing Balloon
  16. 【机器学习详解】SVM解回归问题
  17. Centos更改yum源为阿里云镜像源
  18. 区块链签章 + 云签约 让合同的法律效力无懈可击
  19. Feign详解4-Contract 源码
  20. BiLiBiLi视频转存方法

热门文章

  1. 个人申请并部署阿里云免费Symantec SSL过程浅谈
  2. LeetCode--160--相交链表
  3. 作为前端,你不得不知道的SEO
  4. Spring Boot 参考指南(运行你的应用程序)
  5. Xamarin.Android 使用 Encoding.GetEncoding(GB2312) 报错解决方案
  6. RedHat 7.2配置本地yum源
  7. 《数据库原理与应用(第3版)》——1.4 数据库系统的组成
  8. c# winform笔记
  9. MySQL中各种字段的取值范围
  10. ubuntu source