专栏电商日志财经减肥爱情
投稿投诉
爱情常识
搭配分娩
减肥两性
孕期塑形
财经教案
论文美文
日志体育
养生学堂
电商科学
头戴业界
专栏星座
用品音乐

Maven干货全篇共28232字预计阅读时间110分钟建议收

  文章结构图Maven简介
  Maven这个词可以翻译为知识的积累,也可以翻译为专家或内行。Maven是一个跨平台的项目管理工具。主要服务于基于Java平台的项目构建、依赖管理和项目信息管理。
  仔细想想,作为程序猿,除了编写源代码,我们每天有相当一部分时间花在了编译、运行单元测试、生成文档、打包和部署等繁琐且不起眼的工作上,这就是构建。
  如果我们现在还手工这样做,那成本也太高了,Maven是一款优秀的构建工具,让这一系列工作完全自动化,使得软件的构建可以像全自动流水线一样,只需要一条简单的命令,所有繁琐的步骤都能够自动完成,很快就能得到最终结果。
  Maven不仅是构建工具,还是一个依赖管理工具和项目信息管理工具。
  它提供了中央仓库,能帮我们自动下载构件。几乎任何Java应用都会借用一些第三方的开源类库,这些类库都可通过依赖的方式引入到项目中来。Maven还能帮助我们管理原本分散在项目中各个角落的项目信息,包括项目描述、开发者列表、版本控制系统地址、许可证、缺陷管理系统地址等。这些微小的变化看起来很琐碎,并不起眼,但却在不知不觉中为我们节省了大量寻找信息的时间。
  使用Maven还能享受一个额外的好处,即Maven对于项目目录结构、测试用例命名方式等内容都有既定的规整,只要遵循了这些成熟的规则,用户在项目间切换的时候就免去了额外的学习成本,可以说是约定优于配置。Maven入门编写POM
  Maven项目的核心是pom。xml。POM(ProjectObjectModel,项目对象模型)定义了项目的基本信息,用于描述项目如何构建,声明项目依赖,等等。
  现在先为HelloWorld项目编写一个最简单的pom。xml。首先创建一个名为helloworld的文件夹,打开文件夹,新建一个名为pom。xml的文件,其内容如下:lt;?xmlversion1。0encodingUTF8?projectxmlnshttp:maven。apache。orgPOM4。0。0xmlns:xsihttp:www。w3。org2001XMLSchemainstancexsi:schemaLocationhttp:maven。apache。orgPOM4。0。0http:maven。apache。orgmavenv400。xsdmodelVersion4。0。0modelVersiongroupIdcom。mk。studygroupIdhellowordartifactIdversion1。0SNAPSHOTversionnameMavenHelloWordProjectnameproject
  第一行是XML头,指定了该xml文档的版本和编码方式。紧接着是project元素,project是所有pom。xml的根元素,它还声明了一些POM相关的命名空间及xsd元素,虽然这些属性不是必须的,但使用这些属性能够让第三方工具(如IDE中的XML编辑器)帮助我们快速编辑POM。
  根元素下的第一个元素modelVersion指定了当前POM模型的版本,对于Maven2及Maven3来说,它只能是4。0。0。
  groupId定义了项目属于哪个组,这个组往往和项目所在的组织或公司存在关联。譬如在googlecode上建立了一个名为myapp的项目,那么groupId就应该是com。googlecode。myapp。
  artifactId定义了当前Maven项目在组中唯一的ID。
  顾名思义,version指定了HelloWorld项目当前的版本1。0SNAPSHOT。SNAPSHOT意为快照,说明项目还处于开发中,是不稳定的版本。
  最后一个name元素声明了一个对于用户更为友好的项目名称,虽然这不是必须的,但还是推荐为每个POM声明name,以方便信息交流。
  没有任何实际的Java代码,我们就能够定义一个Maven项目的POM,这体现了Maven的一大优点,它能让项目对象模型最大程度地与实际代码相独立,我们可以称之为解耦,或者正交性。这在很大程度上避免了Java代码和POM代码的相互影响。比如当项目需要升级版本时,只需要修改POM,而不需要更改Java代码;而在POM稳定之后,日常的Java代码开发工作基本不涉及POM的修改。编写主代码
  默认情况,Maven假设项目主代码位于srcmainjava目录,我们遵循Mavan的约定,创建该目录,然后在该目录下创建文件commkstudyhelloworldHelloWorld。java,其内容如下:packagecom。mk。study。helloworld;publicclassHelloWorld{publicStringsayHello(){returnHelloWorld;}publicstaticvoidmain(String〔〕args){System。out。print(newHelloWorld()。sayHello());}}
  首先,在绝大多数情况下,应该把项目主代码放到srcmainjava目录下(遵循Maven的约定),而无须额外的配置,Maven会自动搜该目录找到项目主代码。其次,该Java类的包名是com。mk。study。helloworld,这与之前在POM中定义的groupId和artifactId相吻合。一般来说,项目中Java类的包名都应该基于项目的groupId和artifactId,这样更加清晰,更加符合逻辑,也方便搜索构件或者Java类。
  代码编写完毕后,使用Maven进行编译,在项目根目录下运行mvncleancompile会得到如下输出:INFO〕Scanningforprojects。。。〔INFO〕〔INFO〕com。mk。study:helloword〔INFO〕BuildingMavenHelloWordProject1。0SNAPSHOT〔INFO〕〔jar〕〔INFO〕〔INFO〕mavencleanplugin:2。5:clean(defaultclean)helloword〔INFO〕DeletingC:Desktophellowordarget〔INFO〕〔INFO〕mavenresourcesplugin:2。6:resources(defaultresources)helloword〔WARNING〕Usingplatformencoding(GBKactually)tocopyfilteredresources,i。e。buildisplatformdependent!〔INFO〕skipnonexistingresourceDirectoryC:Desktophellowordsrcmainresources〔INFO〕〔INFO〕mavencompilerplugin:3。1:compile(defaultcompile)helloword〔INFO〕Changesdetectedrecompilingthemodule!〔WARNING〕Fileencodinghasnotbeenset,usingplatformencodingGBK,i。e。buildisplatformdependent!〔INFO〕Compiling1sourcefiletoC:Desktophellowordargetclasses〔INFO〕〔INFO〕BUILDSUCCESS〔INFO〕〔INFO〕Totaltime:0。936s〔INFO〕Finishedat:20221231T21:45:3708:00〔INFO〕
  clean告诉Maven清理输出目录target,compile告诉Maven编译项目主代码,
  从输出中看到Maven首先执行了clean任务,删除target目录,默认情况下,Maven构建的所有输出都在target目录中;接着执行resources任务(未定义项目资源,暂且略过);最后执行compile任务,将项目主代码编译至targetclasses目录(编译好的类为commkstudyhelloworldHelloWorld。class)。编写测试代码
  测试代码与主代码不同,项目的主代码会被打包到最终的构件中(如jar),而测试代码只在运行测试时用到,不会被打包。默认情况下,测试代码目录是srctestjava。因此,在编写测试用例之前,应当先创建该目录。
  我们这里使用JUnit,首先需要为HelloWorld项目添加一个JUnit依赖:lt;?xmlversion1。0encodingUTF8?projectxmlnshttp:maven。apache。orgPOM4。0。0xmlns:xsihttp:www。w3。org2001XMLSchemainstancexsi:schemaLocationhttp:maven。apache。orgPOM4。0。0http:maven。apache。orgmavenv400。xsdmodelVersion4。0。0modelVersiongroupIdcom。mk。studygroupIdhellowordartifactIdversion1。0SNAPSHOTversionnameMavenHelloWordProjectnamedependenciesdependencygroupIdjunitgroupIdjunitartifactIdversion4。10versionscopetestscopedependencydependenciesproject
  代码中添加了dependencies元素,该元素下可以包含多个dependency元素以声明项目的依赖。Maven会自动访问中央仓库(http:repo1。maven。orgmaven2),下载需要的文件。
  上诉POM代码中还有一个值为test的元素scope,scope为依赖范围,若依赖范围为test则表示该依赖只对测试有效。测试代码中的importJUnit代码是没有问题的,但是如果在主代码中用importJUnit代码,就会造成编译错误。如果不声明依赖范围,那么默认值就是compile,表示该依赖对主代码和测试代码都有效。
  接下来编写测试类,在srctestjava目录下创建HelloWorldTest。java文件,内容如下:packagecom。mk。study。helloworld;importstaticorg。junit。Assert。assertEquals;importorg。junit。Test;publicclassHelloWorldTest{TestpublicvoidtestSayHello(){HelloWorldhelloWorldnewHelloWorld();StringresulthelloWorld。sayHello();assertEquals(HelloMaven,result);}}
  一个典型的单元测试包含三个步骤:准备测试类及数据、执行要测试的行为、检查结果。
  上述样例首先初始化了一个要测试的HelloWorld实例,接着执行该实例的sayHello()方法并保存结果到result变量中,最后使用JUnit框架的Assert类检查结果是否为我们期望的HelloMaven。在JUnit3中,约定所有需要执行测试的方法都以test开头,这里使用了JUnit4,但仍然遵循这一约定。在JUnit4中,需要执行的测试方法都应该以Test进行标注。
  测试用例编写完毕之后就可以调用Maven执行测试,运行mvncleantest:〔INFO〕Scanningforprojects。。。〔INFO〕〔INFO〕com。mk。study:helloword〔INFO〕BuildingMavenHelloWordProject1。0SNAPSHOT〔INFO〕〔jar〕〔INFO〕〔INFO〕mavencleanplugin:2。5:clean(defaultclean)helloword〔INFO〕DeletingC:Desktophellowordarget〔INFO〕〔INFO〕mavenresourcesplugin:2。6:resources(defaultresources)helloword〔WARNING〕Usingplatformencoding(GBKactually)tocopyfilteredresources,i。e。buildisplatformdependent!〔INFO〕skipnonexistingresourceDirectoryC:Desktophellowordsrcmainresources〔INFO〕〔INFO〕mavencompilerplugin:3。1:compile(defaultcompile)helloword〔INFO〕Changesdetectedrecompilingthemodule!〔WARNING〕Fileencodinghasnotbeenset,usingplatformencodingGBK,i。e。buildisplatformdependent!〔INFO〕Compiling1sourcefiletoC:Desktophellowordargetclasses〔INFO〕〔INFO〕mavenresourcesplugin:2。6:testResources(defaulttestResources)helloword〔WARNING〕Usingplatformencoding(GBKactually)tocopyfilteredresources,i。e。buildisplatformdependent!〔INFO〕skipnonexistingresourceDirectoryC:Desktophellowordsrcestresources〔INFO〕〔INFO〕mavencompilerplugin:3。1:testCompile(defaulttestCompile)helloword〔INFO〕Changesdetectedrecompilingthemodule!〔WARNING〕Fileencodinghasnotbeenset,usingplatformencodingGBK,i。e。buildisplatformdependent!〔INFO〕Compiling1sourcefiletoC:Desktophellowordargetestclasses〔INFO〕〔INFO〕mavensurefireplugin:2。12。4:test(defaulttest)helloword〔INFO〕Surefirereportdirectory:C:DesktophellowordargetsurefirereportsTESTSRunningcom。mk。study。helloworld。HelloWorldTestTestsrun:1,Failures:0,Errors:0,Skipped:0,Timeelapsed:0。053secResults:Testsrun:1,Failures:0,Errors:0,Skipped:0〔INFO〕〔INFO〕BUILDSUCCESS〔INFO〕〔INFO〕Totaltime:3。061s〔INFO〕Finishedat:20221231T23:40:0208:00〔INFO〕
  我们看到compile:testCompile任务执行成功了,测试代码通过编译后在targettestclasses下生成了二进制文件,紧接着surefire:test任务运行测试,surefire是Maven中负责执行测试的插件,这里它运行测试用例HelloWorldTest,并输出测试报告,显示一共运行了多少测试,失败了多少,出错了多少,跳过了多少。显然,我们是测试通过了。这里记录一个问题
  当所有代码编写完成,执行mvncleantest命令的时候,并不是那么的顺序,maven报出了如下问题:
  〔INFO〕Scanningforprojects。。。
  〔INFO〕mavensurefireplugin:2。12。4:test(defaulttest)helloword
  〔INFO〕Surefirereportdirectory:C:Desktophellowordargetsurefirereports
  Downloadingfromaliyunmaven:https:maven。aliyun。comrepositoryspringorgapachemavensurefiresurefirejunit42。12。4surefirejunit42。12。4。pom
  〔INFO〕Failuredetected。
  〔INFO〕
  〔INFO〕BUILDFAILURE
  〔INFO〕
  〔INFO〕Totaltime:2。973s
  〔INFO〕Finishedat:20221231T23:52:3708:00
  〔INFO〕
  〔ERROR〕Failedtoexecutegoalorg。apache。maven。plugins:mavensurefireplugin:2。12。4:test(defaulttest)onprojecthelloword:Unabletogenerateclasspath:org。apache。maven。artifact。resolver。ArtifactResolutionException:Unabletogetdependencyinformationfororg。apache。maven。surefire:surefirejunit4:jar:2。12。4:FailedtoretrievePOMfororg。apache。maven。surefire:surefirejunit4:jar:2。12。4:Couldnottransferartifactorg。apache。maven。surefire:surefirejunit4:pom:2。12。4fromtoaliyunmaven(https:maven。aliyun。comrepositoryspring):Transferfailedforhttps:maven。aliyun。comrepositoryspringorgapachemavensurefiresurefirejunit42。12。4surefirejunit42。12。4。pom
  〔ERROR〕org。apache。maven。surefire:surefirejunit4:jar:2。12。4
  〔ERROR〕
  〔ERROR〕fromthespecifiedremoterepositories:
  〔ERROR〕aliyunmaven(https:maven。aliyun。comrepositoryspring,releasestrue,snapshotsfalse)
  〔ERROR〕Pathtodependency:
  〔ERROR〕1)dummy:dummy:jar:1。0
  〔ERROR〕
  〔ERROR〕:java。lang。RuntimeException:Unexpectederror:java。security。InvalidAlgorithmParameterException:thetrustAnchorsparametermustbenonempty
  〔ERROR〕〔Help1〕
  〔ERROR〕
  〔ERROR〕Toseethefullstacktraceoftheerrors,rerunMavenwiththeeswitch。
  〔ERROR〕RerunMavenusingtheXswitchtoenablefulldebuglogging。
  〔ERROR〕
  〔ERROR〕Formoreinformationabouttheerrorsandpossiblesolutions,pleasereadthefollowingarticles:
  〔ERROR〕〔Help1〕http:cwiki。apache。orgconfluencedisplayMAVENMojoExecutionException
  从〔ERROR〕信息中可以看出来,原因是因为无法下载org。apache。maven。surefire:surefirejunit4:jar、org。apache。maven。surefire:surefirejunit4:pom两个依赖文件。但是在阿里云仓库中可以搜到,阿里云有多个仓库:central、public、spring,但无论换成哪一个,即使仓库中有,也都无法下载。最后灵机一动,将下载不下来的jar、pom从网页中下载下来,然后copy到本地对应目录,再次编译,执行成功。
  对于以后遇到的一些无论是网络原因还是其他原因,重复几次Maven无法下载的文件,也可以通过直接到阿里云仓库中去下载,然后将其copy到本地仓库中相应的目录来解决。打包和运行jar包
  将项目进行编译、测试之后,下一个重要步骤就是打包(package)。在没有指定打包类型时,默认使用打包类型是jar。可以简单执行命令mvncleanpackage进行打包,之后看到如下输出:〔INFO〕Scanningforprojects。。。〔INFO〕〔INFO〕com。mk。study:helloword〔INFO〕BuildingMavenHelloWordProject1。0SNAPSHOT〔INFO〕〔jar〕〔INFO〕〔INFO〕mavencleanplugin:2。5:clean(defaultclean)helloword〔INFO〕DeletingC:Desktophellowordarget〔INFO〕〔INFO〕mavenresourcesplugin:2。6:resources(defaultresources)helloword〔WARNING〕Usingplatformencoding(GBKactually)tocopyfilteredresources,i。e。buildisplatformdependent!〔INFO〕skipnonexistingresourceDirectoryC:Desktophellowordsrcmainresources〔INFO〕〔INFO〕mavencompilerplugin:3。1:compile(defaultcompile)helloword〔INFO〕Changesdetectedrecompilingthemodule!〔WARNING〕Fileencodinghasnotbeenset,usingplatformencodingGBK,i。e。buildisplatformdependent!〔INFO〕Compiling1sourcefiletoC:Desktophellowordargetclasses〔INFO〕〔INFO〕mavenresourcesplugin:2。6:testResources(defaulttestResources)helloword〔WARNING〕Usingplatformencoding(GBKactually)tocopyfilteredresources,i。e。buildisplatformdependent!〔INFO〕skipnonexistingresourceDirectoryC:Desktophellowordsrcestresources〔INFO〕〔INFO〕mavencompilerplugin:3。1:testCompile(defaulttestCompile)helloword〔INFO〕Changesdetectedrecompilingthemodule!〔WARNING〕Fileencodinghasnotbeenset,usingplatformencodingGBK,i。e。buildisplatformdependent!〔INFO〕Compiling1sourcefiletoC:Desktophellowordargetestclasses〔INFO〕〔INFO〕mavensurefireplugin:2。12。4:test(defaulttest)helloword〔INFO〕Surefirereportdirectory:C:DesktophellowordargetsurefirereportsTESTSRunningcom。mk。study。helloworld。HelloWorldTestTestsrun:1,Failures:0,Errors:0,Skipped:0,Timeelapsed:0。067secResults:Testsrun:1,Failures:0,Errors:0,Skipped:0〔INFO〕〔INFO〕mavenjarplugin:2。4:jar(defaultjar)helloword〔INFO〕Buildingjar:C:Desktophellowordargethelloword1。0SNAPSHOT。jar〔INFO〕〔INFO〕BUILDSUCCESS〔INFO〕〔INFO〕Totaltime:2。673s〔INFO〕Finishedat:20230101T00:12:0708:00〔INFO〕
  类似地,Maven会在打包之前执行编译、测试等操作。这里看到jar:jar任务负责打包,实际上就是jar插件的jar目标将项目主代码打包成一个名为helloworld1。0SNAPSHOT。jar的文件。该文件也位于target输出目录中,它是根据artifactversion。jar规整进行命名的,如有需要,还可以使用finalName来自定义该文件的名称。
  如何才能让其他的Maven项目直接引用这个jar呢?还需要一个安装的步骤,执行mvncleaninstall:〔INFO〕Scanningforprojects。。。〔INFO〕〔INFO〕com。mk。study:helloword〔INFO〕BuildingMavenHelloWordProject1。0SNAPSHOT〔INFO〕〔jar〕〔INFO〕〔INFO〕mavencleanplugin:2。5:clean(defaultclean)helloword〔INFO〕DeletingC:Desktophellowordarget〔INFO〕〔INFO〕mavenresourcesplugin:2。6:resources(defaultresources)helloword〔WARNING〕Usingplatformencoding(GBKactually)tocopyfilteredresources,i。e。buildisplatformdependent!〔INFO〕skipnonexistingresourceDirectoryC:Desktophellowordsrcmainresources〔INFO〕〔INFO〕mavencompilerplugin:3。1:compile(defaultcompile)helloword〔INFO〕Changesdetectedrecompilingthemodule!〔WARNING〕Fileencodinghasnotbeenset,usingplatformencodingGBK,i。e。buildisplatformdependent!〔INFO〕Compiling1sourcefiletoC:Desktophellowordargetclasses〔INFO〕〔INFO〕mavenresourcesplugin:2。6:testResources(defaulttestResources)helloword〔WARNING〕Usingplatformencoding(GBKactually)tocopyfilteredresources,i。e。buildisplatformdependent!〔INFO〕skipnonexistingresourceDirectoryC:Desktophellowordsrcestresources〔INFO〕〔INFO〕mavencompilerplugin:3。1:testCompile(defaulttestCompile)helloword〔INFO〕Changesdetectedrecompilingthemodule!〔WARNING〕Fileencodinghasnotbeenset,usingplatformencodingGBK,i。e。buildisplatformdependent!〔INFO〕Compiling1sourcefiletoC:Desktophellowordargetestclasses〔INFO〕〔INFO〕mavensurefireplugin:2。12。4:test(defaulttest)helloword〔INFO〕Surefirereportdirectory:C:DesktophellowordargetsurefirereportsTESTSRunningcom。mk。study。helloworld。HelloWorldTestTestsrun:1,Failures:0,Errors:0,Skipped:0,Timeelapsed:0。042secResults:Testsrun:1,Failures:0,Errors:0,Skipped:0〔INFO〕〔INFO〕mavenjarplugin:2。4:jar(defaultjar)helloword〔INFO〕Buildingjar:C:Desktophellowordargethelloword1。0SNAPSHOT。jar〔INFO〕〔INFO〕maveninstallplugin:2。4:install(defaultinstall)helloword〔INFO〕InstallingC:Desktophellowordargethelloword1。0SNAPSHOT。jartoC:。m2repositorycommkstudyhelloword1。0SNAPSHOThelloword1。0SNAPSHOT。jar〔INFO〕InstallingC:Desktophellowordpom。xmltoC:。m2repositorycommkstudyhelloword1。0SNAPSHOThelloword1。0SNAPSHOT。pom〔INFO〕〔INFO〕BUILDSUCCESS〔INFO〕〔INFO〕Totaltime:2。660s〔INFO〕Finishedat:20230101T10:28:2308:00〔INFO〕
  打包之后又执行了安装任务install:install。从输出可以看到该任务将项目输出的jar安装到了Maven本地仓库。
  至此,我们已经体验了Maven最主要的命令:mvncleancompile、mvncleantest、mvncleanpackage、mvncleaninstall。执行test之前是会先执行compile的,执行package之前是会先执行test的,而类似地,install之前会执行package。运行jar包
  到目前为止,我们还没有运行过HelloWorld项目,别忘了,在HelloWorld。java类中是有一个main方法的。默认打包生成的jar是不能够直接运行的,因为带有main方法的信息不会添加到manifest中(打开jar文件中的METAINFMANIFEST文件,将无法看到MainClass一行)。ManifestVersion:1。0ArchiverVersion:PlexusArchiverBuiltBy:mkCreatedBy:ApacheMaven3。6。3BuildJdk:1。8。041
  为了生成可执行的jar文件,需要借助mavenshadeplugin,修改POM文件,配置该插件如下:lt;?xmlversion1。0encodingUTF8?projectxmlnshttp:maven。apache。orgPOM4。0。0xmlns:xsihttp:www。w3。org2001XMLSchemainstancexsi:schemaLocationhttp:maven。apache。orgPOM4。0。0http:maven。apache。orgmavenv400。xsdmodelVersion4。0。0modelVersiongroupIdcom。mk。studygroupIdhelloworldartifactIdversion1。0SNAPSHOTversionnameMavenHelloWorldProjectnamedependenciesdependencygroupIdjunitgroupIdjunitartifactIdversion4。10versionscopetestscopedependencydependenciesbuildpluginsplugingroupIdorg。apache。maven。pluginsgroupIdmavenshadepluginartifactIdversion3。2。1versionexecutionsexecutionphasepackagephasegoalsgoalshadegoalgoalsconfigurationtransformerstransformerimplementationorg。apache。maven。plugins。shade。resource。ManifestResourceTransformermainClasscom。mk。study。helloworld。HelloWorldmainClasstransformertransformersconfigurationexecutionexecutionspluginpluginsbuildproject
  我们配置了mainClass为com。mk。study。helloworld。HelloWorld,项目在打包时会将该信息放到MANIFEST中。现在执行mvncleaninstall,待构建完成之后打开target目录,可以看到helloworld1。0SNAPSHOT。jar和originalhelloworld1。0SNAPSHOT。jar,前者是带有MainClass信息的可运行jar,后者是原始的jar,在helloworld1。0SNAPSHOT。jar的MANIFEST。MF文件中可以看到,它包含了MainClass:com。mk。study。helloworld。HelloWorld一行信息:ManifestVersion:1。0ArchiverVersion:PlexusArchiverBuiltBy:mkCreatedBy:ApacheMaven3。6。3BuildJdk:1。8。041MainClass:com。mk。study。helloworld。HelloWorld
  进入target目录中,通过命令javajarhelloworld1。0SNAPSHOT。jar执行jar文件,可以看到控制台输出:hellowordargetjavajarhelloworld1。0SNAPSHOT。jarHelloMavenhellowordarget
  控制台输出HelloMaven,这正是我们所期望的。war包
  基于Java的Web应用,其标准的打包方式是war。war与jar类似,只不过它可以包含更多的内容,如JSP文件、Servlet、Java类、web。xml配置文件、依赖jar包、静态web资源(如HTML、CSS、JavaScript文件)等。
  其实对于Maven来说,jar与war只是打包方式的不同,只需要调整packaging元素值为war即可。我们主要是介绍Maven的用法,并不会创建一个web工程,主要介绍下war包的组成结构。projectgroupIdcom。mk。studygroupIdhelloworldartifactIdpackagingwarpackagingversion1。0SNAPSHOTversionnameMavenHelloWorldProjectnameproject
  一个典型的war文件会有如下目录结构:warMETAINFWEBINFclassesServletA。classconfig。propertieslibdom4j1。4。1。jarmain1。4。1。jarweb。xmlimgcssjsindex。htmlsample。jsp
  一个war包下至少包含两个子目录:METAINF和WEBINF。前者包含了一些打包元数据信息,后者是war包的核心,WEBINF下必须包含一个web资源表述文件web。xml,它的子目录classes包含所有该web项目的类,而另一个子目录lib则包含所有该web项目的依赖jar包,classes和lib目录都会在运行的时候被加入到Classpath中。除了METAINF和WEBINF外,一般的war包都会包含很多web资源,比如html或者jsp文件,还有一些文件夹比如img、css和js。
  web项目的类及资源文件同一般jar项目一样,默认位置都是srcmainjava和srcmainresources,测试类及测试资源文件的默认位置是srctestjava和srctestresources。web项目比较特殊的地方在于:它还有一个web资源目录,其默认位置是srcmainwebapp。一个典型的web项目的Maven目录结构如下:projectpom。xmlsrcmainjavaServletA。javaresourcesconfig。propertieswebappWEBINFweb。xmlimgcssjsindex。htmlsample。jsptestjavaresources
  在srcmainwebapp目录下,必须包含一个子目录WEBINF,该子目录还必须要包含web。xml文件。srcmainwebapp目录下的其他文件和目录包括html、jsp、css、JavaScript等,它们与war包中的web资源完全一致。
  war包中有一个lib目录包含所有依赖jar包,但Maven项目结构中没有这样一个目录,这是因为依赖都配置在POM中,Maven在用war方式打包的时候会根据POM的配置从本地仓库复制相应的jar文件。坐标和依赖依赖详解
  Maven定义了这样一组规整:世界上任何一个构件都可以使用Maven坐标唯一标识,我们只要提供正确的坐标元素,Maven就能找到对应的构件。而一些Maven坐标是通过一些元素定义的,他们是groupId、artifactId、version、packaging、classifier。如下:projectgroupIdcom。mk。studygroupIdhelloworldartifactIdpackagingwarpackagingversion1。0SNAPSHOTversionnameMavenHelloWorldProjectnameproject
  下面详细解释下各个坐标元素:groupId:定义当前Maven项目隶属的实际项目。首先,Maven项目和实际项目不一定是一对一的关系。比如SpringFramework这一实际项目,其对应的Maven项目会有很多,比如springcore、springcontext等。这是由于Maven中模块的概念,因此,一个实际项目往往会被划分成多个模块。其次,groupId不应该对应项目隶属的组织或公司。原因很简单,一个组织下会有很多实际项目,如果groupId只定义到组织级别,而后面我们会看到,artifactId只能定义Maven项目(模块),那么实际项目这个层将难以定义。最后,groupId的表达方式与Java包名的表达方式类似,通常与域名反向一一对应。artifactId:该元素定义实际项目中的一个Maven项目(模块),推荐的做法是使用实际项目名称作为artifactId的前缀。这样做的好处是方便寻找实际构件。在默认情况下,Maven生成的构件,其文件名会以artifactId作为开头,使用实际项目名称作为前缀之后,就能方便从一个lib文件夹中找到某个项目的一组构件。version:该元素定义Maven项目当前所处的版本。packaging:该元素定义Maven项目的打包方式。默认值是jar。classifier:该元素用来帮助定义构件输出的一些附属构件。如artifactIdversionjavadoc。jar、artifactIdversionsources。jar这样一些附属构件,包含了Java文档和源代码。注意,不能直接定义项目的classifier,因为附属构件不是项目直接默认生成的,而是由附加的插件帮助生成。
  项目构件的文件名是与坐标相对应的,一般的规则为artifactedIdversion〔classifier〕。packaging,〔classifier〕是可选的。依赖配置
  上面罗列了一些基本的依赖配置,其实一个依赖声明可以包含如下一些元素:projectdependenciesdenpendencygroupIdgroupIdartifactIdversionversiontypetypescopescopeoptionaloptionalexclusiongsexclusionexclusionexclusiongsdenpendencydependenciesprojecttype:依赖的类型,对应于项目坐标定义的packaging。大部分情况下,该元素不必声明,其默认值为jar。scope:依赖的范围。optional:标记依赖是否可选。exclusions:用来排除传递性依赖。依赖范围
  Maven在编译项目主代码的时候需要使用一套classpath,在编译和执行测试代码的时候会使用另外一套classpath,最后,实际运行Maven项目的时候,又会使用一套classpath。依赖范围就是用来控制依赖于三种classpath的关系,Maven有以下几种依赖范围:compile:编译依赖范围。如果没有指定,就会默认使用该依赖范围。使用此依赖范围的Maven依赖,对于编译、测试、运行三种classpath都有效。test:测试依赖范围。使用此依赖范围的Maven依赖,只对于测试classpath有效,在编译主代码或者运行项目的时候将无法使用此依赖。典型的例子是JUnit,它只有在编译测试代码及运行测试的时候才需要。provided:已提供依赖范围。使用此依赖范围的Maven依赖,对于编译和测试classpath有效,但在运行时无效。典型的例子是servletapi,编译和测试项目的时候需要该依赖,但在运行项目的时候,由于容器已经提供,就不需要Maven重复地引入一遍。runtime:运行时依赖范围。使用此依赖范围的Maven依赖,对于测试和运行classpath有效,但在编译主代码时无效。典型的例子是JDBC驱动实现,项目主代码的编译只需要JDK提供的JDBC接口,只有在测试或者运行项目的时候才需要实现上述接口的具体JDBC驱动。system:系统依赖范围。该依赖与三种classpath的关系,和provided依赖范围完全一致。但是,使用system范围的依赖时必须通过systemPath元素显示地指定依赖文件的路径。由于此类依赖不是通过Maven仓库解析的,而且往往与本机系统绑定,可能造成构件的不可移植,因此应该谨慎使用。import:导入范围依赖。该依赖范围不会对三种classpath产生实际的影响。
  上述除import以外的各种依赖范围与三种classpath的关系如下表:
  Scope
  编译classpath
  测试classpath
  运行时classpath
  例子
  compile
  Y
  Y
  Y
  springore
  test
  Y
  JUnit
  provided
  Y
  Y
  servletapi
  runtime
  Y
  Y
  JDBC驱动实现
  system
  Y
  Y
  本地的,Maven仓库之外的类库文件传递性依赖
  考虑一个基于SpringFramework的项目,如果不使用Maven,那么在项目中就需要手动下载相关依赖。由于SpringFramework又会依赖于其他开源类库,因此实际项目中往往会下载一个很大的如springframework2。5。6withdependencies。zip的包,这里包含了所有SpringFramework的jar包,以及所有它依赖的其他jar包。这么做往往就引入了很多不必要的依赖。另一种做法是只下载springframework2。5。6。zip这样一个包,这里不包含其他相关依赖,到实际使用的时候,根据出错信息,或者查询相关文档,加入需要的其他依赖。很显然,这也是一件非常麻烦的事情。
  Maven的传递性依赖机制可以很好地解决这一问题。假设我们有一个项目myApp,该项目有一个org。springframework:springcore:2。5。6的依赖,而实际上springcore也有它自己的依赖,我们可以直接访问位于中央仓库的该构件的POM:http:repo1。maven。orgmaven2orgspringframeworkspringcore2。5。6springcore2。5。6。pom。该文件包含了一个commonslogging依赖,如下:dependencygroupIdcommonslogginggroupIdcommonsloggingartifactIdversion1。1。1versiondependency
  该依赖没有声明依赖范围,那么其依赖范围就是默认的compile。myApp有一个compile范围的springcore依赖,springcore有一个compile范围的commonslogging依赖,那么commonslogging就会称为myApp的compile范围依赖,commonslogging是myApp的一个传递性依赖,如下所示:
  传递性依赖
  有了传递性依赖机制,不用担心引入多于的依赖。Maven会解析各个直接依赖的POM,将那些必要的间接依赖,以传递性依赖的形式引入到当前项目中。
  以来范围不仅可以控制依赖于三种classpath的关系,还对传递性依赖产生影响。假设A依赖于B,B依赖于C,我们说A对于B是第一直接依赖,B对于C是第二直接依赖,A对于C是传递性依赖。第一直接依赖的范围和第二直接依赖的范围决定了传递性依赖的范围,如下表,最左边一列表示第一直接依赖范围,最上面一行表示第二直接依赖范围,中间的交叉单元格则表示传递性依赖范围:
  compile
  test
  provided
  runtime
  mpile
  compile
  runtime
  test
  test
  test
  provided
  provided
  provided
  provided
  runtime
  runtime
  runtime依赖调解
  传递性依赖机制,一方面大大简化和方便了依赖声明,另一方面,大部分情况下我们只需要关心项目的直接依赖是什么,而不用考虑这些直接依赖会引入什么传递性依赖。
  但是有时候,传递性依赖也会造成一些问题,例如,项目A有这样的依赖关系:ABCX(1。0)、ADX(2。0),X是A的传递性依赖,但是两条依赖路径上有两个版本的X,那么哪个X会被Maven解析使用呢?Maven依赖调解的第一原则是:路径最近者优先。因此,X(2。0)会被解析使用。
  依赖调解第一原则不能解决所有问题,比如这样的依赖关系:ABY(1。0)、ACY(2。0)。从Maven2。0。9开始,为了尽可能避免构建的不确定性,Maven定义了依赖调解的第二原则:第一声明者优先。在依赖路径长度相等的前提下,在POM中依赖声明的顺序决定了谁会被解析使用,顺序最靠前的那个依赖优胜。可选依赖
  假设有这样一个依赖关系:AB、BX(可选)、BY(可选)。根据传递性依赖定义,由于这里X、Y是可选依赖,依赖将不会得以传递。如下图所示:
  可选依赖
  为什么要是用可选依赖这一特性呢?假如项目B是一个持久层隔离工具包,它支持多种数据库,包括MySQL、PostgreSQL等,在构件这个工具包的时候,需要这两种数据库的驱动程序,但在使用这个工具包的时候,只会依赖一种数据库。项目B的依赖声明代码清单如下:projectmodelVersion4。0。0modelVersiongroupIdcom。mk。bgroupIdprojectbartifactIdversion1。0。0versiondependenciesdependencygroupIdmysqlgroupIdmysqlconnectorjavaartifactIdversion5。1。10versionoptionaltrueoptionaldependencydependencygroupIdpostgresqlgroupIdpostgresqlartifactIdversion8。4701。jdbc3versionoptionaltrueoptionaldependencydependenciesproject
  当其他项目依赖于B的时候,这两个依赖不会被传递。因此,当项目A依赖于项目B的时候,如果其实际使用基于MySQL数据库,那么在项目A中就需要显示地声明mysqlconnectorjava这一依赖。
  在理想情况下,是不应该使用可选依赖的。更好的做法是为MySQL和PostgreSQL分别创建一个Maven项目,基于同样的groupId分配不同的artifactId,如com。mk。b:projectbmysql和com。mk。b:projectbpostgresql,在各自的POM中声明对应的JDBC驱动依赖,而且不使用可选依赖,用户则根据需要选择使用。排除依赖
  传递性依赖会给项目隐士地引入很多依赖,极大地简化了项目依赖的管理,但是有些时候这种特性也会带来问题。例如,当前项目有一个第三方依赖,而这个第三方依赖由于某些原因依赖了另外一个类库的SNAPSHOT版本,那么这个SNAPSHOT就会成为当前项目的传递性依赖,而SNAPSHOT的不稳定性会直接影响当前的项目。这时就需要排除掉该SNAPSHOT,并且在当前项目中声明该类库的某个正式发布的版本。或者还有一些情况,你想要的替换某个传递性依赖。projectmodelVersion4。0。0modelVersiongroupIdcom。mk。studygroupIdstudyaartifactIdversion1。0。0versiondependenciesdependencygroupIdcom。mk。studygroupIdprojectbartifactIdversion1。0。0versionexclusionsexclusiongroupIdcom。mk。studygroupIdprojectcartifactIdexclusionexclusionsdependencydependencygroupIdcom。mk。studygroupIdprojectcartifactIdversion1。1。0versiondependencydependenciesproject
  上述代码中,项目A依赖于项目B,但是由于一些原因,不想引入传递性依赖C,而是自己显式地声明对于项目C1。1。0版本的依赖。代码中使用exclusions元素声明排除依赖,exclusions可以包含一个或多个exclusion子元素,因此可以排除一个或者多个传递性依赖。需要注意的是,声明exclusion的时候只需要groupId和artifactId,而不需要version元素,这是因为只需要groupId和artifactId就能唯一定位依赖图中的某个依赖。换句话说,Maven解析后的依赖中,不可能出现groupId和artifactId相同,但是version不同的两个依赖。该例的依赖解析逻辑如下图:
  排除依赖归类依赖(Maven属性)
  假如项目A有很多关于SpringFramework的依赖,分别是org。springframework:springcore:2。5。6、org。springframework:springbeans:2。5。6、org。springframework:springcontext:2。5。6和org。springframework:springcoresupport:2。5。6,他们是来自同一个项目的不同模块。可以预见,如果将来需要升级SpringFramewor,这些依赖的版本会一起升级。
  应该有一个唯一的地方定义版本,并且在dependency声明中引用这一版本。这样,在升级SpringFramework的时候就只需要修改一处,实现代码如下:projectmodelVersion4。0。0modelVersiongroupIdcom。mk。studygroupIdprojectaartifactIdnameProjectAnameversion1。0。0SNAPSHOTversionpropertiesspringframework。version2。5。6springframework。versionpropertiesdependenciesdependencygroupIdorg。springframeworkgroupIdspringcoreartifactIdversion{springframework。version}versiondependencydependencygroupIdorg。springframeworkgroupIdspringbeansartifactIdversion{springframework。version}versiondependencydependencygroupIdorg。springframeworkgroupIdspringcoontextartifactIdversion{springframework。version}versiondependencydependencygroupIdorg。springframeworkgroupIdspringcontextsupportartifactIdversion{springframework。version}versiondependencydependenciesproject
  这里简单用到了Maven属性,首先使用properties元素定义Maven属性,有了这个属性定义之后,就可以使用美元符号和大括弧环绕的方式引用Maven属性。优化依赖
  Maven会自动解析所有项目的直接依赖和传递性依赖,并且根据规则正确判断每个依赖的范围,对于一些依赖冲突,也能进行调解,以确保任何一个构件只有唯一的版本在依赖中存在。最后得到的那些依赖被称为已解析依赖。
  我们以上面Maven入门中的案例为例,可以运行mvndependency:list命令查看当前项目的已解析依赖:〔INFO〕Scanningforprojects。。。〔INFO〕〔INFO〕com。mk。study:helloworld〔INFO〕BuildingMavenHelloWorldProject1。0SNAPSHOT〔INFO〕〔jar〕〔INFO〕〔INFO〕mavendependencyplugin:2。8:list(defaultcli)helloworld〔INFO〕〔INFO〕Thefollowingfileshavebeenresolved:〔INFO〕junit:junit:jar:4。10:test〔INFO〕org。hamcrest:hamcrestcore:jar:1。1:test〔INFO〕〔INFO〕〔INFO〕BUILDSUCCESS〔INFO〕〔INFO〕Totaltime:0。990s〔INFO〕Finishedat:20230101T16:05:3408:00〔INFO〕
  将直接在当前项目POM声明的依赖定义为顶层依赖,而这些顶层依赖的依赖则定义为第二层依赖,以此类推,有第三、四层依赖。当这些依赖经Maven解析后,就会构成一个依赖树,可以运行mvndependency:tree命令查看当前项目的依赖树:〔INFO〕Scanningforprojects。。。〔INFO〕〔INFO〕com。mk。study:helloworld〔INFO〕BuildingMavenHelloWorldProject1。0SNAPSHOT〔INFO〕〔jar〕〔INFO〕〔INFO〕mavendependencyplugin:2。8:tree(defaultcli)helloworld〔INFO〕com。mk。study:helloworld:jar:1。0SNAPSHOT〔INFO〕junit:junit:jar:4。10:test〔INFO〕org。hamcrest:hamcrestcore:jar:1。1:test〔INFO〕〔INFO〕BUILDSUCCESS〔INFO〕〔INFO〕Totaltime:1。242s〔INFO〕Finishedat:20230101T23:50:1108:00〔INFO〕
  在此基础上,还有dependency:analyze工具可以帮助分析当前项目的依赖。运行命令mvndependency:analyze输出如下:〔INFO〕Scanningforprojects。。。〔INFO〕〔INFO〕com。mk。study:helloworld〔INFO〕BuildingMavenHelloWorldProject1。0SNAPSHOT〔INFO〕〔jar〕〔INFO〕〔INFO〕mavendependencyplugin:2。8:analyze(defaultcli)testcompilehelloworld〔INFO〕〔INFO〕mavenresourcesplugin:2。6:resources(defaultresources)helloworld〔WARNING〕Usingplatformencoding(GBKactually)tocopyfilteredresources,i。e。buildisplatformdependent!〔INFO〕skipnonexistingresourceDirectoryC:Desktophellowordsrcmainresources〔INFO〕〔INFO〕mavencompilerplugin:3。1:compile(defaultcompile)helloworld〔INFO〕Nothingtocompileallclassesareuptodate〔INFO〕〔INFO〕mavenresourcesplugin:2。6:testResources(defaulttestResources)helloworld〔WARNING〕Usingplatformencoding(GBKactually)tocopyfilteredresources,i。e。buildisplatformdependent!〔INFO〕skipnonexistingresourceDirectoryC:Desktophellowordsrcestresources〔INFO〕〔INFO〕mavencompilerplugin:3。1:testCompile(defaulttestCompile)helloworld〔INFO〕Nothingtocompileallclassesareuptodate〔INFO〕〔INFO〕mavendependencyplugin:2。8:analyze(defaultcli)testcompilehelloworld〔INFO〕〔INFO〕〔INFO〕mavendependencyplugin:2。8:analyze(defaultcli)helloworld〔WARNING〕Unuseddeclareddependenciesfound:〔WARNING〕org。springframework:springaop:jar:5。3。16:compile〔WARNING〕org。springframework:springbeans:jar:5。3。16:compile〔INFO〕〔INFO〕BUILDSUCCESS〔INFO〕〔INFO〕Totaltime:1。279s〔INFO〕Finishedat:20230101T16:20:2808:00〔INFO〕
  从输出中可以看到,Unuseddeclareddependencies,意指项目中未使用的,但显示声明的依赖。对于这一类依赖,我们不应该简单地直接删除其声明,而是应该仔细分析。由于dependency:analyze只会分析编译主代码和测试代码需要用到的依赖,一些执行测试和运行时需要的依赖它就发现不了。
  对应的,还应该有Useddeclareddependencies,由于示例项目比较简单,此处未打印。Useddeclareddependencies指项目中使用到的,但是没有显示声明的依赖。这种依赖意味着潜在风险,当前项目在直接使用它们,而这种依赖是通过直接依赖传递进来的,当升级直接依赖的时候,相关传递性依赖的版本也可能发生变化,这种变化不易察觉,但是有可能导致当前项目出错。例如由于接口的改变,当前项目中的相关代码无法编译。这种隐藏的、潜在的威胁一旦出现,就往往需要耗费大量的时间来查明原因。因此,显式声明任何项目中直接用到的依赖。生命周期何为生命周期
  Maven的生命周期就是为了对所有的构建过程进行抽象和统一。这个声命周期包含了项目的清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成等几乎所有构件步骤。也就是说,几乎所有项目的构建,都能映射到这样一个生命周期上。
  Maven的生命周期是抽象的,这意味着生命周期本身不做任何实际的工作,在Maven的设计中,实际的任务(如编译源代码)都交由插件来完成。这种思想与设计模式中的模板方法非常相似。
  生命周期抽象了构建的各个步骤,定义了它们的次序,但没有提供具体实现。每个构建步骤都可以绑定一个或者多个插件行为,而且Maven为大多数构建步骤编写并绑定来了默认插件。虽然大多数时间里,用户几乎都不会察觉到插件的存在,但实际上编译是由mavencompileplugin完成的,而测试是由mavensurefireplugin完成的。当用户有特殊需要的时候,也可以配置插件定制构建行为,甚至自己编写插件。
  Maven定义的生命周期和插件机制一方面保证了所有Maven项目有一致的构件标准,另一方面又通过默认插件简化和稳定了实际项目的构建。生命周期详解
  Maven拥有三套相互独立的生命周期,它们分别为clean、default和site。clean生命周期的目的是清理项目,default生命周期的目的是构建项目,而site生命周期的目的是建立项目站点。
  每个生命周期包含一些阶段,这些阶段是有顺序的,并且后面的阶段依赖于前面的阶段,用户和Maven最直接的交互方式就是调用这些生命周期阶段。
  较之于生命周期阶段的前后依赖关系,三套生命周期本身是相互独立的,用户可以仅仅调用clean生命周期的某个阶段,或者仅仅调用default生命周期的某个阶段,而不会对其他生命周期产生任何影响。clean生命周期clean生命周期的目的是清理项目,它包含三个阶段:preclean执行一些清理前需要完成的工作。clean清理上一次构建生成的文件。postclean执行一些清理后需要完成的工作。default生命周期default生命周期定义了真正构建时需要执行的步骤,它是所有生命周期中最核心的部分,其包含的阶段如下:validateinitializegeneratesourcesprocesssources处理项目主资源文件。一般来说,是对srcmainresources目录的内容进行变量替换等工作后,复制到项目输出的主classpath目录中。generateresourcesprocessresourcescompile编译项目的主源代码。一般来说,是编译srcmainjava目录下的Java文件至项目输出的主classpath目录中。processclassesgeneratetestsourcesprocesstestsources处理项目测试资源文件。一般来说,是对srctestresources目录的内容进行变量替换等工作后,复制到项目输出的测试classpath目录中。generatetestresourcesprocesstestresourcestestcompile编译项目的测试代码。一般来说,是编译srctestjava目录下的Java文件至项目输出的测试classpath目录中。processtestclassestest使用单元测试框架运行测试,测试代码不会被打包或部署。preparepackagepackage接受编译好的代码,打包成可发布的格式,如jar。preintegrationtestintegrationtestpostintegrationtestverifyinstall将包安装到Maven本地仓库,提供其他本地Maven项目使用。deploy将最终的包复制到远程仓库,供其他开发人员和Maven项目使用。site生命周期site生命周期的目的是建立和发布项目站点,Maven能够基于POM所包含的信息,自动生成一个友好的站点,方便团队交流和发布项目信息。包含如下阶段:presite执行一些在生成项目站点之前需要完成的工作。site生产项目站点文档。postsite执行一些在生成项目站点之后需要完成的工作。sitedeploy将生成的项目站点发布到服务器上。
  从命令行执行Maven任务的最主要方式就是调用Maven的生命周期阶段。各个生命周期是相互独立的,而一个生命周期的阶段是右前后依赖关系的。下面以一些常见的Maven命令为例,解释器执行的生命周期阶段:mvnclean:该命令调用clean生命周期的clean阶段。实际执行的阶段为clean生命周期的preclean和clean阶段。mvntest:该命令调用default生命周期的test阶段。实际执行的阶段为default生命周期的validate、initialize等,直到test的所有阶段。这也解释了,为什么再执行测试的时候,项目的代码能够自动得以编译。mvncleaninstall:该命令调用clean生命周期的clean阶段和default生命周期的install阶段。实际执行的阶段为clean生命周期的preclean、clean阶段,以及default生命周期的从validate至install的所有阶段。mvncleandeploysitedeploy:该命令调用clean生命周期的clean阶段、dufault生命周期的deploy阶段,以及site生命周期的sitedeploy阶段。实际执行的阶段为clean生命周期的preclean、clean阶段,default生命周期的所有阶段,以及site生命周期的所有阶段。
  Maven中主要的生命周期阶段并不多,而常用的Maven命令实际都是基于这些阶段简单组合而成的,因此只要对Maven生命周期有一个基本的理解,就可以正确而熟练地使用Maven命令。聚合和继承
  Maven的聚合特性能够把项目的各个模块聚合在一起构建,而Maven的继承特性则能帮助抽取各个模块相同的依赖和插件等配置,在简化POM的同时,还能促进各个模块配置的一致性。聚合
  当想要一次构建两个项目,而不是待两个模块的目录下分别执行mvn命令时,可以使用Maven聚合这一特性。
  为了能够使用一条命令就能构建projecta和projectb两个模块,我们需要创建一个额外的名为projectparent的模块,然后通过该模块构建整个项目的所有模块,projectparent本身作为一个Maven项目,它必须要有自己的POM,不过,同时作为一个聚合项目,其POM又有特殊的地方,如下:projectmodelVersion4。0。0modelVersiongroupIdcom。mk。projectgroupIdprojectparentartifactIdversion1。0。0SNAPSHOTversionpackagingpompackagingnameProjectParentnamemodulesmoduleprojectamodulemoduleprojectbmodulemodulesproject
  上述POM使用了与projecta和projectb相同的groupId,artifactId为独立的projectparent,版本也与其他两个模块一致。一个特殊的地方是packaging,其值为pom。对于聚合模块来说,其打包方式packaging的值必须为pom,否则就无法构建。
  元素modules是实现聚合的最核心的配置。可以通过在一个打包方式为pom的Maven项目中声明任意数量的module元素来实现模块的聚合。这里每个module的值都是一个当前POM的相对目录。
  一般来说,为了方便快速定位内容,模块所处的目录名称应当与其artifactId一致。为了方便用户构建项目,通常将聚合模块放在项目目录的最顶层,其他模块则作为聚合模块的子目录存在,这样当用户得到源码的时候,第一眼发现的就是聚合模块的POM,不用从多个模块中去寻找聚合模块来构建整个项目。
  在聚合模块下运行mvncleaninstall命令,Maven会首先解析聚合模块的POM、分析要构建的模块、并计算出一个反应堆构建顺序,然后根据这个顺序依次构建各个模块。反应堆是所有模块组成的一个构件结构。继承
  面向对象设计中,程序猿可以建立一种类的父子结构,然后在父类中声明一些字段和方法供子类继承,这样就可以做到一处声明,多处使用。类似地,我们可以创建POM的父子结构,然后在父POM中声明一些配置供子POM继承,以实现一处声明,多处使用的目的。
  父模块只是为了帮助消除配置的重复,因此它本身不包含除POM之外的项目文件。我们还以聚合中的示例为例,父模块为projectparent,另外还有projecta和projectb两个子模块。父模块的POM内容如下:projectmodelVersion4。0。0modelVersiongroupIdcom。mk。projectgroupIdprojectparentartifactIdversion1。0。0SNAPSHOTversionpackagingpompackagingnameProjectParentnamemodulesmoduleprojectamodulemoduleprojectbmodulemodulesproject
  有了父模块,对应的projecta和projectb模块的POM修改如下(以projecta为例):projectparentgroupIdcom。mk。projectgroupIdprojectparentartifactIdversion1。0。0SNAPSHOTversionrelativePath。。pom。xmlrelativePathparentprojectaartifactIdnameProjectAnamedependenciesdependenciesbuildpluginspluginsbuildproject
  上述POM中使用parent元素声明父模块,parent下的子元素指定了父模块的坐标,groupId、artifactId、version是必须的,元素relativePath表示父模块POM的相对路径。当构建时,Maven首先根据relativePath检查父POM,如果找不到,再从本地仓库查找。relativePaht的默认值是。。pom。xml,也就是说,Maven默认父POM在上一层目录下。
  这个更新过的POM没有为projecta声明groupId和version,实际上,这个子模块隐式地从父模块继承了这两个元素,这也就消除了一些不必要的配置。可继承的POM元素
  上面看到,groupId和version是可以被继承的,那么还有哪些POM元素可以被继承呢?下面是一个完整的列表:groupId:项目组ID,项目坐标的核心元素。version:项目版本,项目坐标的核心元素。description:项目的描述信息。organization:项目的组织信息。inceptionYear:项目的创始你年份。url:项目的URL地址。developers:项目的开发者信息。contributors:项目的贡献者信息。distributionManagement:项目的部署配置。issueManagement:项目的缺陷跟踪系统信息。ciManagement:项目的持续集成系统信息。scm:项目的版本控制系统信息。mailingLists:项目的邮件列表信息。properties:自定义的Maven属性。dependencies:项目的依赖配置。dependencyManagement:项目的依赖管理配置。repositories:项目的仓库配置。build:包括项目的源码目录配置、输出目录配置、插件配置、插件管理配置等。reporting:包括项目的报告输出目录配置、报告插件配置等。依赖管理
  假设子模块projecta和projectb同时依赖了org。springframework:springcore:2。5。6、org。springframework:springbeans:2。5。6、org。springframework:springcontext:2。5。6、org。springframework:springcontextsupport:2。5。6和junit:junit:4。10,既然dependencies元素可以被继承,因此,可以将这些依赖配置放到父模块projectparent中,两个子模块就可以移除这些依赖,简化配置。
  上述做法是可行的,但却存在问题。到目前为止,能够确定这两个子模块都包含那四个依赖,不过无法确定将来添加的子模块就一定需要这四个依赖。假设将来项目中需要加入一个projectutil模块,该模块只是提供一些简单的帮助工具,与springframework完全无关。那上面的方法就有些不太合适了。dependencyManagement
  Maven提供的dependencyManagement元素既能让子模块继承到父模块的依赖配置,又能保证子模块依赖使用的灵活性。在dependencyManagement元素下的依赖声明不会引入实际的依赖,不过它能够约束dependencies下的依赖使用。例如,在projectparent中加入如下的dependencyManagement配置:projectmodelVersion4。0。0modelVersiongroupIdcom。mk。projectgroupIdprojectparentartifactIdversion1。0。0SNAPSHOTversionpackagingpompackagingnameProjectParentnamepropertiesspringframework。version2。5。6springframework。versionjunit。version4。10junit。versionpropertiesdependencyManagementdependenciesdependencygroupIdorg。springframeworkgroupIdspringcoreartifactIdversion{springframework。version}versiondependencydependencygroupIdorg。springframeworkgroupIdspringbeansartifactIdversion{springframework。version}versiondependencydependencygroupIdorg。springframeworkgroupIdspringcontextartifactIdversion{springframework。version}versiondependencydependencygroupIdorg。springframeworkgroupIdspringcontextsupportartifactIdversion{springframework。version}versiondependencydependencygroupIdjunitgroupIdjunitartifactIdversion{junit。version}versionscopetestscopedependencydependenciesdependencyManagementproject
  这里使用dependencyManagement声明的依赖既不会给projectparent引入依赖,也不会给其子模块引入依赖,不过这段配置会被继承。
  现在修改projectparent的子模块的POM修改如下:propertiesjavax。mail。version1。4。1javax。mail。versiongreenmail。version1。3。1bgreenmail。versionpropertiesdependenciesdependencygroupIdorg。springframeworkgroupIdspringcoreartifactIddependencydependencygroupIdorg。springframeworkgroupIdspringbeansartifactIddependencydependencygroupIdorg。springframeworkgroupIdspringcontextartifactIddependencydependencygroupIdorg。springframeworkgroupIdspringcontextsupportartifactIddependencydependencygroupIdjunitgroupIdjunitartifactIddependencydependencygroupIdjavax。mailgroupIdmailartifactIdversion{javax。mail。version}versiondependencydependencygroupIdcom。icegreengroupIdgreenmailartifactIdversion{greenmail。version}versionscopetestscopedependencydependencies
  所有springframework依赖只配置了groupId和artifactId,省去了version,而junit依赖不仅省去了version,还省去了依赖范围scope。这些信息可以省略是因为继承了projectparent中的dependencyManagement配置,完整的依赖声明已经包含在父POM中,子模块只需要配置简单的groupId和artifactId就能获得对应的依赖信息,从而引入正确的依赖。
  使用这种依赖管理机制似乎不能减少太多的POM配置,不过还是强烈建议推荐采用这种方法。原因在于父POM中使用dependencyManagement声明依赖能够统一项目范围中依赖的版本,当依赖版本在父POM中声明之后,子模块在使用依赖的时候就无须声明版本,也就不会发生多个子模块使用依赖版本不一致的情况。这可以帮助降低依赖冲突的几率。
  如果子模块不声明依赖的使用,即使该依赖已经在父POM的dependencyManagement中声明了,也不会产生任何实际的效果。import
  import以来范围只在dependencyManagement元素下才有效果,使用该范围的依赖通常指向一个POM,作用是将目标POM中的dependencyManagement配置导入合并到当前POPM的dependencyManagement元素中。例如,想要在另一个模块中使用与上面完全一样的dependencyManagemnt配置,除了复制配置或者继承这两种方式之外,还可以使用import范围依赖这一配置导入,如下:dependencyManagementdependenciesdependencygroupIdcom。mk。projectgroupIdprojectparentartifactIdversion1。0。0SNAPSHOTversiontypepomtypescopeimportscopedependencydependenciesdependencyManagement
  上述代码中依赖的type值为pom,import范围依赖由于其特殊性,一般都是指向打包类型为pom的模块。如果有多个项目,他们使用的依赖版本都是一致的,则就可以定义一个使用dependencyManagement专门管理的POM,然后在各个项目中导入这些依赖管理配置。插件管理
  Maven提供了dependencyManagement元素帮助管理依赖,类似地,Maven也提供了pluginManagement元素帮助管理插件。在该元素中配置的依赖不会造成实际的插件调用行为,当POM中配置了真正的plugin元素,并且其groupId和artifactId与pluginMangement中配置的插件匹配时,pluginManagement的配置才会影响实际的插件行为。
  与上面的dependencyManagement元素使用方式类似,这里不再举例。聚合与继承的关系
  多模块Maven项目中的聚合与继承其实是两个概念,其目的完全是不同的。前者主要是为了方便快速构建项目,后者主要是为了消除重复配置。
  对于聚合模块来说,它知道有哪些被聚合的模块,但那些被聚合的模块不知道这个聚合模块的存在。
  对于继承关系的父POM来说,它不知道有哪些子模块继承于它,但那些子模块都必须知道自己的父POM是什么。
  如果非要说这两个特性的共同点,那么就是,聚合POM与继承关系中的父POM的packaging都必须是pom,同时,聚合模块与继承关系中的父模块除了POM之外都没有实际的内容。
  在现有的实际项目中,往往会发现一个POM即是聚合POM,又是父POM,这么做主要是为了方便。其实,对于上面的案例,我们也是这么做的。约定优于配置
  Maven提倡约定优于配置,这是Maven最核心的设计理念之一,使用约定可以大量减少配置。
  Maven会假设用户的项目是这样的:源码目录为srcmainjava编译输出目录为targetclasses打包方式为jar包输出目录为target
  遵循约定虽然损失了一定的灵活性,用户不能随意安排目录结构,但是却能减少配置。更重要的是,遵循约定能够帮助用户遵守构建标准。
  Maven允许自定义源码目录,如下:projectmodelVersion4。0。0modelVersiongroupIdcom。mk。projectgroupIdprojectaartifactIdversion1。0versionbuildsourceDirectorysrcjavasourceDirectorybuildproject
  该例中源码目录就成了srcjava而不是默认的srcmainjava。但这往往会造成交流问题,习惯Maven的人会奇怪,源代码去哪里了?当这种定义大量存在的时候,交流成本就会大大提高。
  任何一个Maven项目都隐式地继承自超级POM,这有点类似于任何一个Java类都隐式地继承于Object类。因此,大量超级POM的配置都会被所有Maven项目继承,这些配置也就成了Maven所提倡的约定。
  对于Maven3,超级POM在文件中的路径下。对于,超级在文件MAVENHOMElibmavenx。x。xuber。jar中的orgapachemavenprojectpom4。0。0。xml目录下。这里的x。x。x表示Maven的具体版本。
  超级POM的内容在Maven2和Maven3中基本一致。首先超级POM定义了仓库及插件仓库,两者的地址都为中央仓库http:repo1。maven。orgmaven2,并且都关闭了SNAPSHOT的支持。
  超级POM实际上很简单,但从这个POM我们就能够知晓Maven约定的由来,不仅可以理解什么是约定,为什么要遵守约定,还能明白约定是如何实现的。反应堆
  在一个多模块的Maven项目中,反应堆(Reactor)是指所有模块组成的一个构建结构。对于单模块的项目,反应堆就是该模块本身,但对于多模块项目来说,反应堆就包含了各模块之间的依赖关系,从而能够自动计算出合理的模块构建顺序。反应堆的构件顺序
  为了能更能清楚反应堆的构件数信,将projectaggregator的聚合配置修改如下:modulesmoduleprojectamodulemoduleprojectbmodulemoduleprojectparentmodulemodules
  构建projectaggregator会看到如下输出:〔INFO〕〔INFO〕ReactorBuildOrder:〔INFO〕〔INFO〕ProjectAggregator〔INFO〕ProjectParent〔INFO〕ProjectA〔INFO〕ProjectB〔INFO〕〔INFO〕
  上述输出告诉了我们反应堆的构建顺序。如果按顺序读取POM文件,首先应该读到的是projectaggregator的POM,实际情况与预料的一致,但是接下来的几个模块的构件次序与声明的顺序就不一致了。
  实际的构件顺序是这样形成的:Maven按序读取POM,如果该POM没有依赖模块,那么就构件该模块,否则就先构建其依赖模块,如果该依赖还依赖于其他模块,则进一步先构建依赖的依赖。模块间的依赖关系将反应堆构建成一个有向非循环图,各个模块是该图的节点,依赖关系构成了有向边。裁剪反应堆
  一般来说,用户会选择构建整个项目或者选择构建单个模块,但有些时候,会想要仅仅构建完整反应堆中的某些模块。换句话说,用户需要实时地裁剪反应堆。
  Maven提供很多命令行选项支持裁剪反应堆,输入mvnh可以看到这些选项:am,alsomake同时构建所列模块的依赖amd,alsomakedependents同时构建依赖于所列模块的模块pl,projects构建指定模块,模块间用逗号分隔rf,resumefrom从指定的模块回复反应堆
  默认情况下,从projectaggregator执行mvncleaninstall会得到如下完整的反应堆:〔INFO〕〔INFO〕ReactorBuildOrder:〔INFO〕〔INFO〕ProjectAggregator〔INFO〕ProjectParent〔INFO〕ProjectA〔INFO〕ProjectB〔INFO〕〔INFO〕
  可以使用pl选项指定构建某几个模块,如运行如下命令:
  mvncleaninstallplprojectb
  得到的反映堆为:〔INFO〕〔INFO〕ReactorBuildOrder:〔INFO〕〔INFO〕ProjectAggregator〔INFO〕ProjectParent〔INFO〕ProjectB〔INFO〕〔INFO〕版本管理何为版本管理
  一个健康的项目通常有一个长期、合理的版本演变过程。例如JUnit有3。7、3。8、3。8。1、3。8。2、4。0、4。1等版本。还有Maven特有的快照版本。
  为了方便团队的合作,在项目开发的过程中,都应该使用快照版本,Maven能够很智能地处理这种特殊的版本,解析项目各个模块最小的快照。当项目需要对外发布的时候,显然需要提供非常稳定的版本,使用该版本应当永远只能定位到唯一的构件,而不是像快照版本那样,定位的构件随时可能发生变化。对应地,我们称这类稳定的版本为发布版本。
  版本管理关系的问题之一就是这种快照版本和发布版本之间的转换。
  如下如图,项目经过了一段时间的1。0SNAPSHOT的开发之后,在某个时刻发不了1。0正式版,然后项目又进入了1。1SNAPSHOT的开发,这个版本可能添加了一些有趣的特性,然后在某个时刻发布1。1正式版。项目紧接着进入1。2SNAPSHOT的开发。
  快照版和发布版之间的转换
  由于快照对应了项目的开发过程,因此往往对应了很长的时间,而正式版本对应了项目的发布,因此仅仅代表某个时刻项目的状态。版本管理和版本控制
  版本管理是指项目整体版本的演变过程管理,如从1。0SNAPSHOT到1。0,再到1。1SNAPSHOT。版本控制是指借助版本控制工具(如Git)追踪代码的每一个变更。Maven的版本号定义约定
  Maven的版本号定义约定是这样的:
  主版本。次版本。增量版本里程碑版本
  主版本和次版本之间,以及次版本和增量版本之间用点号分割,里程碑版本之前用连字号分隔。主版本:表示了项目的重大架构变更。例如,Maven2和Maven1相去甚远;Struts1和Struts2采用了不同的架构;JUnit4较JUnit3增加了标注支持。次版本:表示较大范围的功能增加和变化,及Bug修复。但从整体架构来说,没有什么变化。增量版本:一般表示重大Bug的修复。里程碑版本:顾名思义,这往往指某一个版本的里程碑。这样的版本与正式的相比,王旺表示不是非常稳定,还需要很多测试。
  需要注意的是,不是每个版本号都必须拥有这四个部分。一般来说,主版本和次版本都会声明,但增量版本和里程碑就不一定了。

