【Maven】02 - 进阶篇
【Maven】02 - 进阶篇
文章目录
- 【Maven】02 - 进阶篇
- 一:依赖冲突
- 1:依赖的传递性
- 2:依赖冲突的解决
- 3:主动排除依赖exclusion
- 二:分模块开发
- 1:父子工程构建
- 2:聚合工程的依赖管理
- 3:隐藏依赖技术
- 4:父工程的依赖传递
- 5:聚合工程的构建
- 6:聚合打包跳过测试
- 三:Maven属性
- 1:为什么引入属性
- 2:内置属性
- 四:Maven多环境配置
一:依赖冲突
依赖冲突是指:在Maven
项目中,当多个依赖包,引入了同一份类库的不同版本时,可能会导致编译错误或运行时异常
想要解决依赖冲突,可以靠升级/降级某些依赖项的版本,从而让不同依赖引入的同一类库,保持一致的版本号
还可以通过隐藏依赖、或者排除特定的依赖项来解决问题
1:依赖的传递性
所谓依赖的传递性就是说当引入的一个包,如果依赖于其他包(类库),当前的工程就必须再把其他包引入进来
因此,如果自己导入的依赖和其他依赖所依赖的包有所冲突就会产生依赖冲突
2:依赖冲突的解决
在绝对大多数情况下,依赖冲突问题并不需要我们考虑,Maven
工具会自动解决,而解决的方式就是基于前面所说的依赖层级
层级优先原则
Maven
会根据依赖树的层级,来自动剔除相同的包,层级越浅,优先级越高
声明优先原则
假设同级出现两个相同的依赖怎么办?
同层级出现包冲突时,先声明的会覆盖后声明的,为此后者会被剔除
配置优先原则
相同层级出现同版本的类库,前面的会覆盖后面的,可是当相同层级,出现不同版本的包呢?
同级出现不同版本的相同类库时,后配置的会覆盖先配置的
在很多时候,并不需要我们考虑依赖冲突问题,Maven
会依据上述三条原则,帮我们智能化自动剔除冲突的依赖,其他包都会共享留下来的类库,只有当出现无法解决的冲突时,这才需要咱们手动介入。
3:主动排除依赖exclusion
所谓的排除依赖,即是指从一个依赖包中,排除掉它依赖的其他包,如果出现了Maven
无法自动解决的冲突,就可以基于这种手段进行处理
<dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>5.1.8.RELEASE</version><exclusions><!-- 排除web包依赖的beans包 --><exclusion><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId></exclusion></exclusions>
</dependency>
🎉 可以使用maven-helper插件帮助我们查找冲突并且一键主动排重
二:分模块开发
聚合工程:一个项目允许创建多个子模块,多个子模块组成一个整体,可以统一进行项目的构建
- 父工程:不具备任何代码、仅有
pom.xml
的空项目,用来定义公共依赖、插件和配置; - 子工程:编写具体代码的子项目,可以继承父工程的配置、依赖项,还可以独立拓展。
而Maven
聚合工程,就是基于父子工程结构,来将一个完整项目,划分出不同的层次,这种方式可以很好的管理多模块之间的依赖关系,以及构建顺序,大大提高了开发效率、维护性。并且当一个子工程更新时,聚合工程可以保障同步更新其他存在关联的子工程!
1:父子工程构建
父工程
创建一个空的Maven
项目,作为父工程,这时可以在IDEA
创建Maven
项目时,把打包方式选成POM
,也可以创建一个普通的Maven
项目,然后把src
目录删掉,再修改一下pom.xml
<!-- 写在当前项目GAV坐标下面 -->
<packaging>pom</packaging>
子工程
在父工程的项目名上,选择创建一个module,这就是创建子工程了
2:聚合工程的依赖管理
子工程中一样的依赖都放在父工程中
例如,如果子工程1和子工程2的依赖分别是:
发现只有最后一个不同,这时候就可以将红色框中的依赖放入到父pom中
为了防止不同子工程引入不同版本的依赖,最好的做法是在父工程中,统一对依赖的版本进行控制
在父pom的<dependecyManagement>
中规定所有子工程都使用同一版本的依赖
<dependencies>
:定义强制性依赖,写在该标签里的依赖项,子工程必须强制继承;<dependencyManagement>
:定义可选性依赖,该标签里的依赖项,子工程可选择使用。
子工程在使用<dependencyManagement>
中已有的依赖项时,不需要写<version>
版本号
以后为项目的技术栈升级版本时,不需要单独修改每个子工程的POM
,只需要修改父POM
文件即可,大大提高了维护性!
3:隐藏依赖技术
在聚合项目中如果发生了依赖冲突,Maven会基于那三条原则自动剔除重复的依赖,然而如果有些依赖重复问题如果Maven不能自动剔除的话,就要用到“隐藏依赖”的技术了
<dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>5.1.8.RELEASE</version><optional>true</optional> <!-- 注意这个就是隐藏依赖的开关 -->
</dependency>
true
:开启隐藏,当前依赖不会向其他工程传递,只保留给自己用;false
:默认值,表示当前依赖会保持传递性,其他引入当前工程的项目会间接依赖。
当开启隐藏后,其他工程引入当前工程时,就不会再间接引入当前工程的隐藏依赖,因此来手动排除聚合工程中的依赖冲突问题
4:父工程的依赖传递
现在很多项目都是微服务项目,都使用spring-cloud-alibaba的多个依赖项,例如nacos,sentinel等等…
由于这些依赖项可能会在多个子工程用到,最好的方式是定义在父POM
的<dependencyManagement>
标签里,但是alibaba的依赖那么多,一个个的定义比较麻烦,因此为了解决上面的问题,引入了import的方式
<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>
⚠️ 注意点:必须配合<type>pom</type>
一起使用
把spring-cloud-alibaba-dependencies
的所有子依赖,作为当前项目的可选依赖向下传递
而当前父工程下的所有子工程,在继承父POM
时,也会将这些可选依赖继承过来,这时就可以直接选择使用某些依赖项啦
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
5:聚合工程的构建
尾巴上带有root
标识的工程,意味着这是一个父工程
IDEA给父工程提供了一套Lifecycle
命令,这子工程的Lifecycle
区别在哪儿呢?当你双击父工程的某个Lifecycle
命令,它找到父POM
的<modules>
标签,再根据其中的子工程列表,完成对整个聚合工程的构建工作。
当你双击父工程Lifecycle
下的clean
,它会把你所有子工程的target
目录删除。同理,执行其他命令时也一样,比如install
命令,双击后它会把你所有的子工程,打包并安装到本地仓库
Maven
聚合工程的构建流程,跟<modules>
标签里的书写顺序无关,它会自行去推断依赖关系,从而完成整个项目的构建
6:聚合打包跳过测试
当大家要做项目发版时,就需要对整个聚合工程的每个工程打包(jar
或war
包),此时可以直接双击父工程里的package
命令,但test
命令在package
之前,按照之前聊的生命周期原则,就会先执行test
,再进行打包。
test
阶段,会去找到所有子工程的src/test/java
目录,并执行里面的测试用例,如果其中任何一个报错,就无法完成打包工作。而且就算不报错,执行所有测试用例也会特别耗时,这时该怎么办呢?可以选择跳过test
阶段,在IDEA
工具里的操作如下
点击这个图标,就可以跳过测试阶段,会节省很多的时间
同时还可以在pom.xml
里,配置插件来精准控制,比如跳过某个测试类不执行,配置规则如下:
<build><plugins><plugin><artifactId>maven-surefire-plugin</artifactId><version>2.22.1</version><configuration><skipTests>true</skipTests><includes><!-- 指定要执行的测试用例 --><include>**/XXX*Test.java</include></includes><excludes><!-- 执行要跳过的测试用例 --><exclude>**/XXX*Test.java</exclude></excludes></configuration></plugin></plugins>
</build>
三:Maven属性
1:为什么引入属性
在很多的时候,我们很多同组依赖的版本一致,就会导致一旦发生修改,就要改很多地方,例如
<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.1.8.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.1.8.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>5.1.8.RELEASE</version></dependency>...
</dependencies>
上述依赖如果要改变spring的依赖版本5.1.8 - 5.2.0,都要进行修改,这时我们可以在POM
的<properties>
标签中,自定义属性
<properties><spring.version>5.2.0.RELEASE</spring.version>
</properties>
<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>${spring.version}</version></dependency>...
</dependencies>
这样做的好处特别明显,现在我想升级Spring
版本,只需要修改一处地方即可
2:内置属性
除开可以自定义属性外,Maven
也会有很多内置属性,大体可分为四类:
类型 | 使用方式 |
---|---|
Maven 内置属性 | ${ 属性名} ,如${version} |
项目环境属性 | ${setting. 属性名} ,如${settings.localRepository} |
Java 环境变量 | ${xxx. 属性名} ,如${java.class.path} |
系统环境变量 | ${env. 属性名} ,如${env.USERNAME} |
不过这些用的也不多,同时不需要记,要用的时候,IDEA
工具会有提示
四:Maven多环境配置
试想如下场景:
假设有
20
个微服务,此时项目要上线或测试,所以需要更改配置信息,比如把数据库地址换成测试、线上地址等,而不同环境的配置,相信大家一定用application-dev.yml、application-prod.yml……
做好了区分。但就算提前准备了不同环境的配置,可到了切换环境时,还需要挨个服务修改
spring.profiles.active
这个值,从dev
改成prod、test
,然后才能使用对应的配置进行打包,可这里有20
个微服务啊,难道要手动改20
次吗?而Maven的多环境配置就是解决这个问题的,可以使得父pom一处修改环境,所有的子模块生效
1️⃣ 找到父工程的POM
进行修改
<profiles><!-- 开发环境 --><profile><id>dev</id><properties><profile.active>dev</profile.active></properties></profile><!-- 生产环境 --><profile><id>prod</id><properties><profile.active>prod</profile.active></properties><!-- activeByDefault=true,表示打包时,默认使用这个环境 --><activation><activeByDefault>true</activeByDefault></activation></profile><!-- 测试环境 --><profile><id>test</id><properties><profile.active>test</profile.active></properties></profile>
</profiles>
刷新maven后,在Profiles下会出现这个
2️⃣ 去到子工程的application.yml
中,完成Spring
的多环境配置
spring:profiles:active: ${profile.active} # 从pom.xml中,读取profile.active属性值的意思
父POM
中配了三组值:dev、prod、test
,所以当前子工程的POM
,也会继承这组配置
目前默认勾选在prod
上,所以最终spring.profiles.active=prod
3️⃣ 在父POM
中,加一个依赖和插件,这样才能在application.yml
读到pom.xml
的值,还需
<!-- 开启 yml 文件的 ${} 取值支持 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><version>2.1.5.RELEASE</version><optional>true</optional>
</dependency><!-- 添加插件,将项目的资源文件复制到输出目录中 -->
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-resources-plugin</artifactId><version>3.2.0</version><configuration><encoding>UTF-8</encoding><useDefaultDelimiters>true</useDefaultDelimiters></configuration>
</plugin>