使用maven-pom进行依赖管理与自动构建

使用maven-pom进行依赖管理与自动构建html

使用maven-pom进行依赖管理与自动构建

最后编辑于 :pencil:{docsify-updated}java

1、让咱们先了解一下maven

1.maven是优秀的依赖管理工具

随着开发生态环境的不断发展,几乎全部的java应用都会使用第三方的类库,尤为是在这个开源的世界里, java应用依赖管理已经很难再由人工完成——依赖管理包括解决依赖传递、版本冲突、依赖臃肿等问题。git

maven经过groupIdartifactIdversion造成的坐标定位系统能准确的定位每个构件(artifact), 开发者能够经过在pom文件中列出所依赖的构件的坐标,让maven工具从maven仓库中自动下载全部须要的构件; 另外一方面,经过pom文件间的依赖传递、继承等方式下降依赖管理的难度。github

2.maven是优秀的构建工具

在咱们的开发过程当中,除了编写代码之外,有很大一部分时间是花在编译、运行单元测试、生成文档、打包和部署这些工具上, 为也提升工做效率,使开发人员能更多的将精力用于开发工做,咱们须要像maven这样的工具, 如流水线般的将全部部署以自动化的方式完成。web

3.maven是约定大于配置、简单易学的工具

在java开发中,经常使用的构建工具备Ant、maven、gradle三种。spring

3.1 Ant

Ant是过程式的,开发者须要显示的指定每一个目标,以及完成该目标锁须要执行的任务。shell

Ant不只限于对java项目进行构建,也能够对其余语言(如C语言)的项目进行构建。apache

<project name="MyProject" default="dist" basedir="../../../../../../../ProgramFiles/JetBrains/IntelliJ IDEA 2019.3.2/jbr/bin">
  <description>
    simple example build file
  </description>
  <!-- set global properties for this build -->
  <property name="src" location="src"/>
  <property name="build" location="build"/>
  <property name="dist" location="dist"/>

  <target name="init">
    <!-- Create the time stamp -->
    <tstamp/>
    <!-- Create the build directory structure used by compile -->
    <mkdir dir="${build}"/>
  </target>

  <target name="compile" depends="init"
 description="compile the source">
    <!-- Compile the Java code from ${src} into ${build} -->
    <javac srcdir="${src}" destdir="${build}"/>
  </target>

  <target name="dist" depends="compile"
 description="generate the distribution">
    <!-- Create the distribution directory -->
    <mkdir dir="${dist}/lib"/>

    <!-- Put everything in ${build} into the MyProject-${DSTAMP}.jar file -->
    <jar jarfile="${dist}/lib/MyProject-${DSTAMP}.jar" basedir="${build}"/>
  </target>

  <target name="clean"
 description="clean up">
    <!-- Delete the ${build} and ${dist} directory trees -->
    <delete dir="${build}"/>
    <delete dir="${dist}"/>
  </target>
</project>

与maven、gradle不一样,ant自己没有对应的中央仓库,若是想使用maven同样, 经过声明的方式管理依赖,并自动处理依赖管理等问题,则使用集成ivy插件管理依赖。 但ant不支持多模块的管理方式,而maven和gradle支持。windows

ant的这种声明方式,使开发者能够根据本身的须要灵活配置,定制本身的项目构建流程; 另外一方面,这种灵活的配置方式,也使得ant的配置相对繁琐,可读性较差,学习成本也更高 ——事实上,ant的官方网站上列出的插件就有139种,并且部分工具的官网已经404了。api

3.2 maven

maven与gradle同样,都是声明式的配置方式,相对于Ant来讲,配置更加方便。 同时maven有内建生命周期,约定了项目的代码结构,只须简单配置,就能够完成构建任务。

顶级pom中定义的项目结构(即默认项目结构)

项目根目录
  |-- src
  |    |-- main
  |    |    |-- java        -> 主代码文件
  |    |    |-- resources   -> 主资源文件
  |    |     -- scripts     -> 虽然在maven的默认结构里有这个目录,但官网文档中已删除了这一级目录,不建议使用
  |     -- test
  |         |-- java        -> 测试代码文件
  |          -- resources   -> 测试资源文件
   -- pom.xml

小提示:可经过mvn -DarchetypeCatalog=internal archetype:generate初始化标准结构的maven项目

另外一方面,maven内建生命周期,使用没法更加项目的构建顺序,若是想在构建中加上一些其余处理逻辑, 则须要用编写maven插件的方式来完成,成本相对高昂。

3.3 Gradle

gradle是相对新颖的构建工具,它使用一种基于Groovy的特定领域语言(DSL)来声明项目设置, 目前也增长了基于Kotlin语言的kotlin-based DSL,抛弃了基于XML的各类繁琐配置。

gradle没有maven般的生命周期,真正起做用的是所引入的Plugin,所以相对更为灵活。

另外一方面,gradle做为新生工具,不管是程序仍是文档都在不断的完善中,使用和学习相对困难。 同时,因为gradle的配置是基于groovy/kotlin语法的,所以使用gradle须要掌握相应的语法知识。

gradle是对ant和maven特色折衷的结果,相对于ant其可读性更好,相对于maven其更为灵活。

2、pom基本结构

POM是project object mode的简写,maven经过pom文件对项目进行描述, 在开发者在pom文件中对项目属性和构建过程进行定义后,maven便可自动构建项目并生成站点。

1.概览

