首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Maven 核心原理解析(1)

Maven 核心原理解析(1)

作者头像
Java帮帮
发布2018-03-16 17:46:30
2.7K0
发布2018-03-16 17:46:30
举报
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]

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2016-12-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java帮帮 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 MySQL
腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档