第 2 章 Maven 代码构建系统
在了解 Maven 之前,我们先来看看一个 Java 项目需要的东西。首先,我们需要确定引入哪些依赖包。例如,如果我们需要用到 commons logging,我们就必须把 commons logging 的 jar 包放入 classpath。如果我们还需要 log4j,就需要把 log4j 相关的 jar 包都放到 classpath 中。这些就是依赖包的管理。
其次,我们要确定项目的目录结构。例如,src
目录存放 Java 源码,resources
目录存放配置文件,bin
目录存放编译生成的 .class
文件。
此外,我们还需要配置环境,例如 JDK 的版本,编译打包的流程,当前代码的版本号。
最后,除了使用 Eclipse 这样的 IDE 进行编译外,我们还必须能通过命令行工具进行编译,才能够让项目在一个独立的服务器上编译、测试、部署。
这些工作难度不大,但是非常琐碎且耗时。如果每一个项目都自己搞一套配置,肯定会一团糟。我们需要的是一个标准化的 Java 项目管理和构建工具。
这个时候 Maven 出现了。
2.1、Apache Maven 概念
Maven 是一个项目管理和构建自动化工具。但是对于我们程序员来说,我们最关心的是它的项目构建功能。所以这里我们介绍的就是怎样用 maven 来满足我们项目的日常需要。 Maven 使用惯例优于配置的原则 。它要求在没有定制之前,所有的项目都有如下的结构:
目录 | 目的 |
---|---|
${basedir} | 存放 pom.xml 和所有的子目录 |
${basedir}/src/main/java | 项目的 java 源代码 |
${basedir}/src/main/resources | 项目的资源,比如说 property 文件 |
${basedir}/src/test/java | 项目的测试类,比如说 JUnit 代码 |
${basedir}/src/test/resources | 测试使用的资源 |
一个 maven
项目在默认情况下会产生 JAR
文件,另外 ,编译后的 classes
会放在 ${basedir}/target/classes
下面, JAR
文件会放在 ${basedir}/target
下面。
总结起来 Maven 就是专门为 Java 项目打造的管理和构建工具,它的主要功能有:
- 提供了一套标准化的项目结构;
- 提供了一套标准化的构建流程(编译,测试,打包,发布……);
- 提供了一套依赖管理机制。
2.2、Maven 安装
2.2.1、安装 Java JDK 环境
下载地址:https://www.oracle.com/java/technologies/downloads/
[root@armcli ~]# wget https://download.oracle.com/java/17/latest/jdk-17_linux-aarch64_bin.rpm
[root@armcli ~]# ls
jdk-17_linux-aarch64_bin.rpm
[root@armcli ~]# rpm -ivh jdk-17_linux-aarch64_bin.rpm
warning: jdk-17_linux-aarch64_bin.rpm: Header V3 RSA/SHA256 Signature, key ID ec551f03: NOKEY
Preparing... ################################# [100%]
Updating / installing...
1:jdk-17-2000:17.0.2-ga ################################# [100%]
[root@armcli ~]#
[root@armcli ~]# java -version
java version "17.0.2" 2022-01-18 LTS
Java(TM) SE Runtime Environment (build 17.0.2+8-LTS-86)
Java HotSpot(TM) 64-Bit Server VM (build 17.0.2+8-LTS-86, mixed mode, sharing)
[root@armcli ~]#
2.2.2、Maven 安装
下载地址:https://maven.apache.org/download.cgi
# 下载安装
[root@armcli ~]# wget https://dlcdn.apache.org/maven/maven-3/3.8.4/binaries/apache-maven-3.8.4-bin.tar.gz
[root@armcli ~]# tar xf apache-maven-3.8.4-bin.tar.gz -C /usr/local/
[root@armcli ~]# ln -svf /usr/local/apache-maven-3.8.4/ /usr/local/maven
‘/usr/local/maven’ -> ‘/usr/local/apache-maven-3.8.4/’
# 配置环境
[root@armcli ~]# vim /etc/profile.d/maven.sh
export MAVEN_HOME=/usr/local/maven
export PATH=$PATH:$MAVEN_HOME/bin
[root@armcli ~]# source /etc/profile.d/maven.sh
# 最后验证是否安装成功,出现如下信息,说明安装成功
[root@armcli ~]# mvn --version
Apache Maven 3.8.4 (9b656c72d54e5bacbed989b64718c159fe39b537)
Maven home: /usr/local/maven
Java version: 17.0.2, vendor: Oracle Corporation, runtime: /usr/java/jdk-17.0.2
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "4.18.0-193.28.1.el7.aarch64", arch: "aarch64", family: "unix"
[root@armcli ~]#
2.3、Maven 使用
2.3.1、建立 Hello World 项目
[root@armcli ~]# mvn archetype:generate -DgroupId=com.greatwall.helloworld -DartifactId=helloworld -Dpackage=com.greatwall.helloworld -Dversion=1.0-SNAPSHOT -DarchetypeCatalog=internal
archetype:generate
目标会列出一系列的archetype
让你选择。Archetype
可以理解成项目的模型。Maven
为我们提供了很多种的项目模型,包括从简单的Swing
到复杂的Web
应用。- 默认使用
maven-archetype-quickstart
模型,连续回车即可。
- 项目属性是我们在命令行中用 -D 选项指定的。该选项使用 -Dname=value 的格式。
- 如果你是第一次运行
maven
,你需要Internet
连接,因为maven
需要从网上下载需要的插件。
我们看一下 maven
给我们建立的文件目录结构:
[root@armcli ~]# tree helloworld/
helloworld/
├── pom.xml
└── src
├── main
│ └── java
│ └── com
│ └── greatwall
│ └── helloworld
│ └── App.java
└── test
└── java
└── com
└── greatwall
└── helloworld
└── AppTest.java
11 directories, 3 files
[root@armcli ~]#
maven
的archetype
插件建立了一个helloworld
目录,这个名字来自artifactId
。- 这个目录下面,有一个
Project Object Model(POM)
文件pom.xml
。这个文件用于描述项目,配置插件和管理依赖关系。 - 源代码和资源文件放在
src/main
下面,而测试代码和资源放在src/test
下面。
Maven 已经为我们建立了一个 App.java 文件:
package com.greatwall.helloworld;
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args )
{
System.out.println( "Hello World!" );
}
}
2.3.2、构建 Hello World 程序
[root@armcli ~]# cd helloworld/
[root@armcli helloworld]# mvn package
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------< com.greatwall.helloworld:helloworld >-----------------
[INFO] Building helloworld 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
......
[INFO] Building jar: /root/helloworld/target/helloworld-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 32.810 s
[INFO] Finished at: 2022-03-14T13:44:49+08:00
[INFO] ------------------------------------------------------------------------
- 第一次运行
maven
的时候,它会从网上的maven
库(repository)
下载需要的程序,存放在你电脑的本地库(local repository)
中,所以这个时候你需要有Internet
连接。 Maven
默认的本地库是~/.m2/repository/
,在Windows
下是%USER_HOME%\.m2\repository\
。
构建过程中可能会出现以下报错:
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project helloworld: Compilation failure: Compilation failure:
[ERROR] Source option 5 is no longer supported. Use 7 or later.
[ERROR] Target option 5 is no longer supported. Use 7 or later.
报错分析:由于我们这个项目是基于老的 Archetype
项目模型建立的,所以这种不兼容的问题很常见,这种情况下,我们需要将源和目标的 java-11-openjdk 版本传递给 Maven 编译器。
解决方法:通过 pom.xml 项目配置文件传递 java-11-openjdk 版本。
[root@armcli helloworld]# cat pom.xml
<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>com.greatwall.helloworld</groupId>
<artifactId>helloworld</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>helloworld</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source> # 这里传递
<maven.compiler.target>17</maven.compiler.target> # 这里传递
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
最终构建成功后,会得到类似下面的结果:
maven
在 helloworld
下面建立了一个新的目录 target/
,构建打包后的 jar
文件 helloworld-1.0-SNAPSHOT.jar
就存放在这个目录下。
编译后的 class
文件放在 target/classes/
目录下面,测试 class
文件放在 target/test-classes/
目录下面。
[root@armcli helloworld]# tree .
.
├── pom.xml
├── src
│ ├── main
│ │ └── java
│ │ └── com
│ │ └── greatwall
│ │ └── helloworld
│ │ └── App.java
│ └── test
│ └── java
│ └── com
│ └── greatwall
│ └── helloworld
│ └── AppTest.java
└── target
├── classes
│ └── com
│ └── greatwall
│ └── helloworld
│ └── App.class
├── generated-sources
│ └── annotations
├── generated-test-sources
│ └── test-annotations
├── helloworld-1.0-SNAPSHOT.jar
├── maven-archiver
│ └── pom.properties
├── maven-status
│ └── maven-compiler-plugin
│ ├── compile
│ │ └── default-compile
│ │ ├── createdFiles.lst
│ │ └── inputFiles.lst
│ └── testCompile
│ └── default-testCompile
│ ├── createdFiles.lst
│ └── inputFiles.lst
├── surefire-reports
│ ├── com.greatwall.helloworld.AppTest.txt
│ └── TEST-com.greatwall.helloworld.AppTest.xml
└── test-classes
└── com
└── greatwall
└── helloworld
└── AppTest.class
32 directories, 13 files
为了验证我们的程序能运行,执行下面的命令:
[root@armcli helloworld]# java -cp target/helloworld-1.0-SNAPSHOT.jar com.greatwall.helloworld.App
Hello World!
运行成功!
2.4、Maven 深入理解
刚才通过一个 helloworld 的小项目对 maven 有了一点初步认识,接下来的一步是要了解 maven 的核心概念,这样才能在使用 maven 的时候游刃有余。
2.4.1、POM (Project Object Model)
一个项目所有的配置都放置在 POM
文件中:定义项目的类型、名字,管理依赖关系,定制插件的行为等等。比如说,你可以配置 compiler 插件让它使用 java 1.5 来编译。
下面拿刚才的 helloworld POM 配置文件进行一些细节说明:
<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>com.greatwall.helloworld</groupId>
<artifactId>helloworld</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>helloworld</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
在 POM
中,groupId
,artifactId
,packaging
,version
叫作 maven
坐标,它能唯一地确定一个项目。
groupId
类似于 Java 的包名,通常是公司或组织名称;artifactId
类似于 Java 的类名,通常是项目名称;version
是项目版本;packaging
是指打包类型,一般来说所有的父级项目的 packaging 都为 pom 类型,还有 jar、war、ear 和 rar 等类型;packaging 默认类型是 jar 类型,经常省略 packaging 参数。
有了 maven 坐标,我们就可以用它来指定我们的项目所依赖的其他项目,插件,或者父项目。一般 maven
坐标写成如下的格式:
groupId:artifactId:packaging:version
像我们的例子就会写成:
com.greatwall.helloworld: helloworld: jar: 1.0-SNAPSHOT
我们再引用其他第三方库的时候,也是通过 3 个关键变量确定。例如,依赖 commons-logging
:
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
- 使用
<dependency>
声明一个依赖后,Maven 就会自动下载这个依赖包并把它放到 classpath 中。
我们的 helloworld
示例很简单,但是大项目一般会分成几个子项目。在这种情况下,每个子项目就会有自己的 POM 文件,然后它们会有一个共同的父项目。这样只要构建父项目就能够构建所有的子项目了。子项目的 POM
会继承父项目的 POM。
另外,所有的 POM 都继承了一个 Super-POM
。Super-POM
设置了一些默认值,比如在上面提到的默认的目录结构,默认的插件等等,它遵循了惯例优于配置的原则。
所以尽管我们的这个 POM 很简单,但是这只是你看得见的一部分。运行时候的 POM 要复杂的多。 如果你想看到运行时候的 POM 的全部内容的话,可以运行下面的命令:
$ mvn help:effective-pom
2.4.2、Maven 插件
上面 helloworld 项目中,我们用了 mvn archetype:generate
命令来生成一个项目。那么这里的 archetype:generate
是什么意思呢?
-
archetype
是一个插件的名字,generate
是目标(goal)的名字。 -
这个命令的意思是告诉
maven
执行archetype
插件的generate
目标。 -
插件目标通常会写成
pluginId:goalId
。
一个目标是一个工作单元,而插件则是一个或者多个目标的集合。比如说 Jar
插件,Compiler
插件,Surefire
插件等。从看名字就能知道,Jar
插件包含建立 Jar 文件的目标, Compiler
插件包含编译源代码和单元测试代码的目标。Surefire
插件的话,则是运行单元测试。
看到这里,估计你能明白了,mvn
本身不会做太多的事情,它不知道怎么样编译或者怎么样打包。它把构建的任务交给插件去做。插件定义了常用的构建逻辑,能够被重复利用。这样做的好处是,一旦插件有了更新,那么所有的 maven
用户都能得到更新。
我们以 compile
这个 phase 为例,如果执行:
$ mvn compile
Maven 将执行 compile
这个 phase,这个 phase 会调用 compiler
插件执行关联的 compiler:compile
这个 goal。
由此可见,执行每个 phase,都是通过某个插件(plugin)来执行的,Maven 本身其实并不知道如何执行 compile
,它只是负责找到对应的 compiler
插件,然后执行默认的 compiler:compile
这个 goal 来完成编译。
所以,使用 Maven,实际上就是配置好需要使用的插件,然后通过 phase 调用它们。
Maven 已经内置了一些常用的标准插件:
插件名称 | 对应执行的 phase |
---|---|
clean | clean |
compiler | compile |
surefire | test |
jar | package |
如果标准插件无法满足需求,我们还可以使用自定义插件。使用自定义插件的时候,需要声明。例如,使用 maven-shade-plugin
可以创建一个可执行的 jar,要使用这个插件,需要在 pom.xml
中声明它:
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
...
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
自定义插件往往需要一些配置,例如,maven-shade-plugin
需要指定 Java 程序的入口,它的配置是:
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.itranswarp.learnjava.Main</mainClass>
</transformer>
</transformers>
</configuration>
注意,Maven 自带的标准插件例如 compiler
是无需声明的,只有引入其它的插件才需要声明。
下面列举了一些常用的插件:
- maven-shade-plugin:打包所有依赖包并生成可执行 jar;
- cobertura-maven-plugin:生成单元测试覆盖率报告;
- findbugs-maven-plugin:对 Java 源码进行静态分析以找出潜在问题。
2.4.3、Maven 构建流程
Maven 不但有标准化的项目结构,而且还有一套标准化的构建流程,可以自动化实现编译,打包,发布,等等。
2.4.3.1、Lifecycle 和 Phase
使用 Maven 时,我们首先要了解什么是 Maven 的生命周期(lifecycle)。
Maven 的生命周期由一系列阶段(phase)构成,以内置的生命周期 default
为例,它包含以下 phase:
validate
initialize
generate-sources
process-sources
generate-resources
process-resources
compile
process-classes
generate-test-sources
process-test-sources
generate-test-resources
process-test-resources
test-compile
process-test-classes
test
prepare-package
package
pre-integration-test
integration-test
post-integration-test
verify
install
deploy
如果我们运行 mvn package
,Maven 就会执行 default
生命周期,它会从开始一直运行到 package
这个 phase 为止:
validate
...
package
如果我们运行 mvn compile
,Maven 也会执行 default
生命周期,但这次它只会运行到 compile
,即以下几个 phase:
validate
...
compile
Maven 另一个常用的生命周期是 clean
,它会执行 3 个 phase:
pre-clean
clean (注意这个clean不是lifecycle而是phase)
post-clean
所以,我们使用 mvn
这个命令时,后面的参数是 phase,Maven 自动根据生命周期运行到指定的 phase。
更复杂的例子是指定多个 phase,例如,运行 mvn clean package
,Maven 先执行 clean
生命周期并运行到 clean
这个 phase,然后执行 default
生命周期并运行到 package
这个 phase,实际执行的 phase 如下:
pre-clean
clean (注意这个clean是phase)
validate
...
package
在实际开发过程中,经常使用的命令有:
mvn clean
:清理所有生成的 class 和 jar;
mvn clean compile
:先清理,再执行到 compile
;
mvn clean test
:先清理,再执行到 test
,因为执行 test
前必须执行 compile
,所以这里不必指定 compile
;
mvn clean package
:先清理,再执行到 package
。
大多数 phase 在执行过程中,因为我们通常没有在 pom.xml
中配置相关的设置,所以这些 phase 什么事情都不做。
经常用到的 phase 其实只有几个:
- clean:清理
- compile:编译
- test:运行测试
- package:打包
2.4.3.2、Goal
执行一个 phase 又会触发一个或多个 goal:
执行的 Phase | 对应执行的 Goal |
---|---|
compile | compiler:compile |
test | compiler:testCompile surefire:test |
goal 的命名总是 abc:xyz
这种形式。
我们类比一下就明白了:
- lifecycle 相当于 Java 的 package,它包含一个或多个 phase;
- phase 相当于 Java 的 class,它包含一个或多个 goal;
- goal 相当于 class 的 method,它其实才是真正干活的。
大多数情况,我们只要指定 phase,就默认执行这些 phase 默认绑定的 goal,只有少数情况,我们可以直接指定运行一个 goal,例如,启动 Tomcat 服务器:
$ mvn tomcat:run
2.4.4、Maven 依赖管理
如果我们的项目依赖第三方的 jar 包,例如 commons logging,那么问题来了:commons logging 发布的 jar 包在哪下载?如果我们还希望依赖 log4j,那么使用 log4j 需要哪些 jar 包?
类似的依赖还包括:JUnit,JavaMail,MySQL 驱动等等,一个可行的方法是通过搜索引擎搜索到项目的官网,然后手动下载 zip 包,解压,放入 classpath。但是,这个过程非常繁琐。
Maven 解决了依赖管理问题。之前我们说过,maven
坐标能够确定一个项目。换句话说,我们可以用它来解决依赖关系。在 POM
中,依赖关系是在 dependencies 部分中定义的。在上面的 POM 例子中,我们用 dependencies 定义了对于 junit
的依赖:
<dependencies>
<dependency>
<groupid>junit</groupid>
<artifactid>junit</artifactid>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
那这个例子很简单,但是实际开发中我们会有复杂得多的依赖关系,因为被依赖的 jar
文件会有自己的依赖关系。那么我们是不是需要把那些间接依赖的 jar
文件也都定义在 POM
中呢?答案是不需要,因为 maven
提供了传递依赖的特性。
所谓传递依赖是指 maven
会检查被依赖的 jar
文件,把它的依赖关系纳入最终解决的依赖关系链中。针对上面的 junit
依赖关系,如果你看一下 maven
的本地库 ~/.m2/repository/junit/junit/3.8.1/
:
[root@armcli ~]# cd ~/.m2/repository/junit/junit/3.8.1/
[root@armcli 3.8.1]#
[root@armcli 3.8.1]# ls
junit-3.8.1.jar junit-3.8.1.jar.sha1 junit-3.8.1.pom junit-3.8.1.pom.sha1 _remote.repositories
[root@armcli 3.8.1]#
你会发现 maven 不但下载了 junit-3.8.1.jar
,还下载了它的 POM
文件。这样 maven
就能检查 junit
的依赖关系,把它所需要的依赖也包括进来。
因此,Maven 的第一个作用就是解决依赖管理。我们声明了自己的项目需要 abc
,Maven 会自动导入 abc
的 jar 包,再判断出 abc
需要 xyz
,又会自动导入 xyz
的 jar 包,这样,最终我们的项目会依赖 abc
和 xyz
两个 jar 包。
我们再来看一个复杂依赖示例:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>1.4.2.RELEASE</version>
</dependency>
当我们声明一个 spring-boot-starter-web
依赖时,Maven 会自动解析并判断最终需要大概二三十个其他依赖:
spring-boot-starter-web
spring-boot-starter
spring-boot
sprint-boot-autoconfigure
spring-boot-starter-logging
logback-classic
logback-core
slf4j-api
jcl-over-slf4j
slf4j-api
jul-to-slf4j
slf4j-api
log4j-over-slf4j
slf4j-api
spring-core
snakeyaml
spring-boot-starter-tomcat
tomcat-embed-core
tomcat-embed-el
tomcat-embed-websocket
tomcat-embed-core
jackson-databind
...
如果我们自己去手动管理这些依赖是非常费时费力的,而且出错的概率很大。
2.4.4.1、依赖关系
Maven 定义了几种依赖关系,分别是 compile
、test
、runtime
和 provided
:
scope | 说明 | 示例 |
---|---|---|
compile | 编译时需要用到该 jar 包(默认) | commons-logging |
test | 编译 Test 时需要用到该 jar 包 | junit |
runtime | 编译时不需要,但运行时需要用到 | mysql |
provided | 编译时需要用到,但运行时由 JDK 或某个服务器提供 | servlet-api |
其中,默认的 compile
是最常用的,Maven 会把这种类型的依赖直接放入 classpath。
test
依赖表示仅在测试时使用,正常运行时并不需要。最常用的 test
依赖就是 JUnit:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.3.2</version>
<scope>test</scope>
</dependency>
runtime
依赖表示编译时不需要,但运行时需要。最典型的 runtime
依赖是 JDBC 驱动,例如 MySQL 驱动:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
<scope>runtime</scope>
</dependency>
provided
依赖表示编译时需要,但运行时不需要。最典型的 provided
依赖是 Servlet API,编译的时候需要,但是运行时,Servlet 服务器内置了相关的 jar,所以运行期不需要:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.0</version>
<scope>provided</scope>
</dependency>
最后一个问题是,Maven 如何知道从何处下载所需的依赖?也就是相关的 jar 包?答案是 Maven 维护了一个中央仓库(repo1.maven.org),所有第三方库将自身的 jar 以及相关信息上传至中央仓库,Maven 就可以从中央仓库把所需依赖下载到本地。
Maven 并不会每次都从中央仓库下载 jar 包。一个 jar 包一旦被下载过,就会被 Maven 自动缓存在本地目录(用户主目录的 .m2
目录),所以,除了第一次编译时因为下载需要时间会比较慢,后续过程因为有本地缓存,并不会重复下载相同的 jar 包。
2.4.4.2、唯一 ID
对于某个依赖,Maven 只需要 3 个关键变量即可唯一确定某个 jar 包:
- groupId:属于组织的名称,类似 Java 的包名;
- artifactId:该 jar 包自身的名称,类似 Java 的类名;
- version:该 jar 包的版本。
通过上述 3 个变量,即可唯一确定某个 jar 包。Maven 通过对 jar 包进行 PGP 签名确保任何一个 jar 包一经发布就无法修改。修改已发布 jar 包的唯一方法是发布一个新版本。
因此,某个 jar 包一旦被 Maven 下载过,即可永久地安全缓存在本地。
注:只有以 -SNAPSHOT
结尾的版本号会被 Maven 视为开发版本,开发版本每次都会重复下载,这种 SNAPSHOT 版本只能用于内部私有的 Maven repo,公开发布的版本不允许出现 SNAPSHOT。
2.4.4.3、Maven 镜像
除了可以从 Maven 的中央仓库下载外,还可以从 Maven 的镜像仓库下载。如果访问 Maven 的中央仓库非常慢,我们可以选择一个速度较快的 Maven 的镜像仓库。Maven 镜像仓库定期从中央仓库同步:
中国区用户可以使用阿里云提供的 Maven 镜像仓库。使用 Maven 镜像仓库需要一个配置,在用户主目录下进入 .m2
目录,创建一个 settings.xml
配置文件,内容如下:
<settings>
<mirrors>
<mirror>
<id>aliyun</id>
<name>aliyun</name>
<mirrorOf>central</mirrorOf>
<!-- 国内推荐阿里云的Maven镜像 -->
<url>https://maven.aliyun.com/repository/central</url>
</mirror>
</mirrors>
</settings>
2.4.4.4、搜索第三方组件
最后一个问题:如果我们要引用一个第三方组件,比如 okhttp
,如何确切地获得它的 groupId
、artifactId
和 version
?方法是通过 search.maven.org 搜索关键字,找到对应的组件后,直接复制:
2.4.4.5、命令行编译
在命令中,进入到 pom.xml
所在目录,输入以下命令:
$ mvn clean package
如果一切顺利,即可在 target
目录下获得编译后自动打包的 jar。
2.4.5、Maven 库
当第一次运行 maven
命令的时候,你需要 Internet
连接,因为它要从网上下载一些文件。那么它从哪里下载呢?它是从 maven
默认的远程库(http://repo1.maven.org/maven2) 下载的。这个远程库有 maven
的核心插件和可供下载的 jar
文件。
但是不是所有的 jar
文件都是可以从默认的远程库下载的,比如说我们自己开发的项目。这个时候,有两个选择:要么在公司内部设置定制库,要么手动下载和安装所需的 jar 文件到本地库。
本地库是指 maven
下载了插件或者 jar
文件后存放在本地机器上的拷贝。在 Linux
上,它的位置在 ~/.m2/repository
,在 Windows XP
上,在 C:\Documents and Settings\username\.m2\repository
,在 Windows 7
上,在 C:\Users\username\.m2\repository
。当 maven
查找需要的 jar
文件时,它会先在本地库中寻找,只有在找不到的情况下,才会去远程库中找。
运行下面的命令能把我们的 helloworld
项目安装到本地库:
$ mvn install
一旦一个项目被安装到了本地库后,你别的项目就可以通过 maven
坐标和这个项目建立依赖关系。比如如果我现在有一个新项目需要用到 helloworld
,那么在运行了上面的 mvn install
命令后,我就可以如下所示来建立依赖关系:
<dependency>
<groupid>com.greatwall.helloworld</groupid>
<artifactid>helloworld</artifactid>
<version>1.0-SNAPSHOT</version>
</dependency>
2.4.6、Maven 模块管理
在软件开发中,把一个大项目分拆为多个模块是降低软件复杂度的有效方法:
┌ ─ ─ ─ ─ ─ ─ ┐
┌─────────┐
│ │Module A │ │
└─────────┘
┌──────────────┐ split │ ┌─────────┐ │
│Single Project│───────> │Module B │
└──────────────┘ │ └─────────┘ │
┌─────────┐
│ │Module C │ │
└─────────┘
└ ─ ─ ─ ─ ─ ─ ┘
对于 Maven 工程来说,原来是一个大项目:
single-project
├── pom.xml
└── src
现在可以分拆成 3 个模块:
mutiple-project
├── module-a
│ ├── pom.xml
│ └── src
├── module-b
│ ├── pom.xml
│ └── src
└── module-c
├── pom.xml
└── src
Maven 可以有效地管理多个模块,我们只需要把每个模块当作一个独立的 Maven 项目,它们有各自独立的 pom.xml
。例如,模块 A 的 pom.xml
:
<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>com.itranswarp.learnjava</groupId>
<artifactId>module-a</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<name>module-a</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.28</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.5.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
模块B的 pom.xml
:
<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>com.itranswarp.learnjava</groupId>
<artifactId>module-b</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<name>module-b</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.28</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.5.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
可以看出来,模块 A 和模块 B 的 pom.xml
高度相似,因此,我们可以提取出共同部分作为 parent
:
<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>com.itranswarp.learnjava</groupId>
<artifactId>parent</artifactId>
<version>1.0</version>
<packaging>pom</packaging>
<name>parent</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.28</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.5.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
注意到 parent 的 <packaging>
是 pom
而不是 jar
,因为 parent
本身不含任何 Java 代码。编写 parent
的 pom.xml
只是为了在各个模块中减少重复的配置。现在我们的整个工程结构如下:
multiple-project
├── pom.xml
├── parent
│ └── pom.xml
├── module-a
│ ├── pom.xml
│ └── src
├── module-b
│ ├── pom.xml
│ └── src
└── module-c
├── pom.xml
└── src
这样模块 A 就可以简化为:
<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>
<parent>
<groupId>com.itranswarp.learnjava</groupId>
<artifactId>parent</artifactId>
<version>1.0</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>module-a</artifactId>
<packaging>jar</packaging>
<name>module-a</name>
</project>
模块 B、模块 C 都可以直接从 parent
继承,大幅简化了 pom.xml
的编写。
如果模块 A 依赖模块 B,则模块 A 需要模块 B 的 jar 包才能正常编译,我们需要在模块 A 中引入模块 B:
...
<dependencies>
<dependency>
<groupId>com.itranswarp.learnjava</groupId>
<artifactId>module-b</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
最后,在编译的时候,需要在根目录创建一个 pom.xml
统一编译:
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itranswarp.learnjava</groupId>
<artifactId>build</artifactId>
<version>1.0</version>
<packaging>pom</packaging>
<name>build</name>
<modules>
<module>parent</module>
<module>module-a</module>
<module>module-b</module>
<module>module-c</module>
</modules>
</project>
这样,在根目录执行 mvn clean package
时,Maven 根据根目录的 pom.xml
找到包括 parent
在内的共 4 个 <module>
,一次性全部编译。
中央仓库
其实我们使用的大多数第三方模块都是这个用法,例如,我们使用 commons logging、log4j 这些第三方模块,就是第三方模块的开发者自己把编译好的 jar 包发布到 Maven 的中央仓库中。
私有仓库
私有仓库是指公司内部如果不希望把源码和 jar 包放到公网上,那么可以搭建私有仓库。私有仓库总是在公司内部使用,它只需要在本地的 ~/.m2/settings.xml
中配置好,使用方式和中央仓位没有任何区别。
本地仓库
本地仓库是指把本地开发的项目“发布”在本地,这样其他项目可以通过本地仓库引用它。
但是我们不推荐把自己的模块安装到 Maven 的本地仓库,因为每次修改某个模块的源码,都需要重新安装,非常容易出现版本不一致的情况。
更好的方法是使用模块化编译,在编译的时候,告诉 Maven 几个模块之间存在依赖关系,需要一块编译,Maven 就会自动按依赖顺序编译这些模块。
2.4.7、mvnw 使用
mvnw
是 Maven Wrapper 的缩写。因为我们安装 Maven 时,默认情况下,系统所有项目都会使用全局安装的这个 Maven 版本。
但是,对于某些项目来说,它可能必须使用某个特定的 Maven 版本,这个时候,就可以使用 Maven Wrapper,它可以负责给这个特定的项目安装指定版本的 Maven,而其他项目不受影响。
简单地说,Maven Wrapper 就是给一个项目提供一个独立的,指定版本的 Maven。
2.4.7.1、安装 Maven Wrapper
安装 Maven Wrapper 最简单的方式是在项目的根目录(即 pom.xml
所在的目录)下运行安装命令:
$ mvn -N io.takari:maven:0.7.6:wrapper
它会自动使用最新版本的 Maven。注意 0.7.6
是 Maven Wrapper的版本。最新的 Maven Wrapper 版本可以去官方网站查看。
如果要指定使用的 Maven 版本,使用下面的安装命令指定版本,例如 3.3.3
:
$ mvn -N io.takari:maven:0.7.6:wrapper -Dmaven=3.3.3
安装后,查看项目结构:
my-project
├── .mvn
│ └── wrapper
│ ├── MavenWrapperDownloader.java
│ ├── maven-wrapper.jar
│ └── maven-wrapper.properties
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
├── main
│ ├── java
│ └── resources
└── test
├── java
└── resources
发现多了 mvnw
、mvnw.cmd
和 .mvn
目录,我们只需要把 mvn
命令改成 mvnw
就可以使用跟项目关联的 Maven。例如:
$ mvnw clean package
在 Linux 或 macOS 下运行时需要加上 ./
:
./mvnw clean package
Maven Wrapper 的另一个作用是把项目的 mvnw
、mvnw.cmd
和 .mvn
提交到版本库中,可以使所有开发人员使用统一的 Maven 版本。
2.4.8、发布 Artifact
当我们使用 commons-logging
这些第三方开源库的时候,我们实际上是通过 Maven 自动下载它的 jar 包,并根据其 pom.xml
解析依赖,自动把相关依赖包都下载后加入到 classpath。
如果我们把自己的开源库放到 Maven 的 repo 中,那么,别人只需按标准引用 groupId:artifactId:version
,即可自动下载 jar 包以及相关依赖。因此,本节我们介绍如何发布一个库到 Maven 的 repo 中。
2.4.8.1、以静态文件发布
如果我们观察一个中央仓库的 Artifact 结构,例如 Commons Math,它的 groupId 是 org.apache.commons
,artifactId 是 commons-math3
,以版本 3.6.1
为例,发布在中央仓库的文件夹路径就是 https://repo1.maven.org/maven2/org/apache/commons/commons-math3/3.6.1/。
在此文件夹下,commons-math3-3.6.1.jar
就是发布的 jar 包,commons-math3-3.6.1.pom
就是它的 pom.xml
描述文件,commons-math3-3.6.1-sources.jar
是源代码,commons-math3-3.6.1-javadoc.jar
是文档。其它以 .asc
、.md5
、.sha1
结尾的文件分别是 GPG 签名、MD5 摘要和 SHA-1 摘要。
我们只要按照这种目录结构组织文件,它就是一个有效的 Maven 仓库。
我们以广受好评的开源项目 how-to-become-rich 为例,先创建 Maven 工程目录结构如下:
how-to-become-rich
├── maven-repo <-- Maven本地文件仓库
├── pom.xml <-- 项目文件
├── src
│ ├── main
│ │ ├── java <-- 源码目录
│ │ └── resources <-- 资源目录
│ └── test
│ ├── java <-- 测试源码目录
│ └── resources <-- 测试资源目录
└── target <-- 编译输出目录
在 pom.xml
中添加如下内容:
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itranswarp.rich</groupId>
<artifactId>how-to-become-rich</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>how-to-become-rich</name>
<developers>
<developer>
<name>Brinnatt</name>
<email>brinnatt@gmail.com</email>
<organization>Greatwall</organization>
<organizationUrl>https://brinnatt.com</organizationUrl>
</developer>
</developers>
<properties>
<!-- source encoding -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<maven.javadoc.failOnError>false</maven.javadoc.failOnError>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
<distributionManagement>
<repository>
<id>local-repo-release</id>
<name>GitHub Release</name>
<url>file://${project.basedir}/maven-repo</url>
</repository>
</distributionManagement>
<build>
<plugins>
<plugin>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<phase>package</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<executions>
<execution>
<id>attach-javadocs</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
注意到 <distributionManagement>
,它指示了发布的软件包的位置,这里的 <url>
是项目根目录下的 maven-repo
目录,在 <build>
中定义的两个插件 maven-source-plugin
和 maven-javadoc-plugin
分别用来创建源码和 javadoc,如果不想发布源码,可以把对应的插件去掉。
我们直接在项目根目录下运行 Maven 命令 mvn clean package deploy
,如果一切顺利,我们就可以在 maven-repo
目录下找到部署后的所有文件如下:
[root@armcli how-to-become-rich]# tree maven-repo/
maven-repo/
└── com
└── itranswarp
└── rich
└── how-to-become-rich
├── 1.0.0
│ ├── how-to-become-rich-1.0.0.jar
│ ├── how-to-become-rich-1.0.0.jar.md5
│ ├── how-to-become-rich-1.0.0.jar.sha1
│ ├── how-to-become-rich-1.0.0.pom
│ ├── how-to-become-rich-1.0.0.pom.md5
│ ├── how-to-become-rich-1.0.0.pom.sha1
│ ├── how-to-become-rich-1.0.0-sources.jar
│ ├── how-to-become-rich-1.0.0-sources.jar.md5
│ └── how-to-become-rich-1.0.0-sources.jar.sha1
├── maven-metadata.xml
├── maven-metadata.xml.md5
└── maven-metadata.xml.sha1
5 directories, 12 files
最后一步,是把这个工程推到 GitHub 上,并选择 Settings
-GitHub Pages
,选择 master branch
启用 Pages 服务:
[root@armcli how-to-become-rich]# git init
[root@armcli how-to-become-rich]# git remote add origin git@github.com:liangtiansheng/how-to-become-rich.git
[root@armcli how-to-become-rich]# git config user.name 'Brinnatt'
[root@armcli how-to-become-rich]# git config user.email "brinnatt@gmail.com"
[root@armcli how-to-become-rich]# git add .
[root@armcli how-to-become-rich]# git commit -m "This is a maven project for testing publishing"
[master (root-commit) 8be3b1a] This is a maven project for testing publishing
14 files changed, 447 insertions(+)
create mode 100644 .gitignore
create mode 100644 maven-repo/com/itranswarp/rich/how-to-become-rich/1.0.0/how-to-become-rich-1.0.0-sources.jar
create mode 100644 maven-repo/com/itranswarp/rich/how-to-become-rich/1.0.0/how-to-become-rich-1.0.0-sources.jar.md5
create mode 100644 maven-repo/com/itranswarp/rich/how-to-become-rich/1.0.0/how-to-become-rich-1.0.0-sources.jar.sha1
create mode 100644 maven-repo/com/itranswarp/rich/how-to-become-rich/1.0.0/how-to-become-rich-1.0.0.jar
create mode 100644 maven-repo/com/itranswarp/rich/how-to-become-rich/1.0.0/how-to-become-rich-1.0.0.jar.md5
create mode 100644 maven-repo/com/itranswarp/rich/how-to-become-rich/1.0.0/how-to-become-rich-1.0.0.jar.sha1
create mode 100644 maven-repo/com/itranswarp/rich/how-to-become-rich/1.0.0/how-to-become-rich-1.0.0.pom
create mode 100644 maven-repo/com/itranswarp/rich/how-to-become-rich/1.0.0/how-to-become-rich-1.0.0.pom.md5
create mode 100644 maven-repo/com/itranswarp/rich/how-to-become-rich/1.0.0/how-to-become-rich-1.0.0.pom.sha1
create mode 100644 maven-repo/com/itranswarp/rich/how-to-become-rich/maven-metadata.xml
create mode 100644 maven-repo/com/itranswarp/rich/how-to-become-rich/maven-metadata.xml.md5
create mode 100644 maven-repo/com/itranswarp/rich/how-to-become-rich/maven-metadata.xml.sha1
create mode 100644 pom.xml
[root@armcli how-to-become-rich]# git pull origin master # 非clone项目,要先把原仓库代码拉下来合并
warning: no common commits
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
From github.com:liangtiansheng/how-to-become-rich
* branch master -> FETCH_HEAD
Merge made by the 'recursive' strategy.
README.md | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 README.md
[root@armcli how-to-become-rich]#
[root@armcli how-to-become-rich]# git push -u origin master # 不合并直接上传会失败
Counting objects: 24, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (11/11), done.
Writing objects: 100% (23/23), 5.64 KiB | 0 bytes/s, done.
Total 23 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), done.
To git@github.com:liangtiansheng/how-to-become-rich.git
e0b9371..7978741 master -> master
Branch master set up to track remote branch master from origin.
这样,把全部内容推送至 GitHub 后,即可作为静态网站访问 Maven 的 repo,它的地址是 https://liangtiansheng.github.io/how-to-become-rich/。版本 1.0.0
对应的 jar 包地址是:
https://liangtiansheng.github.io/how-to-become-rich/maven-repo/com/itranswarp/rich/how-to-become-rich/1.0.0/how-to-become-rich-1.0.0.jar
现在,如果其他人希望引用这个 Maven 包,我们可以告知如下依赖即可:
<dependency>
<groupId>com.itranswarp.rich</groupId>
<artifactId>how-to-become-rich</artifactId>
<version>1.0.0</version>
</dependency>
但是,除了正常导入依赖外,对方还需要再添加一个 <repository>
的声明,即使用方完整的 pom.xml
如下:
<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>example</groupId>
<artifactId>how-to-become-rich-usage</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<java.version>11</java.version>
</properties>
<repositories>
<repository>
<id>github-rich-repo</id>
<name>The Maven Repository on Github</name>
<url>https://liangtiansheng.github.io/how-to-become-rich/maven-repo/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.itranswarp.rich</groupId>
<artifactId>how-to-become-rich</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
</project>
在 <repository>
中,我们必须声明发布的 Maven 的 repo 地址,其中 <id>
和 <name>
可以任意填写,<url>
填入 GitHub Pages 提供的地址 + /maven-repo/
后缀。现在,即可正常引用这个库并编写代码如下:
Millionaire millionaire = new Millionaire();
System.out.println(millionaire.howToBecomeRich());
此外,通过 GitHub Pages 发布 Maven repo 时需要注意一点,即不要改动已发布的版本。因为 Maven 的仓库是不允许修改任何版本的,对一个库进行修改的唯一方法是发布一个新版本。但是通过静态文件的方式发布 repo,实际上我们是可以修改 jar 文件的,但最好遵守规范,不要修改已发布版本。
2.4.8.2、发布到 Nexus3 私服
Nexus3 私服是什么以及如何搭建,请参考下面的 2.5 章节,这里也是先搭建好了,直接用来 Maven 构建发布。
- 将 2.4.8.1 小节示例中的 pom.xml 作如下修改。
<distributionManagement>
<repository>
<id>nexus3</id>
<name>private nexus3</name>
<url>http://172.16.4.13:8081/repository/maven-releases/</url>
</repository>
<!-- 如果项目版本号中有SNAPSHOT标识,会发布到下面这个Nexus Snapshots Repository
<snapshotRepository>
<id>nexus-snapshots</id>
<name>Nexus Snapshot Repository</name>
<url>http://<ip>:8081/repository/maven-snapshots/</url>
</snapshotRepository>
-->
</distributionManagement>
- 用户家目录下创建 maven 配置文件
~/.m2/settings.xml
,添加如下内容。
<settings>
<servers>
<server>
<id>nexus3</id>
<username>admin</username>
<password>${MYPASSWORD}</password>
</server>
<!-- 对照pom.xml文件,通过ID匹配用户名和密码
<server>
<id>nexus-snapshots</id>
<username>admin</username>
<password>admin123</password>
</server>
-->
</servers>
</settings>
- 用户和密码就是 nexus3 服务器上定义的,关于如何给用户授权请参考 2.5 章节。
- 注意:server 的 id 名称一定要和 pom.xml 配置文件中的 repository id 名称保持一致,用来匹配用户信息。
- 执行 mvn clean package deploy 即可完成清除、构建、打包以及发布到我们的 nexus3 服务器上去,注意目录结构还是 2.4.8.1 小节中的目录结构。
[root@armcli how-to-become-rich]# mvn clean package deploy
......
[INFO] --- maven-deploy-plugin:2.7:deploy (default-deploy) @ how-to-become-rich ---
Uploading to nexus3: http://172.16.4.13:8081/repository/maven-releases/com/itranswarp/rich/how-to-become-rich/1.0.0/how-to-become-rich-1.0.0.jar
Uploaded to nexus3: http://172.16.4.13:8081/repository/maven-releases/com/itranswarp/rich/how-to-become-rich/1.0.0/how-to-become-rich-1.0.0.jar (2.0 kB at 9.3 kB/s)
Uploading to nexus3: http://172.16.4.13:8081/repository/maven-releases/com/itranswarp/rich/how-to-become-rich/1.0.0/how-to-become-rich-1.0.0.pom
Uploaded to nexus3: http://172.16.4.13:8081/repository/maven-releases/com/itranswarp/rich/how-to-become-rich/1.0.0/how-to-become-rich-1.0.0.pom (1.9 kB at 27 kB/s)
Downloading from nexus3: http://172.16.4.13:8081/repository/maven-releases/com/itranswarp/rich/how-to-become-rich/maven-metadata.xml
Uploading to nexus3: http://172.16.4.13:8081/repository/maven-releases/com/itranswarp/rich/how-to-become-rich/maven-metadata.xml
Uploaded to nexus3: http://172.16.4.13:8081/repository/maven-releases/com/itranswarp/rich/how-to-become-rich/maven-metadata.xml (317 B at 4.3 kB/s)
Uploading to nexus3: http://172.16.4.13:8081/repository/maven-releases/com/itranswarp/rich/how-to-become-rich/1.0.0/how-to-become-rich-1.0.0-sources.jar
Uploaded to nexus3: http://172.16.4.13:8081/repository/maven-releases/com/itranswarp/rich/how-to-become-rich/1.0.0/how-to-become-rich-1.0.0-sources.jar (1.9 kB at 31 kB/s)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.597 s
[INFO] Finished at: 2022-03-27T16:14:11+08:00
[INFO] ------------------------------------------------------------------------
2.5、Maven 私服 Nexus3
官方文档:https://help.sonatype.com/repomanager3
下载地址:https://help.sonatype.com/repomanager3/product-information/download
Nexus3 是一个强大的 Maven、Nuget 仓库管理器,它极大地简化了本地内部仓库的维护和外部仓库的访问。
Nexus3 在代理远程仓库的同时维护本地仓库,以降低中央仓库的负荷,节省外网带宽和时间。
Nexus3 是一套"开箱即用"的系统,不需要数据库,它使用文件系统加 Lucene 来组织数据。
Nexus3 使用 ExtJS 来开发界面,利用 Restlet 来提供完整的 REST APIs,通过 m2eclipse 与 Eclipse 集成使用。
Nexus3 支持 WebDAV 与 LDAP 安全身份认证。
Nexus3 还提供了强大的仓库管理功能,构件搜索功能,它基于 REST,友好的 UI 是一个 extjs 的 REST 客户端,它占用较少的内存,基于简单文件系统而非数据库。
在项目开发过程中,研发团队所需的构件都需要通过 Maven 的中央仓库和第三方的 Maven 仓库下载到本地,而一个团队中的所有人都重复的从 Maven 仓库下载构件无疑加大了仓库的负载和浪费了外网带宽,如果网速慢的话,还会影响项目的进程。
这时我们会考虑在内网搭建属于自己的 Maven 私服,这样既节省了网络带宽也会加速项目搭建的进程,当然前提条件就是你的私服中拥有项目所需的所有构件。
2.5.1、Nexus3 安装
运行 nexus3 需要 java jdk 环境,刚才 Maven 安装使用的是 java17(jdk17) 版本,这个版本太高,换成 java8(jdk1.8):
[root@armcli ~]# yum remove jdk-17.aarch64
[root@armcli ~]# yum install java -y
[root@armcli ~]# java -version
openjdk version "1.8.0_322"
OpenJDK Runtime Environment (build 1.8.0_322-b06)
OpenJDK 64-Bit Server VM (build 25.322-b06, mixed mode)
下载 nexus3 安装:
[root@armcli ~]# wget https://sonatype-download.global.ssl.fastly.net/repository/downloads-prod-group/3/nexus-3.38.0-01-unix.tar.gz
[root@armcli ~]# tar xf nexus-3.38.0-01-unix.tar.gz -C /usr/local/
[root@armcli ~]# ln -sv /usr/local/nexus-3.38.0-01/ /usr/local/nexus3
‘/usr/local/nexus3’ -> ‘/usr/local/nexus-3.38.0-01/’
[root@armcli ~]# vim /etc/profile.d/nexus3.sh
export PATH=$PATH:/usr/local/nexus3/bin
[root@armcli ~]# source /etc/profile.d/nexus3.sh
[root@armcli ~]# nexus --help
WARNING: ************************************************************
WARNING: Detected execution as "root" user. This is NOT recommended!
WARNING: ************************************************************
Usage: /usr/local/nexus3/bin/nexus {start|stop|run|run-redirect|status|restart|force-reload}
[root@armcli ~]#
启动 nexus 服务:
# 启动需要点时间
[root@armcli ~]# nexus start
WARNING: ************************************************************
WARNING: Detected execution as "root" user. This is NOT recommended!
WARNING: ************************************************************
Starting nexus
[root@armcli ~]#
[root@armcli ~]# nexus status
WARNING: ************************************************************
WARNING: Detected execution as "root" user. This is NOT recommended!
WARNING: ************************************************************
nexus is running.
访问 nexus 网页:服务的默认端口是 8081,地址:http://172.16.4.13:8081/。
点击右上角 Sign in 按钮,按提示输入用户名和密码,点击 Sign in 登陆。然后出现导航设置,重新设置密码和是否授权匿名用户访问,按照要求设置即可。
2.5.2、Nexus3 功能介绍
Nexus3 的核心功能就是项目构件仓库(Repository),其他的很多功能都是围绕着仓库功能进行配套使用。在 UI 界面直接操作理解更简单,有疑问再查官方文档,可以事半功倍。
2.5.2.1、仓库类型
-
代理仓库(Proxy Repository)
带有 proxy 属性的仓库就是代理仓库,是用来连接远程仓库的。向 Nexus3 发起的项目构件请求都会先进行本地构件校验,如果存在则直接返回;如果没有则向远程代理仓库请求,请求返回的组件会存储在本地,相当于缓存一样。因此,消除了再次从远程仓库检索组件的网络带宽和时间开销。
Nexus3 当前默认有下面两个远程代理仓库:
maven-central:Apache Maven 内置的默认组件库,并得到了其他构建工具(如 Gradle、SBT 或 Ant/Ivy)的良好支持。访问地址:https://repo1.maven.org/maven2。
nuget.org-proxy:它是
.net
开发中使用的 nuget 包管理工具所使用的默认组件库。访问地址:https://www.nuget.org/。 -
托管仓库
带有 hosted 属性的仓库就是托管仓库,作为内部组件的权威仓库。
Nexus3 当前默认有下面几个权威内部组件仓库:
maven-releases:用来发布 maven2 格式的正式版 jar 包,可以用作内部正式版本,也可以用作外部仓库没有的第三方组件,所以通过代理仓库不可获取,但是可以用作专利库等商业用途。
maven-snapshots:用来发布 maven2 格式的开发版 jar 包,相当于快照。
nuget-hosted:用来发布 nuget 格式的正式版 jar 包。
-
仓库组
带有 group 属性的仓库就是仓库组,代表 Nexus3 的强大特性,它可以让你将其他的多个仓库或者多个仓库组集成在一个仓库当中。这意味着你的用户可以依赖一个 URL 来满足他们的配置需求,而管理员可以向仓库组添加更多的仓库和组件。
Nexus3 当前默认有下面几个仓库组:
maven-public:Maven-public 组是一个 maven2 格式的仓库组,集成了远程的 Maven 官方中央仓库、maven-releases 内部权威仓库以及 maven-snapshots 仓库。可以通过一个简单仓库组的 url 来发布集成的多样式仓库中的组件。
nuget-group:nuget-group 仓库组集成了 nuget 格式的 nuget-hosted 仓库和 nuget.org-proxy 代理仓库,用作
.net
开发。
2.5.2.2、Blob 存储
blob 存储是一个内部存储机制,用来存储二进制组件及其附件,可以基于本地文件系统或者 Amazon S3 云存储等,一个 blob 存储可以被多个仓库或者仓库组使用。
2.5.3、Nexus3 使用
2.5.3.1、创建 role 角色
在项目开发中,特别是多个项目交叉开发时,代码有多个分支,人员也会交替开发。这就会带来权限干扰的问题,所以通过 role 角色控制项目人员的权限是很有必要的。
菜单栏 Server administration and configuration --> Security --> Roles --> Create role --> Nexus role,根据要求创建 role。
2.5.3.2、创建用户
创建好 role 角色后可以创建用户了,将用户与具有权限集的角色绑定后,用户就拥有该权限集。下面创建一个 manager 项目经理用户,将 nx-manager 角色与 manager 用户进行绑定。
菜单栏 Server administration and configuration --> Security --> Users --> Create local user,根据要求填写用户信息,并根据预先设计思路绑定用户角色。
2.5.3.3、新用户创建仓库
-
刚才创建角色的时候有一个权限没有加上去,把
nx-repository-admin-*-*-add
加到 nx-manager 角色上去,不然绑定该角色的用户不能创建仓库。 -
使用 manager 用户登陆 UI --> 菜单栏 Server administration and configuration --> Repositories --> Create repository --> maven2(hosted),根据要求填写必要信息。
-
切换到 admin 用户,将
nx-repository-admin-maven2-grepo-*
nx-repository-view-maven2-*-*
nx-component-upload
权限赋给nx-manager
角色,然后切回 manager 用户。- 第一个权限是将新仓库 grepo 的自身配置权交给 nx-manager 角色。
- 第二个权限是将 maven2 格式的所有仓库中的资产控制权交给 nx-manager 角色,目的是可以把 grepo 仓库加入到 maven-public 组当中。
- 第三个权限是允许上传文件到 grepo 仓库中。
-
上传一个测试文件到 grepo 仓库。
菜单栏 Browse server contents --> Upload --> grepo,填写必要信息,点击 Upload 即可上传。
-
查看上传的文件。
关于 maven 和 nexus3 就介绍到这里,上项目还会有具体需求,不过问题不大。需要说明的是,nexus3 用户权限管理我这里梳理的一个思路,实际上是我一个权限一个权限试出来的,即便官方文档也没有具体说明,思路很重要,至于精细化管理,多试几次就行了。