<project xmlns="http://maven.apache.org/POM/4.0.0"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!-- The Basics -->
    <groupId>...</groupId>
    <artifactId>...</artifactId>
    <version>...</version>
    <packaging>...</packaging>
    <dependencies>...</dependencies>
    <parent>...</parent>
    <dependencyManagement>...</dependencyManagement>
    <modules>...</modules>
    <properties>...</properties>

    <!-- Build Settings -->
    <build>...</build>
    <reporting>...</reporting>

    <!-- More Project Information -->
    <name>...</name>
    <description>...</description>
    <url>...</url>
    <inceptionYear>...</inceptionYear>
    <licenses>...</licenses>
    <organization>...</organization>
    <developers>...</developers>
    <contributors>...</contributors>

    <!-- Environment Settings -->
    <issueManagement>...</issueManagement>
    <ciManagement>...</ciManagement>
    <mailingLists>...</mailingLists>
    <scm>...</scm>
    <prerequisites>...</prerequisites>
    <repositories>...</repositories>
    <pluginRepositories>...</pluginRepositories>
    <distributionManagement>...</distributionManagement>
    <profiles>...</profiles>
</project>

2.项目坐标

声明式的项目依赖管理,除了须要一个存储有全部jar包的中央仓库外,还须要一套合适的命名系统, 可以保证每一个jar包都有一个惟一的名字,包括不一样版本。
在maven中,jar包的坐标经过groupIdartifactIdversion三个字段决定, 相似于xyz坐标系的三个轴。

<project xmlns="http://maven.apache.org/POM/4.0.0"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>my-project</artifactId>
  <version>1.0</version>
</project>
2.1 groupId

一般而言,一个组织(公司、基金会等)的项目一般使用同一个groupId, 所以通常而言groupId是该组织所拥有的域名,如jacoco对应的groupId为org.jacoco, 而对应的网站为http://jacoco.org(域名的优先级顺序是反过来的),

事实上不必定严格遵循该规则,如maven的groupId为org.apache.maven, 而maven官方插件的groupId为org.apache.maven.plugins, 尽管同并不存在plugins.maven.apache.org这个四级域名, 并且他们同属于apache基金会(apache.org)下的项目。

另外也有彻底和域名不相关的,如junit包的groupId为junt

另外须要注意的一点是,虽然groupId没必要与项目的包结构相对应,可是遵循这种作法是一种最佳实践, 由于这样能够有效避免包之间的类冲突。

2.2 artifactId

artifactId一般是已知项目(或项目模块)的名称,它与groupId一块儿构成了一个项目(或项目模块)的定位, 用于将该项目(或项目模块)与世界上其余全部项目(或项目模块)分开。

实践

虽然在groupId中就能够看到项目名称,但在artifactId中通常仍会将项目名称做为开头, 这是由于咱们在沟通时一般会用 artifactId 进行交流,而不会带上 groupId , 如张三告诉李四须要在项目中引入maven-enforcer-plugin包作项目自动检查。

2.3 version

如同字面的含义,version表明的是项目(或项目模块)的版本。

groupIdartifactIdversion一同定位了一个发布包的惟一位置。 如maven-enforcer-plugin-3.0.0-M3.jar在仓库中的位置为 $M2_REPO/org/apache/maven/plugins/maven-enforcer-plugin/3.0.0-M3, 而其对应的pom中的内容以下:

<parent>
  <groupId>org.apache.maven.enforcer</groupId>
  <artifactId>enforcer</artifactId>
  <version>3.0.0-M3</version>
</parent>

<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>

parent标签咱们后续会提到,parent标签下是父pom的坐标,子pom继承父pom的全部定义,包括version

3.package标签

package标签定义了构件的打包方式和使用方式,若是不指定值,默认使用jar做为打包方式。

这里只说明两个咱们使用较多的打包方式:pom、jar。

3.1 打包为pom

定义打包方式为pom是有一些特殊的项目,其自己除了pom文件外,不包含其余文件, 这些项目中的pom主要是用来被其余项目引用、继承,或者用来管理项目拥有的模块。

这里的继承概念与java中类的继承概念相似,子pom拥有父pom的全部配置, 能够经过自定义的方式覆盖/复写父pom中已经存在的配置。

咱们拿springboot集成中的两个打包方式为pom的项目举例说明。 在springboot的官方集成教程中,会告诉你可使用两种方式集成,一种经过父pom继承:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.1.RELEASE</version>
</parent>

另外一种是经过引入spring-boot的依赖管理:

<dependencyManagement>
    <dependencies>
        <dependency>
            <!-- Import dependency management from Spring Boot -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.1.1.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

若是咱们找到spring-boot-starter-parent项目,就会发现这个项目里仅仅只有pom文件 springboot-starter

这是由于在项目中,咱们一般会存在多个子模块,而这些子模块会须要一些公用的配置, 好比统一的构建流程、统一的依赖版本管理,而这些内容就以pom的形式抽取出来, 造成了这种打包方式为pom的项目。

须要注意的是,打包方式为pom的项目里,除了pom文件,其余文件不会参与构建。 事实上若是咱们去看pom打包对应的默认插件配置就会发现,只有install和deploy两个生命周期有插件绑定:

<phases>
  <install>
    org.apache.maven.plugins:maven-install-plugin:2.4:install
  </install>
  <deploy>
    org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy
  </deploy>
</phases>
3.2 打包为jar

jar包是java程序包经常使用的发布形式,其能够做为库文件被其余项目使用, 也能够经过java -jar命令直接运行。

jar包中即有静态资源,也有编译后生成的class文件,这意味着其打包流程也更为复杂:

