Maven
# 1. 项目构建工具的前世今生
# 1.Ant
Ant是第一个所谓的“现代构建工具”,用于自动化构建过程。它是用Java实现的,主要用于Java项目。Ant的第一个公开发行版是在2000年,它很快成为Java项目最流行的构建工具,这要归功于它的平台独立性(如Java),设置门槛低,并提供可重复的构建。后来,它还获得了接受插件的能力。
# 2. Maven
Maven于2004年首次发布,旨在改进开发人员在使用Ant和其他构建工具时遇到的一些问题。Maven最大的成就被认为是引入了项目结构约定、依赖关系管理和一个中心工件库Maven central。
# 3. Gradle
Gradle是三个构建工具中最年轻的一个,它的开发人员试图将Ant的能力和灵活性与Maven的依赖关系管理和约定结合起来,以创建漂亮而闪亮的构建工具。经过几年的开发,GradleV1.0于2012年发布,并迅速开始流行起来。它发展得非常快,已经被一些大企业采用——例如,Gradle被选为谷歌Android操作系统的构建工具。
一个有趣的观点是,在Gradle中,XML不再被使用——相反,开发人员拥有一种基于JVM语言Groovy的领域特定语言(DSL),Groovy的发明是为了让开发人员摆脱XML的冗长,编写更简单、更清晰的语句。这在用户中引发了一场争论,即标准的、易于理解的(但冗长的)XML样式比DSL好还是坏。
# 2. Maven基本
# 2.1 什么是Maven
官网:http://maven.apache.org/
Maven是Apache软件基金会唯一维护的一款自动化构建工具,专注于服务Java平台的项目构建和依赖管理。
Maven是一个强大的Java项目构建工具,基于POM(项目对象模型)文件,可用于项目构建、依赖模块管理和Javadoc生成等。
Maven 是一种声明式项目管理工具,通过在 POM 中配置 "who","what","where"等信息,即可满足编译、测试、打包、发布等项目构建需求。
# 2.2 为什么用Maven?
# (1)jar 包的规模
随着我们使用越来越多的框架,或者框架封装程度越来越高,项目中使用的jar包也越来越多。项目中,一个模块里面用到上百个jar包是非常正常的。
比如下面的例子,我们只用到 SpringBoot、SpringCloud 框架中的三个功能:
- Nacos 服务注册发现
- Web 框架环境
- 图模板技术 Thymeleaf
最终却导入了 106 个 jar 包:
org.springframework.security:spring-security-rsa:jar:1.0.9.RELEASE:compile
com.netflix.ribbon: ribbon:jar:2.3.0:compile
org.springframework.boot:spring-boot-starter-thymeleaf:jar:2.3.6.RELEASE:compile
commons-configuration:commons-configuration:jar:1.8:compile
org.apache.logging.log4j:log4j-api:jar:2.13.3:compile
org.springframework:spring-beans:jar:5.2.11.RELEASE:compile
org.springframework.cloud:spring-cloud-starter-netflix-ribbon:jar:2.2.6.RELEASE:compile
org.apache.tomcat.embed:tomcat-embed-websocket:jar:9.0.39:compile
com.alibaba.cloud:spring-cloud-alibaba-commons:jar:2.2.6.RELEASE:compile
org.bouncycastle:bcprov-jdk15on:jar:1.64:compile
org.springframework.security:spring-security-crypto:jar:5.3.5.RELEASE:compile
org.apache.httpcomponents:httpasyncclient:jar:4.1.4:compile
com.google.j2objc:j2objc-annotations:jar:1.3:compile
com.fasterxml.jackson.core:jackson-databind:jar:2.11.3:compile
io.reactivex:rxjava:jar:1.3.8:compile
ch.qos.logback:logback-classic:jar:1.2.3:compile
org.springframework:spring-web:jar:5.2.11.RELEASE:compile
io.reactivex:rxnetty-servo:jar:0.4.9:runtime
org.springframework:spring-core:jar:5.2.11.RELEASE:compile
io.github.openfeign.form:feign-form-spring:jar:3.8.0:compile
io.github.openfeign.form:feign-form:jar:3.8.0:compile
com.netflix.ribbon:ribbon-loadbalancer:jar:2.3.0:compile
org.apache.httpcomponents:httpcore:jar:4.4.13:compile
org.thymeleaf.extras:thymeleaf-extras-java8time:jar:3.0.4.RELEASE:compile
org.slf4j:jul-to-slf4j:jar:1.7.30:compile
com.itheima.demo:demo09-base-entity:jar:1.0-SNAPSHOT:compile
org.yaml:snakeyaml:jar:1.26:compile
org.springframework.boot:spring-boot-starter-logging:jar:2.3.6.RELEASE:compile
io.reactivex:rxnetty-contexts:jar:0.4.9:runtime
org.apache.httpcomponents:httpclient:jar:4.5.13:compile
io.github.openfeign:feign-core:jar:10.10.1:compile
org.springframework.boot:spring-boot-starter-aop:jar:2.3.6.RELEASE:compile
org.hdrhistogram:HdrHistogram:jar:2.1.9:compile
org.springframework:spring-context:jar:5.2.11.RELEASE:compile
commons-lang:commons-lang:jar:2.6:compile
io.prometheus:simpleclient:jar:0.5.0:compile
ch.qos.logback:logback-core:jar:1.2.3:compile
org.springframework:spring-webmvc:jar:5.2.11.RELEASE:compile
com.sun.jersey:jersey-core:jar:1.19.1:runtime
javax.ws.rs:jsr311-api:jar:1.1.1:runtime
javax.inject:javax.inject:jar:1:runtime
org.springframework.cloud:spring-cloud-openfeign-core:jar:2.2.6.RELEASE:compile
com.netflix.ribbon:ribbon-core:jar:2.3.0:compile
com.netflix.hystrix:hystrix-core:jar:1.5.18:compile
com.netflix.ribbon:ribbon-transport:jar:2.3.0:runtime
org.springframework.boot:spring-boot-starter-json:jar:2.3.6.RELEASE:compile
org.springframework.cloud:spring-cloud-starter-openfeign:jar:2.2.6.RELEASE:compile
com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.11.3:compile
com.sun.jersey.contribs:jersey-apache-client4:jar:1.19.1:runtime
io.github.openfeign:feign-hystrix:jar:10.10.1:compile
io.github.openfeign:feign-slf4j:jar:10.10.1:compile
com.alibaba.nacos:nacos-client:jar:1.4.2:compile
org.apache.httpcomponents:httpcore-nio:jar:4.4.13:compile
com.sun.jersey:jersey-client:jar:1.19.1:runtime
org.springframework.cloud:spring-cloud-context:jar:2.2.6.RELEASE:compile
org.glassfish:jakarta.el:jar:3.0.3:compile
org.apache.logging.log4j:log4j-to-slf4j:jar:2.13.3:compile
com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.11.3:compile
org.springframework.cloud:spring-cloud-commons:jar:2.2.6.RELEASE:compile
org.aspectj:aspectjweaver:jar:1.9.6:compile
com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery:jar:2.2.6.RELEASE:compile
com.google.guava:listenablefuture:jar:9999.0-empty-to-avoid-conflict-with-guava:compile
com.alibaba.spring:spring-context-support:jar:1.0.10:compile
jakarta.annotation:jakarta.annotation-api:jar:1.3.5:compile
org.bouncycastle:bcpkix-jdk15on:jar:1.64:compile
com.netflix.netflix-commons:netflix-commons-util:jar:0.3.0:runtime
com.fasterxml.jackson.core:jackson-annotations:jar:2.11.3:compile
com.google.guava:guava:jar:29.0-jre:compile
com.google.guava:failureaccess:jar:1.0.1:compile
org.springframework.boot:spring-boot:jar:2.3.6.RELEASE:compile
com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.11.3:compile
com.itheima.demo:demo08-base-api:jar:1.0-SNAPSHOT:compile
org.springframework.cloud:spring-cloud-starter-netflix-archaius:jar:2.2.6.RELEASE:compile
org.springframework.boot:spring-boot-autoconfigure:jar:2.3.6.RELEASE:compile
org.slf4j:slf4j-api:jar:1.7.30:compile
commons-io:commons-io:jar:2.7:compile
org.springframework.cloud:spring-cloud-starter:jar:2.2.6.RELEASE:compile
org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.39:compile
io.reactivex:rxnetty:jar:0.4.9:runtime
com.fasterxml.jackson.core:jackson-core:jar:2.11.3:compile
com.google.code.findbugs:jsr305:jar:3.0.2:compile
com.netflix.archaius:archaius-core:jar:0.7.6:compile
org.springframework.boot:spring-boot-starter-web:jar:2.3.6.RELEASE:compile
commons-codec:commons-codec:jar:1.14:compile
com.netflix.servo:servo-core:jar:0.12.21:runtime
com.google.errorprone:error_prone_annotations:jar:2.3.4:compile
org.attoparser:attoparser:jar:2.0.5.RELEASE:compile
com.itheima.demo:demo10-base-util:jar:1.0-SNAPSHOT:compile
org.checkerframework:checker-qual:jar:2.11.1:compile
org.thymeleaf:thymeleaf-spring5:jar:3.0.11.RELEASE:compile
commons-fileupload:commons-fileupload:jar:1.4:compile
com.netflix.ribbon:ribbon-httpclient:jar:2.3.0:compile
com.netflix.netflix-commons:netflix-statistics:jar:0.1.1:runtime
org.unbescape:unbescape:jar:1.1.6.RELEASE:compile
org.springframework:spring-jcl:jar:5.2.11.RELEASE:compile
com.alibaba.nacos:nacos-common:jar:1.4.2:compile
commons-collections:commons-collections:jar:3.2.2:runtime
javax.persistence:persistence-api:jar:1.0:compile
com.alibaba.nacos:nacos-api:jar:1.4.2:compile
org.thymeleaf:thymeleaf:jar:3.0.11.RELEASE:compile
org.springframework:spring-aop:jar:5.2.11.RELEASE:compile
org.springframework.boot:spring-boot-starter:jar:2.3.6.RELEASE:compile
org.springframework.boot:spring-boot-starter-tomcat:jar:2.3.6.RELEASE:compile
org.springframework.cloud:spring-cloud-netflix-ribbon:jar:2.2.6.RELEASE:compile
org.springframework:spring-expression:jar:5.2.11.RELEASE:compile
org.springframework.cloud:spring-cloud-netflix-archaius:jar:2.2.6.RELEASE:compile
而如果使用 Maven 来引入这些 jar 包只需要配置三个『依赖』:
<!-- Nacos 服务注册发现启动器 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- web启动器依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 视图模板技术 thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
# (2) jar 包的来源
- 这个jar包所属技术的官网。官网通常是英文界面,网站的结构又不尽相同,甚至找到下载链接还发现需要通过特殊的工具下载。
- 第三方网站提供下载。问题是不规范,在使用过程中会出现各种问题。
- jar包的名称
- jar包的版本
- jar包内的具体细节
- 而使用 Maven 后,依赖对应的 jar 包能够自动下载,方便、快捷又规范。
# (3)jar 包之间的依赖关系
框架中使用的 jar 包,不仅数量庞大,而且彼此之间存在错综复杂的依赖关系。依赖关系的复杂程度,已经上升到了完全不能靠人力手动解决的程度。另外,jar 包之间有可能产生冲突。进一步增加了我们在 jar 包使用过程中的难度。
下面是前面例子中 jar 包之间的依赖关系:
而实际上 jar 包之间的依赖关系是普遍存在的,如果要由程序员手动梳理无疑会增加极高的学习成本,而这些工作又对实现业务功能毫无帮助。
而使用 Maven 则几乎不需要管理这些关系,极个别的地方调整一下即可,极大的减轻了我们的工作量。
# 2.3 Maven安装
Maven – Download Apache Maven (opens new window)
# 配置JDK环境
在环境变量中增加
MAVEN_HOME
- 先在系统变量当中添加一个环境变量 MAVEN_HOME,变量值为 maven 的安装目录的根目录,比如:
D:\dev\apache-maven-3.6.3
- 然后找到系统变量 Path,给该变量的值后面添加:
;%MAVEN_HOME%\bin
即可
# 验证
检验 maven 安装是否成功,直接在命令窗口输入 mvn -v ,如果有输出 maven 的版本说明安装成功:
# 2.4 Maven目录结构
Maven 提倡使用一个共同的标准目录结构,Maven 使用约定优于配置的原则,大家尽可能的遵守这样的目录结构,一个使用Maven管理的普通的Java项目,它的目录结构默认如下:
MavenProject
|-- pom.xml maven项目的配置文件。对项目中的所有jar包依赖进行统一管理
|-- src
|-- main
| -- java 存放项目源代码
| -- resources(可省略) 存放项目配置文件 .xml等
|-- test(可省略)
| -- java 存放单元测试源代码
| -- resources 存放单元测试资源文件 .xml等
|-- target(由maven生成) 存放所有编译、打包生成的文件
|-- classes 存放项目源代码编译输出的字节码文件
|-- test-classes 存放测试代码编译输出的字节码文件
默认情况下,项目在编译过后,会将 src/main/java
编译过后的字节码文件和 src/main/resource
中的文件放在target/classes
目录下。
但是,src/main/java
目录下的非包且非java的文件在编译过后并不会自动被拷贝在 target/classes
目录下,而是会丢失。
如果我们想要将 src/main/java
目录下的非包且非java的文件也一并拷贝在target/classes
目录下,则需要在 pom.xml 文件的 build 标签下进行配置。
# 2.5 maven仓库配置
Maven 仓库是项目中依赖的第三方库
在 Maven 中,任何一个依赖、插件或者项目构建的输出,都可以称之为构件,Maven 仓库能帮助我们管理构件(主要是JAR),它就是放置所有JAR文件(WAR,ZIP,POM等等)的地方
# 2.5.1 仓库类型
Maven 仓库有以下几种类型
- 本地仓库(local)
- 中央仓库
- 私服仓库
注意:除本地仓库以外的仓库都可以叫做远程仓库
当项目编译时,Maven首先从本地仓库中寻找项目所需的Jar包,若本地仓库没有,再到Maven的中央仓库下载所需Jar包,这 3 个仓库中,jar包的查找顺序如下:
# 2.5.1.1 本地仓库
Maven 的本地仓库,在安装 Maven 后并不会创建,它是在第一次执行 maven 命令的时候才被创建。默认位置:
当前用户名\.m2\repository。
运行 Maven 的时候,Maven 所需要的任何构件都是直接从本地仓库获取的,如果本地仓库没有,它会首先尝试从远程仓库下载构件至本地仓库,然后再使用本地仓库的构件。
本地仓库默认位置在 当前用户名\.m2\repository
,我们也可以修改本地仓库的位置,在 maven 安装目录下的 conf/settings.xml
文件中可以修改,在该配置文件中可以看到 localRepository 节点被注释掉了,我们可以直接复制该节点,将值修改为本地路径即可
<!-- 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>E:/develop/maven_repository</localRepository>
当你运行 Maven 命令,Maven 会将下载依赖的文件放到你指定的路径中。
# 2.5.1.2 中央仓库
中央仓库属于远程仓库的一种,Maven 中央仓库是由 Maven 社区提供的仓库,其中包含了绝大多数流行的开源Java构件,以及源码、作者信息、SCM、信息、许可证信息等,一般来说,简单的Java项目依赖的包都可以在这里下载到
中央仓库需要通过网络才能访问,低版本的 maven 中,比如 maven-2.0.10
,可以在 ${M2_HOME}/lib/maven-2.0.10-uber.jar
中找到 pom.xml 配置文件
但是在 3.xxx 版本及之后的版本,在 maven 安装目录下的lib/maven-model-builder-${version}.jar
下,可以在 \org\apache\maven\model\pom-4.0.0.xml
取到 pom-4.0.0.xml
配置文件,该配置文件是所有 Maven POM 的父 POM,所有Maven项目继承该配置
你可以在 pom-4.0.0.xml 配置文件中找到如下配置信息
<repositories>
<repository>
<id>central</id>
<name>Central Repository</name>
<url>https://repo.maven.apache.org/maven2</url>
<layout>default</layout>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
可以看到,中央仓库的id为central,远程url地址为 https://repo.maven.apache.org/maven2
,它关闭了snapshot版本构件下载的支持
# 2.5.1.3 私服仓库
私服也属于远程仓库的一种,一般来说,私服仓库搭建在公司局域网内,专供公司内部开发人员提供服务,不是对外使用的
一般Maven下载jar包的步骤是:本地仓库(本机)--->私服(局域网)--->中心仓库(外部网络)
# 2.5.2 配置镜像仓库
默认的中央仓库地址为
https://repo.maven.apache.org/maven2
,但是国外的远程库下载速度比较慢,甚至可能会出现无法访问的问题,此时我们可以配置国内的仓库,比如阿里云中央仓库等
# 2.6.2.1 项目中配置镜像仓库
我们是可以在自己的项目中配置镜像仓库的
配置我们自己的远程仓库有很多好处,比如你有一个局域网的远程仓库,使用该仓库能大大提高下载速度,继而提高构建速度,也有可能你依赖的一个 jar 在 central 中找不到,它只存在于某个特定的公共仓库,这样你也不得不添加那个远程仓库的配置。
我们可以在项目的 pom.xml 文件中配置远程仓库。比如下面配置远程仓库指向阿里云仓库
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>org.heima</groupId>
<artifactId>maven_project</artifactId>
<version>1.0-SNAPSHOT</version>
<repositories>
<repository>
<id>maven-ali</id>
<url>http://maven.aliyun.com/nexus/content/repositories/central</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
<checksumPolicy>fail</checksumPolicy>
</snapshots>
</repository>
</repositories>
</project>
<repositories>
下面可以添加多个<repository>
,每个<repository>
都有它唯一的ID,一个描述性的name,以及最重要的远程仓库的url。
<releases><enabled>
true</enabled></releases>
告诉Maven可以从这个仓库下载 releases 版本的构件,而<snapshots><enabled>false</enabled></snapshots>
告诉Maven不要从这个仓库下载 snapshot 版本的构件,禁止从公共仓库下载snapshot构件是推荐的做法,因为这些构件不稳定,且不受你控制,你应该避免使用,当然,如果你想使用局域网内组织内部的仓库,你可以激活snapshot的支持。
# 2.5.2.2 settings.xml配置镜像仓库
在项目的 pom.xml 文件中可以配置中央仓库,但是这样每个项目都需要配置一遍,需要重复工作
我们可以直接在 maven 安装目录下的 conf/settings.xml 文件中配置中央仓库。
找到 mirrors 标签,添加如下配置
<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>mirrorId</id>
<mirrorOf>repositoryId</mirrorOf>
<name>Human Readable Name for this Mirror.</name>
<url>http://my.repository.com/repo/path</url>
</mirror>
-->
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
可以看到
<mirrorOf>
的值是 central,意思就是用以上镜像替代 Maven 自带的中央仓库
# 3. Pom层次
# Pom文件简介
而 pom.xml 主要描述了项目的基本信息,用于描述项目如何构建,声明项目依赖等等,是项目级别的配置文件,执行任务或目标时,Maven 会在当前目录中查找 POM,然后读取 POM,获取所需的配置信息,然后执行目标。
pom(project object model)即项目对象模型,maven 把一个项目的结构和内容抽象成一个模型,在 xml 文件中进行声明,以方便进行构建和描述。
# 1、Super POM
经过我们前面的学习,我们看到 Maven 在构建过程中有很多默认的设定。例如:源文件存放的目录、测试源文件存放的目录、构建输出的目录……等等。但是其实这些要素也都是被 Maven 定义过的。定义的位置就是:超级 POM。
关于超级 POM,Maven 官网是这样介绍的:
The Super POM is Maven's default POM. All POMs extend the Super POM unless explicitly set, meaning the configuration specified in the Super POM is inherited by the POMs you created for your projects.
译文:Super POM 是 Maven 的默认 POM。除非明确设置,否则所有 POM 都扩展 Super POM,这意味着 Super POM 中指定的配置由您为项目创建的 POM 继承。
所以我们自己的 POM 即使没有明确指定一个父工程(父 POM),其实也默认继承了超级 POM。就好比一个 Java 类默认继承了 Object 类。
这个POM文件可以在 [Maven Super Pom](Maven Model Builder – Super POM) (opens new window) 找到。
也可以在 本地这个路径找到 $MAVEN_HOME/lib/maven-model-builder-3.8.1.jar!/org/apache/maven/model/pom-4.0.0.xml
1. repositories
定义了一个名叫 central的repository,value是 'https://repo.maven.apache.org/maven2',可以从这个地址拉下来dependency。
2. pluginRepositories
默认 plugin的 repositories
3. build
设置了一些默认的路径,其中还定义了 几个插件,不过Maven官方 (opens new window)也提醒,未来的版本会去掉。
<pluginManagement>
<!-- NOTE: These plugins will be removed from future versions of the super POM -->
<!-- They are kept for the moment as they are very unlikely to conflict with lifecycle mappings (MNG-4453) -->
.....
</pluginManagement>
# 2、父 POM
和 Java 类一样,POM 之间其实也是单继承的。如果我们给一个 POM 指定了父 POM,那么继承关系如下图所示:
# 3、有效 POM
有效 POM 英文翻译为 effective POM,它的概念是这样的——在 POM 的继承关系中,子 POM 可以覆盖父 POM 中的配置;如果子 POM 没有覆盖,那么父 POM 中的配置将会被继承。按照这个规则,继承关系中的所有 POM 叠加到一起,就得到了一个最终生效的 POM。显然 Maven 实际运行过程中,执行构建操作就是按照这个最终生效的 POM 来运行的。这个最终生效的 POM 就是有效 POM,英文叫effective POM
查看有效 POM:
mvn help:effective-pom
综上所述,平时我们使用和配置的 POM 其实大致是由四个层次组成的:
- 超级 POM:所有 POM 默认继承,只是有直接和间接之分。
- 父 POM:这一层可能没有,可能有一层,也可能有很多层。
- 当前 pom.xml 配置的 POM:我们最多关注和最多使用的一层。
- 有效 POM:隐含的一层,但是实际上真正生效的一层。
# Pom文件构成
一个基本的pom.xml文件配置如下
<?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">
<!-- maven模型的版本,对于maven2和maven3来说,只能是4.0.0 -->
<modelVersion>4.0.0</modelVersion>
<!-- 公司或者组织的唯一标志,一般是公司域名的倒写,或者是公司域名倒写+项目名。并且配置时生成的路径也是由此生成, 如com.companyname.project-group,maven会将该项目打成的jar包放本地路径:/com/companyname/project-group -->
<groupId>org.heima</groupId>
<!-- 项目的唯一ID,一个groupId下面可以有多个项目,通过artifactId来区分 -->
<artifactId>maven_project</artifactId>
<!-- 项目的版本号 -->
<version>1.0-SNAPSHOT</version>
</project>
所有 POM 文件都需要 <project>
标签元素和该标签下的三个必需字段:groupId,artifactId,version。
groupId + artifactId + version = 坐标,坐标可用于标识互联网中的唯一资源,在Maven中,坐标是Jar包的唯一标识,Maven通过坐标在仓库中找到项目所需的Jar包。
# 常见标签
下面我们看下Maven中的常用标签
# parent标签
在maven多模块项目中引用父pom依赖,在
springboot
项目中就有父依赖
<!--引用springBoot父项目-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
</parent>
# properties标签
定义一些全局属性值,常用于jar包版本定义全局管理jar包版本后面可以
${}
取值
在springboot项目中父pom会定义一些项目jar包版本依赖 ,所以我们在引用jar时候才不用写jar包版本,会自动跟随父pom中定义的jar包版本
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.4.7</version>
</parent>
<properties>
<activemq.version>5.15.11</activemq.version>
<antlr2.version>2.7.7</antlr2.version>
<appengine-sdk.version>1.9.77</appengine-sdk.version>
<artemis.version>2.10.1</artemis.version>
<aspectj.version>1.9.5</aspectj.version>
<assertj.version>3.13.2</assertj.version>
<atomikos.version>4.0.6</atomikos.version>
<awaitility.version>4.0.1</awaitility.version>
<bitronix.version>2.1.4</bitronix.version>
<build-helper-maven-plugin.version>3.0.0</build-helper-maven-plugin.version>
<byte-buddy.version>1.10.4</byte-buddy.version>
<caffeine.version>2.8.0</caffeine.version>
<cassandra-driver.version>3.7.2</cassandra-driver.version>
<classmate.version>1.5.1</classmate.version>
<commons-codec.version>1.13</commons-codec.version>
...............
</properties>
# dependencyManagement标签
在
Maven
多模块的时候,管理依赖关系是非常重要的,各种依赖包冲突,查询问题起来非常复杂,于是就用到了,在springboot项目中父模块就定义了
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test-autoconfigure</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
...................................
</dependencyManagement>
那么在子模块中只需要
<groupId>和<artifactId>
即可,不需要加入版本号,
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
使用dependencyManagement可以统一管理项目的版本号,确保应用的各个项目的依赖和版本一致,不用每个模块项目都弄一个版本号,不利于管理,当需要变更版本号的时候只需要在父类容器里更新,不需要任何一个子项目的修改;如果某个子项目需要另外一个特殊的版本号时,只需要在自己的模块dependencies中声明一个版本号即可。子类就会使用子类声明的版本号,不继承于父类版本号
# dependencies标签
用于引入项目依赖
<dependencies>
<!--aop 切面-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
# dependencies和dependencyManagement区别
Dependencies
相对于dependencyManagement
,所有声明在dependencies里的依赖都会自动引入,并默认被所有的子项目继承, 而dependencyManagement
里只是声明依赖,并不自动实现引入,因此子项目需要显示的声明需要用的依赖。
如果不在子项目中声明依赖,是不会从父项目中继承下来的;只有在子项目中写了该依赖项,并且没有指定具体版本,才会从父项目中继承该项,并且version和scope都读取自父pom;另外如果子项目中指定了版本号,那么会使用子项目中指定的jar版本。
# build标签
在实际使用 Maven 的过程中,我们会发现 build 标签有时候有,有时候没,这是怎么回事呢?其实通过有效 POM 我们能够看到,build 标签的相关配置其实一直都在,只是在我们需要定制构建过程的时候才会通过配置 build 标签覆盖默认值或补充配置。这一点我们可以通过打印有效 POM 来看到。
所以本质上来说:我们配置的 build 标签都是对超级 POM 配置的叠加。那我们又为什么要在默认配置的基础上叠加呢?很简单,在默认配置无法满足需求的时候定制构建过程。
build 标签组成
从示例中我们能够看到,build 标签的子标签大致包含三个主体部分:
① 定义约定的目录结构
参考示例中的如下部分:
<directory>${project.basedir}/target</directory>
<outputDirectory>${project.build.directory}/classes</outputDirectory>
<finalName>${project.artifactId}-${project.version}</finalName>
<testOutputDirectory>${project.build.directory}/test-classes</testOutputDirectory>
<sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>
<scriptSourceDirectory>${project.basedir}/src/main/scripts</scriptSourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/java</testSourceDirectory>
<resources>
<resource>
<directory>${project.basedir}/src/main/resources</directory>
</resource>
</resources>
<testResources>
<testResource>
<directory>${project.basedir}/src/test/resources</directory>
</testResource>
<testResources>
我们能看到各个目录的作用如下:
目录名 | 作用 |
---|---|
sourceDirectory | 主体源程序存放目录 |
scriptSourceDirectory | 脚本源程序存放目录 |
testSourceDirectory | 测试源程序存放目录 |
outputDirectory | 主体源程序编译结果输出目录 |
testOutputDirectory | 测试源程序编译结果输出目录 |
resources | 主体资源文件存放目录 |
testResources | 测试资源文件存放目录 |
directory | 构建结果输出目录 |
② 备用插件管理
pluginManagement 标签存放着几个插件:
- maven-antrun-plugin
- maven-assembly-plugin
- maven-dependency-plugin
- maven-release-plugin
通过 pluginManagement 标签管理起来的插件就像 dependencyManagement 一样,子工程使用时可以省略版本号,起到在父工程中统一管理版本的效果。情看下面例子:
- 被 spring-boot-dependencies 管理的插件信息:
- 子工程使用的插件信息:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
③生命周期插件
plugins 标签存放的是默认生命周期中实际会用到的插件,这些插件想必大家都不陌生,所以抛开插件本身不谈,我们来看看 plugin 标签的结构:
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<executions>
<execution>
<id>default-compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>default-testCompile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
典型应用:指定 JDK 版本
① 提出问题
前面我们在 settings.xml 中配置了 JDK 版本,那么将来把 Maven 工程部署都服务器上,脱离了 settings.xml 配置,如何保证程序正常运行呢?思路就是我们直接把 JDK 版本信息告诉负责编译操作的 maven-compiler-plugin 插件,让它在构建过程中,按照我们指定的信息工作。
② 暂时取消 settings.xml 配置
为了测试对 maven-compiler-plugin 插件进行配置的效果,我们暂时取消 settings.xml 中的 profile 配置。
<!-- 配置Maven工程的默认JDK版本 -->
<!-- <profile>
<id>jdk-1.8</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.8</jdk>
</activation>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</properties>
</profile> -->
③ 编写源文件代码
很明显这里用到了 Lambda 表达式,这是 JDK 1.8 才支持的语法。
package com.itheima.maven;
public class Hello {
public void hello() {
new Thread(()->{
System.out.println("thread ...");
}).start();
}
}
此时我们执行编译命令:
④ 配置构建过程
<!-- build 标签:意思是告诉 Maven,你的构建行为,我要开始定制了! -->
<build>
<!-- plugins 标签:Maven 你给我听好了,你给我构建的时候要用到这些插件! -->
<plugins>
<!-- plugin 标签:这是我要指定的一个具体的插件 -->
<plugin>
<!-- 插件的坐标。此处引用的 maven-compiler-plugin 插件不是第三方的,是一个 Maven 自带的插件。 -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<!-- configuration 标签:配置 maven-compiler-plugin 插件 -->
<configuration>
<!-- 具体配置信息会因为插件不同、需求不同而有所差异 -->
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
⑤ 再次执行编译命令
⑥ 两种配置方式比较
- settings.xml 中配置:仅在本地生效,如果脱离当前 settings.xml 能够覆盖的范围,则无法生效。
- 在当前 Maven 工程 pom.xml 中配置:无论在哪个环境执行编译等构建操作都有效。
还有另外一种方式
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
# 4. 依赖管理
# 4.1 依赖传递
# 4.2 传递性依赖机制
项目A中,我们为了实现某一个功能通常会引入第三方库,这里是一个compile依赖范围的B依赖,而B依赖同时又依赖于另一个compile依赖范围的C组件。
那么对A而言,C就是它的一个传递性依赖,在Maven中,其会将我们在POM文件中显式声明的直接依赖(本例的B依赖)引入到项目中,对于必要的间接依赖(本例的C依赖)则会以传递性依赖的形式自动地引入到项目A中,而无需我们手动显式地在POM文件中声明C依赖来引入。
Maven的传递性依赖机制,大大地减少了人工维护间接依赖的复杂度
# 4.2.1 传递性依赖的依赖范围
项目A依赖于B组件,B组件依赖于C组件,则我们将A对于B的依赖称之为第一直接依赖,B对于C的依赖称之为第二直接依赖。
根据上文可知,A对于C的依赖是传递性依赖,必要的间接依赖C将通过传递性依赖机制,被自动引入到A中。那么如何判定一个间接依赖是否有必要被引入呢?间接依赖被引入后其依赖范围又是什么呢?
答案其实很简单,就是通过第一直接依赖的依赖范围和第二直接依赖的依赖范围之间的关系,来判定是否有必要引入间接依赖以及确定引入间接依赖后其依赖范围。
如下表所示,若结果为N,则意味着该传递性依赖为非必要的,无需引入;否则,该间接依赖为必要的并自动引入该间接依赖,且引入后该传递依赖的依赖范围如下表单元格中的文字所示
Maven依赖范围不仅控制依赖与classpath的关系,还会影响依赖传递
最左边一列表示第一直接依赖范围,最上面一行表示第二直接依赖范围,中间的交叉单元格则表示传递性依赖范围
compile | test | provided | runtime | |
---|---|---|---|---|
compile | compile | N | N | runtime |
test | test | N | N | test |
provided | provided | N | provided | provided |
runtime | runtime | N | N | runtime |
仔细观察上面表格,我们发现这样的规律
- 当第二直接依赖的范围是compile的时候,传递性依赖的范围与第一直接依赖的范围一致;
- 当第二直接依赖的范围是test的时候,依赖不会得以传递;
- 当第二直接依赖的范围是provided的时候,只传递第一直接依赖的范围也为provided的依赖,切传递性依赖的范围同样为provided;
- 当第二直接依赖的范围是runtime的时候,传递性依赖的范围与第一直接依赖的范围一致,但compile例外,此时传递性依赖的范围为runtime。
# 4.2.2 依赖调解
在Maven中由于传递性依赖的机制,一般情况下我们不需要关心间接依赖的管理。
而当间接依赖出问题时,我们需要知道该间接依赖是通过哪条依赖路径引入的,特别是该间接依赖存在多条引入路径时,确定间接依赖引入的路径就显得尤为重要。当一个间接依赖存在多条引入路径时,为避免依赖重复Maven会通过依赖调解来确定该间接依赖的引入路径
依赖调解遵循以下原则,优先使用第一原则,当第一原则无法解决时,则通过第二原则解决
- 第一原则: 路径最短者优先
- 第二原则: 第一声明者优先
# 4.3.1 路径最短者优先
假设在项目A中存在如下依赖关系:
A -> X -> Y -> Z(2.0) // dist(A->Z) = 3
A -> M -> Z(2.1) // dist(A->Z) = 2
项目A依赖的Z组件有2个版本,很显然不可能同时引入两个版本的间接依赖,这里可以看到,Z(2.0)依赖的依赖路径长度为3,Z(2.1)依赖的依赖路径长度为2。
根据依赖调解的第一原则——路径最短者优先,所以,2.1版本的Z组件将通过 A -> M -> Z(2.1) 路径被引入到A中
# 4.3.2 第一声明者优先
假设在项目B中存在如下依赖关系,间接依赖W在两条依赖路径中的路径长度均为2,这时候就无法通过依赖调解的第一原则来确定引入路径。此时需要使用依赖调解的第二原则——第一声明者优先。
根据项目B的POM文件中直接依赖K、P的声明顺序,先声明的直接依赖,则其间接依赖即通过该路径被引入
B -> K -> W(1.0) // dist(B->W) = 2
B -> P -> W(2.0) // dist(B->W) = 2
项目B的POM文件内容如下所示,由于P依赖比K依赖先声明,则2.0版本的的W组件将通过 B -> P -> W(2.0) 路径被引入到B中
<dependencies>
...
<dependency>
...
<artifactId>P</artifactId>
...
</dependency>
...
<dependency>
...
<artifactId>K</artifactId>
...
</dependency>
...
</dependencies>
# 3.4 依赖处理
有时候依赖调解会引入一些不需要的jar,我们可以通过一些方式进行处理
# 3.4.1 可选依赖 option
可选依赖是通过项目中的POM文件的依赖元素dependency下的option元素中进行配置,只有显式地配置项目中某依赖的option元素为true时,该依赖才是可选依赖;
不设置该元素或值为false时,该依赖即不是可选依赖,其意义在于,当某个间接依赖是可选依赖时,无论依赖范围是什么,其都不会因为传递性依赖机制而被引入
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<optional>true</optional>
</dependency>
optional元素默认值(false) 当父项目添加junit依赖时,并未添加optional选项,也就是默认的optional元素的值为false。 父项目并未设置optional元素为true,那么便具有依赖传递性。此时,子项目B中会直接引入父项目A中引入的Junit的jar包。也就是说B项目打包时,jar/war包中会包含junit的jar包。
optional元素为true 当父项目引入junit依赖时,设置optional元素为true。那么,子项目B便有了更多的选择。
如果项目B不需要Junit的jar包,那么在其pom文件中不需进行任何处理便可以。如果B项目也需要对应的jar包依赖,可以有两种选择:第一、A项目中对应依赖的optional设置为false或去掉;第二、B项目中直接引入需要的该依赖。
# 3.4.1.1 应用场景
这里简单介绍下option元素的实际应用场景
我们在开发项目A的过程中,需要依赖一个第三方持久层的M组件,而这个M组件对两种不同数据库均支持( MySql、Oracle),故M组件中需要依赖这两种不同数据库的驱动实现——X、Y组件。
对于开发项目A的工程师而言,他可能在项目开发只需要使用其中一种数据库(例如MySql,其驱动实现为X依赖),如果数据库驱动实现X、Y不是可选依赖,则均会传递到项目A中。
虽然一般情况下这不会引发任何问题,但是会因引入不必要的依赖Y而造成项目体积增大,如果该M组件还支持更多类型的数据库,就会引入更多的不必要的数据库驱动实现依赖进来;而如果M组件中的X、Y依赖是可选依赖的话,则工程师就可以根据实际需要在A项目的POM文件中显式地引入所需数据库的驱动实现依赖即可
# 3.4.1.2 使用建议
当然,一般情况下是不推荐使用可选依赖的,使用可选依赖一般是因为项目中支持、实现多种特性所造成的
# 3.4.2 排除依赖 exclusions
间接依赖是可以通过传递性依赖机制引入到当前项目中,而有时候第三方组件B的C依赖由于版本(1.0)过低存在安全漏洞。
我们期望能够将该间接依赖直接剔除出去,不通过传递依赖的形式引入到项目中,这时即可通过exclusions元素实现,该元素下可以包含若干个 exclusion 子元素,然后再在POM中显式地引入合适版本(3.3)的C依赖
在exclusion元素中,只需给定groupId、artifactId即可确定依赖,而无需指定版本version
<dependencies>
...
<dependency>
<groupId>com.apple</groupId>
<artifactId>B</artifactId>
<version>2.3</version>
<exclusions>
<exclusion>
<groupId>com.google</groupId>
<artifactId>C</artifactId>
</exclusion>
</exclusions>
</dependency>
...
<dependency>
<groupId>com.google</groupId>
<artifactId>C</artifactId>
<version>3.3</version>
</dependency>
...
</dependencies>
# 5. 合理的依赖范围
依赖范围:控制依赖与三种classpath的关系,可在POM文件的依赖元素dependency中的scope元素中进行配置,缺省值 compile
Maven在编译、测试、运行(含打包)阶段中所需的依赖并不完全一致,所以Maven通过三种不同的classpath实现在不同阶段引入所需的依赖:编译classpath、测试classpath、运行classpath
# 常见依赖范围
常见的依赖范围有:
compile :
不声明scope元素的情况下的默认值;compile表示被依赖包需要参与当前项目的编译,包括后续的测试,运行周期也参与其中,是一个比较强的依赖;打包的时候通常需要包含进去。
provided :
provided
类型的scope只会在项目的编译
、测试
阶段起作用;可以认为在目标容器中已经提供了这个依赖,无需在提供,但是在编写代码或者编译时可能会用到这个依赖;依赖不会被打入到项目jar包中
。runtime :
runtime
与compile
比较相似,区别在于runtime
跳过了编译
阶段,打包的时候通常需要包含进去。
例:当你的代码需要使用jdbc连接一个mysql数据库,通常我们会希望针对标准 JDBC 抽象进行编码,而不是直接错误的使用 MySQL driver实现。这个时候依赖的scope就需要设置为runtime。这意味着我们在编译时无法使用该依赖,该依赖会被包含在最终的产物中,在程序最终执行时可以在classpath下找到它。
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
test :
在一般的编译和运行时都不需要,它们只有在测试编译和测试运行阶段可用,
不会被打包到项目jar包中
,同时如果项目A依赖于项目B,项目B中的test
作用域下的依赖不会被继承。system :
系统依赖范围,其效果与provided的依赖范围一致。其用于添加非Maven仓库的本地依赖,通过依赖元素dependency中的systemPath元素指定本地依赖的路径。鉴于使用其会导致项目的可移植性降低,一般不推荐使用
<!--引用-->
<dependency>
<groupId>xxxx</groupId>
<artifactId>xxx</artifactId>
<systemPath>D:\tempare\itheima-maven-test-aaa-1.0-SNAPSHOT.jar</systemPath>
<scope>system</scope>
<version>1.4.12</version>
</dependency>
import :
import 只能在pom文件的中使用,从而引入其他的pom文件中引入依赖,如:在Spring boot 项目的POM文件中,我们可以通过在POM文件中继承 Spring-boot-starter-parent来引用Spring boot默认依赖的jar包,如下:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.1.BUILD-SNAPSHOT</version> </parent>
但是,通过上面的parent继承的方法,只能继承一个 spring-boot-start-parent。实际开发中,用户很可能需要继承自己公司的标准parent配置,这个时候可以使用 scope=import 来实现多继承。代码如下:
<dependencyManagement> <dependencies> <dependency> <!-- Import dependency management from Spring Boot --> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.0.1.BUILD-SNAPSHOT</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
通过上面方式,就可以获取spring-boot-dependencies.2.0.1.BUILD-SNAPSHOT.pom文件中dependencyManagement配置的jar包依赖。如果要继承多个,可以在dependencyManagement中添加,如:
<dependencyManagement> <dependencies> <!-- Override Spring Data release train provided by Spring Boot --> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-releasetrain</artifactId> <version>Fowler-SR2</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.0.1.BUILD-SNAPSHOT</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
依赖关系
依赖范围与三种classpath的关系一览表,如下所示:
依赖范围(scope) | 对于编译classpath有效 | 对于测试classpath有效 | 对于运行时classpath有效 | 例子 |
---|---|---|---|---|
compile(默认) | Y | Y | Y | Spring-core |
test | N | Y | N | Junit |
provided | Y | Y | N | servlet-api |
runtime | N | Y | Y | JDBC驱动实现 |
system | Y | Y | N | 本地的,Maven仓库之外的类库文件 |
# 6. 体系外jar包引入
# 1. 提出问题
『体系外 jar 包』
而实际开发中确实有可能用到一些 jar 包并非是用 Maven 的方式发布,那自然也没法通过 Maven 导入。
此时如果我们能够拿到该 jar 包的源码那还可以自己建一个 Maven 工程,自己打包。可是如果连源码都没有呢?
这方面的例子包括一些人脸识别用的 jar 包、海康视频监控 jar 包等等。
# 2、解决办法
# ① 准备一个体系外 jar 包
我们通过学 Maven 以前的方式创建一个 Java 工程,然后导出 jar 包即可用来测试。
# ② 将该 jar 包安装到 Maven 仓库
这里我们使用 install 插件的 install-file 目标:
mvn install:install-file -Dfile=[体系外 jar 包路径] \zdy-spring-boot-starter-1.0-SNAPSHOT.jar
-DgroupId=[给体系外 jar 包强行设定坐标] \
-DartifactId=[给体系外 jar 包强行设定坐标] \
-Dversion=1 \
-Dpackage=jar
mvn deploy:deploy-file -Dfile=文件的位置 -DgroupId=文件groupID -Dversion=文件的版本号 -Dpackaging=jar -Durl=http://ip地址/nexus/content/repositories/releases -DrepositoryId=releases
例如(Windows 系统下使用 ^ 符号换行;Linux 系统用 \):
mvn install:install-file -Dfile=D:\cz_workspace\code\maven_demo\zdy-spring-boot-starter-1.0-SNAPSHOT.jar ^
-DgroupId=com.itheima.maven ^
-DartifactId=zdy-spring-boot-starter ^
-Dversion=1.0 ^
-Dpackaging=jar
执行结果:
再看本地仓库中确实有:
# ③ 测试
在其它地方依赖这个 jar 包:
<dependency>
<groupId>com.itheima.maven</groupId>
<artifactId>zdy-spring-boot-starter</artifactId>
<version>1.0</version>
</dependency>
# 7. 生命周期 & 插件
# 1. 生命周期
Maven的生命周期就是为了对所有的构建过程进行抽象和统一
maven把项目的构建划分为不同的生命周期(lifecycle)。粗略一点的话,它这个过程(phase)包括:编译、测试、打包、集成测试、验证、部署。maven中所有的执行动作(goal)都需要指明自己在这个过程中的执行位置,然后maven执行的时候,就依照过程的发展依次调用这些goal进行各种处理。
# 2. 三套生命周期
下面列出了default、clean和site生命周期的所有构建阶段,这些阶段按照指定的顺序执行。
- Clean Lifecycle 在进行真正的构建之前进行一些清理工作
- Default Lifecycle 构建的核心部分,编译,测试,打包,部署等等
- Site Lifecycle 生成项目报告,站点,发布站点
注意:执行某个生命周期的某个阶段不会影响其它的生命周期!
如果要同时执行多个生命周期的阶段可在命令行输入多个命令,中间以空格隔开,例如: clean package 该命令执行clean生命周期的clean阶段和default生命周期的package阶段。
# clean生命周期
包含三个阶段:
1. pre-clean:执行一些清理前需要完成的工作
2. clean:清理上一次构建生成的文件
3. post-clean:执行一些清理后需要完成的工作
# default生命周期
default生命周期定义了真正构建时所需要执行的所有步骤,是所有生命周期中最核心的部分
# site生命周期
Maven能够基于POM包含的信息,自动生成一个友好的站点,方便团队交流和发布项目信息
pre-site:执行一些在生产项目站点前需要完成的工作
site:生成项目站点文档
post-site:执行一些在生产项目站点之后需要完成的工作
site-deploy:将生成的项目站点发布到服务器上
# 3. 插件
# 概念
maven插件主要是为maven中生命周期中的阶段服务的,maven中只是定义了3套生命周期,以及每套生命周期中有哪些阶段,具体每个阶段中执行什么操作,完全是交给插件去干。
- 插件可以通过 mvn 命令的方式调用直接运行
- 插件与生命周期内的阶段绑定,在执行到对应生命周期时执行对应的插件
- maven默认在各个生命周期上都绑定了预先设定的插件来完成相应功能
插件通常提供了一个目标的集合,并且可以使用下面的语法执行:
<code>mvn [plugin-name]:[goal-name]</code>
例如,一个 Java 工程可以使用 maven-compiler-plugin 的 compile-goal 编译,使用以下命令:
<code>mvn compiler:compile</code>
# 插件目标
maven中的插件以jar的方式存在于仓库中,通过坐标进行访问,每个插件中可能为了代码可以重用,一个插件可能包含了多个功能。插件中的每个功能就叫做插件的目标(Plugin Goal),每个插件中可能包含一个或者多个插件目标(Plugin Goal)。
- 例如maven-dependency-plugin插件有十多个目标,常见的有dependency:analyze、dependency:tree、dependency:list等不同的功能
- 列出插件的所有目标:
- mvn 插件goupId:插件artifactId[:插件version]:help
- mvn 插件前缀:help
# 插件前缀
运行插件的时候,可以通过指定插件坐标的方式运行。maven中给插件定义了一些简捷的插件前缀,可以通过插件前缀来运行指定的插件。
可以通过下面命令查看到插件的前缀:mvn help:describe -Dplugin=插件goupId:插件artifactId[:插件version]
mvn help:describe -Dplugin=org.apache.maven.plugins:maven-help-plugin
使用mvn命令调用插件的时候,可以使用插件的前缀来代替繁琐的插件坐标的方式。
插件前缀与插件groupId:artifactId是一一对应的关系,这个关系的配置存储在仓库的元数据中
# 插件和生命周期阶段绑定
将生命周期中的阶段和插件的目标进行绑定的时候,执行 mvn 阶段 就可以执行和这些阶段绑定的 插件目标 。
# 内置绑定
为了让用户不用任何配置就能构件Maven项目,Maven在核心为一些主要的生命周期阶段绑定了很多插件目标,这些绑定就是内置绑定。下面是三个生命周期中的内置绑定:
default生命周期阶段
# 自定义绑定
- 用户可以自己选择将某个插件目标绑定到生命周期的某个阶段上
- 举例:创建项目的源码jar包,将其安装到仓库中,我们将maven-source-plugin插件中的jar-no-fork目标绑定到default生命周期的verify阶段上,这样就可以将项目的主代码打包成jar文件
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version>
<executions>
<!-- 使用插件需要执行的任务 -->
<execution>
<!-- 任务id -->
<id>attach-sources</id>
<!-- 绑定的阶段 -->
<phase>verify</phase>
<!-- 任务中插件的目标,可以指定多个 -->
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
运行命令:mvn install
# 4. 插件配置
# 命令行插件配置
用户可以在Maven命令中使用-D参数,并伴随一个参数键 = 参数值的形式,来配置插件目标的参数,例如,maven-surefire-plugin提供了一个maven.test.skip参数,当其值为true的时候,跳过执行测试
$mvn install -Dmaven.test.skip = true //参数-D是Java自带的
# POM中插件全局配置
用户可以在声明插件的时候,对插件进行一个全局的配置,也就是说,所有基于该插件目标的任务,都会使用这些配置。例如,配置maven-compiler-plugin告诉它编译Java1.5的源文件,生成与JVM1.5兼容的字节码文件,如下
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
</plugins>
</build>
# 8. 依赖纠错
由于实际开发时我们往往都会整合使用很多大型框架,所以一个项目中哪怕只是一个模块也会涉及到大量 jar 包。数以百计的 jar 包要彼此协调、精密配合才能保证程序正常运行。而规模如此庞大的 jar 包组合在一起难免会有磕磕碰碰。最关键的是由于 jar 包冲突所导致的问题非常诡异,这里我们只能罗列较为典型的问题,而没法保证穷举。
但是我们仍然能够指出一点:一般来说,由于我们自己编写代码、配置文件写错所导致的问题通常能够在异常信息中看到我们自己类的全类名或配置文件的所在路径。如果整个错误信息中完全没有我们负责的部分,全部是框架、第三方工具包里面的类报错,这往往就是 jar 包的问题所引起的。
而具体的表现形式中,主要体现为找不到类或找不到方法。
- java.lang.ClassNotFoundException:编译过程中找不到类
- java.lang.NoClassDefFoundError:运行过程中找不到类
- java.lang.NoSuchMethodError
# 分析解决
很多情况下常用框架之间的整合容易出现的冲突问题都有人总结过了,拿抛出的异常搜索一下基本上就可以直接找到对应的 jar 包。我们接下来要说的是通用方法。
不管具体使用的是什么工具,基本思路无非是这么两步:
- 第一步:把彼此冲突的 jar 包找到
- 第二步:在冲突的 jar 包中选定一个。具体做法无非是通过 exclusions 排除依赖,或是明确声明依赖。
# IDEA 的 Maven Helper 插件
这个插件是 IDEA 中安装的插件,不是 Maven 插件。它能够给我们罗列出来同一个 jar 包的不同版本,以及它们的来源
其中三个选项分别表示如下:
- Conflicts(查看冲突)
- All Dependencies as List(列表形式查看所有依赖)
- All Dependencies as Tree(树形式查看所有依赖)
2、IEDA使用Maven命令查看依赖冲突方法
①:想要查看maven的依赖树的时候使用Maven命令来查看依赖:
maven dependency:tree
②:查看是否有依赖冲突也可以使用Maven命令来查看:
mvn dependency:tree -Dverbose -Dincludes=: