前言
当我们在开发项目时,有时需要用到外部依赖组件,例如当我们需要Json序列化的时候需要用到FastJson组件,我们可以通过下载对应jar包加载到项目中。但当一个大的项目同时需要依赖各种各样的外部服务,就存在着配置繁琐、依赖冲突等问题,因此可以通过maven来完成对应的依赖管理功能。
一、Settings配置
settings.xml用来配置maven项目中的各种参数文件,包括本地仓库、远程仓库、私服、认证等信息。
1.1 配置概述
1.1.1 全局settings、用户setting、pom的区别
- 全局 settings.xml 是 maven 的全局配置文件,一般位于${maven.home}/conf/settings.xml,即maven文件夹下的conf中。
- 用户 setting是maven的用户配置文件,一般位于${user.home}/.m2/settings.xml,即每位用户都有一份配置文件。
- pom.xml 文件是项目配置文件,一般位于项目根目录下或子目录下。
配置优先级从高到低:pom.xml > 本地 settings > 全局 settings
如果这些文件同时存在,在应用配置时,会合并它们的内容,如果有重复的配置,优先级高的配置会覆盖优先级低的。
1.1.2 仓库【重要】
如前言所述,我们依赖的外部服务是需要有地方进行存储的,而存储的地方就称之为仓库。其中仓库又分为本地仓库、中央仓库、镜像仓库、私服。
其对应关系为:
(1)本地仓库
当项目在本地编译或运行时,直接加载本地的依赖服务无疑是最快的。默认情况下,不管在Window还是Linux下,每个用户在自己用户目录下都有一个路径名为.m2/repository/的仓库目录。
而原始的本地仓库是为空的,因此maven需要知道一个网络上的仓库,在本地仓库不存在时前往下载网络上的仓库,也就是远程仓库。
(2)中央仓库
当maven未配置时,会默认请求maven的中央仓库,中央仓库包含了这个世界上绝大多数流行的开源java构件,以及源码、作者信息、SCM,信息、许可证信息等,每个月这里都会接受全世界java程序员大概1亿次的访问,它对全世界java开发者的贡献由此可见一斑。
但是由于最常见的例如网络原因等,国外的中央仓库使用起来并不顺利,因此就有了下载地址为国内的中央仓库,也就是镜像仓库。
(3)镜像仓库
总结来说,镜像仓库就是将国外的中心仓库复制一份到国内,这样一来下载速度以及访问速度都将很快。
(4)私服
一般来说中央仓库或者镜像仓库都能满足我们的需求,但是当我们在公司内合作开发代码时,可能因为系统保密性原因,有一些其他同事开发的外部依赖只希望能够被本公司的人使用,而如果上传到镜像仓库则保密性就不复存在了。因此私服最主要的功能时存储一些公司内部不希望被公开的依赖服务。
1.2 settings配置详解
settings.xml配置了本地全局maven的相关配置。
以下是一份seetings.xml的文件配置顶级元素。
1.2.1 localRepository
用来标识本地仓库的位置
D:/Maven/Repository
1.2.2 interactiveMode
maven 是否需要和用户交互以获得输入。默认为true。
true
1.2.3 usePluginRegistry
maven 是否需要使用 plugin-registry.xml 文件来管理插件版本。
false
1.2.4 offline
用来标识是否以离线模式运营maven。
当系统不能联网时,可以通过次配置来离线运行。
false
1.2.5 servers
当使用maven私服时,某些私服需要配置认证信息,需要在此处填写相应的配置。之所以不写在pom.xml中是因为一般项目在上传至代码仓库时同样会将pom.xml上传,而setting.xml一般位于用户本地,因此相对比较安全。
...
server001
my_login
my_password
${usr.home}/.ssh/id_dsa
some_passphrase
664
775
...
1.2.6 mirrors【重要】
用来配置相应的镜像仓库。
如果仓库X可以提供仓库Y存储的所有内容,那么就可以认为X是Y的一个镜像。用过Maven的都知道,国外的中央仓库因为网络原因用起来太慢了,所以选择一个国内的镜像就很有必要,配置简单,修改conf文件夹下的settings.xml文件,添加如下镜像配置:
alimaven
aliyun maven
http://maven.aliyun.com/nexus/content/groups/public/
central
alimaven1
aliyun maven1
http://maven.aliyun.com/nexus/content/groups/public/
*
其中id与name用来标识唯一的仓库,url为镜像仓库地址,mirrorOf用来匹配当请求什么仓库依赖时使用该镜像。
这里介绍下
配置的各种选项
:匹配所有远程仓库。*
:匹配所有远程仓库,使用localhost的除外,使用file://协议的除外。也就是说,匹配所有不在本机上的远程仓库。external:*
:匹配仓库repo1h和repo2,使用逗号分隔多个远程仓库。repo1,repo2
:匹配所有远程仓库,repo1除外,使用感叹号将仓库从匹配中排除。*,!repo1
需要注意的是,由于镜像仓库完全屏蔽了被镜像仓库,当镜像仓库不稳定或者停止服务的时候,Maven仍将无法访问被镜像仓库,因而将无法下载构件。
此外, maven 读取mirror 配置是 从上往下读取的,因此谨慎配置
,因为如果第一个镜像仓库配置了如此标志,那么如果该仓库即使不存在对应依赖也不会向下游查询。
1.2.7 proxies
用来配置代理。
...
myproxy
true
http
proxy.somewhere.com
8080
proxyuser
somepassword
*.google.com|ibiblio.org
...
1.2.8 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被激活时,会使用该远程仓库。
codehausSnapshots
Codehaus Snapshots
false
always
warn
http://snapshots.maven.codehaus.org/maven2
default
(2)properties
定义了一组拓展属性,当对应的profile被激活时该属性有效。
${user.home}/our-project
(3)id
全局唯一标识,如果一个 settings.xml
中的 profile
被激活,它的值会覆盖任何其它定义在 pom.xml
中带有相同 id 的 profile
。
(4)pluginRepositories
同repositories差不多,不过该标签定义的是插件的远程仓库。
(5)activation
触发激活该profile的条件。
false
1.5
Windows XP
Windows
x86
5.1.2600
mavenVersion
2.0.3
${basedir}/file2.properties
${basedir}/file1.properties
1.2.9 ActiveProfiles
在运行时手工激活的profile。
该元素包含了一组 activeProfile
元素,每个 activeProfile
都含有一个 profile id。任何在 activeProfile
中定义的 profile id,不论环境设置如何,其对应的 profile
都会被激活。如果没有匹配的 profile
,则什么都不会发生。
env-test
1.2.10 激活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
可以打包开环环境下的项目。
1.3 Q&A
1.3.1 mirrors与repositories的关系【重要】
从上文可以看到,repository标签与mirror标签都定义了一个远程仓库的位置,那么当一个依赖同时存在于两个仓库时,会先加载那个依赖呢?
这里需要阐述一下maven加载真正起作用的repository的步骤,
- 首先获取pom.xml中repository的集合,然后获取setting.xml中mirror中元素。
- 如果repository的
id
和mirror的mirrorOf
的值相同,则该mirror替代该repository。 - 如果该repository找不到对应的mirror,则使用其本身。
- 依此可以得到最终起作用的repository集合。可以理解mirror是复写了对应id的repository。
mirror相当于一个拦截器,会拦截被mirrorOf
匹配到的repository,匹配原则参照 1.2.6
,在匹配到后,会用mirror里定义的url替换到repository。
没有配置mirror
配置了mirror
二、Pom.xml详解
上章中setting.xml定义了某个环境下全局项目的相关依赖配置,而pom.xml则具体定义了某一个项目中的依赖配置。
2.1 pom元素
2.1.1 基本信息
4.0.0
com.companyname.project-group
project
1.0
jar
itoken dependencies
www.funtl.com
基本信息都比较易懂,主要定义了本项目的相关配置说明,例如唯一坐标、版本、项目介绍等。
2.1.2 dependencies【重要】
(1)dependencies
本元素定义了项目中所需要的相关依赖信息,也是最重要的元素之一。
org.apache.maven
maven-artifact
3.8.1
spring-core
org.springframework
true
test
(2)如何解决依赖传递问题或jar包版本冲突问题
解决上述问题一般有两种方式:
- 当我们作为依赖服务提供者时,可以通过
标签排除掉不想被传递的服务。
...
sample.ProjectB
Project-B
1.0
true
我们的A服务中引用了外部的依赖B服务,此时有A -> B,在别人引用我们时有C -> A ->B,若此时我们A在提供对外服务时不想让别人依赖B服务,可以在引用B时添加
标签为true
,这样带C依赖于A时并不会引入B。如果C在此时想要使用B的相关服务,需要在C的pom中显示的调用B的相关服务。
- 当我们作为依赖服务使用者时,可以通过
来去除掉我们依赖包中所不想依赖的其他服务。
如果此时我们的A服务依赖于B服务,B服务依赖于C服务,则有A ->B ->C,因为某种原因例如jar包冲突,我们在A中并不想依赖于C服务的版本,可以在调用B服务时去除掉C的相关依赖,然后自己再在A中使用C的相关版本。
...
sample.ProjectB
Project-B
1.0
sample.ProjectC
Project-C
sample.ProjectC
Project-C
2.0
2.1.3
当一个服务中存在有多个moudle
时,每个子module
都可能引用了相同的jar包,但是如果将版本维护到子module
的pom中,当需要升级时需要修改所有的pom文件版本。maven提供了继承的方式来解决此问题。
org.aspectj
aspectjweaver
1.0.0
在父pom的
中定义子pom需要的依赖及版本。
org.springframework.boot
spring-boot-starter-parent
1.5.10.RELEASE
org.aspectj
aspectjweaver
当我们的pom中有定义父pom的元素后,可以在指定需要的依赖时不指定其版本,maven会帮我们去父pom中查找相关的版本信息。
2.1.4 properties
properties主要用来定义常量,通过${value}来使用。
1.8
UTF-8
UTF-8
Finchley.RELEASE
2.0.1
2.10.1
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
io.zipkin.java
zipkin
${zipkin.version}
此外,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 |
2.1.5 resources
resources
标签用来标识项目在编译运行时需要额外编译的文件。例如手工引入jar包、不同运行环境对应不同的profile。
src/main/resources
true
**/*.fxml
**/*.yaml
src/main/profiles/product
true
**/*.fxml
lib
BOOT-INF/lib/
**/*.jar
2.1.6 profile
与setting.xml中profile所不同的是(参照1.2.8),pom中的profile有着更多的标签来描述一组环境。从作用上来区分的话,一般setting.xml用来标识不同的远程仓库,而pom中的profile一般用来标识当前项目属于什么环境,如下是一组常见的pom中的profiles。
dev
true
dev
test
test
src/main/${project.active}
true
**/*.fxml
此外,一般通过 mvn -P dev/beta/pre/product
命令来激活不同的环境,也可以通过其他的方式来激活profile,详见1.2.10。
当然,pom中的profile不止有指定编译环境的功能,同样也可以指定远程仓库等其他功能。
2.1.6 modules
当我们项目中有多个模块时,如果我们要单独打包的话需要在每一个模块都执行对应的maven命令,但是通过
标签可以将子服务或平级服务进行聚合,只需要打包该服务,也就会将其对应的子模块同时打包。
springmybatis
../utils
三、null:jrdp-common:null:jar问题解决
3.1包找不到问题
我们某次在开发功能的时候,在我们的项目中引用了伏羲另外一个系统的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文件,发现也是跟项目一样的。
然后我们就突然想到,会不会是仓库中的包有问题,果不其然
没有指定parent也没有指定版本号,于是我们修改了这个版本的pom,至此问题解决。
总结:因为我们的系统已经被好多个团队中转接手过,因此可能在某些地方踩了不少坑,像这种问题应该就是之前的团队上传了一些有问题的jar包,导致依赖不可用,但是因为我们之前一直用的私服是没有什么问题的,只到这次用到了仓库问题才浮现。
另外,此问题并不具有普适性,但是当遇到了groupid不能未空的时候可以按照此方法进行排查。
作者:京东科技 韩国凯
内容来源:京东云开发者社区