转自https://www.cnblogs.com/wangweiNB/p/5261409.html
一、maven简介
1、我们每天除了编写源代码以外,有相当一部分时间花在了编译、单元测试、生成报告、打包和部署等繁琐的且不起眼的工作上,这就是构建.maven是一个异常强大的构建工具,能帮我们自动化构建过程,从清理、编译、测试到生成报告,再到打包部署.
2、几乎所有的Java应用都会用到第三方类库,随着类库的增多,版本不一致,版本冲突,依赖臃肿等问题接踵而来,针对这些问题maven提供了优秀的解决方案,它通过一个坐标系统准确定位每一个构建,为这个类库世界引入了经纬,我们可以借助它来有序管理依赖.
二、maven安装配置
1、首先安装jdk.
2、下载解压apache-maven-3.0-bin.tar.gz. 命令:
tar -xzf apache-maven-3.0-bin.tar.gz
3、推荐在安装目录旁平行的创建一个符号链接,方便日后的升级,不用每次都更新环境变量,只需更新符号链接指向新的版本maven即可.命令:
ln -s apche-maven-3.0 apache-maven
4、设置maven环境变量 略.
5、检查Maven安装
mvn -v
6、设置HTTP代理,如果公司基于安全考虑,需要代理访问英特网,就需要为Maven配置HTTP代理,才能访问外部仓库,已下载所需资源,编辑M2_HOME/conf/settings.xml中的proxies标签,略,
7、设置MAVEN_OPTS环境变量值为 - Xms128m - Xmx512m,默认的可以可用内存往往不能满足maven的需要.
8、配置用户范围的settings.xml,~/.m2下也有一个settings.xml,建议平常修改它,这样Maven升级时,无需再拷贝它,感觉怎么都行.
9、修改IDE默认的maven,编辑Windows->Perferences->Installation,去掉Embedded Maven,然后Add选择M2_HOME安装目录勾选.
三、Maven使用入门
1、编写POM(定义项目的基本信息,描述项目如何构建,声明项目依赖等).
4.0.0
com.wangwei
hello-world
1.0-SNAPSHOT
Maven Hello World Project
2、编写主代码
1>项目的最代码会被打包到最终的jar中,而测试代码只在测试时用到,不会被打包,主代码位于src/main/java目录,我们遵循约定在该目录下创建文件com/wangwei/helloworld/HelloWorld.java,程序打印一个字符串,这里注意一般来说Java类的包名应该基于项目的groupId和artifactId,这样清晰复合逻辑,方便找Java类.
2>运行mvn clean compile进行编译,clean清理输出目录target/,compile编译项目主代码.
3、编写测试代码
1>修改POM如下:
4.0.0
com.wangwei
hello-world
1.0-SNAPSHOT
Maven Hello World Project
junit
junit
4.7
test
有了这段声明,Maven就能自动从中央仓库下载junit-4.7.jar,scope为依赖范围,test表示只对测试有效,主代码中import junit会报错,不声明scope默认是compile,对主代码测试代码都有效.
2>Maven项目默认的测试代码目录src/test/java,在该目录下创建HelloWorld打印一段话,约定测试类都已Test结尾,方法都已test开头.
3>执行mvn clean test命令,maven实际执行的不止这俩个任务,还有clean:clean、resources:resources、compiler:compile、resources:testResources以及compiler:testCompile,也就是在test执行之前会自动执行项目主资源处理,主代码编译,测试资源处理,测试代码编译等工作,这是maven生命周期的特性,后面详解;从输出还可以看到maven从中央仓库下载了junit-4.7.pom和junit-4.7.jar到本地仓库(~/.m2/respository)中,共所有maven项目使用;从输出还看到在执行compiler:testCompile任务失败了,这是由于历史原因,maven核心插件之一的compiler默认只支持Java 1.3,需要配置支持1.5,代码如下(也可在settings.xml中进行全局设置):
...
org.apache.maven.plugins
maven-compiler-plugin
1.7
4、打包和运行
1>HelloWorld中POM中没有指点打包类型默认是jar,执行mvn clean package进行打包,类似的maven在打包之前会执行编译,测试等操作,这里看到jar:jar任务负责打包,实际就是jar插件的jar目标将项目的主代码打包成一个hello-world-1.0-SNAPSHOT.jar文件,该文件位于target/输出目录中,还可以根据finalName定义该文件的名称.
2>我们得到了项目的输出,如果有需要的话,就可以复制这个jar文件到其它项目的ClassPath中从而使用HelloWorld类,但是,其他项目怎么直接引用这个jar呢,还需要一个步骤mvn clean install,从输出可以看到该任务将输出的jar安装到 了maven的本地仓库中,这样其他项目就可使用了.类似的install之前会执行package.
3>到目前为止还没有运行项目,HelloWold类有一个main方法,默认打包的jar是不能直接运行的,因为带有main方法的类信息不会添加到manifest中(jar文件的META-INF/MANIFEST_MF中无法看到Main-Class一行),为了生成可执行的jar,需要借助maven-shade-plugin插件,配置如下(
org.apache.maven.plugins
maven-shade-plugin
2.3
package
shade
com/wangwei/helloworld/HelloWorld.java
执行Java -jar target\hello-world-1.0.SNAPSHOT.jar可以正常看到输出.
5、Archetype插件可以根据我们提供的信息创建项目的骨架,执行mvn archetype:generate即可,实际开发使用IDE创建Archetype.
6、m2eclipse简单使用,导入maven项目,创建maven项目,运行maven构建,略.
四、坐标和依赖
1、简单依赖配置groupId、artifactId、version、packaging、scope略.
2、传递性依赖,例:account-mail中有个spring-core依赖,spring-core里又有一个commons-logging依赖,那么commons-logging就是account-mail的一个传递性依赖,有传递性依赖机制,在使用spring framework的时候就不用考虑它依赖了什么,也不用担心引入多余的依赖,Maven会解析各个直接的依赖POM,并将那些必要的间接依赖,以传递性依赖的形式引入到当前的项目中.
3、依赖调解,maven引入传递性依赖机制,大大简化和方便了依赖声明,另一方面我们只需要关系直接依赖,而不用考虑这些直接依赖会引入什么传递性依赖,但是当传递性依赖造成问题的时候,就需要清楚的知道该传递性依赖是从那条依赖路径引入的.例如:A->B->X(1.0);A->X(2.0),两条路径上有两个不同版本的X,那么哪个X会被maven解析使用呢,Maven依赖调解的第一原则:路径最近者优先,如果路径相同,第二原则:第一声明者优先,也就是在POM中的声明顺序.
4、可选依赖
5、排除依赖
org.unitils
unitils-testng
${unitils.version}
test
//exclusino可以有多个
junit
junit
6、使用maven属性归类依赖,其实就是把版本一致的抽出来统一定义,例spring framework:
3.2.8.RELEASE
org.springframework
spring-context
${org.springframework.version}
runtime
org.springframework
spring-orm
${org.springframework.version}
五、仓库
1、坐标和依赖是任何一个构建在maven世界中的逻辑表示方式;而构建的物理表示方式是文件,maven通过仓库来统一管理这些文件.
2、何为maven仓库,比如我们一台服务器有几十个项目,这些项目都用到了log4j,struts2等jar,在非maven项目中我们往往能发现lib的目录,各个项目的lib存在大量的重复,得益于坐标机制,maven项目中使用任何一个构建的方式都是完全相同的,再次基础上,maven可以在某个位置统一存储所有maven项目共享的构建,这个统一的位置就是仓库,实际的maven项目将不在各自存储其依赖文件,他们只需要声明这些依赖的坐标,在需要的时候,maven会自动根据坐标找到仓库中的构建,并使用它们.
3、仓库的布局,也就是jar存储路径与坐标的对应关系,如下:groupId/artifactId/version/artifactId-version.packaging.
4、仓库的分类,本地仓库和远程仓库,maven寻找构建时首先查看本地,有则直接使用,没有则去远程仓库查找,发现就下载到本地仓库使用,如果远程也没有则报错.中央仓库是Maven核心自带的远程仓库,私服是另一种特殊的远程仓库,是为了节省带宽和时间在局域网内架设的一个私有仓库服务器,内部的项目还能部署到私服上供其他项目使用.除了中央仓库和私服,还有很多公开的远程仓库.
本地仓库
1、本地仓库默认位置,windows:C:\users\wangwei.m2\repository,Linux:/home/wangwei/.m2/repository/,linux(.)开头文件隐藏,ls -a查看,可以自定义本地仓库目录地址,编辑~/.m2/settings.xml,默认settings.xml是不存在的,需要从$M2_HOME/conf/settings.xml复制过去,再进行编辑,建议这么做,而不是直接修改全局目录的settings.xml.
2、一个构建只有在本地仓库中之后,才能被其它maven项目使用,构建进入本地仓库的两种方式,一是从远程仓库下载,二是将本地项目安装到本地仓库,mvn clean install命令将项目的构建输出安装到本地仓库.
3、本地仓库类似书房只有一个,远程仓库类似书店,有很多.
中央仓库
中央仓库是一个默认的远程仓库,maven的安装文件自带了中央仓库的配置,$M2_HOME/lib/maven-model-builder-3.0.jar中:
central
Maven Repository Switchboard
default
http://repo1.maven.org/maven2
false
私服
1、私服是一种特殊的远程仓库,它是架设在局域网内的仓库服务,私服代理广域网的远程仓库,共局域网的maven用户使用,当需要下载构件的时候,它会先从私服请求,如果私服不存在,再从远程下载,缓存在私服上之后,再为maven的下载提供服务,一些无法从外部仓库下载的构件也能从本地上传到私服上供大家使用.
2、私服的好处:节省自己的网络带宽,加速maven构件,部署第三方构件,提高稳定性,降低中央仓库的负荷.
远程仓库的配置
1、有时默认的中央仓库无法满足项目的需求,可能在JBoss Maven仓库,POM中配置该仓库:
jobss
JBoss Repository
default
http://repository.jboss.com/maven2/
true
false
对于releases和snapshots来说,除了enabled,还有两个元素updatePolicy和checksumPolicy,updatePolicy配置远程仓库检查更新的频率,默认值为daily(每天一次),还有nerver、always(每次构件都检查更新)、inteval:X(每隔X分钟检查一次);checksumPolicy用来配置maven检查检验和文件的策略,略.
远程仓库的认证
大部分远程仓库无需认证就可以访问,但是有时候出于安全方面的考虑,我们需要提供一些认证信息才能访问一些远程仓库.配置认证信息和配置仓库信息不同,认证信息必须配置在settings.xml文件中.假设为一个id为my-proj的仓库配置认证信息,编辑settings.xml文件如下:
...
my-proj //这个id必须和POM中需要认证的repository元素id一致
admin
password
...
部署至远程仓库
1、私服的一大作用是部署第三方构件,编辑pom.xml,配置distributionManagement:
pro-release
Proj Release Repository
http://localhost:8081/nexus/content/repositories/RestBus-Releases
pro-snapshot
Proj Snapshot Repository
http://localhost:8081/nexus/content/repositories/RestBus-Snapshots
2、往远程仓库部署构件的时候,往往需要认证,配置已讲,注意id一致即可,配置正确后,运行命令:mvn clean deploy,maven就会将输出构件部署到私服,如果是快照就部署到快照仓库,发布版就部署到发布版仓库.
镜像
1、如果仓库X可以提供仓库Y存储的所有内容,那么X就是Y的一个镜像,例,中央仓库在中国有镜像,由于地理文职的因素,这个镜像能提供比中央仓库更快的服务,因此,可以配置maven使用该镜像来替代中央仓库.编辑settings.xml:
…
maven-net-cn
Maven China Mirror
http://maven.net.cn/content/groups/public/
central //中央仓库id
…
任何对于中央仓库的请求都会转发至该镜像,用户也可以用同样的方式来配置其它仓库的镜像.
2、关于镜像的一个更为常见的用法是结合私服,由于私服是代理任何外部公用仓库的,因此私服就是所有仓库的镜像,编辑settings.xml:
...
internal-repository
Internal Repository Manager
http://192.168.1.100/maven2
*
...
仓库搜索服务-也就是如何查找需要的依赖,略.
六、生命周期和插件
maven使用中,命令行的输入就对应了生命周期,maven的生命周期是抽象的,其实际行为都是插件来完成的.如mvn package命令表示执行默认生命周期阶段package,这个任务由maven-jar-plugin完成,生命周期和插件协调工作,密不可分.
何为生命周期
1、maven生命周期就是对所有构件过程进行抽象和统一,这个生命周期包含了项目的清理、初始化、编译、测试、打包、验证、部署、站点生成等几乎所有的构建过程,也即是说几乎所有的项目构建,都能映射到这样一个生命周期上.maven的生命周期是抽象的本生不做任何实际的工作,实际的任务由插件来完成,这种思想与模板方法设计模式相似,这样既能保证算法有足够的可扩展性,又能严格控制算法的整体结构.
2、每个构建步骤都可以绑定一个或者多个插件行为,maven为大多数构建步骤编写并绑定了默认插件,例如针对编译的maven-compiler-plugin,针对测试的mven-surefire-pulgin,虽然大多数时候用户感觉不到他们的存在,如果有特殊需要,可以配置插件定制构件行为,甚至自己编写插件.
三套生命周期
1、maven有三套相互独立的生命周期,clean、default和site,clean目的是清理项目,default的目的是构建项目,site目的是建立项目站点.每个生命周期都是相互独立的,且每个生命周期都包含一些阶段,用户可以仅仅调用clean生命周期的某个阶段,而不会对其它生命周期产生任何影响,例如,用户调用default生命周期的compile阶段不会触发clean生命周期的任何阶段,反之亦然.
2、clean生命周期包含阶段:pre-clean,clean,post-clean;default生命周期含重要阶段:validate,initialize,process-sources,compile,process-test-sources,test-compile,test,package,install,deploy;site生命周期包含阶段:pre-site,site,post-site,site-deploy.
命令行与生命周期
命令行执行maven任务的最主要方式就是调用maven的生命周期阶段,一个生命周期的阶段是有前后依赖关系的,例如下常用命令:
1>mvn clean,该命令调用clean生命周期的clean阶段,实际执行了clean生命周期的pre-clean和clean阶段.
2>mvn test,调用default生命周期的test阶段,实际执行了default生命周期的validate、initialize直到test所有阶段.
3>mvn clean install,调用clean生命周期的clean阶段和default生命周期的install阶段,实际执行了clean生命周期的pre-clean和clean阶段以及default生命周期的validate到install所有阶段,这是一个很好的实践.
插件目标
maven的生命周期和插件相互绑定,用以完成实际的构建任务,实际是生命周期和插件的目标相互绑定,maven的核心分发包只有不到3MB,maven会在需要的时候下载插件.maven有大量的内置绑定,以及基本用不到的自定义绑定,略,知道即可.
插件解析机制
为了方便使用,maven不需要用户提供插件坐标信息,就可以解析得到正确的插件,这是一把双刃剑,与依赖一样,插件构建同样基于坐标存储在maven仓库中,在需要的时候maven会从本地仓库寻找插件,如果不存在,则从远程仓库查找,找到后下载到本地.同依赖一样,maven内置了插件远程仓库配置:
central
Maven plugin
htpp://repo1.maven.org/maven2
default
true
false
七、聚合和继承
当maven用到实际项目中时,需要将项目分成不同的模块,maven的聚合特性能将项目的各个模块聚合在一起构建(也就是一次构建两个项目,而不是到两个模块下分别执行mvn命令),而继承特性能帮助抽取各个模块相同的依赖和配置,在简化POM的同时,还能促进各个模块配置的一致性.
聚合
1、为了能一条命令就构件account-email和account-persist两个模块,我们需要创建一个额外的account-aggregater模块,通过这个模块构建整个项目的所有模块,聚合模块仅仅是帮助聚合其它模块的构建工具,它本身并无实际的内容,account-aggregator本身也是个 Maven项目,它的目录结构和POM如下:
//父子关系
account-aggregator
--pom.xml
--account-email
----src
----pom.xml
--account-persist
----src
----pom.xml
4.0.0
com.juvenxu.mvnbook.account
account-aggregator
1.0.0-SNAPSHOT
pom
Account Aggregator
account-email
account-persist
上述POM的groupId与其它两个模块一样,版本也一样,artifactId为自己的,packaging必须为pom.modules是聚合的核心配置,这里每个module的值都是一个当前pom的相对目录.
2、聚合模块和其它模块的目录结构可以是父子关系或者平行关系.
account-aggregator
--pom.xml
account-email
--src
--pom.xml
account-persist
--src
--pom.xml
聚合模块对应的POM为:
../account-email
../account-captcha
3、测试,聚合模块运行mvn clean install,可以看到maven解析聚合模块的POM,分析要构建的模块,并计算一个反应堆的构建顺序,然后根据这个顺序依次构建各个模块.
继承
1、多模块maven项目还有一个问题,那就是多个模块的POM有很多相同的配置,maven有继承的机制,让我们抽取重复的配置.
2、在account-aggregator下创建一个名为account-parent的除account-aggregator之外的所有模块的父模块,它的POM如下:
4.0.0
com.juvenxu.mvnbook.account
account-parent
1.0.0-SNAPSHOT
pom
Maven Account-Parent Project
这个POM使用了和其它模块一致的groupId和version,artifactId为自己的,它的packaging也必须为pom,由于它只是为了帮助消除重复配置,本身也没有实际内容,有了父模块,account-email的POM修改如下:
4.0.0
//下面前三个元素指定父模块坐标
com.juvenxu.mvnbook.account
account-parent
1.0.0-SNAPSHOT
../account-parent/pom.xml
account-email
Maven Account-Email Project
...
...
这个POM没有声明groupId和version,实际是从父模块继承了这两个元素,这就消除了一些重复的配置,同理account-persist也这么配置,最后还需要将account-parent加入到account-aggregator:
4.0.0
com.juvenxu.mvnbook.account
account-aggregator
1.0.0-SNAPSHOT
pom
Account Aggregator
account-parent
account-email
account-persist
3、除了上面groupId和version可以被继承,依赖也是可以被继承的,两个子模块中都有的spring,junit的依赖等,可以将这些放到父模块中,简化配置,但是有个问题,现在的这两个模块是需要spring这些依赖的,但是以后新加的子模块就不一定需要这个依赖,那也让它强制加这些依赖是不合理的,Maven提供的dependencyManagement元素既能让子模块继承到父模块的依赖配置,又能保证子模块依赖使用的灵活性,在dependencyManagement元素下的依赖声明不会引入实际的依赖,不过它能够约束dependencies下的依赖使用,如在account-parent中加入dependencyManagement配置:
4.0.0
com.juvenxu.mvnbook.account
account-parent
1.0.0-SNAPSHOT
pom
Maven Account-Parent Project
2.5.6
4.10
org.springframework
spring-core
${springframework.version}
org.springframework
spring-beans
${springframework.version}
junit
junit
${junit.version}
test
继承了dependencyManagement的account-email的POM:
4.0.0
com.juvenxu.mvnbook.account
account-parent
1.0.0-SNAPSHOT
account-email
Maven Account-Email Project
1.4.1
1.3.1b
org.springframework
spring-core
org.springframework
spring-beans
junit
junit
javax.mail
mail
${javax.mail.version}
com.icegreen
greenmail
${greenmail.version}
test
上述POM中的依赖配置只有groupId和artifactI的,省去了version,junit还省去了scope,似乎没有减少太多的配置,但还是强烈建议这么做,dependencyManagement中统一项目依赖的版本,这样不会发生多个子模块使用依赖版本不一致的情况,降低依赖冲突的记几率.ps:1、如果子模块不声明依赖的使用,则dependencyManagement中声明的依赖也不会引入.2、
4、类似的maven也提供了pluginManagement元素管理插件,该元素中配置的依赖不会造成实际的插件调用行为,当子模块POM中配置了真正的plugin元素,并且其groupId和artifactId与pluginManagement中配置的插件一致时,pluginManagement的配置才会被继承到子模块,如下:
org.apache.maven.plugins
maven-compiler-plugin
2.3.2
1.5
org.apache.maven.plugins
maven-resources-plugin
2.5
UTF-8
继承了pluginManagement后的插件子模块配置:
org.apache.maven.plugins
maven-compiler-plugin
org.apache.maven.plugins
maven-resources-plugin
可以将所有用到的插件的版本在父模块中的pluginManagement中声明,子模块使用插件时不再配置版本信息,这么做可以统一项目的插件版本,避免插件不一致或者不稳定问题,更易于维护.
5、聚合和继承的关系
实际项目中,一个POM可能既是聚合POM又是父POM,这么做是为了方便,例将account-aggregator和account-parent合并一个,如下:
4.0.0
com.juvenxu.mvnbook.account
account-parent
1.0.0-SNAPSHOT
pom
Account-Parent
account-email
account-persist
account-captcha
account-service
account-web
2.5.6
4.10
org.springframework
spring-core
${springframework.version}
org.springframework
spring-beans
${springframework.version}
org.springframework
spring-context
${springframework.version}
org.springframework
spring-context-support
${springframework.version}
junit
junit
${junit.version}
test
org.apache.maven.plugins
maven-compiler-plugin
2.3.2
1.5
org.apache.maven.plugins
maven-resources-plugin
2.5
UTF-8
6、反应堆
1>对于多模块maven项目中,反应堆是指所有模块组成的一个构建结构,反应堆包含了各模块间的继承和依赖关系,从而能自动计算出合理的模块构建顺序.
2>反应堆的构建顺序,构建account-aggregator->account-parent->account-email->account-persist.
3>裁剪反应堆,略.