Maven常用插件介绍及如何打一个瘦jar包

零:说在前面

        Maven插件很多,但是本人感觉真正常用的有这么几个:spring-boot-maven-plugin、maven-jar-plugin、maven-resource-plugin、maven-dependency-plugin、maven-assembly-plugin。下面分别介绍并举例

 

一:常用插件的介绍

        1.1:spring-boot-maven-plugin

                负责将源码和依赖、以及springboot loader相关内容打成一个包。保证打出的包能独立运行。

        1.2:maven-jar-plugin

                仅负责将源码打成jar包,不能独立运行。另外,可以根据你的设置,将依赖jar包路径和程序的主入口定义在所打jar包中的 MANIFEST.MF 文件里。

        1.3:maven-dependency-plugin

                负责将各种依赖打包。也可以根据你的设置,将所打的依赖jar包输出到指定位置。

        1.4:maven-resource-plugin

                负责将正式与测试用到的资源文件导出到指定位置。还负责用资源文件中的参数替换pom文件中对应的占位符。
        1.5:maven-assembly-plugin

                负责打包。但是至少要指定需要打哪些内容,这个插件才能正常干活儿。

        1.6:maven-surefire-plugin

                负责测试。这一插件可以在maven的test阶段单独执行,在编译打包阶段可以被执行或跳过。可以指定其测试的范围或全量执行所有以Test开头或结尾的代码。

        

二:常用插件的对比

        2.1:spring-boot-maven-plugin 相当于 jar-plugin 和 dependency-plugin 的功能合成。与那两个插件相比,spring-boot-maven-plugin 会把springboot的一些个性化的内容打到包里。比如上面提到的 springboot loader 内容。

        2.2:maven-assembly-plugin 是基于 jar-plugin 和 dependency-plugin 的成果做了二次加工。它会把其他插件打出的jar包以及用户指定的额外项目也打入到成品之中。换言之,如果你用spring-boot-maven-plugin 搭配 maven-assembly-plugin 一起工作 和 maven-jar-plugin 搭配 maven-assembly-plugin 一起工作得到的产物会有不同。

 

三:打一个瘦jar包

        3.1:背景

        在Dev/Ops开发运维一体化思想大行其道的今天,技术部门对CI/CD的要求也变得越来越高。因此,docker等容器技术的普及也是势在必行。那么如何充分利用docker镜像的分层思想,提高镜像的复用程度,并减少每次生成镜像所花费的时间等问题,就需要我们深入思考。

        3.2:解决方案

        首先,可以将做一个三层的基础docker镜像,分别包括操作系统层、jdk等工具层、基础依赖jar包和配置文件层(这一层,需要用到使用dependency和resource插件,将那些通用的、基本不会变化的依赖包和资源文件独立打包置于这一层),备用。

        其次,每次发布最新代码,只需要将所有可执行代码独立成一个jar包,并加入到前面提到的三层基础docker镜像中,作为第四层存在(这一层的内容主要用到maven-jar-plugin插件),然后发布即可。这样既提高了docker镜像的复用,也减少了每次更新代码时所打的jar包的臃肿程度。