<phases>
  <process-resources>
    org.apache.maven.plugins:maven-resources-plugin:2.6:resources
  </process-resources>
  <compile>
    org.apache.maven.plugins:maven-compiler-plugin:3.1:compile
  </compile>
  <process-test-resources>
    org.apache.maven.plugins:maven-resources-plugin:2.6:testResources
  </process-test-resources>
  <test-compile>
    org.apache.maven.plugins:maven-compiler-plugin:3.1:testCompile
  </test-compile>
  <test>
    org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test
  </test>
  <package>
    org.apache.maven.plugins:maven-jar-plugin:2.4:jar
  </package>
  <install>
    org.apache.maven.plugins:maven-install-plugin:2.4:install
  </install>
  <deploy>
    org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy
  </deploy>
</phases>
3.2 其余

目前maven支持这些打包方式: pom、jar、maven-plugin、ejb、war、ear、rar。 其中每种打包方式都有相应的打包插件对其进行打包,这些插件都已经被maven默认进行了绑定, 具体打包方式对应的插件配置有兴趣的能够去 官网进行查看。

4.项目间关系

在上文中咱们提到了多种项目间关系,包含依赖关系、继承关系、聚合关系(上文提到的模块管理)。

4.1依赖关系

项目间依赖关系是最经常使用的关系,当咱们项目A使用一个项目B打成的jar时,A就对B造成了依赖。

须要注意的是,若是的java项目开发已经没法离开依赖了,A所依赖的项目B,实际上也开发中也依赖了项目C。 此时就生成的依赖的传递,即若是A依赖了B,B依赖了C,那么A依赖C。

依赖传递的存在,会使得单点的依赖关系也变得复杂起来。实际在项目中,咱们会依赖大量的jar包, 此时就可能存A依赖的B、C同时使用两个不一样版本的D:

A
 |-B
    |-D:1.1
 |-C
    |-D:1.2

咱们知道,一个项目的不一样版本一般会有大量的类是重复了,若是jvm在加载同名类时,会忽略重复的类, 这样可能致使出现其中一个包只有一半代码的状况,这明显是不合理的。这种依赖的出现就叫到依赖冲突, 当出现冲突时,maven会选取其中的一个版本放入最终的打包后文件中(如jar),而忽略其余版本。

具体的选取策略能够参考这篇短文

4.2继承关系

继承关系咱们在上文中已经提到,咱们能够经过parent标签引入父级pom, 此时子pom拥有了父级pom中的全部定义,并能够在子pom中复写相应的定义。

因为pom的继承关系可能有多级,所以若是你想确认当前项目的最终pom结构时, 就必需要将全部的pom整合到一块儿,此时咱们能够经过mvn help:effective-pom命令生成整合后的pom (若是使用idea,右键maven菜单下也有相应的生成操做)。

若是在一个没有父pom的项目中执行mvn help:effective-pom,你会发现最终生成的pom中, 忽然出现了大量你没有定义的内容,这是所以maven自身定义了一个super pom, 全部的pom都默认继承该pom(可类比java中的Object类), 在上文中咱们提到的maven项目的默认结构,就是在这里定义的。

super pom的具体内容,能够去官网查看

子pom并不会继承父pom的全部内容,仅如下内容将会被继承: - groupId - version - scm - issueManagement - ciManagement - mailingLists - properties - dependencyManagement - dependencies - repositories - pluginRepositories - build - reporting - profiles - description - url - inceptionYear - organization - licenses - developers - contributors

须要注意的是如下几个标签不会被继承: - artifactId - package - name - prerequisites

4.3聚合关系

聚合关系经常出如今有多个模块的项目中,好比项目A有三个子项目aa、ab、ac, 这时咱们能够在目录A下也添加一个pom,并定义aa、ab、ac是项目A的三个子项目, 这样咱们对项目A作构建(如mvn clean package)时,maven就会分别对aa、ab、ac作相应的构建操做。

A
 |-- pom.xml
 |-- aa
       |-- pom.xml
 |-- ab
       |-- pom.xml
 |-- ac
       |-- pom.xml

子项目的声明方式以经过modules标签进行,当咱们在项目A的pom中添加以下配置, aa、ab、ac就会成为A的子项目:

<modules>
    <module>aa</module>
    <module>ab</module>
    <module>ac</module>
</modules>

aa、ab、ac并非这三个项目的相对路径(相对于A)

在这里,可能有的人已经注意到了,这种项目层级结构,很是适合将A项目的pom做为父pom, 将aa、ab、ac的pom中相同的内容抽取至父pom中进行管理。
实际上在项目的实践中,一般也是这么处理的,但也有一些例外。好比aa、ab是springboot项目, 而ac倒是老旧的spring项目,此时可能出现:A做为aa和ab的父级pom统一管理相关配置, 同时aa和ab做为子项目存在,而ac不继承A,只做为子项目存在,这样咱们即可以统一管理aa和ab的配置, 也可以经过一条命令,对aa、ab、ac一块儿完成构建操做。 (实际实践过程当中,不推荐这种模式,咱们能够把ac提到与A平级的位置,这样的结构更清晰)

值得一提的是,若是aa与ab间存在依赖关系,好比aa依赖于ab,那么即便aa比ab先声明,maven在构建时, 也会很聪明的先对ab进行构建,再构建aa。

5.dependencies标签

<dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <type>jar</type>
      <scope>test</scope>
      <optional>true</optional>
    </dependency>
</dependencies>

dependencies标签下声明了当前项目所使用的依赖构件。

须要注意的是,这里声明的文件必须可以在maven仓库中找到, maven默认使用中央仓库,中央仓库中包括了全部经常使用的开源jar包, 但对于一些闭源的三方jar包,就没法在中央仓库中找到。
所以,公司通常都会有本身的私有仓库,咱们须要修改maven的settings.xml文件, 使maven使用私服处理依赖和发布。
对于私有仓库上没有的项目,咱们可能经过如下命令,将其上传至私有仓库:

