Maven 核心原理解析(1)

Maven 是每一位Java工程师每天都会接触的工具, 但据我所知其实很多人对Maven理解的并不深, 只把它当做一个依赖管理工具(下载依赖、打包), Maven很多核心的功能反而没用上. 最近重读 Maven实战, 虽然这本书年岁较老(10年出版: 那还是Hudson年代), 但绝大部分还是很值得参考的. 本文讲述Maven的核心原理和概念, 因此还是大纲参考了这本书, 但细节大多参考的Maven的官方文档以及网友释出的博客. 本文主要讲解Maven的:

坐标与依赖、

仓库、

生命周期与插件、

模块聚合、

模块继承

等概念, 并通过一个开发Maven插件的实例来深入了解Maven的核心机制. 而对于 如何配置Maven、Nexus私服、Jenkins持续集成、Maven测试、构建Web、资源过滤、自定义Archetype 等相对简单、讲解繁琐且网上有大量实践案例的内容没有涉及. 本文的目标是希望读者能够通过本文能对Maven核心原理有个相对深入的理解.

坐标与依赖

为了能够自动化地解析任何一个Java构件, Maven必须将它们唯一标识, 这就是依赖管理的底层基础-坐标.

坐标

在数学中, 任何一个坐标可以唯一确定一个“点”.

Maven 坐标为Java构件引入了秩序, 任何一个构件都必须明确定义自己的坐标, 坐标元素包括groupId、artifactId、version、packaging、classfier:

<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
<packaging>jar</packaging>

元素

描述

ext

groupId

定义当前模块隶属的实际Maven项目, 表示方式与Java包类似

groupId不应直接对应项目隶属的公司/组织(一个公司/组织下可能会有很多的项目).

artifactId

定义实际项目中的一个Maven模块

推荐使用项目名作为artifactId前缀, 如:commons-lang3以commons作为前缀(因为Maven打包默认以artifactId作为前缀)

version

定义当前项目所处版本(如1.0-SNAPSHOT、4.2.7.RELEASE、1.2.15、14.0.1-h-3等)

Maven版本号定义约定: <主版本>.<次版本>.<增量版本>-<里程碑版本>

packaging

定义Maven项目打包方式, 通常打包方式与所生成构件扩展名对应

有jar(默认)、war、pom、maven-plugin等.

classifier

用来帮助定义构建输出的一些附属构件(如javadoc、sources)

不能直接定义项目的classifier(因为附属构件不是由项目默认生成, 须有附加插件的帮助)

依赖

Maven最著名的(也是几乎每个人最先接触到的)就是Maven的依赖管理, 它使得我们不必再到开源项目的官网一个个下载开源组件, 然后再一个个放入classpath. 一个依赖声明可以包含如下元素:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>4.2.7.RELEASE</version>
    <type>jar</type>
    <scope>compile</scope>
    <optional>false</optional>
    <exclusions>
        <exclusion>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
        </exclusion>
    </exclusions>
</dependency>

依赖传递

Maven依赖传递机制会自动加载我们引入的依赖包的依赖, 而不必去手动指定(好拗口(⊙﹏⊙)b : This allows you to avoid needing to discover and specify the libraries that your own dependencies require, and including them automatically).

如: 我们的项目依赖了spring-core, 而spring-core又依赖了commons-logging:

有了依赖传递机制, 在项目中添加了spring-core依赖时就不用再去考虑它依赖了什么, 也不用担心引入多余的依赖. Maven会解析各个直接依赖的POM, 将必要的间接依赖以传递性依赖的形式引入到当前目录中(inherits from its parents, or from its dependencies, and so on).

(依赖调节原则: 1. 路径最近者优先; 2. 第一声明者优先.)

更多传递依赖信息可参考: Dependency Mechanism-Transitive Dependencies.

声明一个或者多个项目依赖, 可以包含的元素有:

元素

描述

Ext

groupId、artifactId和version

依赖的基本坐标(最最重要)

type

依赖的类型, 对应于项目坐标定义的packaging

默认jar

scope

依赖的范围, 用来控制依赖与三种classpath(编译classpath、测试classpath、运行classpath)的关系

包含compile、provided、runtime、test、system和import 6种, 详见:Dependency Scope.

optional

依赖是否可选

假如一个Jar包支持MySQL与Oracle两种DB, 因此其构建时必须添加两类驱动, 但用户使用时只会选择一种DB. 此时对A、B就可使用optional可选依赖

exclusions

排除传递性依赖

传递性依赖极大的简化了项目依赖的管理, 但也会引入Jar包版本冲突等问题, 此时就需要对传递性依赖进行排除. optional与exclusions详细可参考: Optional Dependencies and Dependency Exclusions

依赖管理

