缘起
Maven于2001年诞生,Java是于1995年发布的,在这6年的时间里。做过一些开发的前辈们也许有众多不堪回首的往事,其中之一可能就是classpath hell
,有兴趣可以回顾这段历史。
包管理
我们在启动应用程序的的时候,可以看到如下的启动参数。
如果把这段参数复制出来的话就会发现,应用依赖的jar包都在这些参数里面。JVM加载类的方式是特别单一的,它只会通过
classpath
查找相关类的绝对路径,一层层找到对应的jar包,然后从jar包里找到对应的类。类的全限定类名唯⼀确定了⼀个类。麻烦的是如果你依赖的jar里面依赖了别的jar,你需要不断的在网络上找到这些jar包然后加入你的工程。这样增加了很多人力成本。
但是,因为全限定类名是类的唯⼀标识,当多个同名类同时出现在Classpath中,就是噩梦的开始。
同名的类的冲突要怎么解决掉,这就是mvn的包管理需要做的事情。
maven的包管理系统
maven的包管理有两个系统
1.坐标系统
• 通过唯⼀的坐标定位包,也就是公有仓库和私有仓库以及代理仓库等。
• 坐标可读性好、简单⽅便
• 语义化版本,⽅便⾃动化⼯具处理
• ⼀旦发布不可修改,实现稳定的构建
2.元数据系统
• pom存储这个jar包的传递性依赖关系
通过这两个系统,节约了资源,规范了流程,大幅度解决的包冲突的问题。
对于元数据系统而言,处理包冲突仅仅使用依赖树是不够用的。
在这个基础上,maven还定义了一个原则:绝对不允许最终classpath出现同名不同版本的jar包。
解决冲突的原则有两种:
- 最近原则
即使用不同版本的同一个方法,在被调用的时候会选择在依赖树靠前的位置的类。 - 最先声明原则
注: 最先声明的。
常见的包冲突异常如下: - AbstractMethodError
- NoClassDefFoundError
- ClassNotFoundException
- LinkageError
依赖的scope
在引入pom文件包依赖的时候,经常能看到scope
关键字,如下:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
它指明了构建的4个步骤:
- compile : 生产代码的依赖
- test:只有测试代码的依赖,例如测试框架JUnit
- provided:编译时需要,但是运⾏时由容器提供,典型如
Servlet API - runtime:编译时候不需要,运⾏时候需要,典型如mysql,connector等JDBC的包
maven本地仓库
maven本地仓库位于~/.m2
目录下,在找依赖包的时候,先从本地仓库中找,找到了就是用,找不到就去中央仓库下载。
maven的生命周期
maven的生命周期maven的生命周期
- default
- clean
- site
生命周期中,一般使用的都是default
,在maven的运行机制中,可以通过命令行指定要运行的阶段,这样,从头到指定阶段的阶段都会被运行一遍。
插件
插件是maven的灵魂,实际上就是一段代码。插件中可以声明多个目标,也可以绑定到多个阶段,执行多次
- mvn dependency:tree
- mvn help:describe -Dplugin=surefire
- mvn surefire:test
- mvn flyway:migrate
例如可以使用之上的例子直接调用插件。如果插件的groupId不指定,则默认org.apache.maven.plugins。如果version不指定,则可从父POM继承。如果groupId/artifactId不指定,可按照prefix调用。
以下是自己写的一个maven插件(使用详情介绍见README):
https://github.com/wangzhaoning/add-local-jar-maven-plugin
Maven的多模块
为什么要进行多模块呢?
可以完成项目的高内聚低耦合,也可以提升系统性能,节省维护成本。
需要注意的两点
- 多模块的组合
需要在父模块里声明子模块,然后使用mvn install
将所有子模块安装到本地。
<module></module>
- 继承
子模块中的pom可以继承父模块中的pom
注意:继承关系中,只能省略依赖的版本和groupId。不能省略本身的依赖关系。这样做的好处是,由父模块统一了版本,也简化了代码,提高可维护性。
网友评论