Introduction

Maven是一个异常强大的构建工具,能够帮我们自动化构建过程,从清理、编译、测试到生成报告,再到打包和部署。通过Maven,我们只需要输入简单的命令(如mvn clean install),就会帮我们处理繁琐的任务。Maven最大化的消除了构建的重复,抽象了构建生命周期,并且为绝大部分的构建任务提供了已实现的插件。比如说测试,我们只需要遵循Maven的约定编写好测试用例,当我们运行构建的时候,这些测试便会自动运行。除此之外,Maven能帮助我们标准化构建过程。在Maven之前,十个项目可能有十种构建方式,但通过Maven,所有项目的构建命令都是简单一致的。有利于促进项目团队的标准化。

Maven是笔者接触的第一个脱离于IDE的命令行构建工具,笔者之前一直是基于Visual Studio下进行Windows驱动开发,并不是很能明白Builder与IDE之间的区别。依赖大量的手工操作。编译、测试、代码生成等工作都是相互独立的,很难一键完成所有工作。手工劳动往往意味着低效,意味着容易出错。很难在项目中统一所有的IDE配置,每个人都有自己的喜好。也正是由于这个原因,一个在机器A上可以成功运行的任务,到了机器B的IDE中可能就会失败。

Make

Make将自己和操作系统绑定在一起了。也就是说,使用Make,就不能实现(至少很难)跨平台的构建,这对于Java来说是非常不友好的。此外,Makefile的语法也成问题,很多人抱怨Make构建失败的原因往往是一个难以发现的空格或Tab使用错误。

Ant

  • 和Make一样,Ant也都是过程式的,开发者显式地指定每一个目标,以及完成该目标所需要执行的任务。针对每一个项目,开发者都需要重新编写这一过程,这里其实隐含着很大的重复。Maven是声明式的,项目构建过程和过程各个阶段所需的工作都由插件实现,并且大部分插件都是现成的,开发者只需要声明项目的基本元素,Maven就执行内置的、完整的构建过程。这在很大程度上消除了重复。

  • Ant是没有依赖管理的,所以很长一段时间Ant用户都不得不手工管理依赖,这是一个令人头疼的问题。幸运的是,Ant用户现在可以借助Ivy管理依赖。而对于Maven用户来说,依赖管理是理所当然的,Maven不仅内置了依赖管理,更有一个可能拥有全世界最多Java开源软件包的中央仓库,Maven用户无须进行任何配置就可以直接享用。

Usage

Installation

可从apache官方下载最新的Maven 压缩包,解压即可。然后设置下系统的环境变量。如下所示:

  • M2HOME:maven安装目录

  • Path:追加maven安装目录下的bin目录

在用户目录下,我们可以发现.m2文件夹。默认情况下,该文件夹下放置了Maven本地仓库.m2/repository。所有的Maven构件(artifact)都被存储到该仓库中,以方便重用。默认情况下,~/.m2目录下除了repository仓库之外就没有其他目录和文件了,不过大多数Maven用户需要复制M2HOME/conf/settings.xml文件到~/.m2/settings.xml

Commands List

本节列举出部分常用的Maven命令:

mvn -v 查看maven版本

mvn compile 编译

mvn test 测试

mvn package 打包

mvn clean 删除target

mvn install 安装jar包到本地仓库中

  • 创建一个新工程

mvn archetype:generate -DgroupId=co.hoteam -DartifactId=Zigbee -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

Pom

就像Make的Makefile,Ant的build.xml一样,Maven项目的核心是pom.xml。