shell script mvn deploy:deploy-file -Dfile=non-maven-proj.jar -DgroupId=some.group \ -DartifactId=non-maven-proj -Dversion=1.0 -Dpackaging=jar

5.1 classifier标签

在引用依赖时,仅仅使用groupIdartifactIdversion做为坐标可能还并不太足够。 考虑一下这种场景:有一个项目,该项目须要提供一个基于jre1.7的jar包, 同时还提供了一个基于jre1.8的jar包,即一套源码须要使用两种jdk打成不一样的jar包。

这种状况下咱们会配置两套打包方式,默认使用1.8打包,并可选择1.7打包,配置以下:

<profile>
    <id>jdk17</id>
    <properties>
        <!--使用jdk1.7打包-->
        <java.version>1.7</java.version>
    </properties>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-jar-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                        <configuration>
                            <!--配置classifier,与1.8版本的包区分-->
                            <classifier>jdk17</classifier>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</profile>

若是用1.8打包发布的jar名称为demo-1.0.jar,那么用1.7打包时, 发布的jar的名称为demo-1.0-jdk17.jar——实际场景下,classifier标签能够配置成任意字符串, 并不必定是jdk17。

而使用者在引入1.7版本的依赖时,就须要额外添加classifier标签

<dependency>
  <groupId>some.example</groupId>
  <artifactId>demo</artifactId>
  <version>1.0</version>
  <classifier>jdk17</classifier>
</dependency>

classifier标签有一处更常见的地方,是sources.jar:
classifier_sources

这就是咱们上文中说到的一个pom,打出了两个包,源码包对应的classifier标签就是sources, 而测试源码包对应的值为test-sources

5.2 type标签

type标签描述了依赖类型,若是不定义该值,使用默认值jar。
对于不一样类型的依赖,maven的处理策略也不相同:

type classifier extension packaging language added to classpath includesDependencies
pom   = type = type none    
jar   = type = type java true  
test-jar tests jar jar java true  
maven-plugin   jar = type java true  
ejb   jar = type java true  
ejb-client client jar ejb java true  
war   = type = type java   true
ear   = type = type java   true
rar   = type = type java   true
java-source sources jar = type java    
javadoc javadoc jar = type java true  

实际开发中,咱们只会用到jarpom两种类型,你们能够注意到pom类型的依赖是不会产生依赖传递, 也不会被加入classpath中。

5.3 scope标签

该标签订义了该包在哪些阶段会被放到classpath中,另外还定义了依赖的可传递性。其聚会范围以下:

classpath即类、类库加载路径,在咱们的依赖中,大多数依赖会在编译、测试、运行都用到, 但也有一些依赖可能只有在编译、测试阶段运到(好比测试相关的包),还有部分包可能在编译、测试时须要, 但实际发布时不须要(好比jdk相关的包)。

含义 是否会将该依赖向下传递
compile 该值是默认值。该依赖在全部阶段的都会放入classpath中,是最经常使用的值。
provided 只有在编译和测试时放入classpath中,实际打包时忽略该包
runtime 编译时不须要,只在测试和打包时须要
test 只在对测试代码作编译和执行时须要
system 手动指定所依赖包的本地存在路径 只传递自己
  • compile是最经常使用的,通常项目引入的包在编译、测试、发布阶段都会用到。
  • provided通常用于基础包,在基础包的开发过程当中会依赖一些三方包, 但可能不想为使用者带来依赖冲突的负担,此时可使用该范围,由使用者自行决定该三方包的版本 (固然须要是兼容的版本);另外一种不推荐的使用方式是将该依赖做为可选依赖向下传递, 举一个例子:咱们开发了一个基础工具类包,里面含有许多工具,像poi工具、时间转换工具、中文数字工具, 另外里面还包含了TapConfigUtil用来获取配置, 此时咱们注意到配置中心的集成是以springboot为基础的, 而咱们想让该工具包也能够被其余类型的项目使用, 这种状况下咱们能够给commons-aap-appconfig包加上provided范围。 若是某个使用者只不须要使用TapConfigUtil,那么他就不须要引入commons-aap-appconfig包。 (事实上commons-aap-appconfig包里引入spring相关的依赖里使用了provided范围)
  • runtime不多用到,通常状况下使用compile就能够了, 使用runtime甚至可能给IDE带来一些问题
  • test用在测试相关的包上,测试代码的安全性会更低(毕竟没人会写测试代码来测试测试代码 ——对外发布的测试工具除外),使用test标记测试相关的包,能够避免被其余开发人员错误的使用。
  • system须要配置systemPath标签使用,用来指定使用仓库中没有的包。 与其相比,上传私服是一种更好的方式,由于system标记的包,并不会将其依赖的包传递给使用者, 这意味着你须要本身处理好依赖关系。
5.4 systemPath标签

仅在依赖项范围是system时使用,不然构建将失败。
systemPath标签中的路径必须是绝对路径,所以建议使用maven变量拼装路径, 例如${project.basedir}/lib。

5.5 optional标签

在上面介绍scope标签provided取值时,咱们讲了它能够实现让使用都按需决定是否引入依赖, 这样以达到避免引入不须要的依赖。
事实上provided更多的是用来要求使用方或运行时系统提供具体的包,而不是用于这种用途。
配置依赖的可选应该使用optional标签来实现。

5.6 exclusions标签

dependency标签下还存在exclusions标签,咱们在前面讲到了依赖传递可能带来的依赖冲突问题, exclusions标签就是排除掉不想被传递过来的依赖。

这里须要注意的是,A->B->C时,若是你在引入依赖A的时候,将B排除,那么C也将被排除, 除非有其余地方也引入了C(有点像对“树”进行操做,排除进至关于直接剪掉了一个“子树”)

