一、前言
本文章是 《Maven实战》徐晓斌著,机械工业出版社 的读书笔记。读这本书后,你将会掌握maven的功能(构建项目、管理依赖),以及这些功能的机制和实现原理(比如:当敲了命令mvn clean complier之后maven做了什么;dependenciesManagement和dependencies的区别;为什么idea默认的源码目录是src/main/java 等等)。
另外,Maven教程|菜鸟教程 这个教程也很不错,配合此书一起阅读,能有更好的效果。
二、初识maven
2.1 什么是pom
POM( Project Object Model,项目对象模型 ) 是 Maven 工程的基本工作单元,是一个XML文件,包含了项目的基本信息,用于描述项目如何构建,声明项目依赖,等等。
2.2.1)pom文件所有标签的含义
参考菜鸟maven教程下的POM 标签大全详解
2.2 maven的主要作用
2.2.1)构建程序
构建程序,即清理,编译,打包。
清理:即删除编译输出目录下的文件。
编译:即将项目的java源码编译成.class,然后和项目的资源一起输出到编译输出目录下,maven默认的项目输出目录是/target/classes
打包:即将项目构建成jar包,或者war包,供其他项目引用或者部署到服务器上。
2.2.2)管理依赖
一个项目往往要依赖很多其他已经非常成熟的第三方框架、工具。比如spring、springboot、fastjson、jedis等等。然后这些第三方框架往往又依赖了很多其他组织提供的第三方框架,这样一层一层的无限套娃。
如果没有maven管理依赖,我们就要自己先引入spring的依赖,在引入spring,我们才能正常使用spring;如果有maven管理依赖,我们只要引入spring的坐标,maven自动会帮我们下载spring的依赖,甚至是spring的依赖的依赖!
2.2.2.1)构件
这是maven的一个概念,在maven中所有东西都是构件,插件是构件,第三方jar是构件,自己打包出来的snapshot.jar快照也是构件。而每个构件都会有独一无二的坐标。
2.2.2.2)坐标 (对应pdf 71页)
坐标,这是maven为了能唯一标识一个构件所引入的概念。一个坐标主要包括3个元素。
<groupId>com.baidu.cn</groupId>
<artifactId>studyProject</artifactId>
<version>1.0</version>
可以这么理解
groupId:一个组织、公司的id
artifactId:项目代号
version:项目的版本
然后上面的maven坐标就可以这么理解了:依赖 百度公司下的1.0版本的studyProject项目。
2.2.3)中央仓库(对应pdf 98页)
那么有了坐标,maven根据坐标在哪里下载呢?答案是maven的中央仓库:https://repo.maven.apache.org/maven2。为什么默认会到maven的中央仓库下载?那就要看后面的 maven约定大于配置 这一小节啦。
2.2.3.1)镜像(对应pdf 107页)
镜像的定义:如果仓库X可以提供仓库Y存储的所有内容,那么就可以任务X是Y的一个镜像。
常见的maven中央仓库镜像有阿里云maven镜像,但是既然有中央仓库了,为什么还要弄镜像仓库呢?
镜像的主要作用
- maven中央仓库是在国外的,可能访问不稳定、或者网速不够快。所以可以通过在自己公司的局域网里搭建镜像,提高下载的速度。
- 如果公司A部门写好了一个工具jar,其他部门也想引入这个jar。这时候就可以在公司的局域网部署仓库镜像,然后把这个工具jar放到仓库镜像上去,然后公司的同事从公司的镜像中下载即可,这样可以使公司的代码不外露。
- maven中央仓库并不是所有的jar都有的,由于版权原因,ojdbc不在maven的中央仓库上。所以为了大一统依赖的下载地址,也可以在自己本地或者公司局域网部署镜像仓库。
<!-- maven的语法 -->
<mirror>
<id>nexus-aliyun</id>
<name>Nexus aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
<mirrorOf>*</mirrorOf>
</mirror>
<mirrorOf>*</mirrorOf> *是通配符,表示所有走远程仓库的请求都会走阿里云镜像。这里的语法详细阅读《Maven实战》一书
2.2.3.2)使用Nexus创建私服
TODO
2.3 maven的约定大于配置(对应pdf 157页)
不得不佩服,2001诞生的maven已经有了“约定大于配置”这个思想,而到现在springboot也一直沿用!约定大于配置,可以将常见的问题规范化,大家在解决这些常见问题的时候都约定俗成的遵守某一规范,可以减少很多沟通成本。
比如maven有以下约定
源代码目录默认为: src/main/java
测试目录默认为: src/test/java
上面这两个只是maven其中的一些约定,那怎么看maven有哪些约定呢?这就要看超级pom.xml文件了,因为所有的pom文件都会继承超级pom,类似于java的所有类都会继承Object。
那么超级pom文件在哪里呢?在$MAVEN_HOME/lib/maven-model-builder-x.x.x.jar中的org/apache/maven/model/pom-4.0.0.xml。只要你打开了超级pom,你就明白了maven有哪些约定了。包括但不限于:源码路径、编译目标文件夹路径、中央仓库等等。
另外要说明的是,虽然pom是可以继承的,但是不是pom中的所有元素都可以被继承,以下是pom中常见的可以被继承的元素:
- grouId、version:项目坐标的核心元素
- description:项目描述
- properties:自定义属性
- dependencies:依赖
- dependencyManagement:依赖版本管理信息
- repositoies:项目自定义仓库配置
- build:插件配置
三、动动手,来使用maven吧
3.1 maven的依赖机制
3.1.1)依赖范围(对应pdf 81页)
编译:就是在IDE里运行src/java/main的代码,就是使用编译classpath
测试:就是在IDE里运行src/java/test的代码,就是使用测试classpath
运行:就是代码写好了,用IDE打包后的jar或者war中的classpath
compile:默认的依赖范围。对编译、测试、运行三种classpath都有效。
test:只对测试的classpath有效。
provided:只对测试、编译的classpath有效。
runtime:只对测试、运行的classpath有效。
system:这种基本不用,忽略。
3.1.2)依赖传递(对应pdf 82页)
依赖传递说的是:jar包之间的依赖传递。比如说我项目要依赖spring,spring又依赖了log4j,这时候我在项目中的pom.xml中只要引入了spring的依赖,maven就会自动帮我引入log4j的依赖。
但是依赖传递会有那么两个问题(对应pdf 84页)
最短路径优先:项目的依赖关系(A -> B -> C -> X(1.0版本)、A -> D -> X(2.0版本)),这时候maven会根据依赖路径最短优先,只会依赖X2.0版本。
先声明优先:项目的依赖关系(A -> B -> Y(1.0版本)、A -> C -> Y(2.0版本)),依赖路径的长度都是2,这时候B和C的依赖,哪个依赖先在pom.xml中声明,便依赖哪个Y。
解决方法:
- 提供依赖方在 项目的依赖设置为可选依赖
<!-- 当有使用方引用这个项目的依赖时,使用方就不会自动引入mysql这个依赖,因为它是可选依赖。 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.10</version>
<optional>true<optional>
</dependency>
- 使用依赖放在 项目中排除依赖,切断依赖传递
<!-- 在使用方项目中依赖某个依赖,但是又不想引入这个依赖的依赖,可以用exclusion来切断依赖链路 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.10</version>
<exclusions>
<exclusion>
<!-- 注意exclusion只需要groupId+artifactId,因为只要这2个就可以确定依赖了 -->
<groupId>xxx</groupId>
<artifactId>xxx</artifactId>
<exclusion>
</exclusions>
</dependency>
总结:optional只是切断依赖传递;但是不会影响依赖继承,optional的依赖还是可以被子项目继承!
3.1.3)依赖继承
聚合项目下依赖继承的问题:比如 A项目下有两个子项目B、C、D。然后B依赖了spring,C依赖了mysql,D依赖了spring、mysql。然后为了整合子项目的依赖,并且避免子项目引入的依赖之间版本不一致导致的版本冲突问题,所以统一将spring、mysql依赖提升到A项目的pom.xml下。但是这时候因为依赖继承的机制,所有子项目都继承了spring和mysql,但是B项目明明只要依赖Spring,不需要依赖mysql,这时候就会使某些项目引入多余的依赖了。
maven提供了dependencyManagent这个机制来解决这个问题,上述问题可以通过以下来解决
<!-- A项目的pom.xml -->
<!-- 使用dependencyManagent管理的只是依赖的版本,子项目继承这个项目的时候,可以省略依赖的版本信息 -->
<dependencyManagent>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.7</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.10</version>
</dependency>
</dependencyManagent>
<!-- B项目的pom.xml -->
<dependencys>
<!-- 子项目在按需引入依赖,并且不需要注明版本,解决了不同子项目可能会引入同一个依赖的不同版本的问题 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
</dependencyManagent>
与dependencyManagent类似,pluginManagement是管理插件的版本
3.2 maven的三大生命周期(对应pdf 115页)
maven有三大生命周期:clean、default、site。 每个生命周期包含很多个阶段,然后每个阶段的实际行为由绑定了该阶段的插件来完成,如果该阶段没有绑定任何插件则不会有任何行为。一句话简述就是:maven的生命周期是抽象的,其实际行为由插件来完成。
3.2.1)clean生命周期
clean是为了清理项目,clean生命周期包括以下阶段
1)pre-clean
2)clean
3)post-clean
比如执行命令mvn clean。那么只会执行pre-clean、clean,而不会执行post-clean
3.2.2)default生命周期
default是为了构建项目,default生命周期包括以下阶段
1)vaildate
2)initialize
3)generate-sources
4)process-sources
5)generate-resources
6)process-resources
7)compile
8)process-classes
9)generate-test-sources
10)process-test-sources
11)generate-test-resouces
12)process-test-resouces
13)test-compiler
14)process-test-classes
15)test
16)prepare-package
17)package
18)pre-integration-test
19)integration
20)post-integration-test
21)verify
22)install
23)deploy
如果执行mvn compiler,就会执行1~7;如果执行mvn test,就会执行1~15
上面的阶段可以精简为下面
阶段 处理 描述
验证 validate 验证项目 验证项目是否正确且所有必须信息是可用的
编译 compile 执行编译 源代码编译在此阶段完成
测试 Test 测试 使用适当的单元测试框架(例如JUnit)运行测试。
包装 package 打包 创建JAR/WAR包如在 pom.xml 中定义提及的包
检查 verify 检查 对集成测试的结果进行检查,以保证质量达标
安装 install 安装 安装打包的项目到本地仓库,以供其他项目使用
部署 deploy 部署 拷贝最终的工程包到远程仓库中,以共享给其他开发人员和工程
3.2.3)site生命周期
site母的是为了建立和发布项目站点,这个很少用,可以忽略。
总结:这个小节只要知道maven有3大生命周期,然后每个生命周期下有很多阶段,然后用maven构建程序的时候,这些生命周期和阶段会按以上顺序依次执行即可。
3.3)命令行与生命周期、插件目标与插件绑定
3.3.1)命令行与生命周期
maven的3个生命周期是相互独立的,所以可以使用mvn clean compile命令。该命令的意思就是调用mvn的clean生命周期,执行到clean阶段;调用clean生命周期后在调用default生命周期,执行到compile阶段。
生命周期的运行顺序:clean -> default -> site
生命周期的阶段的运行顺序:按照上述default生命周期中的顺序
命令行的语法:
mvn [clean生命周期的某个阶段] [clean生命周期的某个阶段] [clean生命周期的某个阶段]
下面列举了一些命令和这些命令的解读
mvn clean complier:运行maven的clean生命周期的clean阶段,在运行default生命周期的complier阶段。这个命令的作用是:先删除编译输出目录下的文件,然后在将当前项目编译,编译后的文件输出到编译目录下。简单来讲就是:重新编译。
mvn clean test:测试
mvn clean package:打包
mvn clean install:将项目打包安装到本地maven仓库
3.3.2)插件目标与插件绑定(对应pdf 120页)
插件目标:一个插件有很多个功能,这些功能是这些插件的目标。在maven中,这个概念叫『插件目标』。
插件绑定:比如default生命周期的compile阶段需要maven-compiler-plugin:compiler这个目标去实现,这个对照关系又称为『插件绑定』。
那么常见的maven命令mvn compile究竟会执行什么呢?这个就得看maven的default生命周期compile阶段绑定了哪个插件的哪个目标,下面是超级pom.xml的部分配置。
<plugin>
<inherited>true</inherited>
<artifactId>maven-source-plugin</artifactId>
<!-- executions执行的意思,即插件目标 -->
<executions>
<execution>
<id>attach-sources</id>
<!-- phase为绑定的阶段,attach-sources插件应该默认绑定到compile阶段 -->
<!-- <phase>compile</phase> -->
<!-- goals为插件目标 -->
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
上面的意思:引入maven-source-plugin插件,然后将这个插件的jar-no-fork目标绑定到default生命周期的compile阶段上。即mvn complier命令会执行maven-source-plugin插件的jar-no-fork功能。
从超级pom.xml可以看出,maven为了开箱即可用,所以默认将某些阶段绑定了某些插件的目标。
一个生命周期的某个阶段具体执行什么功能,要看这个阶段绑定了什么插件的目标,一个阶段可以绑定一个或多个插件目标。
四、maven的多模块配置
4.1 父子模块配置
父模块:
<modules>
<module>子模块的artifactId</module>
<module>子模块的artifactId</module>
<module>子模块的artifactId</module>
</modules>
子模块:
1)其打包方式packaging的值必须为pom
2)<modules>中引入其子模块
3)聚合模块一般只有pom.xml文件,没有代码实现。因为聚合模块的作用就是聚合其他子模块。
4)子模块加入<parent>标签
<parent>
<groupId></groupId>
<artifactId></artifactId>
<version></version>
<!-- 元素relativePath表示父模块pom的相对路径。当项目构建时,maven会首先根据relativePath检查父pom,如果找不到,在从本地仓库查找。 -->
<relativePath><relativePath>
</parent>
4.2 可以被继承的pom元素(对应pdf 149页)
常见的有:groupId、version、properties(变量)、dependencies(依赖)、dependenciesManagement(依赖管理,这个不会真正引入依赖,只是声明了依赖的版本)、build(插件管理配置)
五、零散的知识点
5.1 compiler插件
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
maven的compiler插件默认只支持编译jdk1.3,所以要编译更高版本的jdk,要进行手工指定jdk版本。
5.2 idea的iml文件
.iml文件是什么?如下图所示,.iml文件就是idea存储项目结构的文件格式,类比eclipse的存储结构文件格式就是.classpath。存储结构文件下包含了当前项目依赖的jar。
跟.iml文件相关的一个问题:idea没有编译错误,但出现红色波浪线怎么去掉?问题如下图所示
问题原因:很可能是刚修改过pom.xml,然后maven自定引入了依赖,idea编译成功。但是.iml文件下的依赖信息跟项目的pom.xml的依赖信息不一致,导致报错。
解决办法:重新生成iml文件,在缺少.iml文件项目下运行mvn idea:module,完成后将自动生成.iml文件。
参考博客:https://blog.csdn.net/u012627861/article/details/83028437
5.3 maven项目原型
5.4 快照版本(对应pdf 103页)
TODO
快照版本就是“小步快跑”,每天都会检查一次是否要更新?