首先创建一个名为hello-world的文件夹,打开该文件夹,新建一个名为pom.xml的文件,输入其内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.juvenxu.mvnbook</groupId>
<artifactId>hello-world</artifactId>
<version>1.0-SNAPSHOT</version>
<name>Maven Hello World Project</name>
</project>
  • 代码的第一行是XML头,指定了该xml文档的版本和编码方式。紧接着是project元素,project是所有pom.xml的根元素,它还声明了一些POM相关的命名空间及xsd元素,虽然这些属性不是必须的,但使用这些属性能够让第三方工具(如IDE中的XML编辑器)帮助我们快速编辑POM。

  • 根元素下的第一个子元素modelVersion指定了当前POM模型的版本,对于Maven2及Maven 3来说,它只能是4.0.0。这段代码中最重要的是groupId,artifactId和version三行。这三个元素定义了一个项目基本的坐标,在Maven的世界,任何的jar、pom或者war都是以基于这些基本的坐标进行区分的。

  • groupId定义了项目属于哪个组,这个组往往和项目所在的组织或公司存在关联,譬如你在googlecode上建立了一个名为myapp的项目,那么groupId就应该是com.googlecode.myapp,如果你的公司是mycom,有一个项目为myapp,那么groupId就应该是com.mycom.myapp。本书中所有的代码都基于groupId com.juvenxu.mvnbook。

  • artifactId定义了当前Maven项目在组中唯一的ID,我们为这个Hello World项目定义artifactId为hello-world,本书其他章节代码会被分配其他的artifactId。而在前面的groupId为com.googlecode.myapp的例子中,你可能会为不同的子项目(模块)分配artifactId,如:myapp-util、myapp-domain、myapp-web等等。

  • version指定了Hello World项目当前的版本——1.0-SNAPSHOT。SNAPSHOT意为快照,说明该项目还处于开发中,是不稳定的版本。随着项目的发展,version会不断更新,如升级为1.0、1.1-SNAPSHOT、1.1、2.0等等。

  • 最后一个name元素声明了一个对于用户更为友好的项目名称,虽然这不是必须的,但我还是推荐为每个POM声明name,以方便信息交流。 没有任何实际的Java代码,我们就能够定义一个Maven项目的POM,这体现了Maven的一大优点,它能让项目对象模型最大程度地与实际代码相独立,我们可以称之为解耦,或者正交性,这在很大程度上避免了Java代码和POM代码的相互影响。比如当项目需要升级版本时,只需要修改POM,而不需要更改Java代码;而在POM稳定之后,日常的Java代码开发工作基本不涉及POM的修改。

Main

项目主代码和测试代码不同,项目的主代码会被打包到最终的构件中(比如jar),而测试代码只在运行测试时用到,不会被打包。默认情况下,Maven假设项目主代码位于src/main/java目录,我们遵循Maven的约定,创建该目录,然后在该目录下创建文件com/juvenxu/mvnbook/helloworld/HelloWorld.java,其内容如下:

package com.juvenxu.mvnbook.helloworld;

public class HelloWorld
{
    public String sayHello()
    {
        return "Hello Maven";
    }

    public static void main(String[] args)
    {
        System.out.print( new HelloWorld().sayHello() );
    }
}

关于该Java代码有两点需要注意。首先,在95%以上的情况下,我们应该把项目主代码放到src/main/java/目录下(遵循Maven的约定),而无须额外的配置,Maven会自动搜寻该目录找到项目主代码。其次,该Java类的包名是com.juvenxu.mvnbook.helloworld,这与我们之前在POM中定义的groupId和artifactId相吻合。一般来说,项目中Java类的包都应该基于项目的groupId和artifactId,这样更加清晰,更加符合逻辑,也方便搜索构件或者Java类。 代码编写完毕后,我们使用Maven进行编译,在项目根目录下运行命令 mvn clean compile 即可。

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

Configuration

Network

Proxy

编辑~/.m2/settings.xml文件(如果没有该文件,则复制$M2HOME/conf/settings.xml)。添加代理配置如下:

<settings>
  ...
<pqroxies>
  <proxy>
    <id>my-proxy</id>
    <active>true</active>
    <protocol>http</protocol>
    <host>代理服务器主机名</host>
    <port>端口号</port>
    <!--
        <username>***</username>
        <password>***</password>
        <nonProxyHosts>repository.mycom.com|*.google.com</nonProxyHosts>
-->
  </proxy>
  </proxies>
  ...
</settings>

Mirror

众所周知的原因,国内有时候并不能够很顺畅的访问Maven的中央仓库,往往我们需要访问国内的镜像地址:

  • OSChina Maven教程

<mirror>
<id>CN</id>
<name>OSChina Central</name> 
<url>http://maven.oschina.net/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>

Error List

网络问题

(1)有时候因为众所周知的网络问题,导致Maven无法访问中央仓库然后扔出一大堆错误,这个时候可以尝试参考上文中的设置代理。但是也要注意,是不是有一些私库中的Repository。

编译问题

(1)有时候执行mvn compile时候会爆出无法找到junit的错误,可能的解决方法有:

  • 在Eclipse的Projects选项中使用Projects Clean

  • 在pom.xml中引入junit依赖项,并且保证其scope为compile:

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
    </dependency>

(2)有时候在Eclipse下执行mvn compile或者相关命令时,会报某某文件出现不识别字符或者非UTF-8编码,此时可以做几步检查:

  • 检查对应的Java文件是否有Bom头

  • 检查对应的Java文件的编码

  • 如果都没有问题,在Eclipse中先将文件编码设置为GBK,再改回UTF-8试试。

Reference


王下邀月熊_Chevalier
22.5k 声望8.5k 粉丝

爱代码 爱生活 希望成为全栈整合师