简述
Maven
在我们的目前的工作中是使用最广泛的工具之一,它在我们编译,打包,部署等等各个环节都发挥着非常重要的作用,在敏捷开发中亦扮演中非常重要的角色,比如持续集成,持续交付。距离 Ant
的时代已经过去 很久了,Maven
诞生之际也已经很多年了,但是学习Maven
并熟练掌握其中的技巧在工作中依旧非常重要。本文是我最近这些天重新学习整理的笔记,它不能让你成为Maven
的高手,但是可以让你在平时工作中如果遇到 Maven
相关问题时有一些解决思路,或者说大概知道从什么方向去思考如何解决实际问题。
作为开发者,我们在实际工作中可能对于Maven
的感知仅在于我们当前项目下的POM文件
、Setting文件
,除此之外再多就是公司提供的Nexus私服
,本地仓库
等相关概念,那么入手学习也应该从这些最常见的概念中学习,逐步深入。
POM 文件
POM是指 Project Object Model,以XML的方式对项目进行约束与管理,原则上约定优于配置。
官方POM文件的入口:http://maven.apache.org/pom.html
对于POM
文件的了解与学习,个人建议是梳理POM
文件比较常见的配置项即可,同时如果发现有不清楚的配置项,学会如何从官网了解并从demo中入手学会即可。
配置项 | 描述 | 案例 |
---|---|---|
groupId | <groupId>org.cn.lennon</groupId> |
|
artifactId | 功能模块的名称 | <artifactId>api</artifactId> |
version | 版本号 | <version>0.2.1-RELEASES</version> |
packaging | 打包方式,默认为jar | <packaging>pom</packaging> |
description | 项目描述 | <description>lennon 独立开发者提供开源产品</description> |
properties | 配置项,用于定义变量,常见定义版本号 | <project-version>${project.version}</project-version> |
parent | 父模块信息,常见于各种第三方模块库 | <parent><groupId>....</groupId></parent> |
dependencies | 依赖管理 | <dependencies></dependencies> |
dependency | 依赖 | <dependency></dependency> |
dependencyManagement | 依赖管理 | <dependencyManagement ></dependencyManagement > |
modules | 子模块 | <modules> ... </ modules> |
build | 基础构建参数设置 | <build> ... </ build> |
distributionManagement | 经常用于配置连接私服,在打包完成部署后将会提交至私服仓库中 | <distributionManagement> ... </ distributionManagement> |
注意事项
1、dependencyManagement与dependencies有何区别?
dependencyManagement
常用于父POM
对于子模块的依赖管理,所以一般只用于父POM文件
主要用以统一管理各个依赖的版本号,避免同一个项目对于同一个依赖出现不同版本导致一些版本不兼容之类的问题
用于声明依赖(子模块依赖
父POM
的引用),将所有的外部包统一到父POM文件
,以便于管理维护
2、dependency中的scope详解
依赖jar的作用域,它要解决的关键问题在于什么阶段会被使用,以及会不会被打包
作用域 | 功能 | 描述 |
---|---|---|
compile | 编译,测试,打包(默认值) | 该作用域表示被依赖jar需要参与当前项目的编译,测试,运行,打包等生命周期,可以说它会参与整个生命周期。属于强依赖关系。 |
test | 测试 | 仅参与测试阶段的相关工作,包括测试代码的编译与执行,但是不会被打包到war/jar中 |
provided | 编译,测试 | 仅参与编译,测试,但是不参与打包以及打包后环境的运行,由第二方/第三方提供。即在打包阶段做了excloud操作 |
runtime | 打包 | 不参与项目的编译,但是后期的测试与运行都需要参与,除了编译阶段其他阶段均参与,比如JDBC驱动。 |
system | 编译,测试,打包 | 从参与度来说,和provided相同,不过被依赖项不会从maven仓库下载,而是从本地文件系统拿。需要添加systemPath的属性来定义路径。(不推荐) |
3、传递依赖
第一列表示直接依赖的scope
,第一行表示间接依赖的scope
假如有Maven项目A,项目B依赖A,项目C依赖B。那么我们可以说 C依赖A。也就是说,依赖的关系为:C—>B—>A。那么我们执行项目C时,会自动把B、A都下载导入到C项目的jar包文件夹中。这就是依赖的传递性。
4、依赖冲突(依赖仲裁)
- 最短路径原则,相同包选择最短的依赖路劲
决策在于依赖树长短,以依赖树短为主,谁短依赖谁。上图则说明依赖于X 0.1-R的版本
- 加载先后原则,xml的书写顺序,先声明优先
决策在于在项目A的POM
文件中,<depencies>B</depencies>
先还是<depencies>C</depencies>
先,谁先写就先依赖谁。
5、 依赖传递中排除
如上,C—>B—>A。加入现在不想执行C时把A下载进来,那么我们可以用 <exclusions>
标签。
<dependencies>
<dependency>
<groupId>B</groupId>
<artifactId>B</artifactId>
<version>0.0.1</version>
<exclusions>
<exclusion>
<!--被排除的依赖包坐标-->
<groupId>A</groupId>
<artifactId>A</artifactId>
<version>0.0.1</version>
</exclusion>
</exclusions>
</dependency>
</dependencies>
6、版本管理
官方版本号格式参考:主版本号.次版本号.增量版本号-<里程碑版本> eg: 1.0.0-RELAESE
SNAPSHOT
不稳定版本(迭代版本)RELEASE
稳定版本
发布新的版本,如果没有更新jar的版本号,如何依赖到最新的版本?
repository
将旧的jar包删除mvn clean package -U
(强制拉取最新的版本,在没有更新版本好的情况下。版本发布常用)
7、profile
pom常见使用环境变量:dev/local/prod
<profiles>
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<profiles.active>dev</profiles.active>
</properties>
</profile>
<profile>
<id>local</id>
<properties>
<profiles.active>local</profiles.active>
</properties>
</profile>
</profiles>
<build>
<resources>
<resource>
<directory>${basedir}/src/main/resources</directory>
<excludes>
<exclude>conf/**</exclude>
</excludes>
</resource>
<resource>
<directory>${basedir}/src/main/resources/conf/${profiles.active}</directory>
</resource>
</resources>
</build>
命令:mvn clean package -P local
Setting文件
从download下来的maven工具中就已经包含了相应的setting.xml
文件,位于conf
目录下。
基础配置项
setting.xml文件打开是可以看到,对于每一个配置项都做了注释,有需要便开启并配置相应参数,非常人道。常见的配置项如下:
配置项 | 功能 |
---|---|
localRepository | 本地仓库 |
servers | 服务器认证信息 |
mirrors | 仓库镜像 |
profiles | 配置项,用于配置不同环境下不同使用参数 |
activeProfiles | 用于决定启用那些配置项 |
profiles
<profile>
<id>jdk18</id>
<activation>
<jdk>1.8</jdk>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</properties>
</profile>
<profile>
<id>nexus</id>
<repositories>
<repository>
<id>central</id>
<url>http://central</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>true</enabled></snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>central</id>
<url>http://central</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>true</enabled></snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
<!-- 启用哪个配置项 -->
<activeProfiles>
<activeProfile>jdk18</activeProfile>
<activeProfile>nexus</activeProfile>
</activeProfiles>
Maven 生命周期
概念
Mavenue的生命周期有三个关键概念需要理解
lifecycle 生命周期
phase 阶段
goals 目标
关于如何理解该三个概念,讲的最透彻的大概就是下边两个句子了:
A Build Lifecycle is Made Up of Phases
A Build Phase is Made Up of Plugin Goals
关于生命周期的博客参考:
http://juvenshun.iteye.com/blog/213959
https://www.cnblogs.com/EasonJim/p/6816340.html
常用命令
命令其实就是体现在maven生命周期中的phase
,可以说是maven内置的插件。
compile 编译代码
clean 删除target/
test test case junit/testNG
package 打包
install 把项目install到local repo
deploy 发本地jar发布到remote
插件
常用插件
https://maven.apache.org/plugins/
http://www.mojohaus.org/plugins.html
插件名称 | 功能 | 使用方式 |
---|---|---|
versions | 统一处理版本号 | versions:help; versions:set -DnewVersion=1.1.1-release; |
source | 源代码管理 | source:help;source:jar |
assembly | 打包 | 。。。 |
tomcat7 | 内置tomcat环境 | tomcat:run; tomcat:help; |
spring boot是如何做到jar启动?
从原理上去分析,其实本质是换汤不换药的,其实利用的就是maven的Tomcat插件做到的,怎么做到的呢?
- 从start.spring.io中load一个web应用,最包含约定好的入口类,打开pom可以看到其中插件上有
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
而这个插件又做了什么呢?
通过https://docs.spring.io/spring-boot/docs/1.5.4.RELEASE/maven-plugin/plugin-info.html进入该官网页面可以看到该插件相应的goals
- 将load下来的应用进行打包:
mvn package
后,使用jd-gui对包进行反编译可以看到如下目录
通过查看MANIFEST.MF
文件可以看到其中有两个配置:
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.example.springbootdemo.SpringBootDemoApplication
我们知道
maven-assembly
插件就有一个mainClass
,打包jar的入口类的配置,那么查看该Main-Class
的类呢?翻开spring-boot的配置文件可以看到mainClass作为主类入口
自定义插件
https://maven.apache.org/guides/plugin/guide-java-plugin-development.html
- 自定义插件时,必须将packaging申明为maven-plugin
<groupId>org.cn.lennon</groupId>
<artifactId>lennon-maven-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>maven-plugin</packaging>
- 自定义插件时,类需要实现
AbstractMojo
// mojo的name就是指goals
@Mojo( name = "sayhi", defaultPhase = LifecyclePhase.PACKAGE)
public class GreetingMojo extends AbstractMojo {
@Parameter
private String title;
public void execute() throws MojoExecutionException {
getLog().info( "Hello, world." );
getLog().info( "Hello, world." + title );
}
}
- 如何使用自定义插件呢?
<plugin>
<groupId>org.cn.lennon</groupId>
<artifactId>lennon-maven-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<configuration>
<title>test1</title>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>sayhi</goal>
</goals>
</execution>
</executions>
</plugin>
- 运行:mvn package
重新认识maven的lifecycle\phase\goals
lifecycle
是指maven
的整个生命周期,而在整个生命周期下分为三个阶段,统称为phase
。在命令行中常常可见的方式比如mvn package
,package
就是一个阶段。每执行一个阶段下就会有不同的goals
。比如说我们将自定义好的插件挂载package
阶段下,当我们执行该阶段时就会执行该插件。
理解了phase
与goals
的关系后,对于理解整个maven来说异常重要。
私服
私服搭建需要的资料从官方下载 https://www.sonatype.com/oss-thank-you-win64.zip
概念
- hosted 表示上传的仓库,可以考虑创建两三个hosted类型的仓库
1、releases 用于上传releases版本的相关jar(稳定版本)
2、snapshots 用于上传snapshots版本的相关jar(迭代版本,不稳定版本)
3、3thparty 用户上传第三方jar包(可选择)
proxy 表示当从私服下载不到jar包时,将会从proxy类型的仓库下载
group 表示可以将多个仓库合并成一个。
搭建推荐方式:
proxy为阿里云的maven仓库地址。public为对外的公共仓库地址。
setting.xml配置方式
<servers>
<server>
<id>nexus</id>
<username>admin</username>
<password>admin123</password>
</server>
</servers>
<mirror>
<id>nexus</id>
<mirrorOf>*</mirrorOf>
<url>http://localhost:8081/repository/nexus-public/</url>
</mirror>
<profile>
<id>nexus</id>
<repositories>
<repository>
<id>central</id>
<url>http://central</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>true</enabled></snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>central</id>
<url>http://central</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>true</enabled></snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
pom.xml
<distributionManagement>
<repository>
<id>nexus</id>
<name>nexus-releases</name>
<url>http://localhost:8081/repository/nexus-releases/</url>
</repository>
<snapshotRepository>
<id>nexus</id>
<name>nexus-snapshots</name>
<url>http://localhost:8081/repository/nexus-snapshots/</url>
</snapshotRepository>
</distributionManagement>
一些可能作为身为高级工程师或者架构师需要去做的事
私服
前文中已经讲过Nexus
私服搭建,这不再赘述
项目模块化
项目模块分区分后的种种好处不言而喻,参考Apache RocketMQ
的目录结构
将
父POM
文件中的packaging
设置为pom,如:<packaging>pom</packaging>
新增模块时,将模块引入到
modules
中将项目依赖都放入
dependencyManagement
中管理子模块配置
parent
,依赖可以引用自父POM
,不加版本号。如果使用IDE的话,可以右键创建
Maven Module
项目
archetype 模板化
在平时开发中,创建一个Maven项目时,都会优先选择一个脚手架,比如常用的maven-archetype-quickstart
,但是到了 不同的公司具备了不同的业务场景以及规范,maven提供的通用模板可能不够细致,在微服务盛行的今天,一个项目依赖的应用粒度越细,那么需要的应用就越多,为了统一管理维护,自然需要创建一个合规合理的脚手架以帮助开发者快速创建项目基本架构并快速投入业务开发中。
通过IDE快速创建一个脚手架项目,声明:
<packaging>maven-archetype</packaging>
按照业务与规范将基本的内容填充好
mvn archetype:create-from-project
cd /target/generated-sources/archetype,执行mvn install
从archetype创建项目 mvn archetype:generate -DarchetypeCatalog=local
完成可以本地IDE上看到该脚手架,Catalog
选择Default Catalog
便可看到自己的创建好的脚手架,确认无误后可以推广至整个研发团队,以帮助开发者快速建立maven项目。