本文为阅读书籍《maven实战–》的读书笔记。
maven是声明式的:也就是所有功能由插件实现。
1.项目结构
约定大于配置:如果存在古老的项目不是这种结构,那么maven也有相应的配置来修改。
1 | my-app |
2.maven坐标
- groupId:公司+项目,,eg:
org.springframework.boot
- artifactId: 项目下的模块,eg:
spring-boot-starter-web
- version
- packaging: jar, war等
- classfier: 附属构件,由附加的插件生成,比如
spring-boot-starter-web-2.0.4.RELEASE-javadoc.jar
3.maven的组合和继承
组合
为了快速构建多个模块
1 | <modules> |
继承
为了复用配置
1 | <parent> |
4.dependency
依赖构造的是有向无环图。
1 | <project> |
scope
- compile: 编译、测试、运行都有效
- test: 只对测试有效,eg:
junit
- provided: 编译和测试有效,运行时无效,eg:
servlet-api.jar
- runtime: 测试和运行有效,编译时无效: eg:
JDBC驱动
- system: 和provided一样,只是依赖时必须用systemPath显示指定依赖路径,而不是由maven解析,是与本机绑定:
1
2
3
4
5
6
7<dependency>
<groupId></groupId>
<artifactId></artifactId>
<version></version>
<scope>system</scope>
<systemPath>/xxx/xx.jar</systemPath>
</dependency> - import: [import范围](### import范围)
传递性依赖
如果:A -> B -> C
那么:A–> C
传递性依赖范围影响
compile | test | provided | runtime | |
---|---|---|---|---|
compile | compile | - | - | runtime |
test | test | - | - | test |
provided | provided | - | provided | provided |
runtime | runtime | - | - | runtime |
依赖路径
- 最近优先:
A->B->C->X(1.0)、A->D->X(2.0)
- 路径长度相同,先声明的优先(2.0.8之后):
A->B->Y(1.0)、A->C->Y(2.0)
可选依赖(optional)
如果,A->B、B->X(可选)、B->Y(可选),那么A将不会依赖X和Y
但是不建议使用,因为OOP的单一职责原则。
dependencyManagement
依赖管理:Maven提供的dependencyManagement元素既能让子模块继承到父模块的依赖配置,又能保证子模块依赖使用的灵活性。在dependencyManagement元素下的依赖声明不会引入实际的依赖,不过它能够约束dependencies下的依赖使用。
啥意思?看下面解释:
依赖管理是在继承中使用的
假如父pom声明了一些依赖:但是这并不会给父pom引入依赖
1
2
3
4
5
6
7<dependencyManagement>
<dependency>
<groupId>A</groupId>
<artifactId>a</artifactId>
<version>xx</version>
</dependency>
</dependencyManagement>子pom如果引入了a依赖,那么不需要声明版本,因为父pom里已经有了:
1
2
3
4<dependency>
<groupId>A</groupId>
<artifactId>a</artifactId>
</dependency>如果子pom不声明a依赖,那么a就不会被引入
笔者强烈推荐这种写法,虽然也存在一定冗余,但是会降低冲突的概率。
import范围
import范围的依赖只在dependencyManagement元素下才有效果,使用该范围的依赖通常指向一个POM,作用是将目标POM中的dependencyManagement配置导入并合并到当前POM的dependencyManagement元素中。
比如,A模块(假如groupId为A,artifactId为a)的pom的内容如下:
1 | <dependencyManagement> |
B模块的pom想复用A的pom里的dependencyManagement的依赖,除了复制和继承,还可以使用import导入:
1 | <dependencyManagement> |
5.插件
maven的核心功能只有几兆,生命周期的功能都是由插件完成的,需要时会下载。
插件目标
因为很多任务背后有很多可以复用的代码,因此,这些功能聚集在一个插件里,每个功能就是一个插件目标。
maven-dependency-plugin
有十多个目标,每个目标对应了一个功能,常见的插件目标为dependency:analyze
、dependency:tree
和dependency:list
。
这是一种通用的写法,冒号前面是插件前缀,冒号后面是该插件的目标。类似地,还可以写出compiler:compile
(这是maven-compiler-plugin
的compile
目标)和surefire:test
(这是maven-surefire-plugin
的test
目标)。
插件配置
命令行配置: -Dkey=value
, 实际上是java自带的系统属性方式
1 | $ mvn install -Dmaven.test.skip=true |
POM文件全局配置
1 | <plugin> |
插件帮助描述
mvn help:describe -Dplugin=xxx [-Dgoal=xxx] [-Ddetail]
1 | $ mvn help:describe -Dplugin=org.apache.maven.plugins:maven-compiler-plugin |
6.常用maven命令
执行测试:mvn clean test
打包任务:jar:jar
安装任务:install:install
查看依赖
看3个地方:都是冒号分隔的
- 依赖名
- 中间的版本号
- 最后的依赖范围
1 | $ mvn dependency:list | grep springframework |
查看依赖树,层级关系(第一依赖、第二依赖…)
1 | $ mvn dependency:tree |
分析依赖
1 | $ mvn dependency:analyze |
接下来看其输出,着重关注警告。
Used undeclared dependencies found
:
指项目中使用到的,但是没有显式声明的依赖,这里是spring-context。这种依赖意味着潜在的风险,当前项目直接在使用它们,例如有很多相关的Java import声明,而这种依赖是通过直接依赖传递进来的,当升级直接依赖的时候,相关传递性依赖的版本也可能发生变化,这种变化不易察觉,但是有可能导致当前项目出错。例如由于接口的改变,当前项目中的相关代码无法编译。这种隐藏的、潜在的威胁一旦出现,就往往需要耗费大量的时间来查明真相。因此,显式声明任何项目中直接用到的依赖。
1 | [WARNING] Used undeclared dependencies found: |
Unused declared dependencies
: 意指项目中未使用的,但显式声明的依赖。需要注意的是,对于这样一类依赖,我们不应该简单地直接删除其声明,而是应该仔细分析。由于dependency:analyze只会分析编译主代码和测试代码需要用到的依赖,一些执行测试和运行时需要的依赖它就发现不了。当然,有时候确实能通过该信息找到一些没用的依赖,但一定要小心测试。
1 | [WARNING] Unused declared dependencies found: |
pluginManagement
和dependencyManagement类似。
仓库
为了满足一些复杂的需求,Maven还支持更高级的镜像配置:
<mirrorOf>*</mirrorOf>
:匹配所有远程仓库。<mirrorOf>external:*</mirrorOf>
:匹配所有远程仓库,使用localhost的除外,使用file://
协议的除外。也就是说,匹配所有不在本机上的远程仓库。<mirrorOf>repo1,repo2</mirrorOf>
:匹配仓库repo1和repo2,使用逗号分隔多个远程仓库。<mirrorOf>*,!repo1</mirrorOf>
:匹配所有远程仓库,repo1除外,使用感叹号将仓库从匹配中排除。
1 | <mirrors> |
maven生命周期
Maven拥有三套相互独立的生命周期,它们分别为clean、default和site。
- clean生命周期的目的是清理项目
- default生命周期的目的是构建项目
- site生命周期的目的是建立项目站点。
clean
- pre-clean执行一些清理前需要完成的工作。
- clean清理上一次构建生成的文件。
- post-clean执行一些清理后需要完成的工作。
default
- validate
- initialize
- generate-sources
- process-sources处理项目主资源文件。一般来说,是对src/main/resources目录的内容进行变量替换等工作后,复制到项目输出的主classpath目录中。
- generate-resources
- process-resources
- compile编译项目的主源码。一般来说,是编译src/main/java目录下的Java文件至项目输出的主classpath目录中。
- process-classes
- generate-test-sources
- process-test-sources处理项目测试资源文件。一般来说,是对src/test/resources目录的内容进行变量替换等工作后,复制到项目输出的测试classpath目录中。
- generate-test-resources
- process-test-resources
- test-compile编译项目的测试代码。一般来说,是编译src/test/java目录下的Java文件至项目输出的测试classpath目录中。
- process-test-classes
- test使用单元测试框架运行测试,测试代码不会被打包或部署。
- prepare-package
- package接受编译好的代码,打包成可发布的格式,如JAR。
- pre-integration-test
- integration-test
- post-integration-test
- verify
- install将包安装到Maven本地仓库,供本地其他Maven项目使用。
- deploy将最终的包复制到远程仓库,供其他开发人员和Maven项目使用。
site
- pre-site执行一些在生成项目站点之前需要完成的工作。
- site生成项目站点文档。
- post-site执行一些在生成项目站点之后需要完成的工作。
- site-deploy将生成的项目站点发布到服务器上。
生命周期命令
就是各个生命周期的命令组合使用:
1 | $ mvn clean |
实践操作
写插件实践
自己写maven插件测试插件:maven-invoker-plugin
打包时指定主类
当直接打包运行时会出现找不到主类问题:
1 | $ java -jar target/test-maven-main-1.0-SNAPSHOT.jar |
原因是META-INF里没有主类信息;
这时候增加插件解决:
1 | <build> |
结果:
1 | $ mvn clean package |