Maven提供了dependency插件可以对Maven项目依赖查看以及优化, 如$ mvn dependency:tree可以查看当前项目的依赖树, 详见$ mvn dependency:help.

Maven 仓库

Maven 中, 任何一个依赖、插件或项目构建的输出, 都可称为构件, 而Maven仓库就是集中存储这些构件的地方.

两类仓库

Maven仓库可简单分成两类: 本地仓库与远程仓库. 当Maven根据坐标寻找构件时, 它会首先检索本地仓库, 如果本地存在则直接使用, 否则去远程仓库下载.

本地仓库: 默认地址为~/.m2/, 一个构件只有在本地仓库存在之后, 才能由Maven项目使用.

远程仓库: 远程仓库又可简单分成两类: 中央仓库和私服.

由于原始的本地仓库是空的, Maven必须至少知道一个远程仓库才能在执行命令时下载需要的构件, 中央仓库就是这样一个默认的远程仓库.

关于仓库的详细配置可参考: Settings Reference 的 Servers、Mirrors、Profiles 等章节.

私服

私服是一种特殊的远程仓库, 它设在局域网内, 通过代理广域网上的远程仓库, 供局域网内的Maven用户使用.

当需要下载构件时, Maven客户端先向私服请求, 如果私服不存在该构件, 则从外部的远程仓库下载, 并缓存在私服上, 再为客户提供下载服务. 此外, 一些无法从外部仓库下载到的构建也能从本地上传到私服供大家使用(如公司内部二方包、Oracle的JDBC启动等). 为了节省带宽和时间, 一般在公司内部都会架设一台Maven私服, 但将公司内部项目部署到私服还需要对POM做如下配置:

<project >
    ...
    <distributionManagement>
        <repository>
            <id>releases</id>
            <url>http://mvn.server.com/nexus/content/repositories/releases/</url>
        </repository>
        <snapshotRepository>
            <id>snapshots</id>
            <url>http://mvn.server.com/nexus/content/repositories/snapshots/</url>
        </snapshotRepository>
    </distributionManagement>
</project>

repository表示发布版本构件的仓库, snapshotRepository代表快照版本的仓库.

id为该远程仓库唯一标识, url表示该仓库地址.

配置正确后, 执行$ mvn clean deploy则可以将项目构建输出的构件部署到对应配置的远程仓库.

注: 往远程仓库部署构件时, 往往需要认证: 需要在settings.xml中创建servers元素, 其id与仓库的id匹配, 详见: Password Encryption.

仓库搜索

推荐几个可用的Maven仓库搜索服务:

MVNrepository: http://mvnrepository.com/

Sonatype Nexus: https://repository.sonatype.org/

生命周期与插件

Maven 将所有项目的构建过程统一抽象成一套生命周期: 项目的清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成 … 几乎所有项目的构建,都能映射到这一组生命周期上. 但生命周期是抽象的(Maven的生命周期本身是不做任何实际工作), 任务执行(如编译源代码)均交由插件完成. 其中每个构建步骤都可以绑定一个或多个插件的目标,而且Maven为大多数构建步骤都编写并绑定了默认插件.当用户有特殊需要的时候, 也可以配置插件定制构建行为, 甚至自己编写插件.

生命周期

Maven 拥有三套相互独立的生命周期: clean、default 和 site, 而每个生命周期包含一些phase阶段, 阶段是有顺序的, 并且后面的阶段依赖于前面的阶段. 而三套生命周期相互之间却并没有前后依赖关系, 即调用site周期内的某个phase阶段并不会对clean产生任何影响.

clean

clean生命周期的目的是清理项目:

执行如$ mvn clean;

default

default生命周期定义了真正构建时所需要执行的所有步骤:

执行如$ mvn clean install;

site

site生命周期的目的是建立和发布项目站点: Maven能够基于POM所包含的信息,自动生成一个友好的站点,方便团队交流和发布项目信息

执行命令如$ mvn clean deploy site-deploy;

更多 Maven 生命周期介绍可以参考: Introduction to the Build Lifecycle、Lifecycles Reference.

插件

生命周期的阶段phase与插件的目标goal相互绑定, 用以完成实际的构建任务. 而对于插件本身, 为了能够复用代码,它往往能够完成多个任务, 这些功能聚集在一个插件里,每个功能就是一个目标.

如:$ mvn compiler:compile: 冒号前是插件前缀, 后面是该插件目标(即: maven-compiler-plugin的compile目标).

而该目标绑定了default生命周期的compile阶段:

因此, 他们的绑定能够实现项目编译的目的.

内置绑定

为了能让用户几乎不用任何配置就能使用Maven构建项目, Maven 默认为一些核心的生命周期绑定了插件目标, 当用户通过命令调用生命周期阶段时, 对应的插件目标就会执行相应的逻辑.

