本文章是 《Maven实战》徐晓斌著,机械工业出版社 的读书笔记。读这本书后,你将会掌握maven的功能(构建项目、管理依赖),以及这些功能的机制和实现原理(比如:当敲了命令mvn clean complier之后maven做了什么;dependenciesManagement和dependencies的区别;为什么idea默认的源码目录是src/main/java 等等)。
另外,Maven教程|菜鸟教程 这个教程也很不错,配合此书一起阅读,能有更好的效果。
POM( Project Object Model,项目对象模型 ) 是 Maven 工程的基本工作单元,是一个XML文件,包含了项目的基本信息,用于描述项目如何构建,声明项目依赖,等等。
参考菜鸟maven教程下的POM 标签大全详解
构建程序,即清理,编译,打包。
清理:即删除编译输出目录下的文件。
编译:即将项目的java源码编译成.class,然后和项目的资源一起输出到编译输出目录下,maven默认的项目输出目录是/target/classes
打包:即将项目构建成jar包,或者war包,供其他项目引用或者部署到服务器上。
一个项目往往要依赖很多其他已经非常成熟的第三方框架、工具。比如spring、springboot、fastjson、jedis等等。然后这些第三方框架往往又依赖了很多其他组织提供的第三方框架,这样一层一层的无限套娃。
如果没有maven管理依赖,我们就要自己先引入spring的依赖,在引入spring,我们才能正常使用spring;如果有maven管理依赖,我们只要引入spring的坐标,maven自动会帮我们下载spring的依赖,甚至是spring的依赖的依赖!
这是maven的一个概念,在maven中所有东西都是构件,插件是构件,第三方jar是构件,自己打包出来的snapshot.jar快照也是构件。而每个构件都会有独一无二的坐标。
坐标,这是maven为了能唯一标识一个构件所引入的概念。一个坐标主要包括3个元素。
<groupId>com.baidu.cngroupId>
<artifactId>studyProjectartifactId>
<version>1.0version>
可以这么理解
groupId:一个组织、公司的id
artifactId:项目代号
version:项目的版本
然后上面的maven坐标就可以这么理解了:依赖 百度公司下的1.0版本的studyProject项目。
那么有了坐标,maven根据坐标在哪里下载呢?答案是maven的中央仓库:https://repo.maven.apache.org/maven2。为什么默认会到maven的中央仓库下载?那就要看后面的 maven约定大于配置 这一小节啦。
镜像的定义:如果仓库X可以提供仓库Y存储的所有内容,那么就可以任务X是Y的一个镜像。
常见的maven中央仓库镜像有阿里云maven镜像,但是既然有中央仓库了,为什么还要弄镜像仓库呢?
镜像的主要作用
<mirror>
<id>nexus-aliyunid>
<name>Nexus aliyunname>
<url>http://maven.aliyun.com/nexus/content/groups/publicurl>
<mirrorOf>*mirrorOf>
mirror>
<mirrorOf>*mirrorOf> *是通配符,表示所有走远程仓库的请求都会走阿里云镜像。这里的语法详细阅读《Maven实战》一书
TODO
不得不佩服,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中常见的可以被继承的元素:
编译:就是在IDE里运行src/java/main的代码,就是使用编译classpath
测试:就是在IDE里运行src/java/test的代码,就是使用测试classpath
运行:就是代码写好了,用IDE打包后的jar或者war中的classpath
compile:默认的依赖范围。对编译、测试、运行三种classpath都有效。
test:只对测试的classpath有效。
provided:只对测试、编译的classpath有效。
runtime:只对测试、运行的classpath有效。
system:这种基本不用,忽略。
依赖传递说的是: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。
解决方法:
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.10version>
<optional>true<optional>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.10version>
<exclusions>
<exclusion>
<groupId>xxxgroupId>
<artifactId>xxxartifactId>
<exclusion>
exclusions>
dependency>
总结:optional只是切断依赖传递;但是不会影响依赖继承,optional的依赖还是可以被子项目继承!
聚合项目下依赖继承的问题:比如 A项目下有两个子项目B、C、D。然后B依赖了spring,C依赖了mysql,D依赖了spring、mysql。然后为了整合子项目的依赖,并且避免子项目引入的依赖之间版本不一致导致的版本冲突问题,所以统一将spring、mysql依赖提升到A项目的pom.xml下。但是这时候因为依赖继承的机制,所有子项目都继承了spring和mysql,但是B项目明明只要依赖Spring,不需要依赖mysql,这时候就会使某些项目引入多余的依赖了。
maven提供了dependencyManagent这个机制来解决这个问题,上述问题可以通过以下来解决
<dependencyManagent>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-coreartifactId>
<version>5.3.7version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.10version>
dependency>
dependencyManagent>
<dependencys>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-coreartifactId>
dependency>
dependencyManagent>
与dependencyManagent类似,pluginManagement是管理插件的版本
maven有三大生命周期:clean、default、site。 每个生命周期包含很多个阶段,然后每个阶段的实际行为由绑定了该阶段的插件来完成,如果该阶段没有绑定任何插件则不会有任何行为。一句话简述就是:maven的生命周期是抽象的,其实际行为由插件来完成。
clean是为了清理项目,clean生命周期包括以下阶段
1)pre-clean
2)clean
3)post-clean
比如执行命令mvn clean。那么只会执行pre-clean、clean,而不会执行post-clean
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 部署 拷贝最终的工程包到远程仓库中,以共享给其他开发人员和工程
site母的是为了建立和发布项目站点,这个很少用,可以忽略。
总结:这个小节只要知道maven有3大生命周期,然后每个生命周期下有很多阶段,然后用maven构建程序的时候,这些生命周期和阶段会按以上顺序依次执行即可。
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仓库
插件目标:一个插件有很多个功能,这些功能是这些插件的目标。在maven中,这个概念叫『插件目标』。
插件绑定:比如default生命周期的compile阶段需要maven-compiler-plugin:compiler这个目标去实现,这个对照关系又称为『插件绑定』。
那么常见的maven命令mvn compile究竟会执行什么呢?这个就得看maven的default生命周期compile阶段绑定了哪个插件的哪个目标,下面是超级pom.xml的部分配置。
<plugin>
<inherited>trueinherited>
<artifactId>maven-source-pluginartifactId>
<executions>
<execution>
<id>attach-sourcesid>
<goals>
<goal>jar-no-forkgoal>
goals>
execution>
executions>
plugin>
上面的意思:引入maven-source-plugin插件,然后将这个插件的jar-no-fork目标绑定到default生命周期的compile阶段上。即mvn complier命令会执行maven-source-plugin插件的jar-no-fork功能。
从超级pom.xml可以看出,maven为了开箱即可用,所以默认将某些阶段绑定了某些插件的目标。
一个生命周期的某个阶段具体执行什么功能,要看这个阶段绑定了什么插件的目标,一个阶段可以绑定一个或多个插件目标。
父模块:
<modules>
<module>子模块的artifactIdmodule>
<module>子模块的artifactIdmodule>
<module>子模块的artifactIdmodule>
modules>
子模块:
1)其打包方式packaging的值必须为pom
2)<modules>中引入其子模块
3)聚合模块一般只有pom.xml文件,没有代码实现。因为聚合模块的作用就是聚合其他子模块。
4)子模块加入<parent>标签
<parent>
<groupId>groupId>
<artifactId>artifactId>
<version>version>
<relativePath><relativePath>
parent>
常见的有:groupId、version、properties(变量)、dependencies(依赖)、dependenciesManagement(依赖管理,这个不会真正引入依赖,只是声明了依赖的版本)、build(插件管理配置)
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.1version>
<configuration>
<source>1.8source>
<target>1.8target>
configuration>
plugin>
maven的compiler插件默认只支持编译jdk1.3,所以要编译更高版本的jdk,要进行手工指定jdk版本。
.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
TODO
快照版本就是“小步快跑”,每天都会检查一次是否要更新?