项目管理工具 - Maven
# 1. 约定大于配置
Maven 采用了约定的方式从指项目结构中获取源码与资源文件进行编译打包。
主源码文件:${project}/src/main/java
主资源文件:${project}/src/main/resources
测试源码文件:${project}/src/test/java
测试资源文件:${project}/src/test/resources
2
3
4
# 2. Maven生命周期
# Maven的三种生命周期
maven对项目的构建分为三套相互独立的生命周期。
cleanLifecycle: 在项目构建前,先进行一些清理工作。
defaultLifecycle: 构建的核心部分,编译,测试,打包,部署。
siteLifecycle: 站点文档生成,用于构建站点文档。
maven的每个生命周期都有很多阶段,每个阶段对应一个执行命令。
# 2.1 cleanLifecycle
clean生命周期:清理项目,包含三个phase。
- pre-clean:执行清理前需要完成的工作
- clean:清理上一次构建生成的文件
- post-clean:执行清理后需要完成的工作
# 2.2 defaultLifecycle
default生命周期:构建项目,重要的phase如下。
- validate:验证工程是否正确,所有需要的资源是否可用。
- compile:编译项目的源代码。
- test:使用合适的单元测试框架来测试已编译的源代码。这些测试不需要已打包和部署。
- package:把已编译的代码打包成可发布的格式,比如jar。
- integration-test:如有需要,将包处理和发布到一个能够进行集成测试的环境。
- verify:运行所有检查,验证包是否有效且达到质量标准。
- install:把包安装到maven本地仓库,可以被其他工程作为依赖来使用。
- deploy:在集成或者发布环境下执行,将最终版本的包拷贝到远程的repository,使得其他的开发者或者工程可以共享。
# 2.3 siteLifecycle
site生命周期:建立和发布项目站点,phase如下
- pre-site:生成项目站点之前需要完成的工作
- site:生成项目站点文档
- post-site:生成项目站点之后需要完成的工作
- site-deploy:将项目站点发布到服务器
# 2.4 图表记忆
# 2.5 生命周期与插件的关系
- 生命周期的 阶段(phase) 可以绑定具体的 插件(plugin) 及 目标(target)
- 不同配置下同一个阶段可以对应多个插件和目标
- phase -> plugin -> goal(功能)
生命周期默认引用的插件绑定参见:Maven Core - Plugin Bindings for default Lifecycle Reference (opens new window)
# 2.6 自定义插件开发
# 2.6.1 插件的相关概念
1. 插件坐标定位
groupId, artifactId, version 三个属性定位插件。当使用该插件时会先从本地仓库中搜索,如果没有再从远程仓库下载
<!-- 唯一定位到dependency 插件 -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.10</version>
2
3
4
2. 插件执行 execution
execution 配置包含一组指示插件如何执行的属性: id: 执行器命名 phase: 在什么阶段执行 goals: 执行一组什么目标或功能 configuration: 执行目标所需的配置文件
<!-- 将插件依赖拷贝到指定目录 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.1.1</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/alternateLocation</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>true</overWriteSnapshots>
<excludeTransitive>true</excludeTransitive>
</configuration>
</execution>
</executions>
</plugin>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 2.6.2 常用插件
除了通过配置的方式使用插件以外,Maven也提供了通过命令直接调用插件目标。其命令格式如下:
mvn groupId:artifactId:version:goal -D{参数名}
示例
# 展示 pom 的依赖关系树
mvn org.apache.maven.plugins:maven-dependency-plugin:2.10:tree
# 也可以直接简化版的命令,但前提必须是 maven 官方插件
mvn dependency:tree
# 查看 pom 文件的最终配置
mvn help:effective-pom
# 快速创建一个 WEB程序
mvn archetype:generate -DgroupId=sleepy -DartifactId=web-demo -DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode=false
# 快速创建一个 Java项目
mvn archetype:generate -DgroupId=sleepy -DartifactId=java-demo -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 2.6.3 开发自定义插件
步骤概览:
- 创建 maven插件项目
- 设定 packaging 属性为 maven-plugin
- 添加插件依赖
- 编写插件实现逻辑
- 打包构建插件
<!-- 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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>sleepy</groupId>
<version>1.0.SNAPSHOT</version>
<artifactId>sleepy-maven-plugin</artifactId>
<packaging>maven-plugin</packaging>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>3.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>3.4</version>
</dependency>
</dependencies>
</project>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.sleepy.maven;
import javafx.beans.DefaultProperty;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
/**
* 自定义插件实现类
**/
@Mojo(name = "printmessage")
public class CustomPlugin extends AbstractMojo {
@Parameter
String info;
public void execute() throws MojoExecutionException, MojoFailureException {
getLog().info(String.format("custom plugin print info=%s", info));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 命令测试,在 compile 阶段执行
$ mvn sleepy:sleepy-maven-plugin:1.0.SNAPSHOT:compile -Dinfo=hello
2
# 3. Maven依赖范围
# 3.1 定义
maven项目中,不同的阶段引入到classpath中的依赖是不同的。例如,编译时,maven会将与编译相关的依赖引入classpath中,测试时,maven会将测试相关的的依赖引入到classpath中,运行时,maven会将与运行相关的依赖引入classpath中,而依赖范围就是用来控制依赖于这三种classpath的关系。
# 3.2 具体依赖范围
- 编译依赖范围(compile),该范围就是默认依赖范围,此依赖范围对于编译、测试、运行三种classpath都有效,举个简单的例子,假如项目中有spring-core的依赖,那么spring-core不管是在编译,测试,还是运行都会被用到,因此spring-core必须是编译范围(构件默认的是编译范围,所以依赖范围是编译范围的无须显示指定)
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>2.5</version>
<!--默认为该依赖范围,无须显示指定-->
<scope>compile</scope>
</dependency>
2
3
4
5
6
7
- 测试依赖范围(test),顾名思义就是针对于测试的,使用此依赖范围的依赖,只对测试classpath有效,在编译主代码和项目运行时,都将无法使用该依赖,最典型的例子就是 Junit,构件在测试时才需要,所以它的依赖范围是测试,因此它的依赖范围需要显示指定为test,当然不显示指定依赖范围也不会报错,但是该依赖会被加入到编译和运行的classpath中,造成不必要的浪费。
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
2
3
4
5
6
- 已提供依赖范围(provided),使用该依赖范围的maven依赖,只对编译和测试的classpath有效,对运行的classpath无效,典型的例子就是servlet-api,编译和测试该项目的时候需要该依赖,但是在运行时,web容器已经提供的该依赖,所以运行时就不再需要此依赖,如果不显示指定该依赖范围,并且容器依赖的版本和maven依赖的版本不一致的话,可能会引起版本冲突,造成不良影响。
<dependency>
<groupId>javax-servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
2
3
4
5
6
运行时依赖范围(runtime),使用该依赖范围的maven依赖,只对测试和运行的classpath有效,对编译的classpath无效,典型例子就是JDBC的驱动实现,项目主代码编译的时候只需要JDK提供的JDBC接口,只有在测试和运行的时候才需要实现上述接口的具体JDBC驱动。
系统依赖范围(system),该依赖与classpath的关系与provided依赖范围完全一致,但是系统依赖范围必须通过配置systemPath元素来显示指定依赖文件的路径,此类依赖不是由maven仓库解析的,而且往往与本机系统绑定,可能造成构件的不可移植,因此谨慎使用,systemPath元素可以引用环境变量:
<dependency>
<groupId>javax.sql</groupId>
<artifactId>jdbc-stext</artifactId>
<version>2.0</version>
<scope>system</scope>
<systemPath>${java.home}/lib/rt.jar</systemPath>
</dependency>
2
3
4
5
6
7
- 导入依赖范围(import),该依赖范围不会对三种classpath产生影响,该依赖范围只能与dependencyManagement元素配合使用,其功能为将目标pom文件中dependencyManagement的配置导入合并到当前pom的dependencyManagement中。有关dependencyManagement的功能请了解maven继承特性。
# 表格示意
scope | 编译 | 测试 | 运行 |
---|---|---|---|
compile | Y | Y | Y |
test | Y | ||
provided | Y | Y | |
runtime | Y | Y | |
system | Y | Y |
# 4. 依赖冲突问题
# 4.1 依赖优先原则
基于依赖传播特性,导致整个依赖网络会很复杂,难免会出现相同组件不同版本的情况。Maven此时会基于依赖优先原则选择其中一个版本。
第一原则:最短路径优先。
第二原则:相同路径下配置在前的优先。
# 4.2 可选依赖
可选依赖表示这个依赖不是必须的。通过在 <dependency>
添 <optional>true</optional>
表示,默认是不可选的。可选依赖不会被传递。
# 4.3 排除依赖
即排除指定的间接依赖。通过配置 <exclusions>
配置排除指定组件。
# 5. Maven聚合和继承
# 5.1 继承
在构建多个模块的时候,往往会出现多个模块有相同的groupId、version,或者有相同的依赖,为了减少pom文件的配置,跟我们的项目中类的继承一样,在父工程中配置了pom,子项目中的pom可以继承.
可继承的POM元素
groupId: 项目组ID,项目坐标的核心元素
version: 项目版本,项目坐标的核心元素
description: 项目的描述信息
organnization: 项目的组织信息
inceptionYear: 项目的创始年份
url: 项目的URL地址
developers: 项目的开发者信息
dependencies: 项目的依赖配置
dependencyManagement: 项目的依赖管理配置
repositories: 项目的仓库配置
build: 包括项目的源码目录配置、输出目录配置、插件配置、插件管理配置等
2
3
4
5
6
7
8
9
10
11
# 继承POM的用法
面向对象设计中,程序员可以通过一种类的父子结构,在父类中声明一些字段和方法供子类继承,这样可以做到“一处声明、多处使用”,类似的我们需要创建POM的父子结构,然后在父POM中声明一些配置,供子POM继承。
子工程gropid 和version没写,是因为子工程的groupid和version和父工程的一样.所以子工程继承了父工程,但是当子工程的groupid,version和父工程的不一样的时候,就需要自己重写.父模块只是为了帮助消除配置的重复,因此它本身不包含除POM之外的项目文件,也就不需要src/main/java之类的文件夹了。
# 依赖管理
当多个模块中有相同的依赖时,我们可以将这些依赖提取出来,统一在父POM中声明,这样就可以简化子模块的配置了,但是这样还是存在问题,当想在项目中加入一些,不需要这么多依赖的模块,如果让这个模块也依赖那些不需要的依赖,显然不合理。
Maven提供的dependentcyManagement元素既能让子模块继承到父模块的依赖配置,又能保证子模块依赖使用的灵活度。在dependentcyManagement元素下的依赖声明不会引入实际的依赖,而是定义了依赖的版本,对版本进行同一管理,避免出现版本不一致的情况。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot-dependencies.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud-dependencies.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
dependencyManagement声明的依赖既不会给项目引入依赖,也不会给它的子模块引入依赖,不过这段配置是会被继承的。真正地引入到项目中是在子项目的pom文件中进行定义声明的.
dependencyManagement声明依赖能够统一规范项目中依赖的版本,当依赖的版本在父POM中声明之后,子模块在使用依赖的时候就无需声明版本,也就不会发生多个子模块使用依赖版本不一致的情况。这可以降低依赖冲突的几率。
# 5.2 聚合
在我们的项目中有 controller, service, dao 层,每一个里面都有pom文件,在构建的需要把每一个都进行构建,但是有了聚合,我们创建一个额外的模块。然后通过该模块,来构建整个项目的所有模块把他们聚合到一起,能够使用一条命令就构建多个模块,聚合工程的结构如下: pom文件如下:
<parent>
<artifactId>e3-parent</artifactId>
<groupId>cn.e3mall</groupId>
<version>1.0-SNAPSHOT</version>
<relativePath>../e3parent/pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cn-manager</artifactId>
<version>1.0-SNAPSHOT</version>
<modules>
<module>e3-manager-pojo</module>
<module>e3-manager-dao</module>
<module>e3-manager-interface</module>
<module>e3-manager-service</module>
<module>e3-manager-web</module>
</modules>
<packaging>pom</packaging>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
一个特殊的地方就是packaging,其值为pom,如果没有声明的话,默认为jar,对于聚合模块来说,其打包方式必须为pom,否则无法构建。
modules里的每一个module都可以用来指定一个被聚合模块,这里每个module的值都是一个当前pom的相对位置。
# 6. 参数配置
# 6.1 项目属性 <properties>
通过 <properties>
配置属性参数,可以简化配置。
<!-- 配置proName属性 -->
<properties>
<proName>prop</proName>
</properties>
<!-- 引用方式 -->
${proName}
2
3
4
5
6
7
maven 默认的属性
${basedir} 项目根目录
${version} 表示项目版本
${project.basedir} 同 ${basedir}
${project.version} 表示项目版本,与 ${version} 相同
${project.build.directory} 构建目录,缺省为target
${project.build.sourceEncoding} 表示主源码的编码格式
${project.build.sourceDirectory} 表示主源码路径
${project.build.finalName} 表示输出文件名称
${project.build.outputDirectory} 构建过程输出目录,缺省为target/classes
2
3
4
5
6
7
8
9
# 7. 项目构建配置
# 7.1 步骤
- 构建资源配置
- 编译插件
- profile 指定编译环境
# 7.2 配置示例
# 基本配置
<defaultGoal>package</defaultGoal>
<directory>${basedir}/target</directory>
<finalName>${artifactId}-${version}</finalName>
2
3
defaultGoal,执行构建时默认的goal或phase,如jar:jar或者package等 directory,构建的结果所在的路径,默认为${basedir}/target目录 finalName,构建的最终结果的名字,该名字可能在其他plugin中被改变
# <resources>
配置示例
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.MF</include>
<include>**/*.XML</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*</include>
<include>*</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
resources, build 过程中涉及的资源文件
定义 | 解释 |
---|---|
targetPath | 资源文件的目标路径 |
directory | 资源文件的路径,默认位于 ${basedir}/src/main/resources/ 目录下 |
includes | 一组文件名的匹配模式,被匹配的资源文件将被构建过程处理 |
excludes | 一组文件名的匹配模式,被匹配的资源文件将被构建过程忽略。同时被includes和excludes匹配的资源文件,将被忽略 |
filtering | 默认 false, true 表示通过参数对资源文件中的 ${key} 在编译时进行动态变更 |
# 8. Maven私服搭建 - Nexus
# 8.1 搭建步骤
参见另一篇 沉洋实验室-Nexus搭建
# 8.2 仓库的概念
定义 | 解释 |
---|---|
3rd party | 第三方仓库 |
Apache Snapshots | apache快照仓库 |
Central | maven中央仓库 |
Releases | 私有发布版本仓库 |
Snapshots | 私有快照版本仓库 |
# 8.3 配置
<!-- settings.xml -->
<repositories>
<repository>
<id>nexus-public</id>
<name>my nexus repository</name>
<url>http://192.168.0.147:9999/nexus/content/groups/public/</url>
</repository>
</repositories>
<mirrors>
<mirror>
<id>nexus-aliyun</id>
<mirrorOf>*</mirrorOf>
<name>Nexus aliyun</name>
<url>http://192.168.0.147:9999/nexus/content/groups/public/</url>
</mirror>
</mirrors>
<servers>
<server>
<id>nexus-snapshot</id>
<username>deployment</username>
<password>deployment123</password>
</server>
<server>
<id>nexus-release</id>
<username>deployment</username>
<password>deployment123</password>
</server>
</servers>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!-- pom.xml -->
<distributionManagement>
<repository>
<id>nexus-release</id>
<name>nexus release</name>
<url>http://192.168.0.147:9999/nexus/content/repositories/releases/</url>
</repository>
<snapshotRepository>
<id>nexus-snapshot</id>
<name>nexus snapshot</name>
<url>http://192.168.0.147:9999/nexus/content/repositories/snapshots/</url>
</snapshotRepository>
</distributionManagement>
2
3
4
5
6
7
8
9
10
11
12
13