四:插件应用举例

        4.1:maven-jar-plugin 举例及部分说明

 
  1. <plugin>
  2. <groupId>org.apache.maven.plugins</groupId>
  3. <artifactId>maven-jar-plugin</artifactId>
  4. <version>3.2.0</version>
  5. <configuration>
  6. <!-- 打包时包含的文件配置,在暗黑我的这个工程中,只打包 com 文件夹 -->
  7. <includes>
  8. <include>
  9. **/com/**
  10. </include>
  11. </includes>
  12.  
  13. <archive>
  14. <manifest>
  15. <!-- 配置加入依赖包 -->
  16. <addClasspath>true</addClasspath>
  17. <classpathPrefix>lib/</classpathPrefix>
  18. <useUniqueVersions>false</useUniqueVersions>
  19. <!-- Spring Boot 启动类(自行修改) -->
  20. <mainClass>com.ccccit.springdockerserver.SpringDockerServerApplication</mainClass>
  21. </manifest>
  22. <manifestEntries>
  23. <!-- 外部资源路径加入 manifest.mf 的 Class-Path -->
  24. <Class-Path>resources/</Class-Path>
  25. </manifestEntries>
  26. </archive>
  27. <!-- jar 输出目录 -->
  28. <outputDirectory>${project.build.directory}/pack/</outputDirectory>
  29. </configuration>
  30. </plugin>
 

       4.2:maven-dependency-plugin 举例及部分说明

 
  1. <plugin>
  2. <groupId>org.apache.maven.plugins</groupId>
  3. <artifactId>maven-dependency-plugin</artifactId>
  4. <version>3.2.0</version>
  5. <!-- 复制依赖 -->
  6. <executions>
  7. <execution>
  8. <!-- 这里的id可以随意写,与下面的goal无名称上的必然联系 -->
  9. <id>copy-dependencies</id>
  10. <phase>package</phase>
  11. <goals>
  12. <goal>copy-dependencies</goal>
  13. </goals>
  14. <configuration>
  15. <!-- 依赖包 输出目录 -->
  16. <outputDirectory>${project.build.directory}/pack/lib</outputDirectory>
  17. </configuration>
  18. </execution>
  19. </executions>
  20. </plugin>
 

         对于maven-dependency-plugin插件而言,常用的"goal"包含以下几个内容,后续有时间也许会详细说明。

 
  1. copy:拷贝指定jar包到指定目录,与当前工程的依赖没有关系
  2.  
  3. copy-dependencies:拷贝依赖jar包到指定目录
  4.  
  5. unpack:解压指定jar包到指定目录,与当前工程的依赖没有关系
  6.  
  7. unpack-dependencies:解压依赖jar包到指定目录
 

        4.3:maven-resources-plugin 举例及部分说明

 
  1. <!-- 在打包时,动态将maven的参数传给resource文件夹下的参数 -->
  2. <plugin>
  3. <groupId>org.apache.maven.plugins</groupId>
  4. <artifactId>maven-resources-plugin</artifactId>
  5. <version>3.2.0</version>
  6. <configuration>
  7. <encoding>UTF-8</encoding>
  8. <!-- 当这里为true,那么resource文件夹下的配置文件,比如application.yml这些文件里面的${}包起来的内容就可以被pom文件中profiles标签下的对应名称部分行替换了 -->
  9. <useDefaultDelimiters>true</useDefaultDelimiters>
  10. </configuration>
  11. <!-- 复制资源和bin文件夹 -->
  12. <executions>
  13. <execution>
  14. <id>copy-resources</id>
  15. <phase>package</phase>
  16. <goals>
  17. <goal>copy-resources</goal>
  18. </goals>
  19. <configuration>
  20. <resources>
  21. <resource>
  22. <!-- 文件来源 -->
  23. <directory>src/main/resources</directory>
  24. </resource>
  25. </resources>
  26. <!-- 资源文件的输出目录 -->
  27. <outputDirectory>${project.build.directory}/pack/resources</outputDirectory>
  28. </configuration>
  29. </execution>
  30. <execution>
  31. <id>copy-bin</id>
  32. <phase>package</phase>
  33. <goals>
  34. <goal>copy-resources</goal>
  35. </goals>
  36. <configuration>
  37. <resources>
  38. <resource>
  39. <!-- 文件来源 -->
  40. <directory>src/main/bin</directory>
  41. </resource>
  42. </resources>
  43. <!-- 资源文件的输出目录 -->
  44. <outputDirectory>${project.build.directory}/pack/bin</outputDirectory>
  45. </configuration>
  46. </execution>
  47. </executions>
  48. </plugin>
 

        以下内容是对上面提到的profiles替换的举例。比如下面我自定义了名为 letmesee.reason的标签,那么如果上面的useDefaultDelimiters标签值为true,那么在打包或编译阶段,maven会将resource文件夹下的所有配置文件的对应内容做替换

 
  1. <!-- pom 文件的 profiles部分 -->
  2. <profiles>
  3. <profile>
  4. <id>dev</id>
  5. <properties>
  6. <!-- 这里是让你自己随意定义属性用的,写成什么名字都无所谓,然后可以被resource中使用 -->
  7. <letmesee.reason>dev</letmesee.reason>
  8. <!-- 这里的 profiles.active 与其外层的profiles标签什么的毫无关系,仅仅是我碰巧起了个类似外层标签的名字,仅此而已 -->
  9. <profiles.active>dev</profiles.active>
  10. </properties>
  11. <!-- 是否为默认的环境 -->
  12. <activation>
  13. <activeByDefault>true</activeByDefault>
  14. </activation>
  15. </profile>
  16. <profile>
  17. <id>shanxi</id>
  18. <properties>
  19. <letmesee.reason>shanxi</letmesee.reason>
  20. <profiles.active>shanxi</profiles.active>
  21. </properties>
  22. </profile>
  23. </profiles>
 
 
  1. ## application.yml 文件中的部分内容
  2. spring:
  3. profiles:
  4. #要注意的是,active这里可以使用的是maven配置中<properties> 标签下的任何一个标签,哪怕我自定义了一个letmesee的标签也无所谓
  5. active: ${letmesee.reason}
 

五:附录

 
  1. Maven的内置参数
  2.  
  3. ${basedir} 项目根目录
  4. ${project.build.directory} 构建终产物输出目录,缺省为target
  5. ${project.build.outputDirectory} 构建临时输出目录,缺省为target/classes
  6. {project.build.finalName} 产出物名称,缺省为${project.artifactId}-${project.version}
  7. ${project.packaging} 打包类型,缺省为jar
  8. ${project.xxx} 当前pom文件的名为xxx节点内容
 

       Maven打包SpringBoot项目配置文件第三方依赖包外置_springboot打包包含依赖包-CSDN博客

背景

对于SpringBoot项目,我们可以直接使用Maven将项目打成一个可执行Jar包,上传到Linux服务器启动运行。

原始打包

对于打成单独一个jar包的情况,我们只需要引入打包插件,执行 mvn package即可

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

生成的jar包默认在 项目的编译目录的根目录下
在这里插入图片描述

对于这种打包方式,配置文件和第三方依赖包都包含在jar包里,项目运行的过程中,需要改动配置文件的话需要重新打包并部署。

SpringBoot读取配置文件(application.yml)的优先级

  • jar包同级目录的config目录
  • jar包同级目录
  • classPath(即resources目录)的config目录
  • classPath目录

还有一种最高优先级的方式是项目启动时通过命令的方式指定配置文件:

java –jar -Dspring.config.location=xxx/xxx/xxxx.properties xxxx.jar

所以我们只需要把配置文件拷贝出来放到与jar包同级的config目录中即可

配置文件和第三方jar包外置

所以我们只需要配置 配置文件的打包位置为jar包同级的config目录,并将jar包内的配置文件剔除即可

打包方式与原来无差别 执行 mvn package即可

    <build>
        <defaultGoal>compile</defaultGoal>

        <finalName>${project.artifactId}</finalName>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <targetPath>${project.build.outputDirectory}</targetPath>
                <filtering>false</filtering>
            </resource>

            <resource>
                <directory>src/main/resources</directory>
                <targetPath>${project.build.testOutputDirectory}</targetPath>
                <filtering>false</filtering>
            </resource>
            <!-- 将指定配置文件copy到config目录,通常是那些可以在jar包外部修改的文件 -->
            <resource>
                <directory>src/main/resources</directory>
                <targetPath>${project.build.directory}/${project.artifactId}/config</targetPath>
                <includes>
                    <include>application.yml</include>
                    <include>application-dev.yml</include>
                    <include>application-test.yml</include>
                    <include>logback.xml</include>
                </includes>
            </resource>
        </resources>


        <plugins>
            <!-- 配置资源文件的打包路径 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <!-- 可选配置 -->
                <configuration>
                    <source>1.8</source>
                    <!-- 源代码使用的JDK版本 -->
                    <target>1.8</target>
                    <!-- 生成的目标class文件的JDK编译版本 -->
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <outputDirectory>${project.build.directory}/${project.artifactId}/</outputDirectory>
                    <!-- 排除不需要打进jar包的文件,也就是上面config目录中的文件 -->
                    <excludes>
                        <include>application.yml</include>
                        <include>application-dev.yml</include>
                        <include>application-test.yml</include>
                        <include>logback.xml</include>
                    </excludes>
                    <archive>
                        <!-- 程序入口,即main函数 -->
                        <manifest>
                            <mainClass>com.shsnc.report.Run</mainClass>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>lib/</classpathPrefix>
                            <useUniqueVersions>false</useUniqueVersions>
                        </manifest>
                        <manifestEntries>
                            <Class-Path>config/ .</Class-Path>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>


            <!-- 把jar复制到target目录下的lib目录下 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <!-- ${project.build.directory}就是我们通常看到的target目录 -->
                            <outputDirectory>${project.build.directory}/${project.artifactId}/lib</outputDirectory>
                            <excludeTransitive>false</excludeTransitive>
                            <stripVersion>false</stripVersion>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>jar-no-fork</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

            <!-- 跳过测试编译 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
        </plugins>
    </build>

我们配置的jar包和配置文件在项目的编译目录下的/artifactId/下,打包完成后如图所示

在这里插入图片描述

配置文件

在这里插入图片描述

第三方依赖包

在这里插入图片描述

部署方式

将打包时生成的jar包上级目录,我这里也就是ccic_report,压缩后上传到服务器,解压并进入目录
执行 java -jar xxx.jar即可

IDEA的Maven打包步骤

在这里插入图片描述

胖包和瘦包的区别:

胖包:将maven项目中的依赖包和项目打为一个包
瘦包:直接打包,不打包依赖包,仅打包出项目中的代码到JAR包中。

maven打胖包

胖包的意识就是可以直接使用 java -jar 的命令在拥有 jvm 的主机上运行java文件(就是把需要的依赖一起打包进一个jar包中)

在这里插入图片描述

    <build>
        <!-- 打包之后jar包的名字 -->
        <finalName>student-service</finalName>

        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <skipTests>true</skipTests>
                </configuration>
            </plugin>
        </plugins>
    </build>

maven打瘦包

可以看见maven打瘦包后,jar包会特别的小,上传到服务器就会特别的快

在这里插入图片描述

  <build>
        <!-- 打包之后jar包的名字 -->
        <finalName>student-service</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>2.8</version>
                <executions>
                    <execution>
                        <id>copy</id>
                        <phase>package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/lib</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>2.4</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>lib/</classpathPrefix>
                            <useUniqueVersions>false</useUniqueVersions>
        						<!-- 启动类的地址 -->
                            <mainClass>com.zhao.DingDongSchoolApplication</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>

原文链接
这个插件的设计是为了当你每次构建项目时能够获得一个唯一的构建序列号(build number)。比如说,打包时,包名称以当前构建时间结尾,或者每次生成的jar包中包含唯一的序列号。

使用示例:
本示例中使用了此插件的create goal,也就是基于SCM(Source code management)的版本号来获取构建序列号(build number).

在项目的pom文件中添加如下

<plugin>
	<groupId>org.codehaus.mojo</groupId>
	<artifactId>buildnumber-maven-plugin</artifactId>
	<version>1.4</version>
	<executions>
		<execution>
			<phase>validate</phase>
			<goals>
				<goal>create</goal>
			</goals>
		</execution>
	</executions>
	<configuration>
		<shortRevisionLength>7</shortRevisionLength>
		<doCheck>false</doCheck>
		<doUpdate>false</doUpdate>
	</configuration>
</plugin>

解释:

  • executions部分表示在maven的validate阶段执行此插件的create goal。
  • shortRevisionLength部分用来指定获取SCM版本号的长度,比如git revision为b0c1c69be579175e63eeb1c056d607f01ab61e96,那么此示例中只获取后7位,也就是ab61e96。
  • doCheck设置为false表示不检查是否本地有文件被修改过。如果设置为true,一旦本地有文件被修改,那么构建就会失败。
  • doUpdate表示是否跟新本地的repository,false 表示不跟新。

在打包插件的配置如下,注意这里使用了${buildNumber}来使用生成的构建序列号。

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-jar-plugin</artifactId>
	<configuration>
		<archive>
			<manifest>
				<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
			</manifest>
			<manifestEntries>
				<Implementation-Version>${project.version}-${buildNumber}-${maven.build.timestamp}</Implementation-Version>
			</manifestEntries>
		</archive>
	</configuration>
</plugin>

执行maven的打包命令mvn -DskipTests=true clean package.
此时我们可以看到此插件被执行

在这里插入图片描述

解压生成的jar包,打开 manifest (META-INF/MANIFEST.mf),我们可以发现${buildNumber}被替换成相应的构建序列号了。
在这里插入图片描述

maven配置代理

 

1.找到文件

find / -name "settings.xml"

 maven 无法正常访问网络时候,需要通过代理进行访问

找到Maven的setting.conf文件

2.找到proxies

在maven的 setting.conf文件中找到

默认找到的时候文件 这里是被注释的。

3.配置如下

3.1配置截图

 
  1. <proxies>
  2. <!-- proxy
  3. | Specification for one proxy, to be used in connecting to the network.
  4. | -->
  5.  
  6. <proxy>
  7. <id>optional</id>
  8. <active>true</active>
  9. <protocol>http</protocol>
  10. <!--
  11. <username></username>
  12. <password></password>
  13. -->
  14. <host>10.26.2.36</host>
  15. <port>8981</port>
  16. <!--
  17. <nonProxyHosts>10.26.2.**</nonProxyHosts>
  18. -->
  19. </proxy>
  20.  
  21. </proxies>
 

3.2配置说明

id:可以自定义命名

active:填写true

protocol:填写http

username:有用户名则填写,没有可不填

password:有密码则填写,没有可不填

host:代理主机的地址

port:代理主机的端口

nonProxyHosts:访问这里的是不需要通过代理的

前言

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 文件,这个文件内容基本如下。
在这里插入图片描述引入 spring-boot-starter-parentspring-boot-starter-webspring-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 为插件的 artifactId3.1.0 为插件的版本号,clean 为插件的目标,一个插件可以包含多个目标,生命周期阶段具体是与插件的目标绑定的,插件可以简单理解为一个 Java 类,目标可以简单理解为这个类的方法,到了某个生命周期阶段就会调用该生命周期阶段对应的目标方法。

插件调用方式

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

  1. mvn 生命周期阶段:如 mvn clean,maven 会自动执行与该生命周期绑定的插件目标。
  2. mvn groupId:artifactId[:version]:goal,如 mvn org.apache.maven.plugins:maven-clean-plugin:3.1.0:clean,对于版本号 version 来说是可以省略的,如果省略 maven 会使用本地仓库中最新的版本。
  3. 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 的方式启动应用,下篇将会介绍。

 

posted @ 2024-03-26 10:37  CharyGao  阅读(317)  评论(0)    收藏  举报