Maven详解
[TOC]
前言
Java开发中,不管是用框架还是手写项目,基本离不开代码依赖管理工具,常用的代码依赖管理工具包含Maven,Gradle,目前,个人在项目开发中使用的,接触最多的还是Maven,本篇我们详细介绍Maven的基础概念以及实战配置。
仓库介绍
在 Maven 中,仓库指的是存放代码构建的一个位置。从分类上来说,Maven 仓库有两种类型,分别是:
- 本地仓库(Local)
- 远程仓库(Remote)
本地仓库,是存在于本地的一个仓库,它用来缓存下载的依赖包。这样就不用每次都通过网络去拉取依赖包了,提高了依赖拉取速度,减轻了仓库服务器的压力。
远程仓库,如其名字所述,其实位于远程服务器的一个仓库。例如有些公司自己对外开放了一些 API,需要将这些 API 的依赖开放出去,这时候就可以将 API 的 Jar 包放到公司自己的远程仓库中。公司的客户可以通过连接该仓库下载 Jar 包。例如你们公司开发了自己的基础工具类,并将其打成了一个 Jar 包。此时你可以将该 Jar 包部署到公司自己的远程仓库中,公司其他开发伙伴配置该远程仓库,从而可以拉取到该 Jar 包依赖。
在网上其他资料中,还会提到 Maven 仓库有另一个类型 —— 中央仓库(Central)。但在我看来,中央仓库其实是一个特殊的远程仓库。 它的特殊之处在于,它是 Maven 官方提供的,其中包括了大量常用的库,基本上大多数的依赖包都可以在这里找到。另外一个特殊之处在于,中央仓库的地址是内置在 Maven 源码中的,即默认会向中央仓库拉取依赖,这个在后续的依赖搜索顺序中会讲到。
而我们经常说的私服,其实也是一个特殊的远程仓库,其特殊之处在于:它只对公司内部开放,方便存放一些本团队创建的开发库。我们经常说的阿里云 Maven 库,其实就是一个远程仓库,只不过其是对所有人开放罢了。
依赖搜索顺序
在开发过程中找不到依赖包,有多种原因,例如:
- 依赖包确实没有放到远程仓库中。
- 仓库配置错误。
- 配置的是Maven官方中央仓库,因为官方服务器在国外,所以下载起来会非常慢,直至拉取超时从而失败。
- 等等
Maven的搜索顺序如下:
- 首先,在本地仓库搜索,如果找不到则继续下一步。
- 接着,在中央仓库搜索,如果找不到则继续下一步。
- 最后,在远程仓库中搜索,如果找不到则抛出错误。如果没有设置远程仓库,那么抛出错误。如果找到了依赖,那就下载到本地仓库缓存。
简单地说,Maven 搜索遵循简单的顺序 —— 本地仓库 -> 中央仓库 -> 远程仓库。弄明白了这个依赖搜索顺序,可以帮助我们更好地排查问题。但这可能还不够,我们还需要搞清楚 Maven setting.xml 中的一些配置信息,从而可以排查是否是配置出现了问题。例如:有时候我们配置了 mirror 镜像,会把对某个仓库的请求转发到另一个仓库,这时候你要是不懂 mirror 配置,你就找不到问题所在了。
Settings配置信息
关于仓库的一些常见的配置项有:
- repositories
- mirror
- server
- 等等
repositories
<repositories>
<repository>
<!--公司镜像的唯一标识,这个配置要注意,不能与mirrorOf配置的相同,不然会被拦截,重定向到外网的镜像仓库 -->
<id>nexus</id>
<!--仓库描述,随意写 -->
<name>xxxx</name>
<!-- 公司私有仓库地址,这个很重要不能错-->
<url>http://xxx:8081/nexus/content/groups/public</url>
<!-- 是否开启 releases 包的下载及更新策略 -->
<releases>
<enabled>true</enabled>
<updatePolicy>daily</updatePolicy>
</releases>
<!-- 是否开启 snapshots 包的下载及更新策略 -->
<snapshots>
<enabled>false</enabled>
<checksumPolicy>warn</checksumPolicy>
</snapshots>
<layout>default</layout>
</repository>
</repositories>
远程仓库一般是国内镜像以及用nexus私有仓库居多。在pom.xml配置远程仓库时,顺序也是关键点,是从上往下开始查找的。
在pom.xml的repositories节点上添加远程仓库地址,下面整理了一份比较常用的国内远程仓库地址。
camunda.com中央仓库
<repository>
<id>activiti-repos2</id>
<name>Activiti Repository 2</name>
<url>https://app.camunda.com/nexus/content/groups/public</url>
</repository>
下面的是一些国外的,比如谷歌(如果你没有vp,下载会很慢)*
<repositories>
<repository>
<id>datanucleus</id>
<url>http://www.datanucleus.org/downloads/maven2/</url>
</repository>
<repository>
<id>ibiblio</id>
<url>http://mirrors.ibiblio.org/pub/mirrors/maven2/</url>
</repository>
<repository>
<id>gwt-maven</id>
<url>http://gwt-maven.googlecode.com/svn/trunk/mavenrepo/</url>
</repository>
<repository>
<id>maven2-repository.dev.java.net</id>
<name>Java.net Repository for Maven</name>
<url>http://download.java.net/maven/2/</url>
</repository>
<repository>
<id>jboss</id>
<url>http://repository.jboss.com/maven2</url>
</repository>
<repository>
<id>gilead-maven-repo</id>
<url>https://gilead.svn.sourceforge.net/svnroot/gilead/gilead/maven-repo</url>
</repository>
<repository>
<id>hibernat4gwt-repo</id>
<url>https://hibernate4gwt.svn.sourceforge.net/svnroot/hibernate4gwt/branches/jens_meiss/maven/</url>
</repository>
<repository>
<id>gilead-repo</id>
<name>Gilead Maven Repository</name>
<url>https://gilead.svn.sourceforge.net/svnroot/gilead/gilead/maven-repo</url>
</repository>
</repositories>
localRepository
==用来标识本地仓库的位置==
D:/Maven/Repository
interactiveMode
maven 是否需要和用户交互以获得输入。默认为true。
true
usePluginRegistry
maven 是否需要使用 plugin-registry.xml 文件来管理插件版本。
false
offline
用来标识是否以离线模式运营Maven。
当系统不能联网时,可以通过该配置来离线运行。
false
mirror
mirror 标签用于定义仓库镜像,其相当于一个拦截器。当 mirror 的 mirrorOf 值与 repository 的 id 相同时,repository 定义的仓库会被拦截,转而使用 mirror 中定义的仓库地址。配置范例如下:
<!--使用xx公司私有仓库替换Maven默认的中央仓库 -->
<mirrors>
<mirror>
<!--自己公司的镜像的唯一标识,在mirror标签中,其实没啥用:如xiaoyaziyun -->
<id>xiaoyaziyun</id>
<!--仓库描述,随意写 -->
<name>xx公司私有仓库地址</name>
<!--xx公司私有仓库地址,这个很重要不能错-->
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<!--`central`为Maven中央仓库的标识,替换Maven源码内默认的是中央仓库地址-->
<mirrorOf>central</mirrorOf>
</mirror>
<mirror>
<id>alimaven1</id>
<name>aliyun maven1</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>*</mirrorOf>
</mirror>
</mirrors>
如上图配置所示,Maven 会用 http://maven.aliyun.com/nexus/content/groups/public/ 这个仓库镜像替换 Maven 中央仓库,其中 central 是 Maven 中央仓库的 ID 标识。我们经常说用阿里云的 Maven 仓库可以提速,其实就是使用这种方法实现的。
没有配置mirror
配置了mirror
这里介绍下<mirrorOf>配置的各种选项
<mirrorOf>*<mirrorOf>:匹配所有远程仓库。<mirrorOf>external:*<mirrorOf>:匹配所有远程仓库,使用localhost的除外,使用file://协议的除外。也就是说,匹配所有不在本机上的远程仓库。<mirrorOf>repo1,repo2<mirrorOf>:匹配仓库repo1h和repo2,使用逗号分隔多个远程仓库。<mirrorOf>*,!repo1<mirrorOf>:匹配所有远程仓库,repo1除外,使用感叹号将仓库从匹配中排除。
需要注意的是,由于镜像仓库完全屏蔽了被镜像仓库,当镜像仓库不稳定或者停止服务的时候,Maven仍将无法访问被镜像仓库,因而将无法下载构件。
此外, maven 读取mirror 配置是 从上往下读取的,因此谨慎配置<mirrorOf>*<mirrorOf>,因为如果第一个镜像仓库配置了如此标志,那么如果该仓库即使不存在对应依赖也不会向下游查询。
阿里云仓库
<mirrors>
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
开源中国
<mirror>
<id>CN</id>
<name>OSChina Central</name>
<url>http://maven.oschina.net/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
Maven默认仓库修改
Maven的默认仓库是可以修改的。比如使用阿里云的镜像地址等。
修改步骤:
- 打开{M2_HOME}/conf/settings.xml文件,找到mirrors节点,修改如下代码:
<!-- 阿里云仓库 -->
<mirror>
<id>alimaven</id>
<mirrorOf>central</mirrorOf>
<name>aliyun maven</name>
<url>https://maven.aliyun.com/nexus/content/repositories/central/</url>
</mirror>
仓库举例:
<mirrors>
<!-- mirror
| Specifies a repository mirror site to use instead of a given repository. The repository that
| this mirror serves has an ID that matches the mirrorOf element of this mirror. IDs are used
| for inheritance and direct lookup purposes, and must be unique across the set of mirrors.
|
-->
<!--
<mirror>
<id>nexus-aliyun</id>
<mirrorOf>central</mirrorOf>
<name>Nexus aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>
-->
<!-- 天翼云仓库 -->
<!--
<mirror>
<id>cloud-sri</id>
<mirrorOf>central</mirrorOf>
<name>aliyun maven</name>
<url>http://project-repo01.ctyundao.cn/artifactory/cloud-sri-common-26599-snapshot</url>
</mirror>
-->
<!-- 阿里云仓库 -->
<mirror>
<id>alimaven</id>
<mirrorOf>central</mirrorOf>
<name>aliyun maven</name>
<url>https://maven.aliyun.com/nexus/content/repositories/central/</url>
</mirror>
<!-- 中央仓库1 -->
<!--
<mirror>
<id>repo1</id>
<mirrorOf>central</mirrorOf>
<name>Human Readable Name for this Mirror.</name>
<url>http://repo1.maven.org/maven2/</url>
</mirror>
-->
<!-- 中央仓库2 -->
<!--
<mirror>
<id>repo2</id>
<mirrorOf>central</mirrorOf>
<name>Human Readable Name for this Mirror.</name>
<url>http://repo2.maven.org/maven2/</url>
</mirror>
-->
</mirrors>
Server
大部分远程仓库无须认证就可以访问,但我们自己搭建的 Maven 仓库,处于安全方面的考虑,我们会设置访问权限。此时,我们需要在 setting.xml 文件中配置 server 标签。配置示例如下代码所示:
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
https://maven.apache.org/xsd/settings-1.0.0.xsd">
...
<!--配置服务端的一些设置。一些设置如安全证书不应该和pom.xml一起分发。这种类型的信息应该存在于构建服务器上的settings.xml文件中。 -->
<servers>
<!--服务器元素包含配置服务器时需要的信息 -->
<server>
<!--这是server的id(注意不是用户登陆的id),该id与distributionManagement中repository元素的id相匹配。 -->
<id>server001</id>
<!--鉴权用户名。鉴权用户名和鉴权密码表示服务器认证所需要的登录名和密码。 -->
<username>my_login</username>
<!--鉴权密码 。鉴权用户名和鉴权密码表示服务器认证所需要的登录名和密码。密码加密功能已被添加到2.1.0 +。详情请访问密码加密页面 -->
<password>my_password</password>
<!--鉴权时使用的私钥位置。和前两个元素类似,私钥位置和私钥密码指定了一个私钥的路径(默认是${user.home}/.ssh/id_dsa)以及如果需要的话,一个密语。将来passphrase和password元素可能会被提取到外部,但目前它们必须在settings.xml文件以纯文本的形式声明。 -->
<privateKey>${usr.home}/.ssh/id_dsa</privateKey>
<!--鉴权时使用的私钥密码。 -->
<passphrase>some_passphrase</passphrase>
<!--文件被创建时的权限。如果在部署的时候会创建一个仓库文件或者目录,这时候就可以使用权限(permission)。这两个元素合法的值是一个三位数字,其对应了unix文件系统的权限,如664,或者775。 -->
<filePermissions>664</filePermissions>
<!--目录被创建时的权限。 -->
<directoryPermissions>775</directoryPermissions>
</server>
</servers>
...
</settings>
上面的配置为 repository id 为 shuyi-tech-repo 的远程仓库配置了用户名和密码,其中用户名为 admin,密码为 admin123。这里通过 server.id 与 reposiroty.id 标签将认证信息与仓库绑定在一起,因此在配置的时候需要保持这两个信息一致,否则可能导致访问失败。
proxies
用来配置代理。
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
https://maven.apache.org/xsd/settings-1.0.0.xsd">
...
<proxies>
<!--代理元素包含配置代理时需要的信息 -->
<proxy>
<!--代理的唯一定义符,用来区分不同的代理元素。 -->
<id>myproxy</id>
<!--该代理是否是激活的那个。true则激活代理。当我们声明了一组代理,而某个时候只需要激活一个代理的时候,该元素就可以派上用处。 -->
<active>true</active>
<!--代理的协议。 协议://主机名:端口,分隔成离散的元素以方便配置。 -->
<protocol>http</protocol>
<!--代理的主机名。协议://主机名:端口,分隔成离散的元素以方便配置。 -->
<host>proxy.somewhere.com</host>
<!--代理的端口。协议://主机名:端口,分隔成离散的元素以方便配置。 -->
<port>8080</port>
<!--代理的用户名,用户名和密码表示代理服务器认证的登录名和密码。 -->
<username>proxyuser</username>
<!--代理的密码,用户名和密码表示代理服务器认证的登录名和密码。 -->
<password>somepassword</password>
<!--不该被代理的主机名列表。该列表的分隔符由代理服务器指定;例子中使用了竖线分隔符,使用逗号分隔也很常见。 -->
<nonProxyHosts>*.google.com|ibiblio.org</nonProxyHosts>
</proxy>
</proxies>
...
</settings>
profiles
根据环境参数来调整构建配置的列表。用于定义一组profile
seetings中的profile是 pom.xml 中 profile 元素的裁剪版本。
它包含了id、activation、repositories、pluginRepositories 和 properties 元素。这里的 profile 元素只包含这五个子元素是因为这里只关心构建系统这个整体(这正是 settings.xml 文件的角色定位),而非单独的项目对象模型设置。如果一个 settings.xml 中的 profile 被激活,它的值会覆盖任何其它定义在 pom.xml 中带有相同 id 的 profile。
(1) repositories
定义了一组远程仓库的列表,当该属性对应的profile被激活时,会使用该远程仓库。
<repositories>
<!--包含需要连接到远程仓库的信息 -->
<repository>
<!--远程仓库唯一标识 -->
<id>codehausSnapshots</id>
<!--远程仓库名称 -->
<name>Codehaus Snapshots</name>
<!--如何处理远程仓库里发布版本的下载 -->
<releases>
<!--true或者false表示该仓库是否为下载某种类型构件(发布版,快照版)开启。 -->
<enabled>false</enabled>
<!--该元素指定更新发生的频率。Maven会比较本地POM和远程POM的时间戳。这里的选项是:always(一直),daily(默认,每日),interval:X(这里X是以分钟为单位的时间间隔),或者never(从不)。 -->
<updatePolicy>always</updatePolicy>
<!--当Maven验证构件校验文件失败时该怎么做-ignore(忽略),fail(失败),或者warn(警告)。 -->
<checksumPolicy>warn</checksumPolicy>
</releases>
<!--如何处理远程仓库里快照版本的下载。有了releases和snapshots这两组配置,POM就可以在每个单独的仓库中,为每种类型的构件采取不同的策略。例如,可能有人会决定只为开发目的开启对快照版本下载的支持。参见repositories/repository/releases元素 -->
<snapshots>
<enabled />
<updatePolicy />
<checksumPolicy />
</snapshots>
<!--远程仓库URL,按protocol://hostname/path形式 -->
<url>http://snapshots.maven.codehaus.org/maven2</url>
<!--用于定位和排序构件的仓库布局类型-可以是default(默认)或者legacy(遗留)。Maven 2为其仓库提供了一个默认的布局;然而,Maven 1.x有一种不同的布局。我们可以使用该元素指定布局是default(默认)还是legacy(遗留)。 -->
<layout>default</layout>
</repository>
</repositories>
(2) properties
定义了一组拓展属性,当对应的profile被激活时该属性有效。
<!--
1. env.X: 在一个变量前加上"env."的前缀,会返回一个shell环境变量。例如,"env.PATH"指代了$path环境变量(在Windows上是%PATH%)。
2. project.x:指代了POM中对应的元素值。例如: <project><version>1.0</version></project>通过${project.version}获得version的值。
3. settings.x: 指代了settings.xml中对应元素的值。例如:<settings><offline>false</offline></settings>通过 ${settings.offline}获得offline的值。
4. java System Properties: 所有可通过java.lang.System.getProperties()访问的属性都能在POM中使用该形式访问,例如 ${java.home}。
5. x: 在<properties/>元素中,或者外部文件中设置,以${someVar}的形式使用。
-->
<properties>
<user.install>${user.home}/our-project</user.install>
</properties>
(3) id
全局唯一标识,如果一个 settings.xml 中的 profile 被激活,它的值会覆盖任何其它定义在 pom.xml 中带有相同 id 的 profile。
(4)pluginRepositories
同repositories差不多,不过该标签定义的是插件的远程仓库。
(5) activation
触发激活该profile的条件。
<activation>
<!--profile默认是否激活的标识 -->
<activeByDefault>false</activeByDefault>
<!--当匹配的jdk被检测到,profile被激活。例如,1.4激活JDK1.4,1.4.0_2,而!1.4激活所有版本不是以1.4开头的JDK。 -->
<jdk>1.5</jdk>
<!--当匹配的操作系统属性被检测到,profile被激活。os元素可以定义一些操作系统相关的属性。 -->
<os>
<!--激活profile的操作系统的名字 -->
<name>Windows XP</name>
<!--激活profile的操作系统所属家族(如 'windows') -->
<family>Windows</family>
<!--激活profile的操作系统体系结构 -->
<arch>x86</arch>
<!--激活profile的操作系统版本 -->
<version>5.1.2600</version>
</os>
<!--如果Maven检测到某一个属性(其值可以在POM中通过${name}引用),其拥有对应的name = 值,Profile就会被激活。如果值字段是空的,那么存在属性名称字段就会激活profile,否则按区分大小写方式匹配属性值字段 -->
<property>
<!--激活profile的属性的名称 -->
<name>mavenVersion</name>
<!--激活profile的属性的值 -->
<value>2.0.3</value>
</property>
<!--提供一个文件名,通过检测该文件的存在或不存在来激活profile。missing检查文件是否存在,如果不存在则激活profile。另一方面,exists则会检查文件是否存在,如果存在则激活profile。 -->
<file>
<!--如果指定的文件存在,则激活profile。 -->
<exists>${basedir}/file2.properties</exists>
<!--如果指定的文件不存在,则激活profile。 -->
<missing>${basedir}/file1.properties</missing>
</file>
</activation>
ActiveProfiles
在运行时手工激活的profile。
该元素包含了一组 activeProfile 元素,每个 activeProfile 都含有一个 profile id。任何在 activeProfile 中定义的 profile id,不论环境设置如何,其对应的 profile 都会被激活。如果没有匹配的 profile,则什么都不会发生。
<activeProfiles>
<!-- 要激活的profile id -->
<activeProfile>env-test</activeProfile>
</activeProfiles>
激活profile的三种方式
上面有提到了两种激活的profile的方式,还有一种可以通过命令行激活profile。
(1)通过ActiveProfiles激活
如题1.2.9 可以同时激活多个profile。
(2)通过activation结果
如题1.2.8 (5)
(3)通过命令行的方式激活
也是我们经常使用的方式,例如:
mvn -P
我们可以通过在pom.xml或setting.xml中指定不同环境的profile,在编译构建不同的项目时,通过上述的命令行方式激活对应的profIle。例如在开发环境下:
mvn package -P dev
可以打包开发环境下的项目。
pom.xml的优先级
如果一个配置同时存在于多个位置,那么到底以哪个为准呢?简单的说,这几个配置文件的优先级是怎么样的呢?
其实三者的优先级是pom.xml->/用户/.m2/settings.xml->/maven安装目录/conf/settings.xml。如果要设置全局Maven仓库配置,需要在Maven安装目录/conf下找到settings.xml来修改。
如果这些文件同时存在,在应用配置时,会合并它们的内容,如果有重复的配置,优先级高的配置会覆盖优先级低的。
Pom.xml详解
上章中setting.xml定义了某个环境下全局项目的相关依赖配置,而pom.xml则具体定义了某一个项目中的依赖配置。
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">
<!-- 模型版本 一般不用更改 -->
<modelVersion>4.0.0</modelVersion>
<!-- 公司或者组织的唯一标志,也是打包成jar包路径的依据 -->
<!-- 例如com.companyname.project-group,maven打包jar包的路径:/com/companyname/project-group -->
<groupId>com.companyname.project-group</groupId>
<!-- 项目的唯一ID,一个groupId下面可能多个项目,就是靠artifactId来区分的 -->
<artifactId>project</artifactId>
<!-- 项目当前版本,格式为:主版本.次版本.增量版本-限定版本号 -->
<version>1.0</version>
<!--项目产生的构件类型,
jar、war主要用来标识项目打包出的服务是jar包还是war包
pom一般用作多moudle的项目中 顶层的pom用来指定子moudle中需要依赖的版本 详见2.1.3 -->
<packaging>jar</packaging>
<!--定义了本项目的名称与example的网址 -->
<name>itoken dependencies</name>
<url>www.funtl.com</url>
</project>
基本信息都比较易懂,主要定义了本项目的相关配置说明,例如唯一坐标、版本、项目介绍等。
dependencies
本元素定义了项目中所需要的相关依赖信息,也是最重要的元素之一。
<!--该元素描述了项目相关的所有依赖。 这些依赖自动从项目定义的仓库中下载 -->
<dependencies>
<dependency>
<!------------------- 依赖坐标 ------------------->
<!--依赖项目的坐标三元素:groupId + artifactId + version -->
<!--其中三要素的来源就是2.1.1中别人定义好的相关信息 -->
<groupId>org.apache.maven</groupId>
<artifactId>maven-artifact</artifactId>
<version>3.8.1</version>
<!------------------- 依赖传递 ------------------->
<!--依赖排除,即告诉maven只依赖指定的项目,不依赖该项目的这些依赖。此元素主要用于解决版本冲突问题 -->
<exclusions>
<exclusion>
<artifactId>spring-core</artifactId>
<groupId>org.springframework</groupId>
</exclusion>
</exclusions>
<!-- 可选依赖,用于阻断依赖的传递性。如果在项目B中把C依赖声明为可选,那么依赖B的项目中无法使用C依赖 -->
<optional>true</optional>
<!------------------- 依赖范围 ------------------->
<!--依赖范围。在项目发布过程中,帮助决定哪些构件被包括进来
- compile:默认范围,适用于所有阶段,会随着项目一起发布;
- runtime: 在执行时需要使用,如JDBC驱动,适用运行和测试阶段,不同于例如fastjson,需要在编译时使用;
- test: 只在测试时使用,用于编译和运行测试代码,例如junit,不同于junit,在发布时并不需要;
- optional: 当项目自身被依赖时,标注依赖是否传递。用于连续依赖时使用 -->
<scope>test</scope>
</dependency>
</dependencies>
解决依赖传递问题或jar包冲突问题
解决上述问题一般有两种方式:
- 当我们作为依赖服务提供者时,可以通过
<optional>标签排除掉不想被传递的服务。
<!-- Project A -->
<project>
...
<dependencies>
<!-- declare the dependency to be set as optional -->
<dependency>
<groupId>sample.ProjectB</groupId>
<artifactId>Project-B</artifactId>
<version>1.0</version>
<optional>true</optional>
</dependency>
</dependencies>
</project>
我们的A服务中引用了外部的依赖B服务,此时有A -> B,在别人引用我们时有C -> A ->B,若此时我们A在提供对外服务时不想让别人依赖B服务,可以在引用B时添加<optional>标签为true,这样带C依赖于A时并不会引入B。如果C在此时想要使用B的相关服务,需要在C的pom中显示的调用B的相关服务。
- 当我们作为依赖服务使用者时,可以通过
<exclusions>来去除掉我们依赖包中所不想依赖的其他服务。
如果此时我们的A服务依赖于B服务,B服务依赖于C服务,则有A ->B ->C,因为某种原因例如jar包冲突,我们在A中并不想依赖于C服务的版本,可以在调用B服务时去除掉C的相关依赖,然后自己再在A中使用C的相关版本。
<project>
...
<dependencies>
<dependency>
<groupId>sample.ProjectB</groupId>
<artifactId>Project-B</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<!-- 排除掉B中C的相关依赖 -->
<groupId>sample.ProjectC</groupId>
<artifactId>Project-C</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 自己引用C的相关版本 -->
<dependency>
<groupId>sample.ProjectC</groupId>
<artifactId>Project-C</artifactId>
<version>2.0</version>
</dependency>
</dependencies>
</project>
dependencyManagement
当一个服务中存在有多个moudle时,每个子module都可能引用了相同的jar包,但是如果将版本维护到子module的pom中,当需要升级时需要修改所有的pom文件版本。maven提供了继承的方式来解决此问题。
<!--在父pom中定义子pom需要的相关依赖 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
</dependencyManagement>
在父pom的 <dependencyManagement> 中定义子pom需要的依赖及版本
<!--在子pom中 如下定义了父pom中相关依赖信息 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.10.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<!--因为引用了父pom 所以可以不指定版本 maven会自动去父pom中查找指定版本 此处为1.0.0 -->
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
</dependencies>
当我们的pom中有定义父pom的元素后,可以在指定需要的依赖时不指定其版本,maven会帮我们去父pom中查找相关的版本信息。
properties
properties主要用来定义常量,通过${value}来使用。
<!--配置依赖版本-->
<properties>
<!-- Environment Settings -->
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- Spring cloud Settings -->
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
<spring-boot-admin.version>2.0.1</spring-boot-admin.version>
<zipkin.version>2.10.1</zipkin.version>
</properties>
<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>
<!--zipkin-->
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin</artifactId>
<version>${zipkin.version}</version>
</dependency>
<dependencies>
此外,maven还通过约定大于配置的方式定义了一些常用的属性。
| 属性 | 定义 |
|---|---|
| ${basedir} | 存放pom.xml和所有的子目录 |
| ${basedir}/src/main/java | 项目的java源代码 |
| ${basedir}/src/main/resources | 项目的资源,比如说property文件,springmvc.xml |
| ${basedir}/src/main/webapp/WEB-INF | web应用文件目录,web项目的信息,比如存放web.xml、本地图片、jsp视图页面 |
| ${basedir}/target | 打包输出目录 |
| ${project.version} | 项目版本 |
| ${project.groupId} | 项目groupid |
resource
resources标签用来标识项目在编译运行时需要额外编译的文件。例如手工引入jar包、不同运行环境对应不同的profile。
<build>
<resources>
<!--首先将默认resources目录下的所有文件包括 -->
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<!--只编译所有以.fxml结尾的文件 -->
<includes>
<include>**/*.fxml</include>
</includes>
<!--排除掉所有的yaml文件 -->
<excludes>
<exclude>**/*.yaml</exclude>
</excludes>
</resource>
<!--将不同环境下对应的不同yaml或properties文件编译运行 -->
<resource>
<!--
<directory>src/main/profiles/dev</directory>
<directory>src/main/profiles/beta<directory>
<directory>src/main/profiles/pre</directory>
-->
<directory>src/main/profiles/product</directory>
<filtering>true</filtering>
<includes>
<include>**/*.fxml</include>
</includes>
</resource>
<!--将手工引入的jar包编译运行 -->
<resource>
<directory>lib</directory>
<targetPath>BOOT-INF/lib/</targetPath>
<includes>
<include>**/*.jar</include>
</includes>
</resource>
</resources>
</build>
profile
与setting.xml中profile所不同的是(参照1.2.8),pom中的profile有着更多的标签来描述一组环境。从作用上来区分的话,一般setting.xml用来标识不同的远程仓库,而pom中的profile一般用来标识当前项目属于什么环境,如下是一组常见的pom中的profiles。
<profiles>
<profile>
<id>dev</id>
<!--激活条件 其中默认为该profile 更多激活条件可以参考1.2.8 -->
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<!--当此profile被激活时,会将 project.active 的属性赋值为dev -->
<properties>
<project.active>dev</project.active>
</properties>
</profile>
<profile>
<id>test</id>
<!--当此profile被激活时,会将 project.active 的属性赋值为test -->
<properties>
<project.active>test</project.active>
</properties>
</profile>
</profiles>
<resources>
<resource>
<!--根据不同的环境 project.active 取得不同的值 从而会将不同的环境下的yaml或properties文件编译进项目中 达到只需要在编译时指定环境变量即可 不用每次都要修改配置文件 -->
<directory>src/main/${project.active}</directory>
<filtering>true</filtering>
<includes>
<include>**/*.fxml</include>
</includes>
</resource>
</resources>
此外,一般通过 mvn -P dev/beta/pre/product 命令来激活不同的环境,也可以通过其他的方式来激活profile,详见1.2.10。
当然,pom中的profile不止有指定编译环境的功能,同样也可以指定远程仓库等其他功能。
modules
当我们项目中有多个模块时,如果我们要单独打包的话需要在每一个模块都执行对应的maven命令,但是通过<modules>标签可以将子服务或平级服务进行聚合,只需要打包该服务,也就会将其对应的子模块同时打包。
<modules>
<!-- 引入子模块所在的相对目录 -->
<module>springmybatis</module>
<!-- 引入同级模块所在的相对目录 -->
<module>../utils</module>
</modules>
null:jrdp-common:null:jar问题解决
包找不到问题
我们某次在开发功能的时候,在我们的项目中引用了伏羲另外一个系统的jar包,但是预发环境下编译的时候却发现构建失败,原因是
因为当时项目有用到京东自己的仓库,所以我们当时第一反应是去仓库中查找,结果发现仓库中是有这个jar包的。
在发现并不是最常见的jar包不存在的问题后,我们开始分析报错原因,发现报错的 jrdp-common这个包并不是我们直接引用的,而是在我们引用的jar包中引用的,并且 null:jrdp-common:null:jar可以推测前面应该是 groupID与 version。
假设我们的项目是A项目,引用的项目是B项目,也就是 A->B->jrdp-common
于是我们打开B项目,查看B的pom结构。
发现B项目的pom中确实引用了jrdp-common这个包,但是并没有指定版本,是因为继承了 xx-parent这个包,我们在这个包中确实也找到了指定的版本号,因此就排除了项目中没有指定groupid与版本号的问题。
这个时候好像就问题就陷入了死路,但是我们注意到,我们之前另一个私服上也是有这个包的,那么之前的在另一个私服上引用好像是没有问题的,我们查看了私服了上的pom文件,发现也是跟项目一样的。
然后我们就突然想到,会不会是仓库中的包有问题,果不其然