DevOps工程技术价值流:项目构建工具的选择与实践
在快速迭代的软件工程领域,项目构建工具扮演着举足轻重的角色。它们不仅自动化了构建、测试、打包和部署等关键环节,还显著提升了开发效率和质量。本文将深入探讨后端常用的Maven和Gradle,以及前端不可或缺的NPM,并重点对比Maven与Gradle的优劣,为您的项目构建工具选择提供有力参考。
一、Maven构建:Java项目的基石
Maven,作为Apache软件基金会的明星项目,专为Java项目提供构建和依赖管理支持。其核心在于约定的目录结构,这一结构确保了Maven能够精准定位项目资源,实现高效、准确的自动化构建。
-
目录结构约定:Maven规定了标准的项目目录结构,如
src/main/java
存放Java源文件,src/test/java
存放测试代码等。这种约定不仅简化了构建过程,还提高了项目的可维护性。 -
依赖管理:Maven通过
pom.xml
文件管理项目依赖,支持自动下载和更新依赖库,避免了手动管理依赖的繁琐和错误。 -
生命周期管理:Maven定义了清晰的构建生命周期,包括编译、测试、打包、部署等阶段,每个阶段都有明确的任务和顺序,确保构建过程的可控性和可预测性。
基于第三方工具或框架的约定 Maven 对工程目录结构的要求:
my-maven-project
|-- src
| |-- main
| | |-- java # Java 源代码目录
| | |-- resources # 资源文件目录(如配置文件)
| |-- test
| |-- java # 测试代码目录
| |-- resources # 测试资源文件目录
|-- pom.xml # Maven 项目对象模型文件,用于配置项目信息、依赖等
|-- target # 构建过程中生成的文件(如编译后的字节码文件、打包后的 JAR 文件等)
1 构建过程
在 Java 项目开发过程中,构建指的是使用“原材料生产产品”的过程。Maven 的构建过程主要包含以下环节:
-
清理(clean):删除构建过程中生成的文件,如
target
目录下的内容。 -
编译(compile):将 Java 源代码编译成字节码文件(.class 文件)。
-
测试(test):运行测试代码,验证项目的正确性。
-
打包(package):将编译后的字节码文件和资源文件打包成 JAR 或 WAR 文件。
-
安装(install):将打包后的文件安装到本地仓库中,供其他项目使用。
-
部署(deploy):将打包后的文件部署到远程仓库中,供其他团队或项目使用。
这些环节可以通过 Maven 的生命周期进行管理,每个生命周期阶段都有对应的目标(goal)可以执行。
2 依赖管理
Maven 中最关键的部分是依赖管理。使用 Maven 最主要的就是使用它的依赖管理功能,它可以帮助我们解决以下问题:
-
jar 包的下载:使用 Maven 之后,jar 包会从规范的远程仓库(如 Maven 中央仓库)下载到本地仓库中。这样,我们就无需手动下载和管理这些 jar 包了。
-
jar 包之间的依赖:通过依赖的传递性自动完成。例如,如果项目 A 依赖项目 B,而项目 B 又依赖项目 C,那么 Maven 会自动下载并引入项目 C 的 jar 包。
-
jar 包之间的冲突:在实际开发中,可能会遇到多个 jar 包包含相同类或接口的情况,这会导致类路径冲突。Maven 提供了多种机制来解决这个问题,如排除(exclusion)、版本仲裁(version mediation)等。通过对依赖的配置进行调整,我们可以让某些 jar 包不会被导入,从而解决冲突问题。
3 Maven 开发环境配置
3.1检查jdk安装
在安装 Maven 之前,请确保你已经正确安装了 JDK。Maven 支持 JDK 1.4 及以上的版本,但本书的所有样例都基于 JDK 5 及以上版本。你可以通过打开 Windows 的命令行,并运行 java -version
命令来检查你的 Java 安装情况:
3.2 下载Maven
请访问 Maven 的官方下载页面(Download Apache Maven – Maven),下载适合你的操作系统的 Maven 版本。对于初学者,推荐使用 Maven 3.x 的稳定版本。下载页面还提供了 MD5 校验和文件及 ASC 数字签名文件,用于验证下载的 Maven 分发包的正确性和安全性。
3.3 本地安装与配置环境变量
-
将下载的 Maven 安装文件解压到你指定的目录中,例如
D:\bin\apache-maven-3.x
。 -
设置环境变量:
-
新建一个系统变量,变量名为
M2_HOME
,变量值为 Maven 的安装目录,例如D:\bin\apache-maven-3.x
。 -
在系统变量中找到名为
Path
的变量,并在其值的末尾添加%M2_HOME%\bin;
(注意分号分隔)。
-
配置完成后,你可以通过运行 mvn -v
命令来验证 Maven 是否安装成功。
详细情况如图所示:
3.4 配置基础 JDK 版本
Maven 默认使用 JDK 1.5 进行编译,但你可以通过修改 settings.xml
文件来指定其他版本的 JDK。在 settings.xml
文件的 <profiles>
标签内添加以下配置,以使用 JDK 1.8:
<profile>
<id>jdk-1.8</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.8</jdk>
</activation>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</properties>
</profile>
通过 mvn -v
验证:
3.5 指定本地仓库
Maven 的本地仓库默认位于用户家目录下的 .m2/repository
。为了避免影响系统性能,建议将本地仓库移动到其他盘符。你可以在 settings.xml
文件中添加以下配置来指定本地仓库的位置:
<localRepository>D:\mlz\maven-repository</localRepository>
3.6 配置镜像仓库
为了提高下载速度,你可以将 Maven 的中央仓库替换为国内的镜像仓库,如阿里云提供的镜像。在 settings.xml
文件中添加以下配置:
<mirror>
<id>nexus-aliyun</id>
<mirrorOf>central</mirrorOf>
<name>Nexus aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>
3.7 升级Maven
Maven 更新频繁,你可以通过下载新的 Maven 安装文件并解压到本地目录来更新 Maven。然后,更新 M2_HOME
环境变量以指向新的 Maven 安装目录。
4 Maven 的使用
4.1 核心概念:坐标
Maven 中的坐标使用三个“向量”在 Maven 的仓库中唯一地定位到一个 JAR 包。这三个向量分别是:
-
groupId:公司或组织的唯一标识符,通常使用公司或组织域名的倒序,并可能加上项目名称。例如:
com.mlz.team
。 -
artifactId:项目或项目中的一个模块的标识符,即模块的名称。例如:
team-plan
。 -
version:版本号,用于区分同一个项目或模块的不同版本。例如:
1.0.0
。
这三个坐标组合起来,可以唯一地确定一个 JAR 包在 Maven 仓库中的位置。例如,上述坐标对应的 JAR 包在 Maven 本地仓库中的位置为:
Maven本地仓库根目录\com\mlz\team\team-plan\1.0.0\team-plan-1.0.0.jar
4.2 pom.xml 文件详解
POM(Project Object Model)是 Maven 工程的核心配置文件,它表示将工程抽象为一个模型,并用程序中的对象来描述这个模型。通过 POM 文件,我们可以使用程序来管理项目。
以下是一个典型的 pom.xml
文件的配置示例,并对每个部分进行了详细的解释:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<!-- 当前Maven工程的坐标 -->
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Maven</description>
<!-- 当前Maven工程的打包方式 -->
<packaging>jar</packaging>
<!-- 工程构建过程中使用的属性 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source> <!-- Java源码版本 -->
<maven.compiler.target>1.8</maven.compiler.target> <!-- Java目标版本 -->
</properties>
<!-- 当前工程所依赖的jar包 -->
<dependencies>
<!-- 使用dependency配置一个具体的依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope> <!-- 依赖范围:仅在测试时使用 -->
</dependency>
<dependency>
<groupId>com.mlz.team</groupId>
<artifactId>team-plan</artifactId>
<version>1.0.0</version>
<scope>compile</scope> <!-- 依赖范围:在编译、测试和运行阶段都可用 -->
<!-- 使用excludes标签配置依赖的排除 -->
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<!-- 构建配置 -->
<build>
<plugins>
<!-- 配置编译插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
4.3 继承
在大型 Maven 项目中,管理和维护多个模块的依赖、插件和构建配置可能会变得非常复杂。为了简化这一过程,Maven 提供了继承机制,允许我们创建一个父 POM 来统一管理和配置多个子模块。这样做不仅有助于保持项目的一致性,还能提高开发效率。
4.3.1 创建父工程
-
使用 Maven 命令或 IDE 创建一个新的 Maven 项目。
-
在
pom.xml
文件中,将<packaging>
标签的值设置为pom
,表示这是一个父工程。 -
(可选)删除生成的
src
目录,因为父工程中通常不包含业务代码。
<!-- 当前工程作为父工程,它要去管理子工程,所以打包方式必须是 pom -->
<packaging>pom</packaging>
4.3.2 创建模块工程
在父工程中配置依赖的统一管理:使用 dependencyManagement
标签可以在父 POM 中定义依赖的版本,而不需要在子模块中重复定义。这样做的好处是,当需要更新依赖版本时,只需在父 POM 中修改一次即可,无需逐个修改子模块。
使用dependencyManagement
标签配置对依赖的管理,如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mlz.team</groupId>
<artifactId>maven-demo-parent</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>demo-module</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.3.19</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
4.3.3 子工程中引用依赖
在子模块的 pom.xml
文件中,通过 parent
标签指定当前工程的父工程。然后,在 dependencies
标签中引用父工程管理的依赖时,可以省略版本号。这样做可以确保子模块中的依赖版本与父工程保持一致。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<!-- 使用parent标签指定当前工程的父工程 -->
<parent>
<artifactId>maven-demo-parent</artifactId>
<groupId>com.mlz.team</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<!-- 子工程的坐标 -->
<!-- 如果子工程坐标中的groupId和version与父工程一致,那么可以省略 -->
<artifactId>demo-module</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
</dependencies>
</project>
4.3.4 修改父工程依赖信息的版本
随着技术的不断发展,依赖的版本也会不断更新。为了确保项目的稳定性和兼容性,我们需要定期检查和更新依赖的版本。在父 POM 中修改依赖版本时,可以遵循一定的策略,如优先使用稳定版本、避免频繁升级等。
<!-- 通过自定义属性,统一指定Spring的版本 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- 自定义标签,维护Spring版本数据 -->
<spring.version>5.3.19</spring.version>
</properties>
4.3.5 父工程中声明自定义属性
为了更方便地管理和更新依赖版本,我们可以在父 POM 中声明自定义属性。这些属性可以在整个项目中统一使用,从而实现一处修改、处处生效的效果。例如,我们可以声明一个 spring.version
属性来统一指定 Spring 框架的版本号。
-
父 POM 示例 (pom.xml)
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>parent-pom</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<!-- 自定义属性 -->
<properties>
<java.version>11</java.version>
<spring.version>5.3.10</spring.version>
<junit.version>5.7.2</junit.version>
</properties>
<!-- 依赖管理 -->
<dependencyManagement>
<dependencies>
<!-- Spring 依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- JUnit 依赖 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 构建配置 -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
-
子项目 POM 示例 (child-pom.xml)
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>parent-pom</artifactId>
<version>1.0.0</version>
</parent>
<groupId>com.example</groupId>
<artifactId>child-project</artifactId>
<version>1.0.0-SNAPSHOT</version>
<dependencies>
<!-- 直接引用父 POM 中的依赖,无需指定版本号 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
4.4 指定 JDK 版本
把 Maven 工程部署都服务器上,脱离了 settings.xml 配置,如何保证程序正常运行呢?思路就是我们直接把 JDK 版本信息告诉负责编译操作的 maven-compiler-plugin 插件,让它在构建过程中,按照我们指定的信息工作。如下:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 项目基本信息,如 groupId, artifactId, version 等 -->
<groupId>com.example</groupId>
<artifactId>my-app</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 配置构建行为 -->
<build>
<plugins>
<!-- 配置 Java 编译器插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<!-- 使用较新的版本,以确保兼容性和功能 -->
<version>3.8.1</version> <!-- 或者更高版本 -->
<configuration>
<!-- 指定源代码和目标代码的 JDK 版本 -->
<source>1.8</source>
<target>1.8</target>
<!-- 设置编译使用的字符编码 -->
<encoding>UTF-8</encoding>
<!-- 可选:显示详细的编译输出信息 -->
<verbose>true</verbose>
<!-- 可选:强制覆盖已存在的输出文件 -->
<forceJavacCompilerUse>true</forceJavacCompilerUse>
</configuration>
</plugin>
</plugins>
</build>
</project>
4.5 SpringBoot 定制化打包
在 Maven 项目中,使用 spring-boot-maven-plugin
插件来定制化 Spring Boot 应用的打包过程是非常重要的。这个插件能够生成一个可执行的 jar 文件(通常称为 fat jar 或 uber jar),其中包含了应用所需的所有依赖项,以及 Spring Boot 的加载器,允许你使用 java -jar
命令直接启动应用。
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 项目基本信息,如 groupId, artifactId, version 等 -->
<groupId>com.example</groupId>
<artifactId>my-spring-boot-app</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging> <!-- 明确指定打包方式为 jar -->
<!-- 父项目信息(可选,但通常用于继承 Spring Boot 依赖管理) -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!-- 配置构建行为 -->
<build>
<plugins>
<!-- 配置 Spring Boot Maven 插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<!-- 如果使用了父 POM,这里通常不需要显式指定版本,因为父 POM 已经管理了版本 -->
<!-- <version>2.5.5</version> --> <!-- 可选,如果未使用父 POM 则需要指定 -->
<executions>
<execution>
<goals>
<goal>repackage</goal> <!-- 默认目标,用于重新打包为可执行的 jar -->
</goals>
</execution>
</executions>
<!-- 可选配置,如需要可以添加 -->
<!-- <configuration>
<mainClass>${start-class}</mainClass> <!-- 指定主类,如果 Spring Boot 未能自动检测到 -->
<classifier>exec</classifier> <!-- 可选,为生成的 jar 添加一个分类器 -->
</configuration> -->
</plugin>
</plugins>
</build>
<!-- 项目依赖项 -->
<dependencies>
<!-- 添加你的 Spring Boot 依赖项 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 其他依赖项 -->
</dependencies>
</project>
4.6 依赖配置补充
在 Maven 项目中,当需要管理多个不同体系的依赖,但又不能通过继承多个父 POM 时,使用 <dependencyManagement>
节点并设置 <scope>import</scope>
是一个很好的解决方案。这种方式允许你导入其他 POM 文件中的依赖管理信息,从而在你的项目中统一管理这些依赖的版本。
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<!-- ... 其他项目配置 ... -->
<dependencyManagement>
<dependencies>
<!-- Spring Cloud 依赖管理 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud Alibaba 依赖管理 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 其他依赖管理,如需要可以添加 -->
<!-- 例如:
<dependency>
<groupId>其他组织</groupId>
<artifactId>其他依赖管理POM</artifactId>
<version>版本号</version>
<type>pom</type>
<scope>import</scope>
</dependency>
-->
</dependencies>
</dependencyManagement>
<dependencies>
<!-- 在这里添加你的项目依赖项,不需要指定版本号,因为已经在 <dependencyManagement> 中管理了 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 其他依赖项 -->
</dependencies>
<!-- ... 其他项目配置,如 properties、build 等 ... -->
<!-- 注意:确保在 properties 中定义了 ${spring-cloud.version} 和 ${spring-cloud-alibaba.version} 的值 -->
<properties>
<spring-cloud.version>2021.0.1</spring-cloud.version> <!-- 示例版本,实际使用时请替换为所需版本 -->
<spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version> <!-- 示例版本,实际使用时请替换为所需版本 -->
<!-- 其他属性定义 -->
</properties>
</project>
4.7 多环境管理
在开发过程中,我们的软件会面对不同的运行环境,比如开发环境、测试环境、生产环境,而我们的软件在不同的环境中,有的配置可能会不一样,比如数据源配置、日志文件配置、以及一些软件运行过程中的基本配置,那每次我们将软件部署到不同的环境时,都需要修改相应的配置文件,这样来回修改,很容易出错,而且浪费劳动力。
因此我们可以利用 Maven 的 profile 来进行定义多个 profile,然后每个profile对应不同的激活条件和配置信息,从而达到不同环境使用不同配置信息的效果。
4.7.1 准备 Spring Boot 项目
确保您的 Spring Boot 项目已经创建并配置好。
4.7.2 创建多环境配置文件
在 src/main/resources
目录下创建以下文件:
-
application.yml
:包含所有环境的公共配置。 -
application-dev.yml
:包含开发环境的特定配置。 -
application-test.yml
:包含测试环境的特定配置。 -
application-prod.yml
:包含生产环境的特定配置。
4.7.3 配置 application.yml
在 application.yml
中,您不需要设置 spring.profiles.active
,而是可以添加一些通用的配置。例如:
server:
port: 8080 # 公共配置,所有环境都使用8080端口(或根据需求修改)
# 其他公共配置...
4.7.4 配置环境特定文件
在每个环境特定的配置文件中(application-dev.yml
, application-test.yml
, application-prod.yml
),添加该环境特有的配置。例如,在 application-prod.yml
中:
spring:
datasource:
url: jdbc:mysql://prod-db-server:3306/mydb
username: prod-user
password: prod-password
# 其他生产环境特有的配置...
4.7.5(可选)使用 Maven Profiles
虽然 Spring Boot 已经提供了多环境支持,但如果您想在构建时做一些环境特定的操作(如资源过滤、依赖管理等),可以配置 Maven profiles。在 pom.xml
中添加:
<profiles>
<profile>
<id>dev</id>
<properties>
<spring.profiles.active>dev</spring.profiles.active>
</properties>
<!-- 其他构建配置... -->
</profile>
<profile>
<id>test</id>
<properties>
<spring.profiles.active>test</spring.profiles.active>
</properties>
<!-- 其他构建配置... -->
</profile>
<profile>
<id>prod</id>
<properties>
<spring.profiles.active>prod</spring.profiles.active>
</properties>
<!-- 其他构建配置... -->
</profile>
</profiles>
注意:通常不需要在 Maven 中设置 spring.profiles.active
,因为 Spring Boot 提供了更好的方式来动态指定活动配置文件。这里的 Maven profiles 主要用于构建时的其他配置。
4.7.6 动态指定活动配置文件
在部署或运行应用时,使用以下方法之一来指定活动的配置文件:
-
环境变量:设置
SPRING_PROFILES_ACTIVE
环境变量。例如,在 Linux 上:export SPRING_PROFILES_ACTIVE=prod
。 -
命令行参数:在启动应用时添加
--spring.profiles.active={profile}
参数。例如:java -jar your-app.jar --spring.profiles.active=dev
。 -
外部配置文件:使用
--spring.config.location
参数指定外部配置文件的路径。例如:java -jar your-app.jar --spring.config.location=/path/to/external/application-{profile}.yml
。
4.7.6 (可选)使用配置中心
对于大型项目或微服务架构,考虑使用配置中心(如 Spring Cloud Config)来集中管理配置。
4.8 jar 包冲突问题
在设定项目依赖时,由于初次设定可能存在问题,需要由专人负责调整依赖配置。为避免团队中每个程序员各自添加依赖导致的混乱和冲突,应统一管理依赖。初期需根据项目实际情况不断调整依赖,最终确定一个稳定的版本,并在父工程中统一管理。这样,即使开发中遇到问题,也只需修改父工程的依赖管理配置,而无需改动每个模块。解决依赖冲突的基本思路是找到冲突的jar包,并在其中选定一个版本,通过exclusions排除其他版本或明确声明所需版本。
4.8.1 使用 IDEA 的 Maven Helper 插件
-
安装插件:
-
在 IntelliJ IDEA 中,打开
Settings
(或Preferences
在 macOS 上)。 -
导航到
Plugins
,搜索Maven Helper
并安装它。
-
-
使用插件查找冲突:
-
打开项目的
pom.xml
文件。 -
右键点击
pom.xml
文件,选择Maven
->Show Dependencies
。 -
在弹出的窗口中,选择
Conflicts
标签页。 -
这里将列出所有冲突的 JAR 包及其不同版本和来源。
-
-
解决冲突:
-
根据冲突信息,决定使用哪个版本的 JAR 包。
-
在
pom.xml
文件中,使用<exclusions>
标签排除不需要的版本。 -
或者,使用
<dependencyManagement>
在父 POM 中统一管理依赖版本。
-
4.8.2 使用 Maven 的 enforcer 插件
Maven Enforcer 插件可以帮助检测项目中的潜在问题,包括依赖冲突和类路径中的重复类。
-
配置 enforcer 插件:
-
在项目的
pom.xml
文件中,添加 enforcer 插件的配置
-
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.0.0-M3</version> <!-- 使用最新版本 -->
<executions>
<execution>
<id>enforce-rules</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<!-- 检测依赖冲突 -->
<dependencyConvergence/>
<!-- 检测类路径中的重复类(可选) -->
<banDuplicateClasses/>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
注意:banDuplicateClasses
规则可能需要额外的配置来指定哪些包或类应该被检查。此外,这个规则可能会比较耗时,因为它需要扫描整个类路径。
-
运行 enforcer 插件:
-
在命令行中,运行
mvn enforcer:enforce
来执行插件。 -
如果检测到问题,插件将输出错误信息,包括冲突的依赖或重复的类。
-
5 Maven 流水线配置
Maven是Java后端项目常用的构建依赖管理工具,它通过pom.xml
文件定义项目的依赖包信息和构建配置。
5.1 安装配置Maven环境
5.1.1 下载Maven安装包
从Maven官方源Download Apache Maven – Maven下载最新版本的Maven安装包。例如,使用wget命令下载Maven 3.8.6版本(注意:原示例中的版本号存在笔误,已更正):
wget https://mirrors.bfsu.edu.cn/apache/maven/maven-3/3.8.6/binaries/apache-maven-3.8.6-bin.tar.gz
5.1.2 解压安装包
将下载的安装包解压到指定目录,例如/usr/local/
:
tar zxf apache-maven-3.8.6-bin.tar.gz -C /usr/local/
cd /usr/local/
ln -s apache-maven-3.8.6 maven # 创建一个符号链接,方便后续操作
5.1.3 配置环境变量
编辑/etc/profile
文件,添加Maven的环境变量:
vi /etc/profile
export M2_HOME=/usr/local/maven
export PATH=$M2_HOME/bin:$PATH
source /etc/profilemvn -v
注意:这里使用了符号链接maven
,因此M2_HOME
设置为/usr/local/maven
。
5.1.4 验证安装
使用mvn -v
命令验证Maven是否安装成功,并检查Java版本和Maven版本是否匹配:
mvn -v
输出应类似于:
Apache Maven 3.8.6 (your-maven-home)
Maven home: /usr/local/maven
Java version: 1.8.0_xxx, vendor: Oracle Corporation, runtime: /path/to/jdk
Default locale: zh_CN, platform encoding: UTF-8
OS name: "linux", version: "your-os-version", arch: "amd64", family: "unix"
5.2 Jenkins集成
在Jenkins流水线中集成Maven进行项目构建并不复杂,只需在Pipeline脚本中执行Maven命令即可。
5.2.1 Jenkins Pipeline配置
在Jenkins的Pipeline配置中,添加一个构建阶段(Build Stage),并在该阶段中执行Maven命令。例如:
//Jenkins Pipeline中的Build阶段
stage("Build"){
steps{
script{
//sh执行Shell命令
sh "mvn clean package"
}
}
}
5.2.2 注意事项
-
确保Jenkins服务器已经安装了Maven,并且Maven的路径已经配置在Jenkins的全局工具配置中(虽然直接在Pipeline脚本中指定路径也可以)。
-
如果项目有特定的Maven设置(如settings.xml),可以在Pipeline脚本中指定
--settings
参数来覆盖默认设置。 -
根据项目的需要,可以在
mvn
命令后添加其他参数,如-DskipTests=true
来跳过测试等。
二、Gradle构建:灵活性与扩展性的典范
Gradle,作为一款开源的自动化构建工具,凭借其强大的多语言支持能力,以及对Apache Ant和Maven的精髓融合,已经在构建工具领域崭露头角。与Ant的随意性和Maven的繁琐配置及严格生命周期相比,Gradle以其规范性和更高的灵活性,为开发者带来了全新的构建体验。
作为开发者,我们深知项目自动构建在日常工作中的重要性。想象一下,如果构建代码能像源代码一样易于扩展、测试和维护,那将极大地提升我们的工作效率。Gradle正是为此而生,它采用DSL(领域特定语言,如Groovy)来编写构建脚本,使得脚本更加简洁明了,易于理解和维护。
Gradle的构建脚本是声明式的,能够清晰地表达构建意图。与XML相比,使用Groovy编写构建脚本大大减少了代码量,提高了可读性。更重要的是,Gradle还集成了其他构建工具,如Ant和Maven,使得原有项目能够轻松迁移到Gradle平台,无需进行大规模的改造。
-
Gradle的优势:
-
灵活性强:Gradle提供丰富的API,允许用户根据需求定制项目的构建过程。相较于Maven和Ant,Gradle的构建脚本更加灵活且易于编写。
-
粒度精细:在Gradle中,源码编译、资源编译等任务都是通过Task来完成的。用户可以自由修改Task,实现对构建过程的精细控制。
-
扩展性高:Gradle支持插件机制,用户只需简单配置即可复用这些插件,从而轻松扩展Gradle的功能和适用范围。
-
兼容性好:Gradle不仅功能强大,还能完美兼容Maven和Ant的功能。这意味着用户可以在Gradle中继续使用他们熟悉的Maven和Ant构建脚本和插件。
-
趋势引领:众多大厂的开源代码已经开始转向Gradle管理,这已成为构建工具领域的一大趋势。
-
Gradle的劣势:
-
版本兼容性:虽然Gradle的每个版本都可能带来较大的改动,但Gradle团队正在逐步改善这一问题。同时,使用Gradle Wrapper可以确保团队成员使用相同版本的Gradle,减少因版本差异带来的问题。
-
学习成本:对于不熟悉Groovy的用户来说,学习成本可能较高。但Gradle也提供了Kotlin DSL作为替代方案,降低了学习难度。此外,Gradle社区和文档资源也非常丰富,有助于用户快速上手。
-
踩坑成本:由于Gradle的灵活性和复杂性,用户在使用过程中可能会遇到一些挑战。但Gradle社区和官方支持都非常活跃,用户可以通过社区论坛、官方文档和培训课程等途径寻求帮助,降低踩坑成本。
1 Gradle组成
Gradle是一个功能强大的开源构建自动化工具,它结合了Apache Ant的灵活性和Apache Maven的依赖管理优势,并在此基础上进行了创新。Gradle的组成主要包括以下几个方面:
1.1 Groovy(或其他DSL)
Groovy是一种基于JVM(Java虚拟机)的动态语言,它提供了与Java相似的语法,但更加灵活和动态。Groovy完全兼容Java,并在此基础上增加了闭包、DSL(领域特定语言)支持等特性,使得编写构建脚本变得更加简洁和高效。虽然Gradle也支持使用Kotlin DSL等其他DSL来编写构建脚本,但Groovy仍然是Gradle最原始和最常用的DSL。
对于想要执行Groovy脚本的用户,他们需要安装Groovy环境或使用Java的ClassLoader来调用。然而,对于初学者来说,寻找在线的云编译环境来运行Groovy脚本可能是一个更便捷的选择。
1.2 构建脚本块
构建脚本块是Gradle构建过程的核心组成部分,它们由Groovy(或其他DSL)语法组成。这些脚本块定义了项目的构建过程、依赖关系、任务等。Gradle内置了一些闭包(如build
、dependencies
等)和插件提供的闭包(如war
、java
等),用户可以在这些闭包中编写自定义的构建逻辑。
1.3 Gradle API
Gradle API为用户提供了一系列内置属性和方法,用于在构建脚本中执行各种操作。这些内置属性和方法包括:
-
内置属性:如
project
(表示当前项目对象)、version
(表示项目的版本号)、group
(表示项目的组ID)、parent
(表示父项目对象,如果存在的话)等,用于描述项目的基本信息。 -
内置方法:如
property
(用于定义项目属性)、copy
(用于复制文件和目录)、apply
(用于应用其他脚本或插件)、afterEvaluate
(用于在项目评估后执行某些操作)等,用于执行各种构建任务和操作。
2 Gradle安装
由于Gradle不同版本之间的兼容性较差,为了确保项目的稳定性和可维护性,用户通常不会直接下载安装Gradle的某个特定版本。相反,他们更倾向于采用Gradle Wrapper这种更加灵活和可维护的方式来管理Gradle版本。
2.1 Gradle Wrapper
Gradle Wrapper是一个允许用户在不手动安装Gradle的情况下运行Gradle构建的工具。它包含了一个特定版本的Gradle二进制文件和一个脚本,用于下载和配置正确的Gradle版本。这样,无论用户在哪个环境中工作,都可以确保他们使用的是与项目兼容的Gradle版本。
要使用Gradle Wrapper,用户只需在项目中包含gradlew
(Unix/Linux/Mac)或gradlew.bat
(Windows)脚本,以及gradle/wrapper/gradle-wrapper.properties
文件。这些文件定义了要使用的Gradle版本和其他相关配置。然后,用户就可以通过运行这些脚本来执行Gradle构建任务了。
2.2 集成开发环境(IDE)支持
许多流行的集成开发环境(如IntelliJ IDEA、Eclipse等)都提供了对Gradle的内置支持。这些IDE通常允许用户通过简单的配置来指定Gradle Wrapper的路径,从而自动使用正确的Gradle版本来执行构建任务。此外,这些IDE还提供了丰富的Gradle插件和工具,用于简化构建过程、调试代码、管理依赖关系等。因此,在使用Gradle时,充分利用IDE的支持可以大大提高开发效率和构建过程的可靠性。
3 Gradle项目
Gradle项目结构清晰,功能强大,其核心在于build.gradle
文件,该文件定义了项目的构建逻辑、依赖关系及插件等。以下是对Gradle项目结构的深入剖析及build.gradle
文件的详细解读。
3.1 项目结构解析
├─build.gradle ① 构建脚本核心文件
├─gradlew ② Linux/Unix/Mac下的Gradle Wrapper脚本
├─gradlew.bat ③ Windows下的Gradle Wrapper脚本
├─settings.gradle ④ 项目全局设置文件
├─gradle ⑤ Gradle Wrapper目录
│ └─wrapper
│ ├─ gradle-wrapper.jar
│ ├─ gradle-wrapper.properties
└─src ⑥ 源代码目录
├─main 主代码目录
└─test 测试代码目录
-
build.gradle
(①):-
这是Gradle构建脚本的核心文件,用于定义项目的构建逻辑、依赖关系、插件等。
-
全局
build.gradle
文件通常位于项目根目录,用于设置全局性的配置,如仓库源和Gradle版本号。 -
模块内的
build.gradle
文件则负责该模块的特定构建配置。
-
-
gradlew
(②):-
这是Linux/Unix/Mac系统下的Gradle Wrapper脚本,允许用户在不手动安装Gradle的情况下执行Gradle构建任务。
-
通过运行
./gradlew
命令,用户可以轻松触发构建过程。
-
-
gradlew.bat
(③):-
这是Windows系统下的Gradle Wrapper脚本,功能与
gradlew
类似,但专为Windows环境设计。 -
用户只需双击
gradlew.bat
或在命令行中执行该脚本,即可启动Gradle构建。
-
-
settings.gradle
(④):-
此文件用于定义项目的全局设置,如项目名称、包含的模块等。
-
无论项目包含多少个子模块,
settings.gradle
文件都只有一个,且必须位于根项目目录中。
-
-
gradle/wrapper
(⑤):-
此目录包含Gradle Wrapper所需的两个关键文件:
gradle-wrapper.jar
和gradle-wrapper.properties
。 -
gradle-wrapper.jar
负责实际的Wrapper逻辑,而gradle-wrapper.properties
则存储了Wrapper的配置信息,如Gradle版本等。 -
通过Wrapper,Gradle可以自动下载并安装指定版本的Gradle环境,从而简化版本管理。
-
-
src
(⑥):-
这是源代码的存放目录,通常包含
main
(主代码)和test
(测试代码)两个子目录。 -
main
目录用于存放项目的核心代码和资源文件,而test
目录则用于存放单元测试代码。
-
3.2 build.gradle
文件详解
build.gradle
文件是 Gradle 构建系统中的一个核心配置文件,用于定义项目的构建脚本。Gradle 是一个开源的自动化构建工具,它允许你以声明性的方式定义项目的构建逻辑,包括依赖管理、任务执行等。build.gradle
文件通常包含以下内容:
-
插件应用:通过
plugins
块应用 Gradle 插件,这些插件为 Gradle 添加了额外的功能,如 Java 编译、测试运行、打包等。 -
项目信息:定义项目的组名(
group
)、版本号(version
)、Java 版本兼容性(sourceCompatibility
和targetCompatibility
)等。 -
仓库配置:指定 Gradle 查找依赖项的仓库地址,如 Maven 中央仓库、私有仓库等。
-
依赖管理:在
dependencies
块中声明项目所需的依赖项,Gradle 会自动从配置的仓库中下载这些依赖项。 -
任务配置:定义和配置 Gradle 任务,这些任务可以是自定义的,也可以是插件提供的。
-
其他配置:如构建脚本依赖(
buildscript
块)、发布配置等。
// 插件声明
plugins {
id 'org.springframework.boot' version '2.7.4'
id 'io.spring.dependency-management' version '1.0.14.RELEASE'
id 'java'
id 'war' // 如果您需要构建 WAR 文件
}
// 项目坐标信息
group = 'com.mlz'
version = '1.0.0'
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
// 仓库地址
repositories {
// 推荐使用阿里云的镜像仓库,加速依赖下载
maven { url 'https://maven.aliyun.com/repository/public/' }
maven { url 'https://maven.aliyun.com/repository/gradle-plugin/' }
mavenCentral()
}
// 第三方依赖
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// 其他依赖可以在这里添加
}
// 单元测试声明(已经在 Spring Boot 插件中默认配置,这里可以省略)
// tasks.named('test') {
// useJUnitPlatform()
// }
// buildscript 区域用于配置 Gradle 自身所需的依赖和仓库
buildscript {
repositories {
maven { url 'https://maven.aliyun.com/repository/public/' }
maven { url 'https://maven.aliyun.com/repository/gradle-plugin/' }
}
dependencies {
// 其他 Gradle 构建脚本所需的依赖可以在这里添加
}
}
// 注意:allprojects 和 subprojects 区域通常用于多模块项目
// 如果您的项目是单模块项目,这些区域可以省略
// allprojects 和 subprojects 的配置应该放在根项目的 build.gradle 文件中(如果有的话)
// 自定义任务
task cleanx {
doLast {
println "Hello Gradle"
}
}
// 注意:如果您不需要发布到 Bintray,可以移除相关的插件和配置
// 如果您需要使用 Docker 插件,请确保选择了正确的插件并配置了相应的依赖和仓库
3.3 Project对象的组成
在Gradle中,每个build.gradle
文件都代表一个Project
实例的配置。Project
对象具有丰富的属性和方法,用于定义项目的构建逻辑。
3.3.1 属性
-
内置属性:
-
可以直接赋值,无需声明。
-
例如:
group = 'com.mlz'
和version = '1.0.0'
。
-
-
自定义属性:
-
可以使用Groovy语法或Java语法结合。
-
Groovy定义属性:
def pname = "projectName:${project.name}"
-
Java类型接收:
String pname = "projectName:${project.name}"
-
-
扩展属性:
-
使用
ext
命名空间来扩展属性,定义后可以在project、task、subproject中读取和更新。 -
例如:
ext.prop1 = 'hjw'
和ext.prop2 = 'gradle'
。
-
-
属性作用域:
-
Project对象自身:包含所有通过getters和setters方法定义的属性。
-
ext属性(extra):一个包含任意名称-值对的映射。
-
插件扩展属性(extensions):每个扩展名都可以作为只读属性使用。
-
插件约定属性(convention):通过Convention对象添加到Project中的属性。
-
Tasks:使用任务名称作为属性名称来访问任务,只读。
-
继承属性:ext的属性和约定属性从项目的父级继承,递归到根项目,只读。
-
-
常用project属性:
-
allprojects
:包含此项目及其子项目的集合。 -
buildDir
:当前项目的编译目录,默认值为projectDir/build
。 -
defaultTasks
:当前项目的默认任务集合。 -
group
:当前项目的组名。 -
logger
:当前项目的日志器。 -
name
:此项目的名称。 -
parent
:此项目的父项目。 -
path
:此项目的绝对路径。 -
project
:当前project对象实例。 -
rootDir
:本项目的根目录。 -
rootProject
:当前项目层次结构中的根project。 -
subprojects
:当前项目的子项目集合。 -
tasks
:本项目的task集合。 -
version
:此项目的版本。
-
3.3.2 方法
-
方法作用域:
-
Project对象自身的方法。
-
build.gradle
脚本文件中定义的方法。 -
通过插件添加到Project中的扩展方法。
-
插件添加到项目中的约定方法。
-
项目中的Tasks,每个Task都会添加一个方法,方法名是任务名。
-
-
常用Project方法:
方法 | 描述 |
afterEvaluate | 可以添加一个闭包,它会在项目完成评估后立即执行。当执行属于该项目的构建文件时,会通知此类监听器。 |
allprojects | 配置当前项目以及它的每个子项目 |
apply | 应用零个或多个插件或脚本。 |
beforeEvaluate | 添加一个闭包,它会在项目开始评估前立即执行 |
configure | 通过闭包配置对象集合。 |
copy | 复制指定的文件 |
defaultTasks | 设置此项目的默认任务的名称。当开始构建时没有提供任务名称时使用这些。 |
delete | 删除文件和目录 |
exec | 执行外部命令 |
file | 解析相对于该项目的项目目录的文件路径 |
findProject | 按路径定位项目。如果路径是相对的,则相对于该项目进行解释。 |
findProperty | 找特定属性,如果未找到,则返回给定属性的值或 null |
getAllTasks | 返回此项目中包含的任务的地图 |
hasProperty | 确定此项目是否具有给定的属性 |
javaexec | 执行 Java 主类 |
javaexec | 执行外部 Java 进程。 |
mkdir | 创建一个目录并返回一个指向它的文件。 |
property | 返回给定属性的值。此方法定位属性如下: |
setProperty | 设置此项目的属性。此方法在以下位置搜索具有给定名称的属性,并将该属性设置在它找到该属性的第一个位置。 |
subprojects | 配置本项目的子项目 |
task | 创建Task具有给定名称的 a 并将其添加到此项目中 |
uri | 将文件路径解析为 URI,相对于该项目的项目目录 |
3.3.3 插件
获取插件的渠道主要有两种:
-
Github搜索,有一些插件并没有被官方所收录,但仍然能够使用,但需要承担一些隐藏的风险
当前使用频率较高的几款插件包括:SpringBoot构建插件、Docker容器集成插件、JUnit单元测试插件等。
(1)脚本插件script plugins
脚本插件通常是一个脚本文件,与普通的build.gradle
文件无异。它其实并非一个真正的插件,而是一个扩展脚本,但作用不容小觑,是脚本模块化的基础。我们可以将复杂的脚本文件拆分成多个职责明确的脚本插件,就像封装Utils工具类一样。脚本可以存放在本地或网络上,引用方式如下:
-
使用本地插件
apply from: './other.gradle'
apply from: this.rootProject.file('other.gradle')
-
使用网络远程插件
apply from: 'https://gitee.com/xxx/xx/raw/master/it235.gradle'
(2)二进制插件binary plugins
二进制插件通过插件ID来应用,ID是插件的全局唯一标识符或名字。Gradle中的插件按来源分为核心插件和非核心插件。核心插件有简短ID,如Java插件的ID为java
。非核心二进制插件必须使用完全限定形式的ID(如com.github.foo.bar
)。使用二进制插件有两种方式:
-
结合
buildscript{}
应用插件(老版本)
//build.gradle中的顶级语句,如下分别是使用java/idea/war/docker插件
apply plugin: 'java'//apply plugin: JavaPlugin 也可以通过指定插件类来应用,与java效果一样
apply plugin: 'idea'
apply plugin: "war"//声明
apply plugin: "com.jfrog.bintray"
apply plugin: 'org.akhikhl.gretty'//buildscript
buildscript {
repositories {
maven {url "https://maven.aliyun.com/repository/public"}
maven { url 'https://maven.aliyun.com/repository/jcenter' }
}//应用插件
dependencies {
classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.0"
classpath 'org.akhikhl.gretty:gretty:+'
}
}
-
使用
plugins{}
DSL新写法,对应的是PluginDependenciesSpec实例
//build.gradle中的顶级语句,声明和应用放在一起
plugins {//核心插件,gradle提供
id 'java'
id 'eclipse'
id 'war'//非核心插件(社区插件),必须通过id+version的全限定名的方式进行引用
id 'com.bmuschko.docker-remote-api' version '6.7.0'//apply false 表示在父项目中声明,但是父项目不会立即加载,可以在子项目中通过ID的方式进行使用
id 'com.jfrog.bintray' version '1.8.5' apply false
}
//注意:plugins暂时不支持第三方插件,如果要使用第三方插件请使用老的写法。同时plugins中不能随意编写其他的语句体
(3)自定义插件
自定义插件包括三个步骤:建立插件工程、配置参数、发布插件与使用插件。插件的源码可以存放在以下三个地方:
-
在构建脚本中
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
project.task('hello') {
doLast {
println 'Hello from the GreetingPlugin'
}
}
}
}
apply plugin: GreetingPlugin
使用gradle -q hello
即可执行。这种方式的好处是当前项目能自动编译和加载插件,但插件在构建脚本之外不可见。
-
在
buildSrc
项目中
将插件源码放在rootProjectDir/buildSrc/src/main/java
、rootProjectDir/buildSrc/src/main/groovy
或rootProjectDir/buildSrc/src/main/kotlin
目录下。Gradle会负责编译和测试插件,并使其在构建脚本的类路径中可用。该插件对构建使用的每个构建脚本都可见,但在构建之外不可见。
-
在独立项目中
创建一个单独的项目,生成并发布一个JAR文件,然后可在多个项目中使用该插件,其他开发者也能下载使用。这种方式通常用于编写或依赖一些插件,或将几个相关的任务类捆绑到一起。
4 Gradle任务
在Gradle中,Task是构建脚本的基本执行单元。Gradle提供了多种方式来定义和管理Task,以满足不同构建需求。以下是Gradle Task的详细解析和用法示例。
4.1 Task的定义
4.1.1 直接声明
task taskOne {
println "Executing taskOne"
}
4.1.2 使用字符串参数定义名称
task('taskTwo') {
println "Executing taskTwo"
}
4.1.3 使用TaskContainer的create方法
tasks.create('taskThree') {
println "Executing taskThree"
}
4.1.4 使用register方法(延迟创建)
tasks.register('taskSix') {
println "Executing taskSix"
}
注意:register
方法创建的Task是延迟创建的,只有当Task被实际需要执行时才会被创建。
4.2 TaskContainer解析
tasks
实际上是TaskContainer
的实例对象的映射属性,TaskContainer
实现了TaskCollection
和PolymorphicDomainObjectContainer
,因此tasks
具有这两个类的所有行为。
-
定位Task
-
findByPath
:如果没找到会返回null。
-
println tasks.findByPath('say')?.path
-
-
getByPath
:如果没找到会抛出UnknownTaskException
。
-
println tasks.getByPath('say').path
-
创建Task
-
直接创建:使用
create
。 -
延迟创建:使用
register
。
-
-
替换Task 使用
replace
方法创建一个新的Task,并替换同名的旧Task。
4.3 Task的配置
-
设置Group和Description
task myTask {
group = 'it235'
description = 'My own task'
}
-
doFirst和doLast
doFirst
用于添加需要最先执行的Action,doLast
用于添加需要最后执行的Action。
task myTask {
doFirst {
println 'This is executed first'
}
doLast {
println 'This is executed last'
}
}
4.4 带参Task和指定Type
-
带参Task
task paramTask(group: 'it235', description: 'My parameterized task', dependsOn: ['myTask1', 'myTask2']) {
doLast {
println 'Executing paramTask'
}
}
-
指定Type 通过
type
指定Task的基类,如Copy
、Delete
等。
task copyTask(type: Copy) {
from 'src/main/resources'
into 'build/resources/main'
}
4.5 Task依赖与排序
-
Task依赖
task hello {
doLast { println 'Hello!' }
}
task intro {
dependsOn hello
doLast { println 'I\'m Junge.' }
}
-
Task排序
-
mustRunAfter
:表示必须遵守的顺序关系。 -
shouldRunAfter
:表示不强制的顺序关系,可以在某些情况下被忽略。
-
task taskX {
doLast { println 'www.it235.com' }
}
task taskY {
doLast { println 'hello' }
}
taskY.mustRunAfter taskX
// 或者 taskY.shouldRunAfter taskX
4.6 条件执行与超时
-
条件执行 使用
onlyIf
方法根据条件判断是否执行Task。
task taskX {
doLast { println 'www.it235.com' }
}
task taskY {
doLast { println 'hello' }
}
taskY.mustRunAfter taskX
// 或者 taskY.shouldRunAfter taskX
-
超时设置 使用
timeout
属性设置Task的超时时间。
import java.time.Duration
task hangingTask {
doLast {
Thread.sleep(100000)
}
timeout = Duration.ofMillis(500)
}
4.7 Task规则与Finalizer
-
ask规则 使用
tasks.addRule
方法定义Task规则。
tasks.addRule("Pattern: ping<ID>") { String taskName ->
if (taskName.startsWith("ping")) {
task(taskName) {
doLast { println "Pinging: " + (taskName - 'ping') }
}
}
}
-
Finalizer Task 使用
finalizedBy
方法指定Finalizer Task,该Task会在主Task执行完毕后执行,即使主Task抛出异常也会执行。
task execute {
doLast {
println 'Executing main task'
}
}
task release {
doLast {
println 'Releasing resources'
}
}
execute.finalizedBy release
5 Gradle生命周期
在Gradle的生命周期中,每个阶段都承担着特定的任务,并且Gradle提供了钩子函数(hook),允许开发者在这些阶段插入自定义逻辑。以下是对Gradle生命周期的详细剖析:
4.1 初始化阶段
任务:解析settings.gradle
文件,确定参与构建的项目,并创建Project实例。
具体流程:
-
创建Settings实例:Gradle首先读取并解析
settings.gradle
文件,创建一个Settings实例。这个实例定义了项目的全局设置,例如包含哪些子项目。 -
解析并加载Project:根据Settings实例的配置,Gradle会扫描并解析
settings.gradle
中指定的项目。为每个项目创建对应的Project实例,这些项目的加载顺序是按照配置的层次结构进行的,确保父项目在子项目之前被加载。 -
初始化Project实例:值得注意的是,虽然此阶段会涉及
build.gradle
文件,但初始化Project实例时并不会深入解析build.gradle
文件中的具体配置。这一步主要是为项目创建基本的框架和实例。
4.2 配置阶段
任务:解析所有project中的build.gradle
文件,获取所有的task,构建任务依赖图。
具体流程:
-
执行build.gradle脚本:Gradle会循环执行每个项目的
build.gradle
脚本文件。这个过程中,Gradle会解析脚本中的配置,注册任务(Task),并确定任务之间的依赖关系。 -
构建任务依赖图:在解析完所有的
build.gradle
文件后,Gradle会根据任务之间的依赖关系构建一个任务依赖图(Task Dependency Graph)。这个图是一个有向无环图(DAG),表示了任务之间的执行顺序和依赖关系。 -
任务配置:在配置阶段,Gradle还会对每个任务进行初始化配置,包括设置任务的输入、输出和动作等。
4.3 执行阶段
任务:按照任务依赖图执行所有指定的task。
具体流程:
-
任务调度:Gradle会根据任务依赖图确定需要执行的任务及其执行顺序。如果某个任务的输入是另一个任务的输出,则该任务必须在另一个任务之后执行。
-
任务执行:Gradle会按照确定的顺序执行任务。在执行每个任务之前,Gradle会检查该任务的输入是否发生了变化。如果输入没有变化,并且任务的结果仍然是最新的,则Gradle会跳过该任务的执行,以提高构建效率。
-
任务完成:当所有任务都执行完毕后,Gradle会触发相应的钩子函数,表示构建过程已经完成。
6 Gradle项目实践
在大型企业级项目中,代码复杂性和模块化需求显著增加。Gradle作为一个强大的构建工具,提供了多项目构建(multi-project build)功能,允许开发者将复杂的项目拆分为多个独立但相关的子项目,从而提高代码的可维护性和可扩展性。
6.1 项目模块化
模块化是提高代码可维护性和可扩展性的关键。通过模块化,可以将代码组织成独立的、易于管理的单元。
高内聚低耦合是模块化设计的基本原则。一个优秀的模块化示例是Spring框架,它提供了多种服务,如MVC Web框架、事务管理器、JDBC数据库连接等。架构师可以借鉴Spring框架的设计思路,将项目划分为不同的模块。
6.2 配置项目文件
在Gradle中,多项目构建(multi-project build)是一种强大的特性,允许你将多个相关的项目组织在一起,共享配置和依赖。settings.gradle
文件是定义这种项目层次结构的关键。
6.2.1 settings.gradle 文件的作用
settings.gradle 文件用于定义Gradle项目的层次结构。通过 include 方法,可以包含多个子项目,这些子项目可以是独立的模块,如模型(model)、仓库(repository)和Web应用(web)。
示例:
// 包含多个子项目
include 'model', 'repository', 'web'
这里的参数是子项目的名称(相对于根项目的目录名),而不是文件路径。例如,如果你的项目结构如下:
root/
├── build.gradle
├── settings.gradle
├── model/
│ └── build.gradle
├── repository/
│ └── build.gradle
└── web/
└── build.gradle
那么,你只需在 settings.gradle
中使用 include 'model'
, include 'repository'
, 和 include 'web'
来包含这些子项目。
6.2.2 共享配置
在大型 Java 项目中,子项目之间必然具有相同的配置项。我们在编写代码时,要追求代码重用和代码整洁;而在编写 Gradle 脚本时,同样需要保持代码重用和代码整洁。Gradle 提供了不同的方式使不同的项目能够共享配置。
-
allprojects:allprojects 是父 Project 的一个属性,该属性会返回该 Project 对象以及其所有子项目。在父项目的build.gradle脚本里,可以通过给allprojects传一个包含配置信息的闭包, 来配置所有项目(包括父项目)的共同设置。通常可以在这里配置 IDE 的插件,group 和version 等信息,比如:
allprojects {
apply plugin: 'idea'
}
这样就会给所有的项目(包括当前项目以及其子项目)应用上 idea 插件。
-
subprojects:subprojects 和 allprojects 一样,也是父 Project 的一个属性,该属性会返回所有子项目。在父项目的 build.gradle 脚本里,给 subprojects 传一个包含配置信息的闭包,可以配置所有子项目共有的设置,比如共同的插件、repositories、依赖版本以及依赖配置:
subprojects {
apply plugin: 'java'
repositories {
mavenCentral()
}
ext {
guavaVersion = ’14.0 .1’
junitVersion = ‘4.10’
}
dependencies {
compile(
“com.google.guava: guava: $ {
guavaVersion
}”
)
testCompile(
“junit: junit: $ {
junitVersion
}”
)
}
}
这就会给所有子项目设置上 java 的插件、使用 mavenCentral 作为 所有子项目的 repository 以及对 Guava[4]和 JUnit 的项目依赖。此外,这里还在 ext 里配置依赖包的版本,方便以后升级依赖的版本。
-
configure:在项目中,并不是所有的子项目都会具有相同的配置,但是会有部分子项目具有相同的配置,比如在我所在的项目里除了 cis-war 和 admin-war 是 web 项目之外,其他子项目都不是。所以需要给这两个子项目添加 war 插件。Gradle 的 configure 可以传入子项目数组,并为这些子项目设置相关配置。在我的项目中使用如下的配置:
configure(subprojects.findAll {it.name.contains('war')})
{
apply plugin: 'war'
}
configure 需要传入一个 Project 对象的数组,通过查找所有项目名包含 war 的子项目,并为其设置war 插件。
6.2.3 独享配置
在项目中,除了设置共同配置之外, 每个子项目还会有其独有的配置。比如每个子项目具有不同的依赖以及每个子项目特殊的 task 等。
在父项目的 build.gradle 文件中通过 project(‘:sub-project-name’)来设置对应的子项目的配置。比如在子项目 model需要 Hibernate 的依赖,可以在父项目的 build.gradle 文件中添加如下的配置:
project(‘: model’) {
ext {
hibernateVersion = ‘4.2 .1.Final’
}
dependencies {
compile“ org.hibernate: hibernate - core: $ {
hibernateVersion
}”
}
}
注意:这里子项目名字前面有一个冒号(:)。 通过这种方式,指定对应的子项目,并对其进行配置。
6.2.4 其他共享
在 Gradle 中,除了上面提到的配置信息共享,还可以共享方法以及 Task。可以在根目录的build.gradle 文件中添加所有子项目都需要的方法,在子项目的 build.gradle 文件中调用在父项目build.gradle 脚本里定义的方法。例如我定义了这样一个方法,它可以从命令行中获取属性,若没有提供该属性,则使用默认值:
def defaultProperty(propertyName, defaultValue) {
return hasProperty(propertyName) ? project[propertyName] : defaultValue
}
注意,这段脚本完全就是一段Groovy 代码,具有非常好的可读性。
由于在父项目中定义了 defaultProperty 方法,因而在子项目的 build.gradle 文件中,也可以调用该方法。
6.2.5 设置文件的查找
Gradle允许从根目录或任何子目录中运行构建任务。为了确定一个子项目是否属于多项目构建的一部分,Gradle会按照以下步骤查找 settings.gradle
文件:
-
查找与当前目录具有相同嵌套级别的名为
settings.gradle
的文件,但位于一个名为master
的目录中(这个行为可能因Gradle版本而异)。 -
如果在第一步中没有找到
settings.gradle
文件,Gradle会从当前目录开始逐步向上查找父目录,直到找到settings.gradle
文件或到达根目录为止。
6.3 配置子项目
6.3.1 定义项目特有的行为
上面介绍过在父文件中可以配置字项目的独有配置,但配置简单的小型项目比较实用,若是对于子项目多,并且配置复杂的大型项目,推荐将各个项目的配置分别放到单独的 build.gradle 文件中去,可以方便设置和管理每个子项目的配置信息。
ext {
hibernateVersion = ‘4.2 .1.Final’
}
dependencies {
compile“ org.hibernate: hibernate - core: $ {
hibernateVersion
}”
}
6.3.2 环境的配置
Gradle 为不同环境提供配置信息的方法主要分为使用 Properties 文件和使用 Groovy 配置文件两种。
6.3.2.1 配置文件的加载
要为不同的环境提供不一样的配置信息,Maven 选择使用 profile,而 Gradle 则提供了两种方法为构建脚本提供Properties 配置:
1. 使用 Properties 文件
-
文件结构:在项目的根目录下创建
config
文件夹,并在其中放置development.properties
、test.properties
和production.properties
等不同环境的配置文件。 -
加载逻辑:在
build.gradle
文件中,通过-Pprofile=xxx
传递参数来指定要加载的配置文件。使用ext
块定义profile
变量,并根据该变量加载对应的 properties 文件。 -
示例代码:
ext {
profile = project.hasProperty('profile') ? project['profile'] : 'development' // 默认开发环境
}
def loadProperties() {
def props = new Properties()
new File("${rootProject.projectDir}/config/${profile}.properties").withInputStream { stream ->
props.load(stream)
}
return props
}
def jdbcConfig = loadProperties()
2. 使用 Groovy 配置文件
-
文件结构:在项目的根目录下创建
config.groovy
文件,并在其中定义不同环境的配置信息。 -
加载逻辑:使用
ConfigSlurper
类根据-Pprofile=xxx
传递的参数加载对应的配置信息。 -
示例代码:
environments {
development {
jdbc {
url = 'development'
user = 'xxxx'
}
}
test {
jdbc {
url = 'test'
user = 'xxxx'
password = 'xxxx'
}
}
production {
jdbc {
url = 'production'
user = 'xxxx'
password = 'xxxx'
}
}
}
这里定义了三个环境下的不同数据库配置,在构建脚本中使用如下的代码来加载:
ext {
profile = project.hasProperty('profile') ? project['profile'] : 'development' // 默认开发环境
}
def loadGroovyConfig() {
def configFile = file('config.groovy')
def config = new ConfigSlurper(profile).parse(configFile.toURL()).toProperties()
return config
}
def jdbcConfig = loadGroovyConfig().getProperties().get('jdbc')
-
推荐理由:Groovy 配置文件具有更好的可读性和结构,适合管理复杂的配置信息。对于新项目,建议使用 Groovy 配置文件。
6.3.2.2占位符替换
1. 占位符标注
-
在资源文件中,使用
@key@
的形式标注需要被替换的占位符。例如,在jdbc.properties
文件中:
jdbc.url=@jdbc.url@
jdbc.user=@jdbc.user@
jdbc.password=@jdbc.password@
2. 使用 processResources
Task
-
Gradle 提供了
processResources
Task,用于处理资源文件。默认情况下,它会将src/main/resources
目录下的资源文件复制到build/resources/main
目录下。 -
可以通过配置
processResources
Task,使其在复制过程中替换资源文件中的占位符。但是,对于复杂的项目结构或自定义的配置文件目录,需要自定义 Task。
3. 自定义 Task 替换占位符
-
当配置文件存放在自定义目录(如
config
目录)时,需要定义一个自定义 Task 来替换占位符,并让processResources
Task 依赖于该自定义 Task。 -
示例代码:
task replaceConfigFiles(type: Copy) {
from("${rootProject.projectDir}/config") {
include '**/*.properties'
include '**/*.xml'
filter(org.apache.tools.ant.filters.ReplaceTokens, tokens: loadGroovyConfig().getProperties())
}
into "${buildDir}/resources/main/config"
}
processResources.dependsOn replaceConfigFiles
解释:replaceConfigFiles
Task 会将 config
目录下的 .properties
和 .xml
文件复制到 build/resources/main/config
目录下,并在复制过程中替换占位符。然后,让 processResources
Task 依赖于 replaceConfigFiles
Task,以确保在资源处理之前完成占位符替换。
6.3.2.3 完整示例
apply plugin: 'java' // 或者 'war'、'application' 等
ext {
profile = project.hasProperty('profile') ? project['profile'] : 'development'
}
def loadGroovyConfig() {
def configFile = file('config.groovy')
def config = new ConfigSlurper(profile).parse(configFile.toURL()).toProperties()
return config
}
task replaceConfigFiles(type: Copy) {
from("${rootProject.projectDir}/config") {
include '**/*.properties'
include '**/*.xml'
filter(org.apache.tools.ant.filters.ReplaceTokens, tokens: loadGroovyConfig().getProperties())
}
into "${buildDir}/resources/main/config"
}
processResources.dependsOn replaceConfigFiles
jar {
// 确保替换后的配置文件被包含在 JAR 中
from("${buildDir}/resources/main/config") {
include '**/*.properties'
include '**/*.xml'
}
}
6.3.3声明项目依赖
在Gradle中,依赖管理是一个核心功能,它允许项目声明对其他项目、外部库或框架的依赖,并确保在构建过程中这些依赖被正确解析和包含。
-
项目依赖
对于多模块项目,Gradle允许在子项目之间声明依赖关系。这通过dependencies
块中的project
方法实现。例如:
project(':repository') {
dependencies {
implementation project(':model') // 使用implementation替代compile,以符合现代Gradle实践
}
}
project(':web') {
dependencies {
implementation project(':repository')
providedCompile 'javax.servlet:servlet-api:2.5' // 注意:providedCompile已被deprecated,使用compileOnly替代
runtimeOnly 'javax.servlet:jstl:1.1.2' // 使用runtimeOnly替代runtime
}
}
注意:compile
和runtime
配置已被Gradle 7.x及更高版本弃用,建议使用implementation
、api
、compileOnly
和runtimeOnly
等新的配置。
-
Jar包依赖管理
Gradle支持从Maven仓库、Ivy仓库或本地文件系统解析Jar包依赖。依赖通过dependencies
块中的implementation
、api
、compileOnly
、runtimeOnly
等配置声明。例如:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web:2.5.4'
testImplementation 'junit:junit:4.13.2'
compileOnly 'javax.servlet:servlet-api:2.5' // 仅在编译时使用,不打包进最终产品
runtimeOnly 'com.h2database:h2:1.4.200' // 仅在运行时使用
}
Gradle会从配置的仓库中查找并下载这些依赖,以及它们的传递依赖(即依赖的依赖)。
-
子项目之间的依赖
在多模块项目中,子项目之间的依赖关系通过project
方法声明。Gradle会自动处理这些依赖的解析和构建顺序。例如:
dependencies { implementation project(':core') // 依赖core子项目 }
-
构建脚本的依赖
构建脚本本身也可以有依赖,这通常用于引入非官方的Gradle插件或脚本库。这些依赖通过buildscript
块声明:
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.github.ben-manes:gradle-versions-plugin:0.29.0' // 引入版本插件
}
}
// 应用插件
apply plugin: 'com.github.ben-manes.versions'
-
依赖解析机制和缓存管理
Gradle使用一种高效的依赖解析机制,它会根据项目的依赖关系图计算出最小的构建集,并尽可能重用缓存中的依赖项。这大大减少了构建时间和网络带宽的消耗。
-
依赖解析:Gradle会解析项目的依赖关系图,并计算出需要构建和下载的所有依赖项。
-
缓存管理:Gradle会将下载的依赖项和构建的产物缓存到本地文件系统中。在后续构建中,Gradle会首先检查缓存,如果缓存中存在所需的依赖项或产物,则直接使用,无需重新下载或构建。
-
部分构建和增量构建
为了进一步提高构建效率,Gradle支持部分构建和增量构建。
-
部分构建:通过指定特定的子项目或任务进行构建,Gradle会仅构建这些指定的部分,而忽略其他未更改的部分。
-
增量构建:Gradle会检测项目文件的变化,并仅重新构建那些发生变化的部分。这通过Gradle的输入/输出缓存机制实现。
-
依赖替换和排除
在某些情况下,你可能需要替换或排除某些依赖项。Gradle提供了灵活的机制来处理这些情况。
-
替换依赖:使用
resolutionStrategy
块中的force
方法可以强制使用特定版本的依赖项。 -
排除依赖:使用
exclude
方法可以排除传递依赖中的某些项。
6.4 拆分项目文件
为了简化根项目的构建代码,可以将与特定子项目相关的代码移到相应的 build.gradle
文件中。
6.4.1 定义根项目的构建代码
根项目的 build.gradle
文件可以简化为只包含 allprojects
和 subprojects
配置块,如下所示:
allprojects {
group = 'com.mlz.team'
version = '1.0'
}
subprojects {
apply plugin: 'java'
}
6.4.2 定义子项目的构建代码
将特定于子项目的构建代码移到相应的 build.gradle
文件中。如下所示:
// model 子项目的 build.gradle
// 无特定配置,因为已在根项目中应用 java 插件
// repository 子项目的 build.gradle
dependencies {
implementation project(':model')
}
// web 子项目的 build.gradle
apply plugin: 'war'
apply plugin: 'jetty'
repositories {
mavenCentral()
}
dependencies {
implementation project(':repository')
providedCompile 'javax.servlet:servlet-api:2.5'
runtimeOnly 'javax.servlet:jstl:1.1.2'
}
6.5 自定义脚本
在多项目构建环境中,可以通过自定义脚本名称来避免混淆。这需要在 settings.gradle
文件中使用 buildFileName
属性来设置。如下所示:
// 通过目录来添加子项目
include 'todo-model', 'todo-repository', 'todo-web'
// 设置根项目的名字
rootProject.name = 'todo'
// 迭代访问所有根目录下的子项目,设置自定义的构建脚本名称
rootProject.children.each {
it.buildFileName = "${it.name.replace('todo-', '')}.gradle"
}
通过这种方式,每个子项目都可以有独特的构建脚本名称,如 model.gradle
、repository.gradle
和 web.gradle
6.6 指定Gradle版本
为了确保项目中使用的Gradle版本一致,Gradle Wrapper提供了一个便捷的方法。通过Wrapper,你可以在项目中指定一个Gradle版本,团队成员无论本地安装了哪个版本的Gradle,都可以使用项目指定的版本来构建项目。
6.6.1步骤:
-
在项目中创建Wrapper: 运行
gradle wrapper
命令。这将在项目的根目录下生成一个gradlew
(Unix/Linux/macOS)和gradlew.bat
(Windows)脚本,以及一个wrapper
文件夹,里面包含Gradle Wrapper的Jar包和gradle-wrapper.properties
文件。 -
配置Wrapper: 编辑
gradle-wrapper.properties
文件,指定Gradle的版本。例如:
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-x.y.z-bin.zip
-
将
x.y.z
替换为你想要使用的Gradle版本号。 -
使用Wrapper运行Gradle: 使用
./gradlew
(Unix/Linux/macOS)或gradlew.bat
(Windows)脚本来运行Gradle任务,而不是直接使用gradle
命令。Wrapper会下载并缓存指定的Gradle版本(如果尚未下载),并使用该版本来执行任务。
6.6.2 示例:
假设你想在项目中使用Gradle 7.4.2版本。
-
运行命令:
gradle wrapper --gradle-version=7.4.2
这将在项目根目录下生成必要的Wrapper文件和文件夹。
-
编辑
gradle-wrapper.properties
文件,确认distributionUrl
属性如下:
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
-
现在,你可以使用
./gradlew
(或gradlew.bat
)来运行任何Gradle任务。例如:
./gradlew build
第一次运行时,Wrapper会下载Gradle 7.4.2版本并将其缓存到项目的gradle/dists
目录下。之后的构建将直接使用这个缓存的版本。
通过这种方式,你可以确保整个团队都使用相同版本的Gradle来构建项目,从而避免由于版本差异导致的构建问题。
7 Gradle流水线配置
7.1 安装Gradle
-
下载与安装:从Gradle官方网站下载适合您操作系统的Gradle安装包。
-
配置环境变量:
-
Windows:将Gradle的
bin
目录添加到系统的PATH
环境变量中。 -
macOS/Linux:通常可以通过在
.bash_profile
、.zshrc
或类似文件中添加export PATH="$PATH:/path/to/gradle/bin"
来设置。
-
-
验证安装:打开终端或命令提示符,输入
gradle -v
,如果显示Gradle的版本信息,则说明安装成功。
7.2 全局工具配置(在Jenkins中)
-
登录Jenkins。
-
进入“系统管理”(通常位于页面顶部的菜单中)。
-
选择“全局工具配置”。
-
在“Gradle安装”部分,添加一个新的Gradle安装条目,并指定Gradle的安装路径(即您解压Gradle包的目录)。
-
保存配置。
7.3 插件安装(在Jenkins中)
-
进入“系统管理”。
-
选择“管理插件”。
-
在“可用”标签页中,搜索“Gradle Plugin”。
-
选中它并点击“安装无重启”或“下载并安装后重启”(根据您的需求)。
7.4 Jenkins流水线集成Gradle
在Jenkins流水线中集成Gradle通常涉及在Jenkinsfile中编写Pipeline脚本。以下是一个简单的示例:
pipeline {
agent any
stages {
stage('Checkout') {
steps {
// 从版本控制系统中检出代码
checkout scm
}
}
stage('Build') {
steps {
script {
// 使用Gradle构建项目
sh './gradlew build' // 注意使用./gradlew来确保使用项目内的Gradle Wrapper
}
}
}
// 可以添加更多阶段,如测试、部署等
}
post {
always {
// 清理工作区(可选)
cleanWs()
}
success {
// 构建成功后的操作(可选)
}
failure {
// 构建失败后的操作(可选)
}
}
}
注意:
-
使用
./gradlew
而不是gradle
可以确保使用项目自带的Gradle Wrapper,这样可以避免版本不一致的问题。 -
在
Checkout
阶段,scm
是一个占位符,它会自动根据您在Jenkins项目配置中设置的源码管理设置来检出代码。 -
您可以在
post
部分添加构建后操作,如发送通知、归档构建产物等。
三、NPM构建:前端工程化的加速器
NPM(Node Package Manager)作为Node.js的包管理器,在前端工程化中发挥着不可替代的作用。它不仅简化了依赖包的管理,还促进了前端开发的组件化、模块化和自动化。
-
依赖管理:NPM通过
package.json
文件管理项目依赖,支持安装、更新和删除依赖包,确保项目环境的一致性和稳定性。 -
脚本执行:NPM支持在
package.json
中定义自定义脚本,如构建、测试、启动等,提高了开发效率和便捷性。 -
生态系统:NPM拥有丰富的开源库和工具,如Webpack、Babel等,支持前端项目的构建、打包、优化等各个环节。
1 NPM基础
NPM允许开发者发布、安装和管理Node.js项目中的依赖包。通过NPM,我们可以轻松地将各种优秀的第三方库和工具集成到项目中,从而提高开发效率和代码质量。
2 package.json文件
package.json是NPM项目的核心配置文件,它包含了项目的元数据以及依赖信息。以下是对package.json文件及其关键属性的详细解析:
-
name:项目名称,必须唯一且符合NPM的命名规范。
-
version:项目版本号,遵循语义化版本控制规范(SemVer)。
-
description:项目的简短描述。
-
main:项目入口文件的路径,通常是项目的JavaScript文件。
-
scripts:自定义脚本命令,用于执行项目构建、测试等任务。
-
dependencies:生产环境依赖的包,这些包在项目运行时是必需的。
-
devDependencies:开发环境依赖的包,这些包在项目开发过程中使用,但在生产环境中不需要。
-
repository:项目的仓库地址,通常用于指定Git仓库的URL。
-
keywords:项目的关键词,有助于在NPM上搜索到该项目。
-
author:项目作者的信息。
-
license:项目的许可证类型。
3 目录结构
一个高效、可维护的前端项目需要有一个合理的目录结构。以下是一个推荐的目录结构示例,它结合了NPM包管理的最佳实践:
my-project/
├── node_modules/ # 存放安装的依赖包
├── package.json # 项目配置文件
├── package-lock.json # 锁定依赖包的版本,确保项目的一致性
├── .npmignore # 指定不包含在npm包中的文件和目录
├── bin/ # 存放可执行二进制文件的目录(可选)
├── lib/ # 存放JavaScript代码的地方
│ ├── index.js # 项目入口文件
│ └── ... # 其他JavaScript文件
├── doc/ # 存放文档的目录
│ └── README.md # 项目说明文档
├── test/ # 存放单元测试用例的代码
│ └── ... # 测试用例文件
├── config/ # 存放配置文件的目录(可选)
│ └── ... # 配置文件
├── public/ # 存放静态资源的目录(如HTML、CSS、图片等,对于前端项目通常很重要)
│ └── ... # 静态资源文件
└── src/ # 存放源代码的目录(对于使用构建工具的项目,如Webpack,这是常见的结构)
├── components/ # 存放React/Vue等组件的目录
├── pages/ # 存放页面文件的目录
├── styles/ # 存放CSS/SCSS等样式文件的目录
├── utils/ # 存放工具函数的目录
└── ... # 其他源代码文件
4 项目初始化与依赖管理
4.1 项目初始化
使用npm init
命令初始化项目,根据提示填写项目信息,生成package.json
文件。这个文件是项目的核心配置文件,包含了项目的元数据、依赖、脚本等信息。
npm init
4.2 依赖管理
依赖管理是项目构建和开发的关键部分。使用 npm install
命令可以安装项目所需的依赖。根据项目需求,选择合适的工具、框架和库。
常见依赖:
-
构建工具:如 Webpack、Vite 等,用于打包和构建项目。
-
前端框架:如 React、Vue、Angular 等,用于构建用户界面。
-
路由工具:如 React Router、Vue Router 等,用于管理页面路由。
-
状态管理:如 Redux、Vuex 等,用于管理应用状态。
-
CSS 预编译器:如 Sass、Less 等,用于编写更高效的 CSS 代码。
-
UI 库:如 Bootstrap、Ant Design 等,用于快速构建用户界面。
-
测试框架:如 Jest、Mocha 等,用于编写和运行测试。
5. 构建工具配置
5.1 Webpack 配置
Webpack 是一个功能强大的模块打包工具,广泛应用于现代 JavaScript 应用程序的开发中。为了充分利用 Webpack 的能力,我们需要仔细配置其各个方面,包括入口、输出、加载器和插件等。以下是对 Webpack 配置的深度优化指南:
(1)入口(Entry)
-
单一入口:对于简单的项目,可以指定一个入口文件,如
src/index.js
或src/main.js
。 -
多入口:对于复杂项目,可以配置多个入口点,以支持多页面应用(MPA)或库的开发。
示例:
entry: {
main: './src/index.js',
vendor: ['react', 'react-dom'] // 提取第三方库
},
(2)输出(Output)
-
配置输出目录:确保打包后的文件放置在合适的目录中,如
dist
。 -
文件名和路径:使用占位符(如
[name]
,[hash]
)来生成具有唯一标识的文件名,以便于缓存管理和版本控制。
示例:
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
clean: true, // 自动清理未使用的文件
},
(3)加载器(Loaders)
-
Babel Loader:用于将 ES6+ 代码转换为向后兼容的 JavaScript 代码。
-
Vue Loader:用于处理
.vue
文件,将其编译为 JavaScript 模块。 -
CSS Loader + Style Loader:用于处理 CSS 文件,并将其注入到 DOM 中。
-
Less Loader:用于处理 Less 文件,将其编译为 CSS。
-
File Loader 和 URL Loader:用于处理文件和图片资源,根据文件大小决定是嵌入到代码中还是作为外部资源引用。
示例:
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
{
loader: 'less-loader',
options: {
lessOptions: {
modifyVars: {},
javascriptEnabled: true
}
}
}
]
},
{
test: /\.(png|jpe?g|gif|svg)$/i,
use: [
{
loader: 'file-loader',
options: {
name: '[path][name].[hash].[ext]',
},
},
],
},
],
},
(4)插件(Plugins)
-
HtmlWebpackPlugin:用于自动生成 HTML 文件,并自动将打包后的资源注入到该文件中。
-
CleanWebpackPlugin:用于在每次打包前清理输出目录,避免旧文件的残留。
-
DefinePlugin:用于定义全局常量,以便在代码中直接使用环境变量。
-
MiniCssExtractPlugin:用于将 CSS 从 JavaScript 包中分离出来,生成独立的 CSS 文件。
-
TerserPlugin:用于压缩和优化 JavaScript 代码。
-
OptimizeCSSAssetsPlugin:与 TerserPlugin 配合使用,用于压缩 CSS 代码。
示例:
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
minify: {
collapseWhitespace: true,
removeComments: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
}),
new CleanWebpackPlugin(),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
}),
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
}),
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true, // 去除console.log
},
},
}),
new OptimizeCSSAssetsPlugin({}),
],
5.2 环境变量配置
为了在不同环境下使用不同的配置,我们可以使用 dotenv
和 cross-env
来管理环境变量。
(1)安装依赖
npm install dotenv cross-env --save-dev
(2)创建环境变量文件
在项目的根目录下创建 .env.dev
和 .env.prod
文件,分别配置开发环境和生产环境的变量。
示例:
-
.env.dev
API_URL=http://localhost:3000/api
-
.env.prod
API_URL=https://example.com/api
(3)在 Webpack 配置文件中读取环境变量
在 Webpack 配置文件的顶部引入 dotenv
并加载相应的环境变量文件。
示例:
const dotenv = require('dotenv');
const webpack = require('webpack');
const env = process.env.NODE_ENV || 'development';
const envConfig = dotenv.config({ path: `./.env.${env}` }).parsed;
for (const key in envConfig) {
process.env[key] = envConfig[key];
}
// 在 DefinePlugin 中使用这些环境变量
new webpack.DefinePlugin({
'process.env': JSON.stringify(process.env),
}),
(4)在 package.json 中配置脚本
在 package.json
的 scripts
部分配置用于启动开发服务器和构建生产环境的脚本。
示例:
"scripts": {
"start": "cross-env NODE_ENV=development webpack serve --open --config webpack.config.js",
"build": "cross-env NODE_ENV=production webpack --config webpack.config.js"
}
6 前端框架与路由配置
6.1. Vue框架配置
6.1.1 安装Vue及其相关依赖
首先,确保你已经安装了Vue 3及其相关依赖。使用npm或yarn进行安装,这里以npm为例:
npm install vue@next
npm install vue-loader@next @vue/compiler-sfc --save-dev
6.1.2 配置Webpack以支持Vue文件
在Webpack配置文件中添加对.vue
文件的支持。通常,你会有一个webpack.config.js
或类似的配置文件。在这个文件中,你需要添加vue-loader
的规则,并引入VueLoaderPlugin
。
// webpack.config.js
const { VueLoaderPlugin } = require('vue-loader/dist/index');
module.exports = {
// ... 其他配置
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
// 其他加载器配置
]
},
plugins: [
new VueLoaderPlugin()
// 其他插件配置
]
};
6.1.3 配置Babel以支持ES6+
为了确保你的代码可以在所有现代浏览器中运行,你需要使用Babel将ES6+代码转换为ES5。
npm install @babel/core @babel/preset-env babel-loader --save-dev
然后,在Webpack配置文件中添加Babel加载器,并在项目根目录下创建babel.config.js
文件。
// webpack.config.js
module.exports = {
// ... 其他配置
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
// 其他加载器配置
]
}
// ... 其他配置
};
// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', {
targets: {
browsers: ['last 2 versions', 'not ie <= 11'] // 根据需要调整目标浏览器
}
}]
]
};
6.1.4 使用html-webpack-plugin处理HTML文件
安装html-webpack-plugin
并配置它,以便自动生成index.html
文件并插入打包后的资源。
npm install html-webpack-plugin --save-dev
// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// ... 其他配置
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html', // 模板文件路径
filename: 'index.html',
minify: {
// 压缩选项,根据需要调整
collapseWhitespace: true,
removeComments: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
}),
// 其他插件配置
]
};
6.1.5 添加Vue Router
安装Vue Router并配置路由规则。
npm install vue-router@next --save
在src/router/index.js
中定义路由规则,并在src/main.js
中挂载路由。
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue';
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
// 其他路由规则
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
});
export default router;
// src/main.js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
createApp(App)
.use(router)
.mount('#app');
6.1.6 添加Less预处理器
安装Less及其加载器,并在Webpack配置中添加相应的规则。
npm install style-loader css-loader less less-loader --save-dev
// webpack.config.js
module.exports = {
// ... 其他配置
module: {
rules: [
// ... 其他加载器配置
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
{
loader: 'less-loader',
options: {
lessOptions: {
// Less选项,根据需要调整
}
}
}
]
}
]
}
};
6.2. 路由配置
6.2.1 路由懒加载
使用动态导入来实现路由的懒加载,以减少初始加载时间。
// src/router/index.js
const Home = () => import(/* webpackChunkName: "home" */ '../views/Home.vue');
const About = () => import(/* webpackChunkName: "about" */ '../views/About.vue');
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: About
},
// 其他路由规则
];
6.2.2 路由守卫
使用路由守卫来处理导航前的逻辑,如权限验证、页面跳转等。
// src/router/index.js
router.beforeEach((to, from, next) => {
// 权限验证逻辑
if (to.matched.some(record => record.meta.requiresAuth)) {
// 判断用户是否已登录
if (!isUserLoggedIn()) {
next({
path: '/login',
query: { redirect: to.fullPath }
});
} else {
next();
}
} else {
next(); // 确保一定要调用 next()
}
});
2.3 路由元信息
在路由规则中添加元信息(meta),以便于在路由守卫或其他地方使用。
const routes = [
{
path: '/',
name: 'Home',
component: Home,
meta: { title: 'Home Page' }
},
{
path: '/about',
name: 'About',
component: About,
meta: { title: 'About Page' }
},
// 其他路由规则
];
2.4 滚动行为
配置路由的滚动行为,以控制页面在导航时的滚动位置。
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition;
} else {
return { x: 0, y: 0 };
}
}
});
7 代码规范与自动化检测
7.1 代码规范
使用Prettier和ESLint配置代码规范。Prettier用于格式化代码,支持多种语言;ESLint用于检测代码中的潜在问题,提高代码质量。在根目录下创建.prettierrc
和.eslintrc.js
文件,配置代码格式化规则和ESLint规则。
7.2 自动化检测
使用Git Hook工具(如husky)在git提交阶段触发ESLint检测,确保提交的代码符合规范。在package.json
文件中配置husky和ESLint的脚本命令。
8 自动化部署
8.1 Jenkins服务器
-
确保Jenkins服务器安装并配置:
-
安装Jenkins:可以通过官方网站下载并安装Jenkins服务器。
-
配置Jenkins:启动Jenkins服务后,通过浏览器访问Jenkins管理界面,并进行初始配置,如管理员账户、插件安装等。
-
-
安装npm插件:
-
在Jenkins管理界面,进入插件管理,搜索并安装NodeJS Plugin,该插件提供了npm的支持。
-
8.2 前端项目
-
项目初始化与配置:
-
使用npm进行项目初始化,确保
package.json
文件配置正确,包含所有必要的依赖和构建脚本。 -
选择Vue、React等现代前端框架,并配置Webpack等构建工具。
-
8.3 Nginx服务器
-
安装与配置Nginx:
-
使用包管理器(如apt-get、yum等)安装Nginx。
-
配置Nginx作为前端服务的静态文件服务器,修改Nginx配置文件以指定静态文件目录和默认索引页。
-
8.4 Jenkins构建流程
-
创建Jenkins任务:
-
在Jenkins上创建一个新的任务,选择“Freestyle project”或“Pipeline project”。
-
-
配置源码管理:
-
在“Source Code Management”部分,选择适合你的版本控制系统(如Git),并配置好仓库地址、分支和访问权限。
-
-
配置构建触发器:
-
根据需要配置构建触发器,GitLib hook trigger(代码提交后触发构建)。
-
-
配置构建步骤:
npm install # 安装项目依赖 npm run build # 执行构建命令(确保package.json中有对应的脚本)
-
添加“Execute shell”或“Execute Windows batch command”构建步骤,根据你的操作系统选择。
-
在命令框中输入以下命令用于编译前端项目:
-
你可以根据需要添加其他构建步骤,如代码质量检查、单元测试等。
-
-
配置构建后操作:
-
在“Post-build Actions”部分,配置构建后的操作,如使用SSH插件或scp命令将构建好的文件部署到Nginx服务器。
-
可以通过配置“Send files or execute commands over SSH”插件来实现文件的远程传输和部署。
-
-
流水线配置(如果创建任务用了 Pipeline project)
pipeline {
agent any // 或者指定一个具有必要工具和环境的代理
environment {
// 定义环境变量,例如NGINX服务器的部署路径
NGINX_DEPLOY_PATH = '/var/www/your-app' // 请根据您的实际情况修改
// 如果需要指定Node.js版本,也可以在这里设置(但通常这应该在Jenkins节点配置中完成)
}
stages {
stage('Checkout') {
steps {
// 从GitLab检出代码
checkout scm // scm是Jenkins内置的一个步骤,它会根据配置的源代码管理仓库检出代码
// 如果您需要指定GitLab的特定分支或仓库URL,可以在Jenkins的作业配置中设置
}
}
stage('Build') {
steps {
script {
// 在Jenkins上编译代码
// 假设您的项目是一个Node.js项目,并且有一个package.json文件
sh 'npm install' // 安装项目依赖
sh 'npm run build' // 执行构建脚本(根据您的项目实际情况可能是其他命令)
// 如果构建产物不在当前目录,您可能需要将它们复制到某个特定目录
// 例如:sh 'cp -r dist/* ${WORKSPACE}/build-output/'
}
}
}
stage('Deploy') {
steps {
script {
// 将构建产物部署到NGINX服务的目录
// 注意:这里假设Jenkins有权限写入NGINX的部署路径
// 如果不是,您可能需要设置适当的权限或使用SSH/SCP等步骤来传输文件
sh "cp -r ${WORKSPACE}/dist/* ${NGINX_DEPLOY_PATH}/" // 根据您的实际构建产物目录修改
// 如果您需要重启NGINX来使更改生效,可以添加以下步骤(但请谨慎使用,因为这可能会导致服务中断)
// sh 'sudo systemctl restart nginx' // 请确保Jenkins用户有执行此命令的权限
// 或者,如果您不想在Pipeline中直接重启NGINX,可以发送一个通知给负责重启的人或系统
}
}
}
}
post {
success {
// 构建成功后的操作,例如发送通知
echo 'Deployment successful!'
// 可以添加其他通知步骤,如发送邮件、Slack消息等
}
failure {
// 构建失败后的操作,例如发送通知
echo 'Deployment failed!'
// 可以添加错误日志记录、发送失败通知等步骤
}
}
}
8.5 Nginx配置
-
配置Nginx静态文件服务:
-
打开Nginx配置文件(通常位于
/etc/nginx/nginx.conf
或/etc/nginx/sites-available/default
)。 -
配置一个server块,用于处理前端静态文件,如:
-
server {
listen 80;
server_name your_domain.com; # 替换为你的域名
location / {
root /path/to/your/nginx/static/files; # 替换为你的静态文件目录
index index.html;
try_files $uri $uri/ /index.html; # 解决单页应用刷新404问题
}
# 其他配置...
}
-
部署前端静态文件:
-
将Jenkins构建好的前端静态文件通过SSH或其他方式传输到Nginx服务器的指定目录。
-
确保文件权限和所有权设置正确,以便Nginx能够正确读取文件。
-
四、Gradle or maven
在为后端构建选择Gradle还是Maven时,需要综合考虑多个因素,包括性能、灵活性、易用性、插件生态等。以下是对两者的比较:
1 性能
-
Gradle:
-
Gradle通常比Maven更快,尤其是在处理大型项目和子项目时。
-
Gradle实现了增量构建机制、构建缓存机制和守护进程机制等策略来保证构建速度。
-
增量构建可以分析源文件和类文件之间的依赖关系,并只重新编译改变的部分。
-
Gradle的智能类路径分析器可以避免不必要的编译,当二进制接口没有改变时,不会重新编译。
-
-
Maven:
-
Maven的构建速度相对较慢,尤其是在处理大型项目和复杂依赖时。
-
Maven也支持增量构建和构建缓存,但相比之下效果可能不如Gradle显著。
-
2 灵活性
-
Gradle:
-
Gradle具有非常强的灵活性,可以满足各种自定义需求。
-
Gradle使用基于Groovy或Kotlin的DSL来声明项目设置,相比Maven的XML配置更加简洁和易读。
-
Gradle允许开发者自定义构建逻辑和任务,更适合需要动态配置的复杂项目。
-
-
Maven:
-
Maven的灵活性相对较低,遵循固定的项目结构和生命周期。
-
Maven的XML配置文件相对冗长,且限制了开发者的自定义能力。
-
3 易用性
-
Gradle:
-
Gradle的学习曲线可能更陡峭,需要掌握Groovy或Kotlin语言和Gradle的构建脚本编写方法。
-
但对于熟悉Groovy或Kotlin的开发者来说,Gradle的易用性会更高。
-
-
Maven:
-
Maven的学习曲线相对较低,XML语法易于理解。
-
Maven的项目结构和生命周期固定,使得初学者更容易上手。
-
4 插件生态
-
Gradle:
-
Gradle拥有丰富的插件生态系统,但相比Maven来说插件数量可能稍少。
-
Gradle支持自定义插件的开发,可以满足更多特定的需求。
-
-
Maven:
-
Maven拥有更加成熟和丰富的插件生态系统,可以满足更多的构建需求。
-
Maven的插件数量众多,且有很多高质量的插件可供选择。
-
5 总结与选择建议
-
如果项目需要高性能和灵活性:
-
选择Gradle作为构建工具。Gradle的增量构建、构建缓存和守护进程机制可以显著提高构建速度。
-
Gradle的灵活性和可扩展性可以满足各种自定义需求。
-
-
如果项目需要易于理解和上手:
-
选择Maven作为构建工具。Maven的项目结构和生命周期固定,使得项目相对容易看懂。
-
Maven的XML配置文件虽然冗长,但易于理解和维护。
-
-
如果项目需要丰富的插件支持:
-
也可以考虑选择Maven作为构建工具。Maven的插件生态系统更加成熟和丰富。
-
原文地址:https://blog.csdn.net/heijunwei/article/details/144306490
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!