<exclusions>
    <exclusion>
        <groupId>org.objenesis</groupId>
        <artifactId>objenesis</artifactId>
    </exclusion>
</exclusions>

exclusion下只有groupIdartifactId两个标签,由于只不可能同时依赖一个包的不一样版本。

5.7 其余

version标签除了指定具体版本外,还能够指定范围版本,有兴趣的能够查看 官网, 不过不推荐在项目中使用。

各小版本在maven项目中的前后发布顺序能够看 这里

6.dependencyManagement标签

正如其名字所表明的含义,该标签的做用就是管理依赖。
在上面的dependency标签的介绍中,咱们看到在dependency标签下有众多的子标签, 想象一下咱们有多个服务,若是每一个服务都在引入同一依赖时配置好全部内容, 那将是很是麻烦,并且若是须要对某个所依赖的jar包作升级时,咱们不得不改每个pom中的version信息。

可能有人已经想到,咱们能够将这些依赖放到父pom里面就能够了。这是一种很好的策略, 但让咱们考虑项目A、B,A中引入了a、b三个包,B中引入b、c两个包, 若是咱们使用父pom管理公用依赖,将只有b会被放到父pom里,而a、c由A、B本身管理。 若是后来B项目的功能增长,也须要引入a包,那么咱们就须要把a包从A项目中提到父pom中, 以保证对依赖的统一管理——由于,这种方式并不能真正实现依赖的统一管理。

如今考虑另外一种方式,咱们在父pom里列出项目中全部使用到的依赖包,并配置好全部内容, 但并不真正的对这些包产生依赖,仅仅是列出来。若是那个子pom真正须要用到, 那么他只须要声明一些尽量少的信息,就能够对对应的包产生依赖 ——这种方案就是使用dependencyManagement标签

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring.boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>com.demo.company</groupId>
            <artifactId>someproject-commons-versions</artifactId>
            <version>1.0.0</version>
        </dependency>
    </dependencies>
</dependencyManagement>

dependencyManagement标签下只有一个dependencies标签, 里面的内容和上文中的dependencies标签几乎同样,惟一不同的是, 在dependencyManagement标签下的scope标签新增一种取值——import

import只能用于导入package值为pom的依赖包, 当在项目A的pom中使用import导入项目B时,A、B的dependencyManagement标签下的内容将被合并。 这是一个很是有用的特性,这意味着咱们能够建立一个项目专门用来作统一版本控制, 在这个项目里,咱们将全部的包的versionscope等内容都定义好, 其余项目只须要在dependencyManagement下导入该包,而不须要破坏其自己的继承结构。

虽然dependencyManagement有这样的方便, 但须要注意的是dependencyManagement下的内容不只对当前pom、子pom生效, 一样会覆盖因为依赖传递而引入的jar包。好比在项目A中,咱们依赖了aa-1.0、bb-2.0, 而在bb-2.0中依赖了aa-2.0,此时,若是aa的版本是在dependencyManagement中定义, 那么aa-2.0将被放弃,aa-1.0生效,这样将致使bb-2.0可能使用了根本不存在的类和api, 最终项目构建失败。

7.Properties标签

如其字面意思——属性。properties标签下定性的属性,能够在pom的任何地方被引用, 引用方式为${属性名},其中属性名即标签名,咱们能够利用这些属性管理一组依赖, 或者将一些容易变化的值集中到properties标签下管理。

<properties>
    <org.powermock.version>2.0.2</org.powermock.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-module-testng</artifactId>
        <version>${org.powermock.version}</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-api-mockito2</artifactId>
        <version>${org.powermock.version}</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-module-junit4</artifactId>
        <version>${org.powermock.version}</version>
        <scope>test</scope>
    </dependency>
</dependencies>

