为什么我们需要Maven

编程项目构建工具简介

在进行编程操作的时候,我们常常会遇到很多与编程无关的项目管理工作,如下载依赖、编译源码、单元测试、项目部署等操作。一般的,小型项目我们可以手动实现这些操作,然而大型项目这些工作则相对复杂。构建工具是帮助我们实现一系列项目管理、测试和部署操作的工具。

软件构建(Software Build)是指软件开发过程中涉及到的一系列处理工作,如将源代码编译成二进制代码,打包二进制代码,运行自动化测试等。为了方便编程人员的操作,人们开发了自动构建(Build Automation)工具来帮助人们处理这些工作。这篇博客将简单介绍构建工具的概念。

  • 一、为什么需要构建工具
  • 二、构建工具的功能
  • 三、流行的构建工具
  • 四、如何识别项目构建工具

一、为什么需要构建工具

在国内高校的编程课中,比如,以合肥工业大学管理学院电子商务专业的编程课为例。一般都是从Java编程基础教起,在安装好JDK之后,开始使用一个记事本编写一个HelloWorld.java,里面内容是打印出Hello World。接下来教大家使用JCreator工具来编写程序,实现输出。之后的教学主要内容在Java知识上,对于项目开发的经验和知识则很少描述。于是很多同学不理解,为什么要用JCreator编写程序,或者是JCreator很好用,为什么后面大家用Eclipse、IntelliJ IDEA这种工具来开发Java。

