文章目录
- 一、mvn依赖的特性
- 1. 依赖的范围
- 2. 依赖的传递
- 3. 依赖的排除
- 二、mvn中的继承和聚合
- 1. 聚合
- 2. 继承
- 3. Demo
- 1、首先创建一个父工程并且修改它的打包方式为 `pom`
- 2、创建子模块工程
- 3、依赖管理
- 三、企业级知识扩展
- 1. 属性
- 2. 版本管理
- 3. 资源配置
- 4. 多环境开发配置
Maven工程约定目录结构
目前开发领域的技术发展趋势就是 “约定大于配置,配置大于编码”。
Maven 为了让构建过程能尽可能自动化完成,必须约定项目的目录结构。比如说:Maven 执行编译操作,必须先去 Java源程序目录读取 Java源代码,然后执行编译,最后把编译结果存放在 target 目录中。
Maven对于目录结构这个问题,没有采用配置的方式,而是基于约定。这样会让我们在开发过程中非常方便。如果每次创建 Maven 工程后,还需要针对各个目录的位置进行详细的配置,那肯定非常麻烦。
各个目录的作用如图:
另外还有一个 target 目录专门存放构建操作输出的结果。
一、mvn依赖的特性
1. 依赖的范围
maven依赖范围就是用来控制依赖与三种classpath(编译,测试,运行)的关系,我们可以在dependencies/dependency标签中通过 scope
标签指定依赖的范围,默认为compile。
如:
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
<scope>compile</scope>
</dependency>
</dependencies>
maven有以下几种依赖范围:
-
compile:编译依赖范围。如果没有指定,就会默认使用该依赖范围。使用此依赖范围的maven依赖,对于编译,测试,运行三种classpath都有效。典型的例子是spring-core,在编译,测试,运行的时候都需要使用该依赖。
-
test:测试依赖范围。使用此依赖范围的maven依赖,只对于测试classpath有效,在编译主代码或运行项目的时候无法使用此类依赖。典型的例子就是junit,它只有在编译测试代码及运行测试的时候才需要。
-
provided:已提供依赖范围。使用此依赖范围的maven依赖对于编译和测试classpath有效,在运行时无效。在开发过程中需要用到的“服务器上的 jar 包”通常以 provided 范围依赖进来。比如 servlet-api、jsp-api。而这个范围的 jar 包之所以不参与部署、不放进 war 包,就是避免和服务器上已有的同类 jar 包产生冲突,同时减轻服务器的负担。说白了就是:“服务器上已经有了,你就别带啦!”
-
runtime:运行时依赖范围。使用此依赖范围的maven依赖对于测试和运行classpath有效,但在编译主代码时无效。典型的例子是JDBC的驱动实现,项目主代码的编译只需要JDK提供的JDBC接口,只有在执行测试或者运行项目的时候才需要实现上述接口的具体JDBC驱动。
-
system:系统依赖范围。该范围与三种classpath的关系和provider依赖范围完全一致。但是使用system范围的依赖时必须通过systemPath元素显示地指定依赖文件的路径。由于此依赖不是通过maven仓库解析而且与本机系统绑定,可能造成构建的不可移植因此应该谨慎使用。systemPath元素可以引用环境变量。如:
<dependency>
<groupId>dm.jdbc</groupId>
<artifactId>DmDialect</artifactId>
<version>5.3</version>
<scope>system</scope>
<systemPath>${pom.basedir}/src/main/resources/lib/DmDialect-for-hibernate5.3.jar</systemPath>
</dependency>
- import: 导入依赖范围。该依赖范围不会对三种classpath产生实际的影响
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
依赖范围与classpath的关系如下:
依赖范围(scope) | main目录(对于编译classpath有效) | test目录(对于测试classpath有效) | 部署到服务器(对于运行时classpath有效) |
---|---|---|---|
compile | 有效 | 有效 | 有效 |
test | 无效 | 有效 | 无效 |
provided | 有效 | 有效 | 无效 |
runtime | 无效 | 有效 | 有效 |
2. 依赖的传递
maven引入的传递性依赖机制,一方面大大简化和方便了依赖声明,另一方面,大部分情况下我们只需要关心项目的直接依赖是什么,而不用考虑这些直接依赖会引入什么传递性依赖。但有时候,当传递性依赖造成问题的时候,我们就需要清楚地知道该传递性依赖是从哪条依赖路径引入的。
在 A 依赖 B,B 依赖 C 的前提下,C 是否能够传递到 A,取决于 B 依赖 C 时使用的依赖范围。
- B 依赖 C 时使用 compile 范围:可以传递
- B 依赖 C 时使用 test 或 provided 范围:不能传递,所以需要这样的 jar 包时,就必须在需要的地方明确配置依赖才可以。
例如,项目A有这样的依赖关系 : A–>B–>C–>X(1.0)、A–>D–>X(2.0),X是A的传递性依赖,但是两条依赖路径上有两个版 本的X,那么哪个X会被maven解析使用呢?两个版本都被解析显然是不对的,因为那会造成依赖重复,因此必须选择一个。maven依赖调解的第一原则:路径最近者优先。该例中X(1.0)的路径长度为3,而X(2.0)的路径长度为2,因此X(2.0)会被解析使用。
依赖调解第一原则不能解决所有问题,比如这样的依赖关系:A–>B–>Y(1.0),A–>C–>Y(2.0),Y(1.0)和Y(2.0)的依赖路径长度是一样的,都为2。那么到底谁会被解析使用呢?在maven2.0.8及之前的版本中,这是不确定的,但是maven2.0.9开始,为了尽可能避免构建的不确定性,maven定义了依赖调解的第二原则:第一声明者优先。在依赖路径长度相等的前提下,在POM中依赖声明的顺序决定了谁会被解析使用。顺序最靠前的那个依赖优胜。
3. 依赖的排除
当 A 依赖 B,B 依赖 C 而且 C 可以传递到 A 的时候,A 不想要 C,需要在 A 里面把 C 排除掉。而往往这种情况都是为了避免 jar 包之间的冲突。
所以配置依赖的排除其实就是阻止某些 jar 包的传递。因为这样的 jar 包传递过来会和其他 jar 包冲突。
二、mvn中的继承和聚合
1. 聚合
使用一个“总工程”将各个“模块工程”汇集起来,作为一个整体对应完整的项目。
- 项目:整体
- 模块:部分
聚合可以带来哪些好处呢?
-
一键执行 Maven 命令:很多构建命令都可以在“总工程”中一键执行。
以 mvn install 命令为例:Maven 要求有父工程时先安装父工程;有依赖的工程时,先安装被依赖的工程。我们自己考虑这些规则会很麻烦。但是工程聚合之后,在总工程执行 mvn install 可以一键完成安装,而且会自动按照正确的顺序执行。
-
配置聚合之后,各个模块工程会在总工程中展示一个列表,让项目中的各个模块一目了然。
实现
- 创建一个父模块,设置打包方式为
pom
,定义该工程用于进行构建管理
<packaging>pom</packaging>
- 配置 modules ,定义当前模块进行构建操作时关联的其他模块名称
<modules>
<module>pro01-mvn-module01</module>
<module>pro01-mvn-module02</module>
<module>pro01-mvn-module03</module>
</modules>
循环依赖问题,如果 A 工程依赖 B 工程,B 工程依赖 C 工程,C 工程又反过来依赖 A 工程,那么在执行构建操作时会报下面的错误:
[ERROR] [ERROR] The projects in the reactor contain a cyclic reference:
注意:参与聚合操作的模块最终执行顺序与模块间的依赖关系有关,与配置顺序无关
2. 继承
多个子模块依赖的版本不一致可能会导致冲突,如何进行统一的依赖管理呢?
一个Maven项目可以继承自另一个Maven项目,比如多个子项目都需要父项目的依赖,就可以使用继承关系来快速配置。子项目直接继承父项目的groupId
和所有依赖,还可以让父Maven项目统一管理所有的依赖,包括版本号等,子项目可以选取需要的作为依赖,而版本全由父项目管理,我们可以将dependencies
全部放入dependencyManagement
节点,这样父项目就完全作为依赖统一管理。
在父工程中统一管理项目中的依赖信息,具体来说是管理依赖信息的版本。
它的背景是:
- 对一个比较大型的项目进行了模块拆分。
- 一个 project 下面,创建了很多个 module。
- 每一个 module 都需要配置自己的依赖信息。
它背后的需求是:
- 在每一个 module 中各自维护各自的依赖信息很容易发生出入,不易统一管理。
- 使用同一个框架内的不同 jar 包,它们应该是同一个版本,所以整个项目中使用的框架版本需要统一。
- 使用框架时所需要的 jar 包组合(或者说依赖信息组合)需要经过长期摸索和反复调试,最终确定一个可用组合。这个耗费很大精力总结出来的方案不应该在新的项目中重新摸索。
通过在父工程中为整个项目维护依赖信息的组合既保证了整个项目使用规范、准确的 jar 包;又能够将以往的经验沉淀下来,节约时间和精力
实现
首先,在子模块的pom.xml中通过parent
标签指定当前工程的父工程
<!-- 使用parent标签指定当前工程的父工程 -->
<parent>
<!-- 父工程的坐标 -->
<artifactId>pro01-mvn-parent</artifactId>
<groupId>com.hgw.mvn</groupId>
<version>1.0-SNAPSHOT</version>
<!-- 父工程的pom文件 -->
<relativePath>../pom.xml</relativePath>
</parent>
其次,在父工程中定义依赖管理
<!-- 使用dependencyManagement标签声明此处进行依赖管理 -->
<!-- 被管理的依赖并没有真正被引入到工程 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${springboot.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
在子工程中定义依赖关系,无需声明依赖版本,版本参照父工程中依赖的版本
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
继承的资源
groupId
:项目组ID,项目坐标的核心元素version
:项目版本,项目坐标的核心因素description
:项目的表述信息organization
:项目的组织信息inceptionYear
:项目的创始年份url
:项目的URL地址developers
:项目的开发者信息contributors
: 项目的贡献者信息distributionManagement
:项目的部署信息issueManagement
:项目的缺陷跟踪系统信息
3. Demo
1、首先创建一个父工程并且修改它的打包方式为 pom
<groupId>com.hgw.mvn</groupId>
<artifactId>pro01-mvn-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 当前工程作为父工程,它要去管理子工程,所以打包方式必须是 pom -->
<packaging>pom</packaging>
只有打包方式为 pom 的 Maven 工程能够管理其他 Maven 工程。打包方式为 pom 的 Maven 工程中不写业务代码,它是专门管理其他 Maven 工程的工程。
2、创建子模块工程
- 进入 pro01-mvn-parent 工程的根目录,创建模块
- parent指定父项目,创建三个子模块分别用来做controller、service、dao
标签解读
查看父工程的pom.xml,下面 <modules>
和 <module>
标签是聚合功能的配置
解读子工程的pom.xml
依赖统一管理
- 在父工程中配置依赖的统一管理
- 子工程中引入那些被父工程管理的依赖
3、依赖管理
- 首先在父工程中配置依赖的统一管理:
- 在pro01-mvn-module03模块(模拟dao),在其模块引入springboot等相关配置并编写伪代码。
- 在pro01-mvn-module02模块(模拟service),在其模块中引入 pro01-mvn-module03
- 在pro01-mvn-module01模块(模拟controller),在其模块中引入 pro01-mvn-module02
大家发现pro01-mvn-module01模块依赖于pro01-mvn-module02模块,pro01-mvn-module02模块又依赖于 pro01-mvn-module03。
三、企业级知识扩展
1. 属性
在Maven中,属性(Properties)是一种常量和变量的定义方式,可以在POM文件中定义和使用,用于简化配置和管理Maven项目。属性可以在POM文件中定义,也可以在命令行中定义,还可以从其他文件中引入。
在Maven中,属性(Properties)可以分为以下几种类型:
-
内置属性(Built-in Properties):这些属性是Maven自带的,可以在POM文件中直接使用,如
${project.version}
、${project.groupId}
、${basedir}
等。 -
自定义属性(Custom Properties):这些属性是由用户在POM文件中定义的,可以用于简化Maven项目的配置和管理。自定义属性可以通过在
<properties>
元素中定义,如下所示:<project> ... <properties> <my.property>value</my.property> </properties> ... </project>
在下述代码中,
<my.property>
是自定义属性的名称,value
是属性的值。在POM文件中,自定义属性可以使用${propertyName}
的形式进行引用。 -
POM属性(POM properties):也称为内置属性或预定义属性,是Maven中预定义的属性,可以在POM文件中直接使用,如
${project.version}
、${project.groupId}
、${basedir}
等。 -
Settings属性,使用Maven配置文件setting.xml中的标签属性,用于动态配置。例如:
${settings.localRepository}
,获取settings.xml文件中配置的指向本地仓库的地址属性值。 -
Java系统属性,所有Java系统属性都可以使用Maven属性引用,例如
${user.home}
指向了用户目录。可以通过命令行mvn help:system
查看所有的Java系统属性。 -
环境变量属性,使用Maven配置文件
setting.xml
中标签属性,用于动态配置。所有环境变量都可以使用以env.
开头的Maven属性引用。例如${env.JAVA_HOME}
指代了JAVA_HOME环境变量的值。也可以通过命令行mvn help:system查看所有环境变量。
自定义属性配置与使用
在POM文件中,属性可以通过在<properties>
元素中定义,如下所示:
<project>
...
<properties>
<my.property>value</my.property>
</properties>
...
</project>
在上述代码中,<my.property>
是属性的名称,value
是属性的值。在POM文件中,属性可以使用${propertyName}
的形式进行引用。例如:
<project>
...
<properties>
<my.property>value</my.property>
</properties>
...
<build>
<plugins>
<plugin>
<groupId>my.groupId</groupId>
<artifactId>my.artifactId</artifactId>
<version>${my.property}</version>
</plugin>
</plugins>
</build>
</project>
在上述代码中,${my.property}
是属性的引用,该属性的值是value
。在这种情况下,Maven将${my.property}
替换为value
,并将其用作<version>
元素的值。
除了在POM文件中定义属性,还可以在命令行中通过-D
选项定义属性,例如:
mvn clean install -Dmy.property=value
在上述命令中,-Dmy.property=value
定义了一个名为my.property
的属性,其值为value
。
总之,属性是Maven中一种用于简化配置和管理Maven项目的常量和变量的定义方式,可以在POM文件中定义和使用,也可以在命令行中定义,还可以从其他文件中引入。
2. 版本管理
在Maven中,版本管理是指如何管理项目的版本号及其相关依赖和构建过程。版本管理是Maven项目的重要组成部分,它可以帮助开发者更好地维护、更新和发布项目。
- SNAPSHOT(快照版本)
- 项目开发过程中,为方便团队合作,解决模块间互相依赖和实时更新的问题,开发者对每个模块进行构建的时候,输出的临时性版本叫快照版本(测试阶段版本)
- 快照版本会随着开发的进展不断更新
- RELEASE(发布版本)
- 项目开发进入阶段里程碑后,向团队外部发布较为稳定的版本,这种版本所对应的构建文件是稳定的,即便进行功能的后续开发,也不会改变当前发布版本内容,这种版本称为发布版本。
3. 资源配置
概述
在 Maven 项目中,resources
元素用于指定项目中的资源文件,并定义在项目的 pom.xml
文件中。resources
元素中包含了一个或多个 resource
元素,每个 resource
元素定义了一个资源文件的目录、文件名、文件过滤等信息。
下面是 resources
元素的基本结构:
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
<excludes>
<exclude>**/*.txt</exclude>
</excludes>
<filtering>true</filtering>
</resource>
</resources>
</build>
其中,resource
元素包含了以下子元素:
directory
:指定资源文件所在的目录。includes
:指定需要包含的资源文件,可以使用通配符来匹配多个文件。excludes
:指定需要排除的资源文件,同样可以使用通配符来匹配多个文件。filtering
:指定是否对资源文件进行过滤,即将文件中的占位符替换为实际的值。默认值为false
。
Demo
在 pom.xml 文件中,resources 元素通常用于指定项目中需要打包的资源文件,例如配置文件、图片、HTML 等静态资源。
除了指定需要打包的资源文件,resources 元素还可以用来指定一些需要在编译期间处理的资源文件,例如模板文件、代码生成器等。
例如,在上面的示例中,resources
元素包含了一个 resource
元素,它指定了 src/main/resources
目录下所有的 .xml
和 .properties
文件都是需要包含的资源文件,但排除了所有 .txt
文件。同时,对这些资源文件进行了过滤,即将文件中的占位符替换为实际的值。
resources
元素的作用是将项目中的资源文件打包到最终的构建结果中,例如 JAR 或 WAR 文件。在 Maven 的构建过程中,resources
元素会将指定的资源文件复制到构建目录中,然后打包到最终的构建结果中。这样,我们就可以在运行时从构建结果中获取到这些资源文件。
总之,resources
元素是 Maven 中非常重要的一个元素,它可以帮助我们管理项目中的资源文件,并将这些资源文件打包到最终的构建结果中。
4. 多环境开发配置
在 Maven 项目中,profiles
元素用于配置不同的构建环境,例如开发环境、测试环境、生产环境等等。通过使用 profiles
元素,我们可以在同一个 pom.xml
文件中定义多个不同的构建配置,然后根据需要选择特定的配置进行构建。
profiles
元素的基本结构如下:
<project>
...
<profiles>
<profile>
<id>development</id>
...
</profile>
<profile>
<id>production</id>
...
</profile>
</profiles>
...
</project>
其中,每个 profile
元素包含了一个 id
元素和其他需要配置的元素。id
元素用于唯一标识一个 profile,其他的元素用于配置该 profile 下的属性和插件。当我们在一个 Maven 项目中使用 profiles
元素时,通常需要考虑以下几个方面:
1、定义 profiles
在 profiles
元素中,我们可以定义多个不同的 profile,每个 profile 都有一个唯一的 id
标识符。在每个 profile 中,我们可以定义一些属性、插件和依赖项等。例如,下面是一个典型的 profiles
元素定义:
<profiles>
<!--定义具体的环境,如 集成环境-->
<profile>
<!--定义环境对应的唯一名称-->
<id>development</id>
<!--定义环境中专用的属性值-->
<properties>
<db.url>jdbc:mysql://localhost:3306/mydb_dev</db.url>
<db.username>dev_user</db.username>
<db.password>dev_password</db.password>
</properties>
<!--定义环境中专用的插件-->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>production</id>
<properties>
<db.url>jdbc:mysql://localhost:3306/mydb_prod</db.url>
<db.username>prod_user</db.username>
<db.password>prod_password</db.password>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
在上面的例子中,我们定义了两个 profile,分别是 development
和 production
。每个 profile 都包含了一些属性和插件配置,用于指定不同的构建环境。
2、激活 profiles
默认情况下,Maven 不会激活任何 profile,因此需要手动激活。我们可以通过在命令行中指定 -P
参数来激活一个或多个 profile。例如,下面的命令将激活 development
profile:
mvn clean install -P development
在上面的例子中,我们使用 -P
参数指定要激活的 profile,这里是 development
。我们还可以同时激活多个 profile,例如:
mvn clean install -P development,testing
这将同时激活 development
和 testing
两个 profile
除了在命令行中指定 -P
参数之外,还可以通过其他方式来激活 profile。例如,我们可以在 settings.xml
文件中定义 activeProfiles
元素,用于指定默认激活的 profile。
<settings>
<activeProfiles>
<activeProfile>development</activeProfile>
</activeProfiles>
</settings>
在上面的例子中,我们将 development
profile 设置为默认激活的 profile。
在构建过程中,Maven 会自动加载 development
profile,并使用其中定义的属性和插件配置。例如,当我们使用 ${db.url}
这个属性时,Maven 会将其替换为 jdbc:mysql://localhost:3306/mydb_dev
,这是 development
profile 中定义的值。
3、使用 profiles 中的配置
一旦激活了一个或多个 profile,就可以使用其中定义的配置。例如,在上面的例子中,development
profile 定义了一个名为 db.url
的属性,用于指定数据库连接 URL。我们可以在项目的 pom.xml
文件中使用这个属性:
<project>
...
<build>
<plugins>
<plugin>
<groupId>com.example</groupId>
<artifactId>my-plugin</artifactId>
<version>1.0</version>
<configuration>
<url>${db.url}</url>
<username>${db.username}</username>
<password>${db.password}</password>
</configuration>
</plugin>
</plugins>
</build>
...
</project>
在项目的 pom.xml
文件中,我们可以使用 ${}
语法来引用定义在 profiles
元素中的属性。例如,当我们在 my-plugin
插件中使用 ${db.url}
时,它会被替换为当前激活的 profile 中定义的值。这样,我们就可以在不同的环境中使用不同的数据库连接信息来构建项目,而不需要手动修改配置文件。