除了自定义属性外,maven插件也会提供一些属性,以达到对其进行配置的目的。 同时,maven还提供如下属性能够在pom文件中使用: - env.X : 之前缀env开头的属性,对应于系统环境变量, 好比经过${env.PATH}能够取到环境变量PATH的值。这里须要注意的是, maven对环境变量的大小写是敏感的,windows下的环境变量在maven中必须所有大写。 - project.X : project为前缀是属性是项目的属性值, 包括了pom中的标签——好比${project.parent.artifactId}能够获取 *<parent>* 下 *<artifactId>* 的值;除此以外还有一些其余属性,具体可参考 [官网说明](http://maven.apache.org/ref/3.6.3/maven-model-builder/#Model_Interpolation)。 值得一提是,该属性值并非取当前pom中的标签,而是取**effective-pom**中的标签, 也就是说能够取到从父pom中继承的一些内容。 - settings.X : 与**project.X**相似,还过**settings.X**取的是**settings.xml**中的内容, 具体内容一样参考 [官网说明](http://maven.apache.org/ref/3.6.3/maven-model-builder/#Model_Interpolation)。 - java系统属性 : 全部能够经过`java.lang.System.getProperties()`方法获取的变量, 都可以在pom中获取,如**${java.home}**。具体可参考 oracle官方文档

3、配置构建任务

在上一章的内容是pom的一些基础标签的使用,灵活使用这些基础标签就能够达成项目的基本构建任务。

除了基本构建外,咱们还每每在构建时须要进行打包前依赖冲突检测、发布前进码检查、 发布时同步发布源码包、自动生成并部署项目站点等任务,maven做为强大的构建工具, 经过构建插件的方式,将全部的这些任务所有沿着maven构建生命周期自动执行。

1.maven构建的生命周期

maven构建的生命周期定义了打包和发布项目时须要经历的流程,maven内置的生命周期有 clean(清理)、default(打包发布)、site(生成站点文档)三种。

这三种生命周期由一些阶段(phase)组成,三种生命周期对应的阶段以下:

1.1 clean生命周期
Phase Description
pre-clean execute processes needed prior to the actual project cleaning
clean remove all files generated by the previous build
post-clean execute processes needed to finalize the project cleaning
1.2 default生命周期
Phase Description
validate validate the project is correct and all necessary information is available.
initialize initialize build state, e.g. set properties or create directories.
generate-sources generate any source code for inclusion in compilation.
process-sources process the source code, for example to filter any values.
generate-resources generate resources for inclusion in the package.
process-resources copy and process the resources into the destination directory, ready for packaging.
compile compile the source code of the project.
process-classes post-process the generated files from compilation, for example to do bytecode enhancement on Java classes.
generate-test-sources generate any test source code for inclusion in compilation.
process-test-sources process the test source code, for example to filter any values.
generate-test-resources create resources for testing.
process-test-resources copy and process the resources into the test destination directory.
test-compile compile the test source code into the test destination directory
process-test-classes post-process the generated files from test compilation, for example to do bytecode enhancement on Java classes.
test run tests using a suitable unit testing framework. These tests should not require the code be packaged or deployed.
prepare-package perform any operations necessary to prepare a package before the actual packaging. This often results in an unpacked, processed version of the package.
package take the compiled code and package it in its distributable format, such as a JAR.
pre-integration-test perform actions required before integration tests are executed. This may involve things such as setting up the required environment.
integration-test process and deploy the package if necessary into an environment where integration tests can be run.
post-integration-test perform actions required after integration tests have been executed. This may including cleaning up the environment.
verify run any checks to verify the package is valid and meets quality criteria.
install install the package into the local repository, for use as a dependency in other projects locally.
deploy done in an integration or release environment, copies the final package to the remote repository for sharing with other developers and projects.
1.3 site生命周期
Phase Description
pre-site execute processes needed prior to the actual project site generation
site generate the project's site documentation
post-site execute processes needed to finalize the site generation, and to prepare for site deployment
site-deploy deploy the generated site documentation to the specified web server

每一个生命周期拥有的阶段互不重叠,且在构建时每一个生命周期中的阶段按顺序进行。 这意味着当咱们使用mvn命令构建时,若是指定了某一阶段,maven会先执行全部前置阶段, 再执行指定的阶段——这使得maven的使用至关容易,使用都只须要记住经常使用的几个阶段 (如clean、deploy、site)便可单独完成项目的构建和发布。

2.插件的“构建阶段”

除了maven内置的阶段外,maven容许插件自定义阶段,好比maven-dependency-plugin 插件中经常使用的copy-dependencies阶段。插件阶段与内置阶段同样可使用mvn命令运行, 惟一不一样的是须要在阶段前加上插件的坐标信息,如mvn dependency:copy-dependencies (使用非maven官方插件时,须要将dependency换成groupId:artifactId的形式)。

在使用这些插件前,咱们须要在pom文件中引入插件:

<build>
  <plugins>
    <plugin>
      <artifactId>maven-dependency-plugin</artifactId>
      <version>2.8</version>
    </plugin>
  </plugins>
</build>

事实上基本构建插件maven都有内置,super pom中甚至进行了缺乏配置,不须要咱们再手动添加

虽然maven容许插件自定义阶段,但不容许修改内置的生命周期,

3.build标签

build标签下定义了构建的相关信息和配置:

<build>
  <defaultGoal>install</defaultGoal>
  <directory>${basedir}/target</directory>
  <finalName>${artifactId}-${version}</finalName>
  <resources>
    <resource>
      <filtering>true</filtering>
      <directory>${basedir}/src/main/resources</directory>
      <includes>
        <include>**/application*.yml</include>
        <include>**/application*.yaml</include>
        <include>**/application*.properties</include>
      </includes>
    </resource>
    <resource>
      <targetPath>META-INF/plexus</targetPath>
      <filtering>false</filtering>
      <directory>${basedir}/src/main/plexus</directory>
      <includes>
        <include>configuration.xml</include>
      </includes>
      <excludes>
        <exclude>**/*.properties</exclude>
      </excludes>
    </resource>
  </resources>
  <testResources>
    ...
  </testResources>
  <filters>
    <filter>filters/filter1.properties</filter>
  </filters>
</build>
3.1 defaultGoal

默认构建目标(阶段),若是配置该值,在执行mvn命令时能够不指定构建阶段。

<defaultGoal>clean package</defaultGoal>
3.2 directory

构建生成的相关文件的存放位置,默认配置为${project.basedir}/target

3.3 finalName

构建生成jar包(或其余类型的包)的名称,默认为${artifactId}-${version}

须要注意的是,finalName并不是如字面意思通常是打包后的最终名称, 实际打包插件可能会在后面添加后缀:若是咱们配置finalName配置为demo, 同时为打包插件配置classifier标签为jdk17(上面咱们讲到过), 那么实际生成的jar名称为demo-jdk17.jar

3.4 resources 与 testResources

resources标签用来指定资源文件的存放路径,这些文件不会参与编译,会直接拷贝到相应的路径下。

  • targetPath: 拷贝目标路径,在default周期的process-resources阶段,资源将被拷贝到对应的路径下。 若是不指定,文件将被拷贝至根目录下。
  • filtering: 是否启用过滤,若是启用,则容许在resource指定的文件中使用占位符替换 ,默认不启用。这里的替换范围包括全部在pom中可使用的变量,替换方式为${placeholder} ——这是官方文档的说法,实际在spring boot项目中的有效使用格式为@placeholder@
  • directory: 资源文件的源路径,默认为${project.basedir}/src/main/resources
  • includes: 源路径下须要包含的文件,容许使用通配符的方式匹配
  • excludes: 源路径下须要排除的文件,容许使用通配符的方式匹配

testResources中的内容与resources一致,不一样的是其对应的生命周期和directory的默认值。

3.5 filter

filter能够用来引入一些properties文件,在这样文件里定义的属性, 也会加入到开启了filtering的资源文件的占位符替换范围。

3.6 plugins 与 pluginManagement
3.6.1 plugins

plugins标签用于引入和配置打包插件。

<build>
  ...
  <pluginManagement>
    <plugins>
        ...
    </plugins>
  </pluginManagement>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-jar-plugin</artifactId>
      <version>2.6</version>
      <extensions>false</extensions>
      <inherited>true</inherited>
      <configuration>
        <classifier>test</classifier>
      </configuration>
      <dependencies>...</dependencies>
      <executions>...</executions>
    </plugin>
  </plugins>
</build>
  • extensions: 是否加载该插件拥有的扩展包(后方会讲到),默认为false
  • inherited: 是否容许子级pom继承,默认为true
  • configuration: configuration下的内容较为特殊,maven并未约束其子级标签的范围。 实际上子标签对应是的插件的可配置属性,所以configuration下的内容须要经过阅读插件的使用文档来进行配置 这里须要注意的是,不管configuration出如今父级pom仍是在pluginManagement中, 其中的定义属性最终会在合并后传递给插件。 (合并策略参考官方网站)
  • dependencies: 与项目的dependencies结构一致,不一样的是这些依赖不做用于项目,而是做用的插件, 经过指定依赖,咱们能够用来指定插件依赖的包的版本等信息。
  • executions: 对插件提供的maven阶段进行配置,该标签下的内容较为复杂,实际使用也比较多,值得单独一讲

executions下包含的是execution列表,每一个execution能够理解为一个任务。

<plugin>
  <artifactId>maven-antrun-plugin</artifactId>
  <version>1.1</version>
  <executions>
    <execution>
      <id>echodir</id>
      <goals>
        <goal>run</goal>
      </goals>
      <phase>verify</phase>
      <inherited>false</inherited>
      <configuration>
        <tasks>
          <echo>Build Dir: ${project.build.directory}</echo>
        </tasks>
      </configuration>
    </execution>
  </executions>
</plugin>
  • id: 任务的id,用来区分不一样的execution,该值会在该任务执行时打印出来, 格式:[plugin:goal (id)]
  • goals: 须要插件执行的构建目标(阶段),每一个插件具体提供的构建目标能够参考官方文档, 或者经过maven-help-plugin查看插件的信息: mvn help:describe -Dplugin=com.github.spotbugs:spotbugs-maven-plugin:3.1.12.2
  • phase: 指定须要绑定的maven内置构建阶段。在这里须要注意,插件的构建阶段为goal, 内置构建阶段为phase,由于插件的阶段是没有生命周期的。phase标签的做用, 就是将execution对应的任务绑定到maven的某个内置构建阶段,在maven生命周期时, 当执行到该phase时,会自动运行该execution下的goals,从而对maven构建进行扩展。 好比咱们能够将pmd插件的pmd、check关联到verfiy阶段,这样当咱们运行mvn verify时, 会自动进行代码问题的检查。
  • inherited: 是否容许该execution被子pom继承,默认为true
  • configuration: 与外层的configuration内容一致,不过仅对该execution生效
3.6.2 pluginManagement

pluginManagement的做用与dependencyManagement的做用相似,咱们能够在父pom里配置全部插件, 但不实际引入插件,子pom按需在plugins标签下引入具体插件,这样就能够达到插件配置集中管理的效果 —— 同dependencies下引入dependencyManagement中的依赖同样, plugins下引入pluginManagement中的插件只须要声明groupIdartifactId

<build>
  <pluginManagement>
    <plugins>
      <plugin>
        <artifactId>maven-antrun-plugin</artifactId>
        <version>1.1</version>
        <executions>
          <execution>
            <id>echodir</id>
            <goals>
              <goal>run</goal>
            </goals>
            <phase>verify</phase>
            <inherited>false</inherited>
            <configuration>
              <tasks>
                <echo>Build Dir: ${project.build.directory}</echo>
              </tasks>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </pluginManagement>
  <plugins>
    <plugin>
      <artifactId>maven-antrun-plugin</artifactId>
    </plugin>
  </plugins>
</build>

4.profiles标签

profiles容许咱们定义多个profile,并设置每一个profile的激活条件,以达到自如切换多套配置的目的。

<profiles>
  <profile>
    <id>test</id>
    <activation>...</activation>
    <build>...</build>
    <modules>...</modules>
    <repositories>...</repositories>
    <pluginRepositories>...</pluginRepositories>
    <dependencies>...</dependencies>
    <reporting>...</reporting>
    <dependencyManagement>...</dependencyManagement>
    <distributionManagement>...</distributionManagement>
  </profile>
</profiles>
4.1 id

如字面意思,即套profile的标识,咱们能够经过mvn命令的-P选项直接经过id激活对应的profile: mvn -P test clean deploy

4.2 activation

profile的自动激活条件。

除了经过手动指定profile外,还能够根据一些条件,自动激活profile

<profile>
  <id>test</id>
  <activation>
    <activeByDefault>false</activeByDefault>
    <jdk>1.5</jdk>
    <os>
      <name>Windows XP</name>
      <family>Windows</family>
      <arch>x86</arch>
      <version>5.1.2600</version>
    </os>
    <property>
      <name>sparrow-type</name>
      <value>African</value>
    </property>
    <file>
      <exists>${basedir}/file2.properties</exists>
      <missing>${basedir}/file1.properties</missing>
    </file>
  </activation>
</profile>

其中activeByDefault表示是否为默认激活,默认值为false。若是配置为true, 当没有配置文件被激活时,激活该配置文件

其余的标签是一些具体激活条件,在maven3.2.2版本只要知足一项激活条件就成立(或关系), 在maven3.2.2之后必须知足全部项激活条件才成立(与关系): - jdk: 指定构建时的jdk版本,除了指定具体版本外,也能够指定范围 - os: 操做系统信息,具体约束条件内容可查看这里 - property: 将该属性的key-value知足条件时该项成立。这里的属性的范围与pom文件一致。 - file: 文件存在或缺失,配置路径须为绝对路径。该项须要注意的是,file标签下的属性替换, 只容许出现basedir、系统变量、maven命令传递变量这三种。

在咱们配置好profiles,若是想知道在当前环境下哪一个配置将被激活, 能够经过mvn help:active-profiles查看。

4.3其余标签

modulesdependenciesdependencyManagement标签咱们在前文中已经讲过, profile下这些标签的可配置内容与其在根标签(project)下的一致, profile下的配置能够覆盖根标签下的配置。

repositoriespluginRepositoriesreportingdistributionManagement 这些标签在根标签下也存在,只是咱们尚未聊到,与上面的这些标签同样, 这些标签的可配置内容与其在根标签下的一致,profile下的配置能够覆盖根标签下的配置。

build标签较为特殊,其在profile下的可配置内容只包含前文中咱们提到的全部内容, 但在根标签下的build标签还有一些独占内容咱们没有提到。

5.project标签下的build标签

project标签下的build标签下还有几个标签,这些标签仅出如今project标签下的build中, 没法在profile中的内容配置。

这些标签包括目录结构标签——sourceDirectorysourceDirectorytestSourceDirectoryoutputDirectorytestOutputDirectory,以及extensions标签。

5.1 *Directory标签

这些标签订义了项目的目录结构,标签的含义和其英文名字一致,标签的值必须为绝对路径 (通常使用`${project.basedir}进行拼接)。

这样须要注意的是除了上述的几个标签外,咱们还能够找到一个scriptSourceDirectory标签, 该标签实际没有用处,已经被官宣废弃。

5.2 extensions标签

extensions下能够引入一些jar对maven能力进行扩展,好比引入wagon-ssh添加对ssh协议的支持, 与plugins中的插件不一样,扩展程序不须要提供构建目标(gloas),只是提供能力基础, 所以须要另行引入插件配合使用。

咱们举一个利用ssh协议进行包自动部署的例子:

<build>
  <extensions>
    <groupId>org.apache.maven.wagon</groupId>
    <artifactId>wagon-ssh</artifactId>
    <version>2.10</version>
  </extensions>
  <plugins>
    <plugin>
      <groupId>org.codehaus.mojo</groupId>
      <artifactId>wagon-maven-plugin</artifactId>
      <version>1.0</version>
      <configuration>
        <serverId>devServerRoot</serverId>
        <formFile>target/demo.jar</formFile>
        <url>scp://root@x.x.x.x:/root/applications/demo</url>
        <commands>
          <command>/root/applications/demo/restart.sh</command>
        </commands>
        <desplayCommandOutputs>true</desplayCommandOutputs>
      </configuration>
    </plugin>
  </plugins>
</build>

其中serverId中的的devServerRootserver标签中的idserver标签下能够定义服务器的帐号密码信息,通常在settings.xml中配置,也能够在pom中配置。

<server>
  <id>devServerRoot</id>
  <username>root</username>
  <password>password</password>
</server>

5.Reporting标签

reporting下的内容与site生命周期相关,reporting标签下能够定义一些插件用于生成报告文档, 好比javadoc。
build相似,reporting也提供了插件的配置能力,与build不一样的是, reporting下的插件没法被绑定到指定的maven阶段(默认绑定site阶段)

<reporting>
  <plugins>
    <excludeDefaults>false</excludeDefaults>
    <outputDirectory>${basedir}/target/site</outputDirectory>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-javadoc-plugin</artifactId>
      <version>3.1.1</version>
      <reportSets>
        <reportSet>
          <id>sunlink</id>
          <reports>
            <report>javadoc</report>
          </reports>
          <inherited>true</inherited>
          <configuration>
            <links>
              <link>http://java.sun.com/j2se/1.5.0/docs/a
              </link>
            </links>
          </configuration>
        </reportSet>
      </reportSets>
    </plugin>
  </plugins>
</reporting>
  • excludeDefaults: 是否禁用默认报告信息,默认为false。这里的默认报告信息指的是 maven-project-info-reports-plugin,该插件会生成项目信息以及左侧的导航栏。
  • outputDirectory: 报告生成位置。与build下的outputDirectory不一样,报告的默认生成位置为 ${basedir}/site
  • plugins: 引入报告生成插件并对其进行配置。
6.1 plugins中的reportSet标签

build下的plugin相比,reporting下的配置就相对较为简单,只有一个相似于executionsreportSets标签——二者均定义了插件在某一maven阶段的构建任务。

  • id: 构建任务的id,与execution相似,会在构建时打印出来
  • reports: 相似于executiongoals,但实际上插件会将其构建目标作出区分, 报告相关的目标只能够出如今reportSet中(好比javadoc:javadoc),而不容许出如今execution
  • inherited: 是否容许子pom继承
  • configuration: 与execution中的配置相似,对该次任务的插件进行配置

有一点须要注意的是,一样的插件能够同时出如今build下和reporting下, 虽然咱们能够用pluginManagement统一管理版本, 但build下的相关配置(configuration)不会传递到reporting

相关文章
相关标签/搜索