Maven自动化构建工具

作者: 书上得来终觉浅 | 来源:发表于2019-01-21 14:46 被阅读14次

    Maven是Apache软件基金会的一个开源项目,用来处理Java工程的自动化构建,它有两个主要特征:

    • 构建
    • 自动化处理依赖关系

    构建就是将源代码、第三方的依赖包(jar包)、各种资源和配置打包成为一个可执行文件(如war包)的过程。现在的高级IDE,比如IDEA,Eclipse都有自动化构建的功能,我们只需要点一下run按钮,IDE就会自动的帮我们构建项目,并部署IDE内置的容器(Tomcat)中,供我们预览和调试。

    既然有了这些高级IDE,为什么Maven还存在呢?实际上Maven项目建立之初,还没有这些高级的IDE,关于自动构建工具的发展史如下:

    自动构建工具发展史

    其中Maven取代Ant的时候,高级的IDE还没有出现,现在即使有这些高级的IDE,它也仅仅是解决了在开发阶段自动构建的问题,并没有解决项目的依赖问题,更何况是在DevOps中的持续交互和持续集成。

    1 Maven安装与配置

    要运行Maven需要下载JDK,这里不做JDK的安装的介绍。

    Maven官网下载Maven后,解压到指定的目录,这里我放在了/usr/local/maven目录下。Mac系统环境变量配置,在/etc/paths.d目录下新建maven文件,写入maven目录的路径,需要切换到root用户。

    echo "/usr/local/maven/bin" > /etc/paths.d/maven
    

    使用mvn -v查看Maven的信息:

    mvn -v
    Apache Maven 3.6.0 (97c98ec64a1fdfee7767ce5ffb20918da4f719f3; 2018-10-25T02:41:47+08:00)
    Maven home: /usr/local/maven
    Java version: 1.8.0_151, vendor: Oracle Corporation, runtime: /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre
    Default locale: zh_CN, platform encoding: UTF-8
    OS name: "mac os x", version: "10.14", arch: "x86_64", family: "mac"
    

    用户tree命令查看Maven的目录结构:

    # Maven的目录结构
    tree -L 1 /usr/local/maven/
    /usr/local/maven/
    ├── LICENSE
    ├── NOTICE
    ├── README.txt
    ├── bin
    ├── boot
    ├── conf
    └── lib
    

    比较重要的是conf/setting.xml文件,它是Maven的配置文件,有以下几个标签需要注意:

    • localRepository 表示的Maven本地仓库的位置,默认值是${user.home}/.m2/repository,及当前用户根目录下。仓库的概念放在后面说,这里大概知道是用来存放从网上下载下来的第三方jar包的。
    • mirrors 表示要从哪里下载第三方jar,及中央仓库的镜像(后面说),Maven的中央仓库在国外,一般为了提高下载速度,我们都会配置国内的镜像,这里我配置了阿里云的镜像,也可以配置多个镜像。
    <mirrors>
        <mirror>
              <id>alimaven</id>
              <name>aliyun maven</name>
              <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
              <mirrorOf>central</mirrorOf>        
        </mirror>
    </mirrors>
    

    2 构建过程

    Maven的构建过程大致分为以下步骤:

    构建阶段
    • Clean 清空编译结果,为下一次编译做准备
    • Compile 编译主程序源文件,Maven将源文件分为主程序源文件和测试程序源文件,他们放在不同的目录
    • Test 编译测试程序源文件
    • Package 将主程序编译后的class文件、资源文件以及配置文件,打包为对应的类型(jar包或war包,需要在pom.xml中设置)
    • Install 将打包后的文件按照一定规则(groupId+artifactId的方式)安装到本地仓库中
    • Deploy 部署程序

    构建过程是按上面的顺序依次进行的,我们可以通过IDE或CLI选择执行到某一步。比如我们选择执行package打包这一步,Maven会先执行package前面的所有步骤,最后才做打包这个步骤,也就是说,不管我们要执行哪一个步骤,Maven都会从Clean开始,顺序往后执行到我们选择的那一步。

    通过CLI的方式执行构建命令时,只需在项目的根目录输入mvn后面加要执行到的步骤即可。例如,要执行打包这一步,可在项目的根目录中输入以下命令:

    mvn package
    

    3 Maven工程目录结构

    Maven工程采用约定大于配置(Convention over Configuration)的设计,及源文件放哪个目录,测试文件放哪个目录是约定好了的,如果不按这个约定存放,那么Maven会不能正确的构建这个项目。

    maven_webApp
    ├── pom.xml
    └── src
        ├── main
        │   ├── java
        │   ├── resrouce
        │   └── webapp
        │       ├── WEB-INF
        │       │   └── web.xml
        │       └── index.jsp
        └── test
            ├── java
            └── resource
    
    

    上面是Maven WebApp的一个目录结构,在根目录下有一个src目录和pom.xmlpom.xml 是Maven工程的标志,可以简单的理解为只有有了pom.xml文件,这个工程才是Maven工程。

    src目录下面有main目录和test目录,分别用来存放主程序和测试程序,其下面又分为javaresource目录,java目录用来存放源码文件,resource目录用来存放资源文件。如果是webapp的工程,建议(非强制约定)在main目录下添加webapp的目录,用来存放于web相关的资源。

    4 仓库与坐标

    4.1 仓库

    仓库是用来存放东西的,以方面我们引用,这些东西大致可以分类3类:

    1. 存放Maven构建过程需要的plugin,Maven核心程序仅定义了构建的接口,具体的实现是由单独的plugin完成的。比如pom.xml文件中定义如下的plugin插件

      <build>
          <pluginManagement>
              <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
              </plugin>
          </pluginManagement>
      </build>
      

      maven-compiler-plugin是Maven的编译插件,在执行mvn compile命令时,由Maven核心程序调用。它存放在~/.m2/repository/org/apache/maven/plugins/maven-compiler-plugin目录中。

    2. 存放通过mvn install命令安装的本地工程

    3. 存放第三方的jar包

    仓库按位置又可以分为:本地仓库,私有仓库,中央仓库以及中央仓库镜像。比如,我们需要引用一个jar包,Maven会现在本地仓库查找,如果没有就到私有仓库中查找,私有仓库有该包,将该报下载到本地仓库;如果私有仓库没有该包,从中央仓库(镜像)中下载该包到私有仓库和本地仓库。这里私有仓库并不是必须的,一般有条件的企业会通过Nexus等软件自行搭建一个私有仓库,以方便企业内部开发者对jar包的引用。中央仓库的镜像的目录是提升jar包的下载速度。

    4.2 坐标

    坐标是jar包的元数据,用来描述jar包在仓库中的位置。包括groupId组织名称,artifactId项目名称,version信息等,通过这三个属性,Maven就能准确的找到该包。比如,程序中要引用mysql驱动,可以在pom.xml文件中添加如下的配置(依赖),该配置中包含了坐标信息,Maven通过这个坐标就能找到对应的jar包。

    <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    
    

    5 依赖

    我们创建了一个项目A,如果它要完成某些工作,需要引入项目B,那么我们就说A依赖于B。在没有用Maven时,我们需要手动的将B复制到项目A中,并添加引用关系。使用Maven后,我们只需在pom.xml文件中做如下配置即可,Maven会为我们下载对应坐标的包,以及添加引用。

    <dependency>
      <groupId>组织名称</groupId>
      <artifactId>项目名称</artifactId>
      <version>版本号</version>
    </dependency>
    

    如果B又依赖C,那么A就间接依赖C。为了区分间接依赖,我们也可以说A是直接依赖B,如下图所示:

    maven依赖

    Maven会将B加入工程A中,而项目C作为A的间接依赖,也为加入到A中,我们叫这种情况为依赖传递。

    5.1 依赖传递原则

    依赖传递的原则:就近原则+声明先后原则。

    考虑如下情况:

    maven-依赖

    项目C有两个版本,分别是V1和V2版本。项目A直接依赖B和C V2版本,B直接依赖C的V1版本,此时Maven会通过就近原则添加C V2版本。就近原则基本是按步长来计算的,那什么是步长呢?比如A到C V2只要一步,而A到C V1需要通过B,及A到B为一步,B到C V1是一步,总计2步,所以A到C V2的步长小于A到CV1,此时Maven会添加C V2到项目中。

    如果A到达C的两个版本的步长是相同的,Maven会按pom中的定义的先后顺序加入对应的依赖,先定义的会加入到A的依赖中。

    maven依赖

    上面两种情况,A到C的步长是相同的,Maven会更加定义的先后顺序添加C,因为C1比C2先定义,所以Maven不会添加C2版本。

    5.2 依赖排除

    依赖排除实际上是通过配置pom.xml文件对间接依赖的排除,这个不是常用。具体的做法是在dependency标签中使用exclusions标签

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>4.3.22.RELEASE</version>
      <exclusions>
        <exclusion>
          <groupId>commons-logging</groupId>
          <artifactId>commons-logging</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    

    我们知道spring-core模块依赖commons-logging模块做日志输出,如果我们不希望加载commons-logging,可以使用上面的配置将logging排除在依赖之外。

    5.3 依赖的范围

    依赖的范围和构建的阶段(过程)是密不可分的,Maven使用scope标签来标识依赖的范围,

    • compile - scope的默认值,标识依赖在整个生命周期都适用。

    • provided - 仅在编译和测试阶段有效,也不做依赖的传递。比如我们在开发web app时,在开发阶段我们需要引用servlet-api的jar包,而当我们部署app到tomcat容器时,tomcat容器为我们提供了servlet-api的jar包,如果我们将引用的servlet-api打包到war包中,就会与容器的jar冲突。此时我们可以标记servlet-api为provided,使得这个jar包只在编译和测试阶段有效,而我们在打包项目时,将该jar排除在外,以解决冲突。

      <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
      <dependency>
          <groupId>javax.servlet</groupId>
          <artifactId>javax.servlet-api</artifactId>
          <version>3.1.0</version>
          <scope>provided</scope>
      </dependency>
      
    • runtime - 这个scope的范围表示依赖不在参与编译阶段,这个依赖是服务与执行阶段的,这个很少用到。

    • test - 仅在编译和测试阶段有效,不参与打包等其他阶段,标记了test的依赖不会做依赖传递。

    • system - 与 provided相似, 只是这个依赖包不会从仓库中查找,这个也很少用到。

    源码编译阶段 测试源码编译阶段 打包阶段 部署阶段
    compile
    test - -
    provided - -

    注意test和scope的区别:test在源码编译和测试源码编译都会参与,这个依赖包只是做测试用的,后面打包部署的时候不会添加该依赖包,容器中也不会提供该依赖包;provided在源码编译和测试源码编译都会参与,打包的时候,也不会加入该依赖包。但是,一般是tomcat等容器会提供该依赖包。

    6 POM

    上面零散的说了些POM的内容,并没有提到POM的概念,这里补充一下,POM(Project Object Model)翻译过来是项目对象模型。我们可以将POM项目对象模型分开理解,一个Maven工程就是一个项目对象,比如,一个Mavne的java工程和一个Maven的web工程,他们都是一个独立的项目对象。知道了项目对象后,我们对项目对象进行抽象,建立一个模型,用这个模型来描述这个Maven工程,我们这个模型是POM项目对象模型。

    我们抽象出来的模型总的有个地方存,存在个模型的文件就是pom.xml,也可说POM的具体表现形式就是pom.xml。前面提到的工程的坐标,用到的插件,依赖的包等都是用来描述Maven工程的,这些信息就是模型的一部分,所以我们定义在pom.xml文件中。除了上面提到的模型信息外,下面在介绍几个模型的抽象。

    6.1 Properties

    可以把properties想象为POM的自定义占位符,用来替换POM中的硬编码。下面的例子使用properties中自定义的标签spring.version替换spring-webmvc的版本号。

    <properties>
        <spring.version>4.3.22.RELEASE</spring.version>
    </properties>
    
    <dependenies>
        <dependeny>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependeny>
    </dependenies>
    
    

    6.2 更多参考

    Apache官网上列出POM模型的更多参考。连接地址

    相关文章

      网友评论

        本文标题:Maven自动化构建工具

        本文链接:https://www.haomeiwen.com/subject/frjcjqtx.html