买手机就选12512G大内存,这4款几乎零差评,而且价格还亲民头条创作挑战赛您在阅读前请点击上面的关注二字,后续会第一时间为您提供更多有价值的相关内容,感谢您的支持。在购买手机的时候选择大内存的版本,可以使手机运行的更加流畅,同时手机运行多个手机膜还可以减少电量消耗?苹果上架348元iPhone14Pro屏幕保护膜要说手机厂商哪一家最赚钱,那肯定是漂亮国的苹果公司了,其旗舰iPhone售价一直居高不下,最贵有的可以卖到上万块,而且很多相关配件也是很高贵,是很多厂商售价的几十倍,但奈何果粉就是C罗扳回一城?曝C罗本该6金球,媒体人做梦,梅西金球也该取消如今世界足坛依旧处于梅罗时代,虽然38岁的C罗状态身价严重下滑,并且已经远走西亚联赛,淡出主流赛场视野,但其影响力仍旧很强,预计在他退役之前双骄的名头依旧要比哈兰德姆巴佩等新生力量象棋小技巧,十个要点,五要诀,领悟了棋力大涨我们知道,各行各业,成为高手,都要有个循序渐进的大过程。象棋易学难精,易学是掌握基本规则和走法很容易,难精是精通成为高手很困难。那么,象棋提高有什么技巧?有没有提升的口诀?还真的有京东旗下内购群对外招募,所有商品3折起,限时招募500人朋友们是否听说过京东的内购群呢?我敢说80的人都还不知道京东内购群这个网购必备神器。京东内购群是京东旗下依托于京东内部商家推广计划,成立的一套社群推广模式,目的是帮助京东商家(含自还敢炒作chatgpt吗股市2022年有哪些猪头三都不如的骗局,西气东输概念碳中和元宇宙,仅1年过去就让股盲输掉老根内裤。新的庞氏骗局又来CHATGpTChatGpt只是相对于人类比较低层次的智能,而且没以太坊价格飙升至1,700美元以上,然后兑美元走低以太坊价格飙升至1,700美元以上,然后兑美元走低。ETH在1,600美元区域和100小时均线上方仍受到良好支撑。在空头站稳脚跟之前,以太坊攀升至1,700美元的阻力位上方。价格仍骞驭机器人,奔驰在智能化精准理疗赛道的千里马人工智能(AI)的浪潮正在以迅猛的势头席卷全球,它不仅会改变人们的日常生活方式,而且将给各行各业带来颠覆性变革的挑战与机遇。今天,传统的健康管理方式面临新技术的挑战,人们对精准与个微信杀入饭碗大战,外卖三国杀变四方混战2月财经新势力文丨商业数据派,作者丨黄小艺戴昊彤,编辑丨刘雨琦抖音外卖还未全国上线,微信已悄然杀入,欲抢饭碗。近日,有网友发现,微信小程序在广州深圳两地开始内测外卖服务。据了解,点新余市600余名快递小哥参加驾考来源江西日报江西新闻客户端江西新闻客户端讯(江西日报全媒体记者江拓华通讯员姜涛)近日,新余市组织全市10家快递企业600余名快递从业人员参加驾考,首批快递小哥已完成科目一考试。前期腾讯离职创业系,2万亿商业帝国1腾讯劝退员工创业,反手吊打腾讯。他开发的手游,不到1年,就成为AppStore中国区收入榜第一(日入2000万,月入2。8亿,年入21。6亿)。不到4年,就名扬海外,把腾讯引以为
突发!董事长被查,涉嫌严重违纪违法9月20日晚间,长江传媒发布公告称,公司董事长陈义国因涉嫌严重违纪违法,目前正接受湖北省纪委监委纪律审查和监察调查。图片来源公司公告涉嫌严重违纪违法据湖北省纪委监委通报,湖北长江出Ram全新电动皮卡预告图将洛杉矶车展首发日前,车质网从海外媒体获悉,Ram全新纯电动皮卡概念版将于今年11月,即洛杉矶车展的前一天晚亮相,而最终的量产版也将在2023年亮相,并在2024年上市。目前,新车已经公布了其预告英国今年年底或加息至3。75!英镑仍在低位!减税将惠及富人9月21日英国日更重点有英国明天或加息0。75,年底英国基准利率将加到3。75特拉斯承认减税将惠及富人,但为了提振经济必须这样做欧洲政治共同体首次峰会10月初举行,英国收到邀请但不德国或将迎来第四轮救助方案通胀补贴,限制能源费和365欧年票尽管德国政府第三轮救助方案中的津贴尚未完全发放,但经济学家和协会已经在呼吁第四轮救助方案。原因是第三轮救助方案缺乏对低收入劳动者和养老金领取者的援助。所以下面这些措施可能会包括在第港媒曝2022年港姐内定三甲!翻版孙艺珍入围,风头港姐疑出局2022年的港姐选举可谓是风波不断,除了前段时间曝出的染疫风波之外,近段时间又因为监警会委员的指责登上热搜(个个少布到不得了),时隔不到一天的时间,港媒再度曝料疑似内定三甲名单流出她们的名字五大渣男浮出水面,一人最可恨,一人隐藏得最深职场情感剧她们的名字以雷粒任多美和沈佳男三位女性为视角,带领吃瓜群众领略职场生活以及人生的真谛。她们三位性格迥异,经历大不同,有着各自的目标和方向。但有一点她们相同,那就是都有一颗3个警虎邓恢林龚道安刘新云,同在今天被判刑三名警虎邓恢林龚道安刘新云今天同在河北受审邓恢林在保定龚道安在唐山刘新云在廊坊邓恢林被指参与在党内搞团团伙伙,捞取政治资本龚道安被指参与在党内搞团团伙伙刘新云被指参与在党内搞团团伙女人这样向你索要,看似不爱,实则爱你很深文森屿鹿林爱一个人,就想占据对方的心扉。想要被你偏爱,想要被你宠溺,想要和你携手共度一生,时时刻刻都黏在一起。或许,在遇见你之前,她是一个无欲无求的人,对感情并没有什么期待,直到后女人最大的底气,是独立文安冬悦什么叫底气?所谓底气,其实就是心里有底,骨子里自信,在任何场合,任何时候,都不会畏畏缩缩,唯唯诺诺。用最通俗的话说,就是腰杆直,无需低眉顺眼看他人脸色行事,遇到困难可以镇定(外代二线)伦敦时装周BoraAksu品牌时装秀(外代二线)伦敦时装周BoraAksu品牌时装秀9月16日,模特在英国伦敦时装周上展示BoraAksu品牌2023春夏新款服装。新华社欧新9月16日,模特在英国伦敦时装周上展示Bo林丹回归家庭,谢杏芳被幸福滋润成女神,酷似李嘉欣林丹回归家庭后,夫妻二人忙碌并幸福着。近日,林丹去了石狮,老婆谢杏芳忙着家族生意,夫妻二人同心,如今生意越做越好,谢杏芳也被幸福滋润的越来越美,神似港星李嘉欣。林丹与谢杏芳曾号称是
友情链接:快好找快生活快百科快传网中准网文好找聚热点快软网