重构谷粒商城06——Maven快速入门教程
前言:这个系列将使用最前沿的cursor作为辅助编程工具,来快速开发一些基础的编程项目。目的是为了在真实项目中,帮助初级程序员快速进阶,以最快的速度,效率,快速进阶到中高阶程序员。
本项目将基于谷粒商城项目,并且对谷粒商城项目进行二次重构,使其满足最新的主流技术栈要求。
上一篇文章我们对docker容器化技术进行了讲解,这一篇文章我们将快速、精准、全面的学习maven。准备好项目所需要的maven环境。
1.Maven简介
Maven 是一个用于 Java 项目的构建和管理工具,旨在简化项目的构建过程,方便地管理项目所需的依赖库。
其主要作用包括:
依赖管理:通过配置 pom.xml
文件,Maven 可以自动下载并管理项目所需的所有依赖库,避免手动下载和导入的繁琐过程。
标准化项目结构:Maven 提供了一套标准的项目目录结构,使得不同开发者之间的协作更加顺畅,项目的可维护性更高。
构建自动化:Maven 定义了一系列标准的构建生命周期阶段,如编译、测试、打包、部署等,开发者只需执行相应的命令即可完成这些操作,提高了开发效率。
在java项目中需要打包很多源代码文件。如果没有maven,就会很麻烦。
Maven 的构建过程遵循一系列预定义的生命周期,每个生命周期包含多个阶段(phase),每个阶段执行特定的任务。在执行 mvn package
命令时,Maven 会按照以下主要阶段顺序进行操作:
- validate:验证项目是否正确,所有必要的信息是否可用。比如检查项目的目录结构:确保项目的目录布局符合标准规范,例如源代码应位于
src/main/java
,资源文件应位于src/main/resources
等。 - compile:编译项目的源代码。 Maven 的
compile
阶段,Maven 会使用 Java 编译器将项目的源代码(即.java
文件)编译成字节码文件(即.class
文件)。这些.class
文件通常位于项目的target/classes
目录下。 - test:使用适当的单元测试框架测试编译的源代码。
- package:将编译后的代码打包成可分发的格式(如 JAR、WAR)。打包会将应用程序及其所有依赖项打包成一个文件,简化了分发和部署过程,确保在不同环境中都能一致运行。还对代码进行混淆和压缩,增加反编译的难度,保护知识产权。
此外,Maven 还有其他生命周期和阶段,例如 clean
生命周期用于清理项目,site
生命周期用于生成项目站点文档等。
按照流程走。so easy。
通过使用 Maven,开发者可以更高效地管理项目的构建过程和依赖关系,提升开发效率,减少重复劳动。
2、基本概念
2.1 pom.xml文件
pom.xml
是 Maven 项目的核心配置文件,用于定义项目的基本信息、依赖关系、构建配置等。通过配置 pom.xml
,Maven 可以自动管理项目的构建过程和所需的依赖库,提高开发效率,减少手动配置的繁琐。
maven通过很多工具,安装pom文件对项目进行编译、打包等。
2.2 Maven仓库
Maven 仓库是用于存储项目所需的依赖库(如 JAR 包)的地方,方便 Maven 在构建项目时获取和管理这些依赖。
Maven 仓库主要分为三种类型:
- 本地仓库:存储在开发者本地计算机上的仓库,默认路径为用户主目录下的
.m2/repository
。当构建项目时,Maven 会首先从本地仓库中查找所需的依赖。 - 中央仓库:由 Maven 官方提供的公共仓库,包含了大量常用的开源库。当本地仓库中没有找到所需依赖时,Maven 会从中央仓库下载。
- 远程仓库:由第三方提供的仓库,如公司内部的私有仓库或其他公共仓库。开发者可以在项目的
pom.xml
中配置这些仓库的地址,以获取特定的依赖。
3、Maven安装
先需要安装jdk(1.7+)。这里不再赘述。
maven官网:https://maven.apache.org/
傻瓜式安装。
配置下环境变量。这么基础的内容,我就偷个懒,跳过了。(下图为mac中环境变量配置示意,具体自行查下)
4、maven的配置
在安装目录下,找到配置文件。
配置下本地仓库的位置。
<!-- localRepository
| The path to the local repository maven will use to store artifacts.
|
| Default: ${user.home}/.m2/repository
<localRepository>/path/to/local/repo</localRepository>
-->
# 改成自己的目录
<localRepository>C:\Users\banjiu\workspace\mvn-repository</localRepository>
配置镜像。
</mirrors>
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
配置jdk版本。可以修改为自己本地安装的jdk版本。
<profiles>
<profile>
<id>jdk-22</id>
<activation>
<jdk>22</jdk>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>22</source>
<target>22</target>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
执行下面命令,确认下配置是否不存在问题。
mvn help:system
看到build success,说明ok。
补充:win11控制台中文乱码,可以输入下面命令临时解决。
chcp 65001
5、mvn命令的使用
可以参考官网文档:https://maven.apache.org/guides/getting-started/index.html
先快速创建一个maven项目。
mvn archetype:generate -DgroupId=com.mycompany.app -DartifactId=my-app -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.5 -DinteractiveMode=false
mvn archetype:generate
:调用 Maven 的 Archetype 插件,生成项目骨架。
-DgroupId=com.mycompany.app
:指定项目的 groupId
,通常是组织或公司域名的反向表示。
-DartifactId=my-app
:指定项目的 artifactId
,即项目的名称。是项目的唯一标识。
-DarchetypeArtifactId=maven-archetype-quickstart
:指定使用的原型模板,这里选择了 maven-archetype-quickstart
,这是一个用于快速启动 Java 项目的模板。
在下面输出信息中,可以看到项目生成位置。
使用vscode打开项目。
code C:\Users\半旧\my-app
下面我们可以来看看下pom文件。
首先是一个固定的声明。不用太关注。
<?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">
接下来是groupId、artifactId、version,是我们命令行输入的参数。
<groupId>com.mycompany.app</groupId>
<artifactId>my-app</artifactId>
<version>1.0-SNAPSHOT</version>
我们编译下项目。
可以看到多了一个target目录,存放的是编译生成的字节码文件。
接下来打包项目。
mvn package
可以看到生成了jar包。
运行下这个jar包。
PS C:\Users\半旧\my-app> java -cp .\target\my-app-1.0-SNAPSHOT.jar com.mycompany.app.App
Hello World!
-cp 指定类路径(Classpath),即 Java 程序运行时查找类文件的位置。
6、在IDEA中使用maven
创建一个maven项目。
如果您出现了使用maven archetype创建项目的没有生成src目录,而且pom文件为空的问题:
请将创建项目时,选择的jdk版本降级。你的版本太高了。实测选择20是没有问题的。
好了,现在看看生成的项目,和我们之前用命令生成的一模一样。
可以将maven换成自己下载部署的maven。
还有配置文件记得替换。
7、Maven的生命周期和插件
打开maven工具栏,即可看到maven的生命周期
上面生命周期,其实是依靠插件来实现的,点开插件,可看到,与生命周期可以对应上。
插件,本质上就是java的类,它们实现了maven的接口,在不同的阶段被调用。
maven提供了三种主要的生命周期,分别是clean
、default
和site
。
clean用来清理项目,把之前生成的字节码文件、jar包都清理掉。可以双击执行下,看看效果(也可以执行mvn clean命令)
先编译下。看到生成了target目录,里面包含了class文件。
再clean,执行后target目录被清除
在编译时,可能出现如下错误。
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to C:\Users\半旧\Desktop\wz\javawrokspace\test\target\classes
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] 不再支持源选项 7。请使用 8 或更高版本。
[ERROR] 不再支持目标选项 7。请使用 8 或更高版本。
[INFO] 2 errors
[INFO] -------------------------------------------------------------
解决方法:在pom.xml中限定maven使用的jdk版本。
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version> <!-- 确保使用的是最新版本 -->
<configuration>
<source>1.8</source> <!-- 设置为 Java 8 -->
<target>1.8</target> <!-- 设置为 Java 8 -->
</configuration>
</plugin>
</plugins>
</build>
reload maven project。
第二个生命周期,default。包含了我们从头开始构建一个项目的主要步骤,是 Maven 的主要生命周期,负责处理项目的编译、测试、打包等工作。
验证(validate):验证配置项文件是否正确。比如pom文件有没有格式错误。
编译(compile): 编译源代码(src/main/java
)。如果有编译错误,这个阶段会失败
测试(test): 运行项目中的单元测试(src/test/java
)。测试代码会被编译,然后执行。通常使用 JUnit 运行单元测试。
打包(package): 将编译后的代码打包成可发布的格式(如 .jar
、.war
文件)。这一步会把项目构建成可以部署的形式。执行package,会自动先执行编译、测试。maven生命周期,每一个阶段都是依赖上一个阶段的。如果上一周期执行失败,下一个周期就不会再执行了。
verify: 运行任何验证任务,比如集成测试,确保项目打包后的内容是可用的。
install: 将打包好的文件安装到本地 Maven 仓库,供本地的其他项目使用。这样其他项目就可以通过 groupId
, artifactId
, version
来依赖这个构建产物。
deploy: 将构建好的文件上传到远程 Maven 仓库(一般是项目的私服仓库,需要提前部署配置),供其他开发者或者项目使用。通常用于生产环境。
第三个生命周期,site。站点生命周期负责生成项目的站点文档(如 Javadoc、项目报告等)。站点生命周期一般用于生成和发布项目相关的静态文档。
浏览器打开,可以看到项目基本的一些信息。
我们前面说过,maven生命周期,每一个阶段都是依赖上一个阶段的,会自动执行前面的生命周期。
在实际项目中,我们经常使用的是mvn package和mvn install。
8、Maven的坐标和存储位置
maven标识存储jar包,依靠坐标定位。pom文件中的groupId(公司/组织标识)、artifactId(项目标识)、version(版本号)可以唯一标识一个jar包。
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
在maven repository网站,可以找到几乎所有开源jar包的坐标。https://mvnrepository.com/
找个jdbc测试玩一下。
粘贴。
reload maven project。
在本地仓库中就可以找到了。
9、依赖管理
Maven 的依赖管理是其最强大的功能之一,它帮助开发者管理项目中的外部库、框架和其他组件的依赖关系。通过依赖管理,Maven 自动处理各种版本和依赖冲突,简化了开发人员的工作流程。
9.1 指定依赖范围
scope可以指定依赖的范围。
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
常见的依赖范围包括:
- compile:默认的范围,表示该依赖对所有阶段都是可见的(编译、测试、运行)。可省略不写。
- provided:表示该依赖在编译时需要,在运行时不需要。比如Lombok,主要功能是简化编写代码时Getter、Setter的编写。运行时不需要。比如在 Web 应用中,运行时Servlet API 可以被 Web 容器提供。运行时也不需要。
- runtime:表示在编译时不需要,但在运行时需要。最典型的就是jdbc。
- test:表示该依赖只在测试时需要。无法在非测试代码中导入,不会被打包到最终的jar包中。比如Junit框架。
- system:不推荐。表示依赖于本地系统中某个特定的文件。配合systempath使用。这样maven就不会去私服仓库或者中央仓库下载,而是直接使用本地的jar包。
- import:用于导入依赖的 BOM 文件(Bill of Materials)。不会实际引入依赖,只是用来管理版本号,后面讲解父子工程时,再详细解释。
9.2 添加依赖
在maven repository网站,找到jar包的坐标,添加到pom文件中,刷新下maven项目就可以了。
9.3 依赖传递
依赖是具有传递性的。直接写再pom.xml中的依赖是直接依赖。
依赖传递(Transitive Dependency)是指当你的项目依赖于某个库时,该库可能也依赖于其他的库。Maven 会自动将这些 传递性依赖 添加到你的项目中,而无需你显式地声明它们。
举个例子
假设有三个项目:项目 A、项目 B 和 项目 C。它们的依赖关系如下:
- 项目 A 依赖于 项目 B
- 项目 B 依赖于 项目 C
在这种情况下,项目 C 是 项目 A 的传递性依赖,Maven 会自动将 项目 C 添加到 项目 A 的依赖中。
注意:只有依赖范围是compile的依赖才会被传递,其它依赖不会被传递。
可以通过exclusion标签,人为的排除一个依赖,也可以通过optional标签,标记一个依赖是可选的,这样就不会被继承。在父子工程中,我们将详细讲解这一点。
9.4 依赖冲突
如果一个项目中依赖了两个不同的依赖A、B,而这两个依赖A、B又同时依赖另外一个依赖C的不同版本,那么就会出现依赖冲突。
Maven会根据一定规则解决依赖冲突。
首先是最短路径优先原则。即 Maven 会选择在项目依赖树中离当前依赖最近的版本。
第二个是先声明优先。路径长度相同情况下,在pom.xml文件中先声明的依赖,会被优先使用。
如果需要,你也可以通过 <dependencyManagement>
来显式声明一个特定版本,避免自动选择。
9.5 父子工程
在 Maven 中,父子工程(Parent-Child Projects)是通过继承来管理多个模块的构建过程的一种机制。父子工程结构通常用于多模块项目(Multi-Module Projects),使得多个子项目可以共享构建配置、依赖管理和插件配置,从而提高项目的可维护性和一致性。
父工程(Parent Project) 是一个包含共同配置、依赖管理、插件管理等的项目。它通常不会被独立构建,而是作为一个父级项目存在。
子工程(Child Project) 是继承自父工程的项目,通常是具体的模块或子项目。子工程可以继承父工程的配置,同时还可以进行额外的自定义和覆盖。
新建一个项目。
改下打包方式。改为pom,这样不会父模块在打包时生成任何的jar包或者war包,而是用于管理其它子工程。
父工程的src一般也不需要,删除。
建个子模块。
看下子工程的pom文件。可以看到,子工程pom文件中,指定了父工程目标,子工程自己的groupid省略了,因为会继承父工程的groupid。
再建立子工程child-b,child-c。
再回过头看父工程的pom文件。
执行父工程的maven命令,子工程都会被执行一次。
9.6 手动解决依赖冲突
在模块a中引入模块b,c的依赖。
在模块b,c中分别引入不同版本的spring。
maven会根据先生们原则,自动引用模块b中的spring依赖。
如果我们不希望模块b的spring模块被模块a优先使用(该依赖非必要)。我们可以在模块a中pom文件,增加exclusion标签,
reload maven project,关闭idea重新打开。
在模块b的pom文件中,添加optional标签,可以实现同样效果。
使用optional标签和exclusion标签解决依赖冲突,有什么不同?
在 Maven 中,
optional
标签和exclusion
标签 都是用来解决依赖冲突和管理依赖关系的工具,但它们的使用方式和目的有所不同。我们来分别看看这两个标签的区别:1.
optional
标签
- 作用:
optional
标签用于标识某个依赖是可选的。也就是说,该依赖不是项目必需的,并且不会传递给依赖该项目的其他模块。它只在当前模块需要时才会被包含。- 使用场景:你可以使用
optional
来告诉 Maven,某个依赖是当前模块使用的,但不希望这个依赖在子项目中被传递下去。- 默认行为:如果没有使用
optional
标签,Maven 会将所有的直接依赖传递给依赖这个模块的其他模块。- 典型用途:当你希望某个库在你的模块中可用,但不希望它在依赖你的模块的其他模块中自动生效时,使用
optional
。示例:
假设你依赖于某个库,但它是可选的,不希望传递给子模块。
<dependency> <groupId>com.example</groupId> <artifactId>some-library</artifactId> <version>1.0.0</version> <optional>true</optional> </dependency>
在这个例子中,
some-library
被标记为可选依赖,它仅在当前模块中使用,而不会传递给依赖当前模块的其他模块。2.
exclusion
标签
- 作用:
exclusion
标签用于排除传递性依赖,通常是你不希望引入到项目中的传递性依赖。如果某个库是你依赖的其他库的传递性依赖,而你不希望它出现在项目中,你可以使用exclusion
来显式地排除它。- 使用场景:当你发现某个传递性依赖可能会引起冲突或者不需要时,可以使用
exclusion
标签排除它。- 典型用途:用于排除冲突的依赖项、过时的版本或不需要的传递性依赖。
示例:
假设你依赖的库 A 引入了
spring-context
,但是你不希望它出现在你的项目中:<dependency> <groupId>com.example</groupId> <artifactId>library-a</artifactId> <version>1.0.0</version> <exclusions> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> </exclusion> </exclusions> </dependency>
在这个例子中,
spring-context
是一个传递性依赖,通过exclusion
排除了它。
9.7 依赖继承
下面我们讲下如何使用父子依赖,来管理一些公共的依赖。
先将子模块的依赖,全部删除。
在父模块中定义依赖,子模块也会继承。
不过,这样无论子模块是否需要这个依赖,都会继承。因此,我们一般会使用depencymanagement标签。父工程定义版本。子模块按需继承,不关心版本。
标签properties,可以定义一些公共的属性。
10、私服仓库
最常用的是Nexus。这里了解下基本的作用和用途,需要时再学习下就行。
私服仓库(私有 Maven 仓库)是一个组织或团队内部使用的专用仓库,用来存储和管理公司或项目特定的依赖、构件(如 JAR、WAR 文件)以及其他 Maven 构建成果。私服仓库通常是托管在公司内部的一个服务器上,或者使用第三方服务(如 Nexus、Artifactory、GitHub Packages 等)来搭建。私服仓库的作用和优势主要有以下几个方面:
- 管理和存储内部依赖
- 许多企业和组织会开发自己的内部库、工具或者服务,这些库通常是私有的、公司专有的,不会公开发布到公共 Maven 中央仓库。私服仓库可以用来存储这些内部的构件,方便团队和项目使用。
- 例如,一个公司内部的共享库,其他项目或团队可以通过 Maven 直接引用这个库。
- 解决外部依赖的缓存和优化构建速度
- 当项目依赖于外部的公共库时,每次构建时 Maven 会去公共仓库下载这些依赖文件。如果网络不稳定或外部仓库的访问速度较慢,可能会影响构建速度。
- 私服仓库充当了一个 代理 的角色,缓存来自公共 Maven 仓库的依赖,所有开发者都可以从私服仓库获取这些依赖,这样就避免了每次构建时都去公共仓库下载依赖,提高了构建的速度和稳定性。
- 版本控制和依赖管理
- 私服仓库可以帮助组织控制和管理所有使用的库和版本。你可以在私服仓库中上传指定版本的构件,这样不同的团队可以确保使用的是相同版本的依赖,从而避免了版本冲突。
- 通过私服仓库,可以方便地管理内部构件的版本,进行统一的发布和管理。
- 保证安全性和合规性
- 有些库和工具是内部开发的,需要保护源代码或确保外部无法访问。私服仓库能够提供访问权限控制,确保只有授权的用户或团队能够上传或下载特定的依赖,保护公司的知识产权和敏感信息。
- 如果你依赖的库包含了有法律或合规要求的组件,可以通过私服仓库来确保所有使用的构件都是经过合规检查的。
- 定制化构件发布流程
- 私服仓库允许组织实现自定义的构件发布和分发流程。开发人员可以将构件上传到私服仓库,并按照企业内部的要求发布构件。
- 如果某个构件有安全问题或需要更新版本,私服仓库可以帮助管理这些更新,并确保所有团队都能快速获取到新的版本。
- 减少依赖的外部风险
- 公共仓库(如 Maven Central)可能会遇到不可用或故障的情况,这可能会导致构建失败或依赖无法下载。私服仓库提供了更高的可靠性,因为它可以由公司自己托管,并且不依赖外部服务。
- 如果使用私服仓库,即使公共仓库出现问题,构建依赖仍然可以从本地或内部的私服仓库中获取。
- 自定义构建工具和插件的管理
- 除了常见的依赖管理,私服仓库还可以用来存储公司内部开发的插件、工具和构建脚本,供所有开发人员和项目共享。这样,团队可以更好地控制和管理工具链。
- 版本兼容性和逐步迁移
- 如果你的团队正在从某个版本迁移到新版本,私服仓库可以作为迁移工具,管理不同版本的构件,并帮助团队在旧版本和新版本之间平滑过渡。