Maven入门介绍

导语

最近在学习Java了,以后分享的文章主要就以Java为主了,偶尔也会分享一下Objective-C方面的文章,这篇读书笔记主要介绍了Maven的一些核心概念和常用的一些插件。

Maven

什么是Maven?

什么是Maven呢?我们看下官网给出的一段介绍:

Apache Maven is a software project management and comprehension tool. Based on the concept of a project object model (POM), Maven can manage a project's build, reporting and documentation from a central piece of information.

从介绍中我们可以看到Apache Maven是一个项目管理和理解工具,它基于项目对象模型(POM)的概念,它可以管理项目的构建、报告和文档。我们也会经常听到有人对Maven是这样理解的:

  • Maven是一个站点和文档工具。
  • Maven扩展Ant,让你下载依赖关系。
  • Maven是一组可重用的Ant脚本。

这几个方面都是Maven的一些功能,Maven所提供的功能远比这强大的许多,不过对于我们在日常项目当中用到最多的就是管理第三方库的依赖、项目的构建。如果开发工具使用的是IntelliJ IDEA,自动就帮你安装好了Maven,不是的话,也可以参考下官方给出的Maven安装教程,也是比较简单的。接下来看下怎样用Maven来满足我们项目的日常需要。

约定优于配置

Maven使用约定优于配置的原则,如下所示:

目录 目的
${basedir} 存放pom.xml和所有的子目录
${basedir}/src/main/java 项目的java源代码
${basedir}/src/main/resources 项目的资源,比如说property文件,springmvc.xml
${basedir}/src/test/java 项目的测试类,比如说Junit代码
${basedir}/src/test/resources 测试用用的资源
${basedir}/src/main/webapp/WEB-INF web应用文件目录,web项目的信息,比如存放web.xml、本地图片、jsp视图页面
${basedir}/target 打包输出目录
${basedir}/target/classes 编译输出目录
${basedir}/target/test-classes 测试编译输出目录
Test.java Maven只会自动运行符合该命名规则的测试类
~/.m2/repository Maven默认的本地仓库目录位置

一个maven项目在默认情况下会产生jar文件,另外,编译后的classes会放在${basedir}/target/classes下面,jar文件会放在${basedir}/target下面。使用约定优于配置带来最大的好处就是项目的统一,任何人在使用Maven项目的时候,文件的存放位置都是一样的,通用性比较好。这也是为什么我们在用intellij创建一个maven项目的时候,需要配置源文件、资源文件路径的原因。如下图就是常用的几个配置:

maven 1-1.png

Maven的几个核心概念

POM(Project Object Model)

一个项目所有的配置都放在POM文件中:定义项目的类型、名字、管理依赖关系,定制插件的行为等等。看下我自己写的小demo中pom中配置如下:

<groupId>com.dodonew</groupId>
<artifactId>springmvc</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>springmvc</name>
<url>http://maven.apache.org</url>

<dependencies>
    <!-- spring日志依赖 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
        <version>1.7.21</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.1.7</version>
    </dependency>
</dependencies>

在POM中,groupId、artifactId、packaging、version叫做maven坐标,它能唯一的确定一个项目。有了maven坐标,我们就可以用它来指定我们的项目所依赖的其他项目、插件、或者父项目。我写的demo很简单,但是比较大的项目一般会分成几个子项目,在这种情况下,每个子项目就会有自己的POM文件,它们会有一个共同的父项目。这样只要构建父项目就能够构建所有的子项目了。同时子项目的POM会继承父项目的POM。关于多个子项目具体怎么操作,在文章后面会有回答的。

Maven插件

Maven常用的插件比如compiler插件、surefire插件、jar插件。比如说jar插件包含建立jar文件的目标,compiler插件包含编译源代码和单元测试代码的目标,surefire插件则是运行单元测试的目标。为什么需要这些插件呢?因为maven本身不会做太多的事情,它不知道怎么样编译或者怎么样打包。它把构建的任务交给插件去做。插件定义了常用的构建逻辑,能够被重复利用。这样做的好处是,一旦插件有了更新,那么所有maven用户都能得到更新。

Maven生命周期

生命周期指项目的构建过程,它包含了一系列的有序的阶段,而一个阶段就是构建过程中的一个步骤,比如package阶段、compiler阶段等。那么生命周期阶段和上面说的插件目标之间是什么关系呢?插件目标可以绑定到生命周期阶段上,一个生命周期可以绑定多个插件目标。当maven在构建过程中逐步的通过每个阶段时,会执行该阶段所有的插件目标。目前生命周期阶段有clean、vavidate、compiler、test、package、verify、install、site、deploy阶段。

Maven依赖管理

我们能够通过maven坐标确定一个项目,换句话说,我们可以用它来解决依赖关系。在POM中,依赖关系是在dependencies部分中定义的。比如对slf4j库和logback库的依赖关系如下:

<dependencies>
    <!-- spring日志依赖 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
        <version>1.7.21</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.1.7</version>
    </dependency>
</dependencies>

这个依赖关系是比较简单的,但是实际开发中我们会有复杂多的依赖关系,因为被依赖的jar文件会有自己的依赖关系,那么我们是不是也得把间接依赖的jar文件也都定义在POM中呢?答案是不需要,因为maven提供了传递依赖的特性,会把依赖的jar文件它所依赖的库也自动添加进来。比如spring-webmvc库,这个库自己又依赖spring-core、spring-beans、spring-web、spring-expression、spring-context,因为依赖传递性,我们在使用spring-webmvc的时候,只需要声明对spring-webmvc的依赖关系即可,其他的库会自动帮你引入的。之所以能实现依赖传递性关键就在于下载spring-webmvc库,同时也下载了pom文件,在pom文件定义了这个库需要的依赖关系。

scope决定了依赖关系的适用范围,比如junit的scope是test,那么它只会在执行compiler:testCompile和surefire:test目标的时候才会被加载到classpath中,在执行compiler:compile目标时是拿不到junit的。换句话说就是在maven哪个生命周期中起作用了。scope的默认值是compile,即任何时候都会被包含在classpath中,在打包的时候也会被包括进去。

Maven库

