mvn spring-boot:run 是怎样运行 Spring Boot 项目的?

简介: 前言Spring Boot 项目的运行方式大概可以分为这么几种:IDE 中直接运行 main 方法、mvn spring-boot:run 命令启动、打包后通过 java -jar 方式启动、打包后通过 Tomcat 启动,其中前两种是开发环境下运行的主要方式。

前言


Spring Boot 项目的运行方式大概可以分为这么几种:IDE 中直接运行 main 方法、mvn spring-boot:run 命令启动、打包后通过 java -jar 方式启动、打包后通过 Tomcat 启动,其中前两种是开发环境下运行的主要方式。


今天我们先来探讨下 mvn spring-boot:run 命令运行 Spring Boot 项目的原理,通过这篇文章你能学到的是 maven 插件基本内容以及 Spring Boot 对 maven 插件的应用。


maven 插件


初学 Spring Boot 时,我们最先接触到的是一个 pom 文件,这个文件内容基本如下。


63.png

引入 spring-boot-starter-parent、spring-boot-starter-web、spring-boot-maven-plugin 之后我们就可以通过 mvn spring-boot:run 的方式运行了,当时的我由于对 Spring Boot 了解较少,只能依葫芦画瓢,如今来看,mvn spring-boot:run 运行 Spring Boot 就是使用了自定义的 maven 插件 spring-boot-maven-plugin,想要深入理解这个插件我们需要先对 maven 的插件机制具有一定认识。


认识 maven 插件


maven 作为一个项目管理工具,将项目分成了三个生命周期 clean、default、site,每个生命周期又包括多个阶段,例如 clean 生命周期的阶段包含 pre-clean、clean、post-clean,每个阶段绑定了 0 个或多个插件,功能都是由插件来实现的,例如对于 clean 生命周期的 clean 阶段就是由 maven-clean-plugin 插件来执行的。我们可以通过一个示例来看下,还是使用上面 pom 文件对应的项目,当执行 mvn clean 时可以看到控制台打印如下的日志。


➜  spring-boot-demo mvn clean
[INFO] Scanning for projects...
[INFO] 
[INFO] ---------------------< com.zzuhkp:project-parent >----------------------
[INFO] Building project-parent 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:3.1.0:clean (default-clean) @ project-parent ---
[INFO] Deleting /Users/zzuhkp/hkp/project/spring-boot-demo/target
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.434 s
[INFO] Finished at: 2022-03-29T21:14:31+08:00
[INFO] ------------------------------------------------------------------------
➜  spring-boot-demo 


注意观察控制台出现了 maven-clean-plugin:3.1.0:clean 字样,maven-clean-plugin 为插件的 artifactId,3.1.0 为插件的版本号,clean 为插件的目标,一个插件可以包含多个目标,生命周期阶段具体是与插件的目标绑定的,插件可以简单理解为一个 Java 类,目标可以简单理解为这个类的方法,到了某个生命周期阶段就会调用该生命周期阶段对应的目标方法。


插件调用方式

插件有如下几种调用方式:


mvn 生命周期阶段:如 mvn clean,maven 会自动执行与该生命周期绑定的插件目标。

mvn groupId:artifactId[:version]:goal,如 mvn org.apache.maven.plugins:maven-clean-plugin:3.1.0:clean,对于版本号 version 来说是可以省略的,如果省略 maven 会使用本地仓库中最新的版本。

mvn 插件前缀:goal:插件前缀可以理解为插件的标识,用于简化插件的调用,例如 mvn spring-boot:run 中的 spring-boot 就是 spring-boot-maven-plugin 插件的前缀,自定义插件如果遵循 xxx-maven-plugin 的形式,maven 默认会将 maven-plugin 前面的内容作为插件前缀。


自定义 maven 插件


maven 将插件的目标称为 MOJO,即 Plain-old-Java-object,表示 maven 中的 POJO,每个目标使用一个实现 Mojo 接口的类表示。当然了自定义插件需要先引入一些依赖,必选的依赖是 maven-plugin-api,这个依赖允许使用 Java doc 作为目标的元数据,坐标如下:


<dependency>
    <groupId>org.apache.maven</groupId>
    <artifactId>maven-plugin-api</artifactId>
    <version>3.8.5</version>
</dependency>


Java doc 是最早定义 maven 插件目标的元数据的方式,出现于注解之前,目前使用注解较多,因此还需要引入 maven-plugin-annotations 依赖。


<dependency>
    <groupId>org.apache.maven.plugin-tools</groupId>
    <artifactId>maven-plugin-annotations</artifactId>
    <version>3.6.4</version>
    <scope>provided</scope>
</dependency>