这些工具都是IDE,也就是为了方便开发人员组织代码文件,管理项目而开发的。IDE其实也可以实现编译、打包的功能。既然如此为何还需要构建工具呢?为什么很多IDE软件都提供了各种构建工具插件呢?个人认为,IDE本身是为了提供开发人员编程的工具,因此整合了项目管理和构建的一些功能。而构建工具的目标是为了管理依赖、编译、打包和部署。因此,后者更像是提供编程的依赖环境和外部包,并帮助发布部署项目的。它的对项目管理支持的功能和目标比IDE更纯粹也更强大(比如大部分构建工具都有中央库,收集了几乎所有开源的外部包提供给开发者自动导入外部包的功能,而一般IDE的这方面功能也是通过构建工具实现的。同时,构建工具几乎都支持命令行运行,来帮助我们打包、编译和发布等,证明它并不是提供一个编写代码的环境,而是作为管理的工具存在。而IDE几乎都是有界面,可以提供代码编写的)。此外,网络上还有一些说法(https://www.oschina.net/question/558461_117208):

一般而言.一个比较正规的项目都不会基于IDE 进行构建..一般会用ant, maven, gradle ,
为什么不用ide 呢?首先,是ide的选择,有人喜欢,用vim,eclipse,intellijidea,收费的,免费的.
特别是公开的项目,你用什么IDE 相当于为这个IDE 打广告了..
所以,一般而言都是用构建工具,而不是IDE .实际上各种IDE 也是基于各种构建系统,也正是不同的IDE,它们的构建方式不同,所以要让不同的IDE间能一起开发,于是需要一个统一的构建工具,只是你平时不关注而已..

对于小型的项目而言,比如大学开始的Java课可能要求我们写一个简单的计算器等。该项目依赖的外部的代码很少,几乎使用Java自带的SDK就可以了。但是,对于大中型的项目,都会依赖很多外部开发资源。网络上开源了大量的代码,在我们编写程序的时候可以帮助我们减少重复性的工作,大大提升复用情况,降低编程难度。对于这种项目的代码维护,以及依赖维护是很复杂的。什么程序依赖什么版本的什么外部包,如果不使用构建工具帮助我们管理这些依赖,那将增加开发人员大量的负担。因此,包括上述编译、打包和发布等功能,构建工具在帮助我们管理这些东西,大大提升编程效率。

二、构建工具的功能

基本上构建的自动化是编写或使一大部分任务自动执行的一个动作,而这些任务则是软件开发者的日常,像是

 
  1. 下载依赖
  2. 将源代码编译成二进制代码
  3. 打包生成的二进制代码
  4. 进行单元测试
  5. 部署到生产系统

三、流行的构建工具

历史上,自动构建工具主要是通过makefiles(环境变量文件)进行,它是一个文件,包含了自动构建的指令。大多数情况下,makefile都是指示如何编译并连接项目的。目前,不同的编程语言有不同的构建工具。主要包括如下:

 
  1. Java - Ant, Maven, Gradle
  2. .NET Framework - NAnt
  3. C# - MsBuild

在Java的世界里,目前在被使用的常用构建工具有三个:Ant,Maven,Gradle。

Ant的核心是由Java编写,采用XML作为构建脚本,这样就允许你在任何环境下,运行构建。Ant基于任务链思想,任务之间定义依赖,形成先后顺序。缺点是使用XML定义构建脚本,导致脚本臃肿,Ant自身没有为项目构建提供指导,导致每个build脚本都不一样,开发人员对于每个项目都需要去熟悉脚本内容,没有提供在Ant生态环境内的依赖管理工具。

Maven团队意识到Ant的缺陷,采用标准的项目布局,和统一的生命周期,采用约定由于配置的思想,减少构建脚本需要的编写内容,活跃的社区,可以方便找到合适的插件,强大的依赖管理工具。缺点是采用默认的结构和生命周期,太过限制,编写插件扩展麻烦,XML作为构建脚本。

而Gradle同时拥有Ant和Maven的优点,它是基于Groovy的DSL,提供声明式的构建语言,采用标准的项目布局,但拥有完全的可配置性,就是可以改,通过插件,提供默认的构建生命周期,也可以自己定义任务,单独运行任务,定义任务间的依赖,强大的依赖管理工具,与Maven和Ivy仓库结合,与Ant天生兼容,有效的重用Ant的任务,多种实现插件的方式,强大的官方插件库,从构建级别,支持从Ant或者Maven的逐步迁移,通过包装器,无缝的在各个平台运行。

四、如何识别项目构建工具

一般地,一个项目的根目录中就会包含构建工具的配置文件信息,也表明了该项目使用的构建工具,通常有如下的对应关系:

 
  1. build.xml - 该项目使用Ant构建
  2. pom.xml - 该项目使用Maven构建
  3. build.gradle - 该项目使用Gradle构建


 

接下来介绍一下Maven

 

背景

  在使用Eclipse进行项目合作开发的时,我们是如何进行项目依赖管理的呢?

  我们通常会在新建项目的时候,同时建立一个lib目录,在其中放着项目所依赖的各方类库,这样提交到SVN之后, 每个开发人员检出项目到本地,得到项目的工作副本,这样所有开发人员就会持有统一的项目依赖了。 
  【1】依赖冗余,浪费空间:但是,随着项目的增多,模块的增多,这种方式就会有问题。很多模块都会引用相同的依赖,当每个模块都把自己的依赖提交到SVN,那么相同的依赖就会占用服务器SVN的Repository很大的空间,造成空间浪费。 
  【2】版本问题:同时,如果一个项目中依赖的版本和另一个项目依赖的版本不一致。比如这个项目依赖hibernate2.x,而另一个可能依赖hibernate3.x, 当合并两个项目发布的时候, 可能因为这种依赖类库详细版本信息的缺失,造成问题。

是什么

  为了解决以上依赖管理过程中出现的问题, 我们寻求出一种集中式的依赖管理方式。各个项目只要通过统一的依赖描述文件(pom.xml)来指定自己需要的依赖就可以, 而不用自己来管理真正的依赖库,因为所有的项目都使用同一个中央依赖库(中央仓库), 所以即使各个项目中有相同的依赖, 也不会出现依赖冗余的问题。 
  Maven是基于POM的一款进行项目依赖管理,构建管理和项目信息管理的工具。

优点

【1】“约定优于配置(Convention over Configuration)”,maven提供了约定的项目的目录结构,自动创建项目目录,提高开发效率。

目录结构: 
     main :源代码 
     test : 放测试类 
     resource:资源文件

     src 
     –main 
     —-java 
     ——package 
     –test 
     —-java 
     ——package

用命令自动创建目录的两种方式:如:mvn archetype:generate

为什么我们需要Maven_第1张图片

【2】简单的构建过程:编译、清理、测试、打包、部署等。通过输入简单的命令就可以完成这些工作。 
   compile:编译 
   test:测试 
   package:打包 
   clean :清理target文件,删除maven生成的目标文件,target中存放的是编译后的class文件和测试报告 
   install:安装jar包到本地仓库中

Maven编译项目流程

 

最原始的java程序运行:

Java 程序是由若干个 .class 文件组成的。这些 .class 文件必须根据它们所属的包不同而分级分目录存放;运行前需要把所有用到的包的根目录指定给 CLASSPATH 环境变量或者通过 java 命令的 -cp 参数;运行时还要到控制台下去使用 java 命令来运行。

使用maven编译项目的流程:

1.执行mvn compile 编译源代码 
2.如果用到其他jar包时会去pom.xml中的找坐标 
3.根据坐标去本地仓库中查找: 
如果有,则将jar包加入到classpath中 
如果没有,会去maven的中央仓库找,下载放到本地仓库中,再加入到classpath中。

基本概念

仓库:管理和存放项目依赖 
坐标:项目依赖的唯一标示


生命周期: 
  clean :清理项目 clean 
  default:构建项目 compile test package install 
  site:生成项目站点 
  【备注】:如果执行mvn package命令,会先自动执行compile 和test命令。


依赖范围: 
    有三种classpath(编译,测试,运行),规定依赖范围就是控制依赖与classpath的关系, 不同的范围,就会寻找不同classpath下的jar包进行执行。 
compile 【默认方式】:编译,测试,运行,打包的时候都会加入依赖(默认方式) 
provided编译和测试的时候会加入依赖,运行时不会,打包时不会。如:项目依赖Servlet API,但是运行时不依赖。因为Web Container中已经有,这样避免了冲突 。 
runtime:在运行和测试的时候依赖,在编译的时候不依赖 
♥test:测试时依赖,编译和打包时不依赖,测试时才有用的依赖。如:要把(如Junit)scope设置成test。 
system:和provided一样 编译和测试时候有效,与本机系统相关联,可移植性差 
import:导入的范围,表示如将项目B中的依赖导入到A当中,只是用在dependencyManagement中


依赖:

传递依赖:A–>B–>C,项目A对项目C是间接依赖,此时项目A中就会有项目C的jar包,可以通过exclution标签排除jar包。 
依赖冲突: 
1.短路优先:A–>B–>C–>X(V1.0)同时A–>D–>X(V2.0),那么A会依赖V2.0的X。 
2.先声明优先:A–>B–>X(V1.0),A–>C–>X(V2.0),那么看在A的pom的xml文件中,先声明的是B的依赖还是C的依赖,A会依赖先声明者所依赖的该版本的X。 
聚合:需要将多个项目同时安装时,可以通过module进行配置 
继承:多个项目中含有共同的依赖,将这些依赖抽到父类中,用dependencyManagement标签进行管理,子类中通过parent标签进行继承。

 

一个Maven编译实例

 

 "font-size: small;">[INFO] Scanning for projects...  

2. [INFO] ------------------------------------------------------------------------  

3. [INFO] Building Maven Hello World Project  

4. [INFO]    task-segment: [clean, compile]  

5. [INFO] ------------------------------------------------------------------------  

6. [INFO] [clean:clean {execution: default-clean}]  

7. [INFO] Deleting directory D:\code\hello-world\target  

8. [INFO] [resources:resources {execution: default-resources}]  

9. [INFO] skip non existing resourceDirectory D: \code\hello-world\src\main\resources  

10. [INFO] [compiler:compile {execution: default-compile}]  

11. [INFO] Compiling 1 source file to D: \code\hello-world\target\classes  

12. [INFO] ------------------------------------------------------------------------  

13. [INFO] BUILD SUCCESSFUL  

14. [INFO] ------------------------------------------------------------------------  

15. [INFO] Total time: 1 second  

16. [INFO] Finished at: Fri Oct 09 02:08:09 CST 2009  

17. [INFO] Final Memory: 9M/16M  

18. [INFO] ------------------------------------------------------------------------  

19.   

 

clean告诉Maven清理输出目录target/,compile告诉Maven编译项目主代码,从输出中我们看到Maven首先执行了clean:clean任务,删除target/目录,默认情况下Maven构建的所有输出都在target/目录中;接着执行resources:resources任务(未定义项目资源,暂且略过);最后执行compiler:compile任务,将项目主代码编译至target/classes目录(编译好的类为com/juvenxu/mvnbook/helloworld/HelloWorld.Class)。

上文提到的clean:clean、resources:resources,以及compiler:compile对应了一些Maven插件及插件目标,比如clean:clean是clean插件的clean目标,compiler:compile是compiler插件的compile目标,后文会详细讲述Maven插件及其编写方法。

至此,Maven在没有任何额外的配置的情况下就执行了项目的清理和编译任务,接下来,我们编写一些单元测试代码并让Maven执行自动化测试。

1. [INFO] Scanning for projects...  

2. [INFO] ------------------------------------------------------------------------  

3. [INFO] Building Maven Hello World Project  

4. [INFO]    task-segment: [clean, test]  

5. [INFO] ------------------------------------------------------------------------  

6. [INFO] [clean:clean {execution: default-clean}]  

7. [INFO] Deleting directory D:\git-juven\mvnbook\code\hello-world\target  

8. [INFO] [resources:resources {execution: default-resources}]  

9. …  

10. Downloading: http://repo1.maven.org/maven2/junit/junit/4.7/junit-4.7.pom  

11. 1K downloaded  (junit-4.7.pom)  

12. [INFO] [compiler:compile {execution: default-compile}]  

13. [INFO] Compiling 1 source file to D: \code\hello-world\target\classes  

14. [INFO] [resources:testResources {execution: default-testResources}]  

15. …  

16. Downloading: http://repo1.maven.org/maven2/junit/junit/4.7/junit-4.7.jar  

17. 226K downloaded  (junit-4.7.jar)  

18. [INFO] [compiler:testCompile {execution: default-testCompile}]  

19. [INFO] Compiling 1 source file to D:\ code\hello-world\target\test-classes  

20. [INFO] ------------------------------------------------------------------------  

21. [ERROR] BUILD FAILURE  

22. [INFO] ------------------------------------------------------------------------  

23. [INFO] Compilation failure  

24. D:\code\hello-world\src\test\java\com\juvenxu\mvnbook\helloworld\HelloWorldTest.java:[8,5] -source 1.3 中不支持注释  

25. (请使用 -source 5 或更高版本以启用注释)  

26.     @Test  

27. [INFO] ------------------------------------------------------------------------  

28. [INFO] For more information, run Maven with the -e switch  

29.   …  

不幸的是构建失败了,不过我们先耐心分析一下这段输出(为了本书的简洁,一些不重要的信息我用省略号略去了)。命令行输入的是mvn clean test,而Maven实际执行的可不止这两个任务,还有clean:clean、resources:resources、compiler:compile、resources:testResources以及compiler:testCompile。暂时我们需要了解的是,在Maven执行测试(test)之前,它会先自动执行项目主资源处理,主代码编译,测试资源处理,测试代码编译等工作,这是Maven生命周期的一个特性,本书后续章节会详细解释Maven的生命周期。

从输出中我们还看到:Maven从中央仓库下载了junit-4.7.pom和junit-4.7.jar这两个文件到本地仓库(~/.m2/repository)中,供所有Maven项目使用。

构建在执行compiler:testCompile任务的时候失败了,Maven输出提示我们需要使用-source 5或更高版本以启动注释,也就是前面提到的JUnit 4的@Test注解。这是Maven初学者常常会遇到的一个问题。由于历史原因,Maven的核心插件之一compiler插件默认只支持编译Java 1.3,因此我们需要配置该插件使其支持Java 5。

 

 

总结

 

  所谓构建就是包括编译,运行单元测试,生成测试报告(还记得target文件夹吗?那里存放着测试报告),打包,部署等一系列过程。我们通过Maven可以方便地自动化的完成这些,仅仅是通过一个mvn clean install命令。虽然我最初用它时,以为它仅仅是一个管理项目依赖的工具,类似于NuGet(.NET平台下的免费、开源的包管理开发工具)。现在看来它的功能还有待我去实践和发掘。有兴趣地可以阅读一下《Maven实战》这本书。

微信公众号【黄小斜】大厂程序员,互联网行业新知,终身学习践行者。关注后回复「Java」、「Python」、「C++」、「大数据」、「机器学习」、「算法」、「AI」、「Android」、「前端」、「iOS」、「考研」、「BAT」、「校招」、「笔试」、「面试」、「面经」、「计算机基础」、「LeetCode」 等关键字可以获取对应的免费学习资料。 

 

 

                     为什么我们需要Maven_第2张图片

你可能感兴趣的:(为什么我们需要Maven)