我们所依赖的库是从maven默认的远程库 (http://repo.maven.org/maven2) 下载的,这个是公有的库。有时公司自己封装了一些私有库,这个时候我们就可以搭建自己的私有库了。本地库是指maven下载了插件或者jar文件后存放在本地机器上的拷贝。在Mac上,它的位置在~/.m2/repository。当maven查找需要的jar文件时,它会先在本地库中查找,只有在找不到的情况下,才会去远程库中找的。由于maven默认的远程库服务器在国外,国内访问的时候比较慢,建议替换成阿里云的镜像,仓库地址如下:

http://maven.aliyun.com/nexus/content/groups/public/

Maven多模块项目POM注意的事项

denpendencyManagement

在项目开发过程中,有时一个项目下面包含了几个子模块,在多模块的情况,POM的配置应该要注意写什么呢?我们通过一个例子来说明下。
有这样一个工程,里面有A模块、B模块和C模块,A模块需要引入junit和log4j库,配置如下:

<dependency>
    <groupId>junit</groupId>
    <artifactid>junit</artifactId>
    <version>3.8.2</version>
</dependency>
<dependency>
    <groupId>log4j</groupId>
    <artifactid>log4j</artifactId>
    <version>1.2.9</version>
</dependency>

此时B模块也需要引入这两个库,配置如下:

<dependency>
    <groupId>junit</groupId>
    <artifactid>junit</artifactId>
    <version>4.8.2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>log4j</groupId>
    <artifactid>log4j</artifactId>
    <version>1.2.16</version>
</dependency>

会发现A模块和B模块对junit和log4j库依赖的版本是不同的,出现这种情况是十分危险的,因为依赖不同版本的库可能会造成很多未知的风险。怎么解决不同模块之间对同一个库的依赖版本一样呢?Maven提供了优雅的解决办法,使用继承机制以及dependencyManagement元素来解决这个问题。如果你在父模块中配置dependencies,那么所有的子模块都自动继承,不仅达到了依赖一致的目的,还省了大段的代码,但这样来做会存在问题的。比如B模块需要spring-aop模块,但是C模块不需要spring-aop模块,如果用dependencies在父类中统一配置,C模块中也会包含有spring-aop模块,不符合我们的要求。但是用dependencyManagement就没有这样的问题。dependencyManagement只会影响现有依赖的配置,但不会引入依赖。这样我们在父模块中的配置可以更改如下所示:

<!-- dependencyManagement只会影响现有依赖的配置,但不会引入依赖。 -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>
</dependencyManagement>

这段配置不会给任何子模块引入依赖,如果某个子模块需要junit和log4j,只需要这样配置即可:

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
    </dependency>
</dependencies>

在多模块Maven项目中,使用dependencyManagement能够有效地帮我们维护依赖一致性。

pluginManagement

上面介绍了在多模块中对依赖库的管理,接下来介绍下对插件的管理。与dependencyManagement类似,我们可以使用pluginManagement元素管理插件。一个常见的用法就是我们希望项目所有模块的使用compiler插件的时候,都是用java1.7,以及指定Java源文件编码为UTF-8,这时可以在父模块的POM中如下配置pluginManagement:

<build>
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                <source>1.5</source>
                <target>1.5</target>
                <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </pluginManagement>
</build>

这段配置会被应用到所有子模块的compiler插件中,因为Maven内置了与compiler插件与生命周期的绑定,因此子模块不需要任何maven-compiler-plugin的配置了。

Maven常用的几个插件

Maven本质上是一个插件框架,它的核心并不执行任何具体的构建任务,所有这些任务都交给插件来完成。下面说几个常用的插件:

maven-compiler-plugin(编译插件)

用来编译Java代码,在对Java代码进行编译的时候,可以指定使用哪个JDK的版本来进行编译,配置如下所示:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.6.0</version>
    <configuration>
        <source>1.7</source> <!-- 源代码使用jdk1.7 -->
        <target>1.7</target> <!-- 使用jvm1.7编译目标代码 -->
    </configuration>

</plugin>

maven-resources-plugin(资源插件)

Maven区别对待Java代码和资源文件,maven-resources-plugin则用来处理资源文件。默认的主资源文件目录是src/main/resources,很多时候会需要添加额外的资源文件目录,这个时候就可以通过配置maven-resources-plugin来实现,配置如下所示:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-resources-plugin</artifactId>
    <version>2.5</version>
    <executions>
        <execution>
            <phase>compile</phase> <!-- 与Maven编译生命周期绑定在一起 -->
        </execution>
    </executions>
</plugin>

maven-surefire-plugin(测试插件)

Maven2/3中用于执行测试的插件不是maven-test-plugin,而是maven-surefire-plugin,其实在大部分情况下,只要你的测试类遵循通用的命令约定(以Test结尾,以TestCase结尾、或者Test开头),就几乎不用知晓该插件的存在。但是当你想要跳过测试、排除某些测试类、或者使用一些TestNG特性的时候,就要用到了maven-surefire-plugin的一些配置选项了,配置如下所示:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.14</version>
    <configuration>
        <skipTests>true</skipTests> <!-- 跳过测试 -->
    </configuration>

</plugin>

maven-clean-plugin(清除插件)

主要作用就是清理构建目录下的全部内容,有些项目,构建时需要清理构建目录以外的文件,比如指定的库文件,这时候就需要配置<filesets>来实现了,配置如下所示:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-clean-plugin</artifactId>
    <version>3.0.0</version>
    <configuration>
        <!--<skip>true</skip>-->
        <!--<failOnError>false</failOnError>-->
        <!--当配置true时,只清理filesets里的文件,构建目录中得文件不被清理.默认是flase.-->
        <excludeDefaultDirectories>false</excludeDefaultDirectories>
        <filesets>
            <fileset>
                <!--要清理的目录位置-->
                <directory>${basedir}/logs</directory>
                <!--是否跟随符号链接 (symbolic links)-->
                <followSymlinks>false</followSymlinks>
             </fileset>
        </filesets>
    </configuration>
</plugin>

maven-war-plugin(打包插件)

主要作用就是用来打包的,在打包的时候经常需要排除一些文件,就需要对warSourceExcludes进行配置了,配置如下所示:

<plugin>
    <artifactId>maven-war-plugin</artifactId>
    <version>2.0.2</version>
    <configuration>
        <warSourceExcludes>WEB-INF/lib/**</warSourceExcludes>
    </configuration>
</plugin>

总结

Maven是一个项目管理和自动化构建工具,项目遵循约定优于配置,这也是maven项目的一大特色。另外,maven本质上是一个插件框架,它的核心不执行任何具体的构建工作,全部都交给插件去执行,maven插件是与maven生命周期绑定在一起的。理解这些重要的核心点,对于maven的使用会有很大的帮助。

参考文章

Maven入门介绍

http://www.oracle.com/technetwork/cn/community/java/apache-maven-getting-started-1-406235-zhs.html

http://www.oracle.com/technetwork/cn/community/java/apache-maven-getting-started-2-405568-zhs.html

https://maven.apache.org/what-is-maven.html

Maven常用插件

http://www.infoq.com/cn/news/2011/04/xxb-maven-7-plugin

http://www.infoq.com/cn/news/2011/05/xxb-maven-8-plugin

欢迎关注国士梅花

国士梅花

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 118,003评论 14 132
  • 转自:http://www.cnblogs.com/crazy-fox/archive/2012/02/09/23...
    晴天哥_王志阅读 1,892评论 2 27
  • 我们都知道Maven本质上是一个插件框架,它的核心并不执行任何具体的构建任务,所有这些任务都交给插件来完成,例如编...
    付鹏丶阅读 1,223评论 0 15
  • 今天看到一条朋友圈 女生:七夕快来了啊 男生:什么是七夕 女生:七夕想要一起过 男生:我不过七夕 动力君想说,↑↑...
    AYSAN创意站阅读 89评论 0 0
  • 今天早上的心情本是不大快乐的,有点郁闷,打算去周边游,背着背包,低头听着音乐,悠悠的往车站漫步。 刚走过一片居...
    木羽521阅读 73评论 0 0