插件编译后会根据 Java doc 或注解生成 META-INF/maven/plugin.xml 文件,这个文件中包含插件的一些基本信息,如插件前缀是什么、有哪些目标等。


maven 提供了一个实现 Mojo 的抽象类 AbstractMojo,自定义插件目标直接继承这个类即可,下面看下我们自定义的插件目标。


@Mojo(name = "custom", defaultPhase = LifecyclePhase.CLEAN)
@Execute(phase = LifecyclePhase.CLEAN)
public class CustomMojo extends AbstractMojo {
    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        getLog().info("自定义插件执行");
    }
}


@Mojo 注解表示这个类是一个插件目标,name 属性用于指定目标的名称,defaultPhase 用于指定这个目标默认绑定的生命周期阶段。此外自定义的插件目标还添加了一个 @Execute 注解,并使用 phase 属性指定了生命周期阶段。我们自定义插件仅打印了表示已执行的日志。


defaultPhase 和 phase 属性表示生命周期阶段,有何不同呢?


区别主要在于 defaultPhase 仅用于简化使用插件时对插件绑定生命周期阶段的配置,具体如下:


<plugin>
    <groupId>com.zzuhkp</groupId>
    <artifactId>custom-maven-plugin</artifactId>
    <version>1.0-SNAPSHOT</version>
    <executions>
        <execution>
            <!--<phase>clean</phase>-->
            <goals>
                <goal>custom</goal>
            </goals>
        </execution>
    </executions>
</plugin>


由于我们自定义插件 custom-maven-plugin 目标 custom 使用 defaultPhase 指定了默认绑定的生命周期阶段,那么就不必在 execution 中指定了,添加上面的配置后执行 maven clean 可以自动执行我们自定义的插件目标 custom。


➜  demo mvn clean
[INFO] Scanning for projects...
[INFO] 
[INFO] --------------------------< com.zzuhkp:bean >---------------------------
[INFO] Building demo 1.0-SNAPSHOT
[INFO] ----------------------------[ maven-plugin ]----------------------------
[INFO] 
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ bean ---
[INFO] Deleting /Users/zzuhkp/hkp/project/demo/target
[INFO] 
[INFO] >>> custom-maven-plugin:1.0-SNAPSHOT:custom (default) > clean @ bean >>>
[INFO] 
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ bean ---
[INFO] 
[INFO] <<< custom-maven-plugin:1.0-SNAPSHOT:custom (default) < clean @ bean <<<
[INFO] 
[INFO] 
[INFO] --- custom-maven-plugin:1.0-SNAPSHOT:custom (default) @ bean ---
[INFO] 自定义插件执行
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.273 s
[INFO] Finished at: 2022-03-29T22:06:11+08:00
[INFO] ------------------------------------------------------------------------
➜  demo 


@Execute 注解中的 phase 指定的生命周期阶段用于直接执行该插件目标时,等该生命周期阶段对应的插件执行后再执行该插件,执行 maven custom:custom可以看到如下的控制台日志。


➜  demo mvn custom:custom
[INFO] Scanning for projects...
[INFO] 
[INFO] --------------------------< com.zzuhkp:bean >---------------------------
[INFO] Building demo 1.0-SNAPSHOT
[INFO] ----------------------------[ maven-plugin ]----------------------------
[INFO] 
[INFO] >>> custom-maven-plugin:1.0-SNAPSHOT:custom (default-cli) > clean @ bean >>>
[INFO] 
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ bean ---
[INFO] 
[INFO] <<< custom-maven-plugin:1.0-SNAPSHOT:custom (default-cli) < clean @ bean <<<
[INFO] 
[INFO] 
[INFO] --- custom-maven-plugin:1.0-SNAPSHOT:custom (default-cli) @ bean ---
[INFO] 自定义插件执行
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.272 s
[INFO] Finished at: 2022-03-29T22:11:23+08:00
[INFO] ------------------------------------------------------------------------
➜  demo 


@Execute 注解 phase 指定的生命周期阶段是 clean,因此这个阶段对应的 maven-clean-plugin 执行后才执行我们自定义的插件。


spring-boot-maven-plugin


了解 maven 插件的机制后就可以看在 Spring Boot 项目引入的 spring-boot-maven-plugin 插件了。在引入 spring-boot-maven-plugin 插件的项目下执行命令 mvn help:describe -Dplugin=org.springframework.boot:spring-boot-maven-plugin -Ddetail 查看插件帮助信息,部分结果如下:


➜  spring-boot-demo mvn help:describe -Dplugin=org.springframework.boot:spring-boot-maven-plugin -Ddetail           
[INFO] Scanning for projects...
[INFO] 
[INFO] ---------------------< com.zzuhkp:project-parent >----------------------
[INFO] Building project-parent 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- maven-help-plugin:3.2.0:describe (default-cli) @ project-parent ---
[INFO] org.springframework.boot:spring-boot-maven-plugin:2.2.7.RELEASE
Name: Spring Boot Maven Plugin
Description: Spring Boot Maven Plugin
Group Id: org.springframework.boot
Artifact Id: spring-boot-maven-plugin
Version: 2.2.7.RELEASE
Goal Prefix: spring-boot
This plugin has 6 goals:
spring-boot:run
  Description: Run an executable archive application.
  Implementation: org.springframework.boot.maven.RunMojo
  Language: java
  Bound to phase: validate
  Before this goal executes, it will call:
    Phase: 'test-compile'
  Available parameters:
    mainClass
      User property: spring-boot.run.main-class
      The name of the main class. If not specified the first compiled class
      found that contains a 'main' method will be used.


Goal Prefix: spring-boot 表明了 spring-boot-maven-plugin 的插件前缀是 spring-boot,我们还得到信息这个插件拥有 6 个目标。


对于我们这篇分析的 spring-boot:run 目标来说,它用于执行应用程序,实现类是 RunMojo,默认绑定了生命周期 validate,并且在目标执行时将会先调用 test-compile 生命周期阶段。此外目标还具有一些参数用于调整自身的行为,例如对于 mainClass 来说可以手动指定主类。


mvn spring-boot:run 分析


那么 spring-boot:run 目标到底如何执行 Spring Boot 应用程序的呢?根据帮助信息的描述,我们找到 org.springframework.boot.maven.RunMojo 类的代码。


@Mojo(name = "run", requiresProject = true, defaultPhase = LifecyclePhase.VALIDATE,
    requiresDependencyResolution = ResolutionScope.TEST)
@Execute(phase = LifecyclePhase.TEST_COMPILE)
public class RunMojo extends AbstractRunMojo {
}

这个目标类继承了 AbstractRunMojo 类,execute 方法由这个父类实现,因此我们看下这个父类执行目标的核心代码。


public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
  @Parameter(property = "spring-boot.run.skip", defaultValue = "false")
  private boolean skip;
  @Override
  public void execute() throws MojoExecutionException, MojoFailureException {
    if (this.skip) {
      getLog().debug("skipping run as per configuration.");
      return;
    }
    run(getStartClass());
  }
}


核心方法比较简单,如果不需要跳过则获取并运行启动类,先看下如何获取启动类的吧。


public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
  @Parameter(property = "spring-boot.run.main-class")
  private String mainClass;
  private String getStartClass() throws MojoExecutionException {
    String mainClass = this.mainClass;
    if (mainClass == null) {
      try {
        // 如果没有配置主类,则从输出路径中查找标注了 @SpringBootApplication 注解且存在 main 方法的类
        mainClass = MainClassFinder.findSingleMainClass(this.classesDirectory,
            SPRING_BOOT_APPLICATION_CLASS_NAME);
      } catch (IOException ex) {
        throw new MojoExecutionException(ex.getMessage(), ex);
      }
    }
    if (mainClass == null) {
      throw new MojoExecutionException("Unable to find a suitable main class, please add a 'mainClass' property");
    }
    return mainClass;
  }
}


获取主类时优先使用了配置的主类,如果没有配置则会查找标注了 @SpringBootApplication 注解且存在 main 方法的类作为主类。如果存在多个主类时为了保证使用自己想要的主类只能手动进行配置了。示例如下。


<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <mainClass>com.zzuhkp.DemoApplication</mainClass>
    </configuration>
</plugin>


获取到主类后下一步就是运行主类了。


public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
  @Parameter(property = "spring-boot.run.fork", defaultValue = "true")
  private boolean fork;
  private void run(String startClassName) throws MojoExecutionException, MojoFailureException {
    boolean fork = isFork();
    this.project.getProperties().setProperty("_spring.boot.fork.enabled", Boolean.toString(fork));
    if (fork) {
      doRunWithForkedJvm(startClassName);
    } else {
      logDisabledFork();
      runWithMavenJvm(startClassName, resolveApplicationArguments().asArray());
    }
  }
}


如果需要 fork 则创建新的进程运行应用,否则在当前 JVM 进程中运行应用,默认需要 fork,因此 spring-boot:run 会启一个新的进程。


总结

本文先提出了几种执行 Spring Boot 的方式,然后介绍 maven 插件的机制以及 spring-boot-maven-plugin 如何利用 maven 插件运行 Spring Boot 应用。除了插件执行,Spring Boot 最重要的特性之一是使用 java -jar 的方式启动应用,下篇将会介绍。