clean生命周期阶段绑定

生命周期阶段

插件目标

pre-clean

-

clean

maven-clean-plugin:clean

post-clean

-

default声明周期阶段绑定

生命周期阶段

插件目标

执行任务

process-resources

maven-resources-plugin:resources

复制主资源文件到主输出目录

compile

maven-compiler-plugin:compile

编译主代码到主输出目录

process-test-resources

maven-resources-plugin:testResources

复制测试资源文件到测试输出目录

test-compile

maven-compiler-plugin:testCompile

编译测试代码到测试输出目录

test

maven-surefire-plugin:test

执行测试用例

package

maven-jar-plugin:jar

打jar包

install

maven-install-plugin:install

将项目输出安装到本地仓库

deploy

maven-deploy-plugin:deploy

将项目输出部署到远程仓库

注: 上表只列出了打包方式为jar且拥有插件绑定关系的阶段, 其他打包类型生命周期的默认绑定关系可参考: Built-in Lifecycle Bindings、Plugin Bindings for default Lifecycle Reference.

site生命周期阶段绑定

生命周期阶段

插件目标

pre-site

-

site

maven-site-plugin:site

post-site

-

site-deploy

maven-site-plugin:deploy

自定义绑定

除了内置绑定以外, 用户还能够自定义将某个插件目标绑定到生命周期的某个阶段上. 如创建项目的源码包, maven-source-plugin插件的jar-no-fork目标能够将项目的主代码打包成jar文件, 可以将其绑定到verify阶段上:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-source-plugin</artifactId>
            <version>3.0.0</version>
            <executions>
                <execution>
                    <id>attach-sources</id>
                    <phase>verify</phase>
                    <goals>
                        <goal>jar-no-fork</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

executions下每个execution子元素可以用来配置执行一个任务.

关于Maven插件的自定义绑定及其他详细配置可参考: Guide to Configuring Plug-ins.

插件查询

在线

Maven 官方插件

https://maven.apache.org/plugins/index.html

CodeHaus 插件

http://www.mojohaus.org/plugins.html

maven-help-plugin

mvn help:describe -Dplugin=org.apache.maven.plugins:maven-source-plugin[:3.0.0] [-Ddetail] [-Dgoal=jar-no-fork]

原文发布于微信公众号 - Java帮帮(javahelp)

原文发表时间:2016-12-26

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏散尽浮华

SVN和Git对比梳理

在日常运维工作中,经常会用到版本控制系统,目前用到最广泛的版本控制器就是SVN和Git,那么这两者之间有什么不同之处呢? SVN(Subversion)是集中式...

3056
来自专栏desperate633

深入理解--异步和非阻塞同步和阻塞异步和非阻塞

异步和非阻塞的概念实际上已经出现了很长一段时间。但是异步真正开始流行起来,是因为AJAX技术逐渐成为主流的web开发技术。非阻塞的概念真正流行起来,是当java...

944
来自专栏java一日一条

为什么要用 Node.js

传统意义上的 JavaScript 运行在浏览器上,这是因为浏览器内核实际上分为两个部分:渲染引擎和 JavaScript 引擎。前者负责渲染 HTML + C...

2332
来自专栏程序你好

Zookeeper的功能以及工作原理

1271
来自专栏北京马哥教育

Linux 初步知识详解

1. 描述计算机的组成及其功能 一个完整的计算机系统由硬件系统和软件系统两大部分组成 冯诺依曼体系下的计算机五大部件 CPU:运算器、控制器、寄存器、缓存(一二...

3927
来自专栏LanceToBigData

struts2(一)之初识struts2

前言   我们都知道struts2是一个框架,那什么是框架呢?很多人其实不太明白,其实框架就是一个半成品,别人将一些功能已经写好了,我们只需要拿来用即可,像我们...

2299
来自专栏向治洪

Node.js原理

概述 Node.js是什么 Node 是一个服务器端 JavaScript 解释器,用于方便地搭建响应速度快、易于扩展的网络应用。Node.js 使用事件驱动,...

4736
来自专栏Java学习123

IBM WebSphere MQ 系列(一)基础知识

3974
来自专栏企鹅号快讯

学好webpack,一名前端开发工程师的自我修养

前言 webpack 前端工程中扮演的角色越来越重要,它也是前端工程化很重要的一环。本文将和大家一起按照项目流程学习使用 wwbpack,由浅入深的学习,妈妈再...

28210
来自专栏数据库

数据恢复-SQL被注入攻击程序的应对策略

前几天某客户紧急求助我们,其Oracle数据库由于重启之后无法正常启动。最后通过数据库全备进行了一天一夜的恢复,最后仍然无法正常打开数据库。 alter dat...

2108

扫码关注云+社区

领取腾讯云代金券