目录
相关文章
|
8天前
|
网络协议 Java Shell
java spring 项目若依框架启动失败,启动不了服务提示端口8080占用escription: Web server failed to start. Port 8080 was already in use. Action: Identify and stop the process that’s listening on port 8080 or configure this application to listen on another port-优雅草卓伊凡解决方案
java spring 项目若依框架启动失败,启动不了服务提示端口8080占用escription: Web server failed to start. Port 8080 was already in use. Action: Identify and stop the process that’s listening on port 8080 or configure this application to listen on another port-优雅草卓伊凡解决方案
37 7
|
23天前
|
监控 Java 应用服务中间件
SpringBoot是如何简化Spring开发的,以及SpringBoot的特性以及源码分析
Spring Boot 通过简化配置、自动配置和嵌入式服务器等特性,大大简化了 Spring 应用的开发过程。它通过提供一系列 `starter` 依赖和开箱即用的默认配置,使开发者能够更专注于业务逻辑而非繁琐的配置。Spring Boot 的自动配置机制和强大的 Actuator 功能进一步提升了开发效率和应用的可维护性。通过对其源码的分析,可以更深入地理解其内部工作机制,从而更好地利用其特性进行开发。
42 6
|
1月前
|
缓存 安全 Java
Spring Boot 3 集成 Spring Security + JWT
本文详细介绍了如何使用Spring Boot 3和Spring Security集成JWT,实现前后端分离的安全认证概述了从入门到引入数据库,再到使用JWT的完整流程。列举了项目中用到的关键依赖,如MyBatis-Plus、Hutool等。简要提及了系统配置表、部门表、字典表等表结构。使用Hutool-jwt工具类进行JWT校验。配置忽略路径、禁用CSRF、添加JWT校验过滤器等。实现登录接口,返回token等信息。
358 12
|
1月前
|
负载均衡 IDE Java
SpringBoot整合XXL-JOB【04】- 以GLUE模式运行与执行器负载均衡策略
在本节中,我们将介绍XXL-JOB的GLUE模式和集群模式下的路由策略。GLUE模式允许直接在线上改造方法为定时任务,无需重新部署。通过一个测试方法,展示了如何在调度中心配置并使用GLUE模式执行定时任务。接着,我们探讨了多实例环境下的负载均衡策略,确保任务不会重复执行,并可通过修改路由策略(如轮训)实现任务在多个实例间的均衡分配。最后,总结了GLUE模式和负载均衡策略的应用,帮助读者更深入理解XXL-JOB的使用。
47 9
|
1月前
|
存储 安全 Java
Spring Boot 3 集成Spring AOP实现系统日志记录
本文介绍了如何在Spring Boot 3中集成Spring AOP实现系统日志记录功能。通过定义`SysLog`注解和配置相应的AOP切面,可以在方法执行前后自动记录日志信息,包括操作的开始时间、结束时间、请求参数、返回结果、异常信息等,并将这些信息保存到数据库中。此外,还使用了`ThreadLocal`变量来存储每个线程独立的日志数据,确保线程安全。文中还展示了项目实战中的部分代码片段,以及基于Spring Boot 3 + Vue 3构建的快速开发框架的简介与内置功能列表。此框架结合了当前主流技术栈,提供了用户管理、权限控制、接口文档自动生成等多项实用特性。
82 8
|
2月前
|
存储 JSON 前端开发
【Spring项目】表白墙,留言板项目的实现
本文主要介绍了表白墙项目的实现,包含前端和后端代码,以及测试
|
2月前
|
JSON 前端开发 Java
|
2月前
|
缓存 前端开发 Java
【Spring】——SpringBoot项目创建
SpringBoot项目创建,SpringBootApplication启动类,target文件,web服务器,tomcat,访问服务器
|
25天前
|
JavaScript Java 测试技术
基于SpringBoot+Vue实现的留守儿童爱心网站设计与实现(计算机毕设项目实战+源码+文档)
博主是一位全网粉丝超过100万的CSDN特邀作者、博客专家,专注于Java、Python、PHP等技术领域。提供SpringBoot、Vue、HTML、Uniapp、PHP、Python、NodeJS、爬虫、数据可视化等技术服务,涵盖免费选题、功能设计、开题报告、论文辅导、答辩PPT等。系统采用SpringBoot后端框架和Vue前端框架,确保高效开发与良好用户体验。所有代码由博主亲自开发,并提供全程录音录屏讲解服务,保障学习效果。欢迎点赞、收藏、关注、评论,获取更多精品案例源码。
59 10
|
25天前
|
JavaScript Java 测试技术
基于SpringBoot+Vue实现的家政服务管理平台设计与实现(计算机毕设项目实战+源码+文档)
面向大学生毕业选题、开题、任务书、程序设计开发、论文辅导提供一站式服务。主要服务:程序设计开发、代码修改、成品部署、支持定制、论文辅导,助力毕设!
45 8