maven基础知识

maven基础知识,第1张

maven基础

Maven是一个Java项目管理和构建工具,它可以定义项目结构、项目依赖,并使用统一的方式进行自动化构建,是Java项目不可缺少的工具。

1、maven介绍

在了解Maven之前,我们先来看看一个Java项目需要的东西。首先,我们需要确定引入哪些依赖包。例如,如果我们需要用到commons logging,我们就必须把commons logging的jar包放入classpath。如果我们还需要log4j,就需要把log4j相关的jar包都放到classpath中。这些就是依赖包的管理。

其次,我们要确定项目的目录结构。例如,src目录存放Java源码,resources目录存放配置文件,bin目录存放编译生成的.class文件。

此外,我们还需要配置环境,例如JDK的版本,编译打包的流程,当前代码的版本号。

最后,除了使用Eclipse这样的IDE进行编译外,我们还必须能通过命令行工具进行编译,才能够让项目在一个独立的服务器上编译、测试、部署。

这些工作难度不大,但是非常琐碎且耗时。如果每一个项目都自己搞一套配置,肯定会一团糟。我们需要的是一个标准化的Java项目管理和构建工具。

Maven就是专门为Java项目打造的管理和构建工具,它的主要功能有:

  • 提供了一套标准化的项目结构;
  • 提供了一套标准化的构建流程(编译,测试,打包,发布……);
  • 提供了一套依赖管理机制。
1.1 maven项目结构

一个使用Maven管理的普通的Java项目,它的目录结构默认如下:

maven-project
├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   └── resources
│   └── test
│       ├── java
│       └── resources
└── target

项目的根目录maven-project是项目名,它有一个项目描述文件pom.xml,存放Java源码的目录是src/main/java,存放资源文件的目录是src/main/resources,存放测试源码的目录是src/test/java,存放测试资源的目录是src/test/resources,最后,所有编译、打包生成的文件都放在target目录里。这些就是一个Maven项目的标准目录结构。

所有的目录结构都是约定好的标准结构,我们千万不要随意修改目录结构。使用标准结构不需要做任何配置,Maven就可以正常使用。

我们再来看最关键的一个项目描述文件pom.xml,它的内容长得像下面:

<project ...>
    <modelVersion>4.0.0modelVersion>
    <groupId>com.itranswarp.learnjavagroupId>
    <artifactId>helloartifactId>
    <version>1.0version>
    <packaging>jarpackaging>
    <properties>
        ...
    properties>
    <dependencies>
        <dependency>
            <groupId>commons-logginggroupId>
            <artifactId>commons-loggingartifactId>
            <version>1.2version>
        dependency>
    dependencies>
project>

其中,groupId类似于Java的包名,通常是公司或组织名称,artifactId类似于Java的类名,通常是项目名称,再加上version,一个Maven工程就是由groupIdartifactIdversion作为唯一标识。我们在引用其他第三方库的时候,也是通过这3个变量确定。例如,依赖commons-logging

<dependency>
    <groupId>commons-logginggroupId>
    <artifactId>commons-loggingartifactId>
    <version>1.2version>
dependency>

使用声明一个依赖后,Maven就会自动下载这个依赖包并把它放到classpath中。

1.2 安装maven

要安装Maven,可以从Maven官网下载最新的Maven 3.8.x,然后在本地解压,设置几个环境变量:

M2_HOME=/path/to/maven-3.8.x
PATH=$PATH:$M2_HOME/bin

Windows可以把%M2_HOME%\bin添加到系统Path变量中。

然后,打开命令行窗口,输入mvn -version,应该看到Maven的版本信息:

┌────────────────────────────────────────────────────────┐
│Command Prompt                                    - □ x │
├────────────────────────────────────────────────────────┤
│Microsoft Windows [Version 10.0.0]                      │
│(c) 2015 Microsoft Corporation. All rights reserved.    │
│                                                        │
│C:\> mvn -version                                       │
│Apache Maven 3.6.0 (97c98ec64a1fdfee7767ce5ffb20918...) │
│Maven home: C:\Users\liaoxuefeng\maven                  │
│Java version: ...                                       │
│...                                                     │
│C:\> _                                                  │
│                                                        │
│                                                        │
└────────────────────────────────────────────────────────┘

如果提示命令未找到,说明系统PATH路径有误,需要修复后再运行。

1.3 小结

Maven是一个Java项目的管理和构建工具:

  • Maven使用pom.xml定义项目内容,并使用预设的目录结构;
  • 在Maven中声明一个依赖项可以自动下载并导入classpath;
  • Maven使用groupIdartifactIdversion唯一定位一个依赖。
2、依赖管理

如果我们的项目依赖第三方的jar包,例如commons logging,那么问题来了:commons logging发布的jar包在哪下载?

如果我们还希望依赖log4j,那么使用log4j需要哪些jar包?

类似的依赖还包括:JUnit,JavaMail,MySQL驱动等等,一个可行的方法是通过搜索引擎搜索到项目的官网,然后手动下载zip包,解压,放入classpath。但是,这个过程非常繁琐。

Maven解决了依赖管理问题。例如,我们的项目依赖abc这个jar包,而abc又依赖xyz这个jar包:

┌──────────────┐
│Sample Project│
└──────────────┘
        │
        ▼
┌──────────────┐
│     abc      │
└──────────────┘
        │
        ▼
┌──────────────┐
│     xyz      │
└──────────────┘

当我们声明了abc的依赖时,Maven自动把abcxyz都加入了我们的项目依赖,不需要我们自己去研究abc是否需要依赖xyz

因此,Maven的第一个作用就是解决依赖管理。我们声明了自己的项目需要abc,Maven会自动导入abc的jar包,再判断出abc需要xyz,又会自动导入xyz的jar包,这样,最终我们的项目会依赖abcxyz两个jar包。

我们来看一个复杂依赖示例:

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-webartifactId>
    <version>1.4.2.RELEASEversion>
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.1 依赖关系

Maven定义了几种依赖关系,分别是compiletestruntimeprovided

scope说明示例
compile编译时需要用到该jar包(默认)commons-logging
test编译Test时需要用到该jar包junit
runtime编译时不需要,但运行时需要用到mysql
provided编译时需要用到,但运行时由JDK或某个服务器提供servlet-api

其中,默认的compile是最常用的,Maven会把这种类型的依赖直接放入classpath。

test依赖表示仅在测试时使用,正常运行时并不需要,java包下的代码引用scope为test的jar包是不行的。最常用的test依赖就是JUnit:

<dependency>
    <groupId>org.junit.jupitergroupId>
    <artifactId>junit-jupiter-apiartifactId>
    <version>5.3.2version>
    <scope>testscope>
dependency>

runtime依赖表示编译时不需要,但运行时需要。最典型的runtime依赖是JDBC驱动,例如MySQL驱动:

<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
    <version>5.1.48version>
    <scope>runtimescope>
dependency>

provided依赖表示编译时需要,但运行时不需要。最典型的provided依赖是Servlet API,编译的时候需要,但是运行时,Servlet服务器内置了相关的jar,所以运行期不需要:

<dependency>
    <groupId>javax.servletgroupId>
    <artifactId>javax.servlet-apiartifactId>
    <version>4.0.0version>
    <scope>providedscope>
dependency>

最后一个问题是,Maven如何知道从何处下载所需的依赖?也就是相关的jar包?答案是Maven维护了一个中央仓库(repo1.maven.org),所有第三方库将自身的jar以及相关信息上传至中央仓库,Maven就可以从中央仓库把所需依赖下载到本地。

Maven并不会每次都从中央仓库下载jar包。一个jar包一旦被下载过,就会被Maven自动缓存在本地目录(用户主目录的.m2目录或者maven的setting文件中指定的本地仓库地址),所以,除了第一次编译时因为下载需要时间会比较慢,后续过程因为有本地缓存,并不会重复下载相同的jar包。

2.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.3 maven镜像

除了可以从Maven的中央仓库下载外,还可以从Maven的镜像仓库下载。如果访问Maven的中央仓库非常慢,我们可以选择一个速度较快的Maven的镜像仓库。Maven镜像仓库定期从中央仓库同步:

           slow    ┌───────────────────┐
    ┌─────────────>│Maven Central Repo.│
    │              └───────────────────┘
    │                        │
    │                        │sync
    │                        ▼
┌───────┐  fast    ┌───────────────────┐
│ User  │─────────>│Maven Mirror Repo. │
└───────┘          └───────────────────┘

中国区用户可以使用阿里云提供的Maven镜像仓库。使用Maven镜像仓库需要一个配置,在用户主目录下进入.m2目录,创建一个settings.xml配置文件,内容如下:(配置全局的maven的setting文件也可以,推荐这种)

<settings>
    <mirrors>
        <mirror>
            <id>aliyunid>
            <name>aliyunname>
            <mirrorOf>centralmirrorOf>
            
            <url>https://maven.aliyun.com/repository/centralurl>
        mirror>
    mirrors>
settings>

配置镜像仓库后,Maven的下载速度就会非常快。

2.4 搜索第三方组件

如果我们要引用一个第三方组件,比如okhttp,如何确切地获得它的groupIdartifactIdversion?方法是通过https://mvnrepository.com/搜索关键字,找到对应的组件后,直接复制:


<dependency>
    <groupId>com.squareup.okhttp3groupId>
    <artifactId>okhttpartifactId>
    <version>5.0.0-alpha.6version>
dependency>

也可以通过阿里云的仓库搜索地址进行搜索阿里云仓库地址。

2.5 命令行编译

在命令中,进入到pom.xml所在目录,输入以下命令:

$ mvn clean package

如果一切顺利,即可在target目录下获得编译后自动打包的jar。

2.6 小结

Maven通过解析依赖关系确定项目所需的jar包,常用的4种scope有:compile(默认),testruntimeprovided

Maven从中央仓库下载所需的jar包并缓存在本地;

可以通过镜像仓库加速下载。

3、构建流程 3.1 构建流程

Maven不但有标准化的项目结构,而且还有一套标准化的构建流程,可以自动化实现编译,打包,发布,等等。

3.2 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:打包
3.3 Goal

执行一个phase又会触发一个或多个goal:

执行的Phase对应执行的Goal
compilecompiler:compile
testcompiler: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
3.4 小结

Maven通过lifecycle、phase和goal来提供标准的构建流程。

最常用的构建命令是指定phase,然后让Maven执行到指定的phase:

  • mvn clean
  • mvn clean compile
  • mvn clean test
  • mvn clean package

通常情况,我们总是执行phase默认绑定的goal,因此不必指定goal。

4、使用插件

我们在前面介绍了Maven的lifecycle,phase和goal:使用Maven构建项目就是执行lifecycle,执行到指定的phase为止。每个phase会执行自己默认的一个或多个goal。goal是最小任务单元。

我们以compile这个phase为例,如果执行:

mvn compile

Maven将执行compile这个phase,这个phase会调用compiler插件执行关联的compiler:compile这个goal。

实际上,执行每个phase,都是通过某个插件(plugin)来执行的,Maven本身其实并不知道如何执行compile,它只是负责找到对应的compiler插件,然后执行默认的compiler:compile这个goal来完成编译。

所以,使用Maven,实际上就是配置好需要使用的插件,然后通过phase调用它们。

Maven已经内置了一些常用的标准插件:

插件名称对应执行的phase
cleanclean
compilercompile
surefiretest
jarpackage

如果标准插件无法满足需求,我们还可以使用自定义插件。使用自定义插件的时候,需要声明。例如,使用maven-shade-plugin可以创建一个可执行的jar,要使用这个插件,需要在pom.xml中声明它:

<project>
    ...
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.pluginsgroupId>
				<artifactId>maven-shade-pluginartifactId>
                <version>3.2.1version>
				<executions>
					<execution>
						<phase>packagephase>
						<goals>
							<goal>shadegoal>
						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.MainmainClass>
        transformer>
    transformers>
configuration>

注意,Maven自带的标准插件例如compiler是无需声明的,只有引入其它的插件才需要声明。

下面列举了一些常用的插件:

  • maven-shade-plugin:打包所有依赖包并生成可执行jar;
  • cobertura-maven-plugin:生成单元测试覆盖率报告;
  • findbugs-maven-plugin:对Java源码进行静态分析以找出潜在问题。
4.1 小结

Maven通过自定义插件可以执行项目构建时需要的额外功能,使用自定义插件必须在pom.xml中声明插件及配置;

插件会在某个phase被执行时执行;

插件的配置和用法需参考插件的官方文档。

5、模块管理

在软件开发中,把一个大项目分拆为多个模块是降低软件复杂度的有效方法:

                        ┌ ─ ─ ─ ─ ─ ─ ┐
                          ┌─────────┐
                        │ │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.0modelVersion>

    <groupId>com.itranswarp.learnjavagroupId>
    <artifactId>module-aartifactId>
    <version>1.0version>
    <packaging>jarpackaging>

    <name>module-aname>

    <properties>
        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
        <maven.compiler.source>11maven.compiler.source>
        <maven.compiler.target>11maven.compiler.target>
        <java.version>11java.version>
    properties>

    <dependencies>
        <dependency>
            <groupId>org.slf4jgroupId>
            <artifactId>slf4j-apiartifactId>
            <version>1.7.28version>
        dependency>
        <dependency>
            <groupId>ch.qos.logbackgroupId>
            <artifactId>logback-classicartifactId>
            <version>1.2.3version>
            <scope>runtimescope>
        dependency>
        <dependency>
            <groupId>org.junit.jupitergroupId>
            <artifactId>junit-jupiter-engineartifactId>
            <version>5.5.2version>
            <scope>testscope>
        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.0modelVersion>

    <groupId>com.itranswarp.learnjavagroupId>
    <artifactId>module-bartifactId>
    <version>1.0version>
    <packaging>jarpackaging>

    <name>module-bname>

    <properties>
        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
        <maven.compiler.source>11maven.compiler.source>
        <maven.compiler.target>11maven.compiler.target>
        <java.version>11java.version>
    properties>

    <dependencies>
        <dependency>
            <groupId>org.slf4jgroupId>
            <artifactId>slf4j-apiartifactId>
            <version>1.7.28version>
        dependency>
        <dependency>
            <groupId>ch.qos.logbackgroupId>
            <artifactId>logback-classicartifactId>
            <version>1.2.3version>
            <scope>runtimescope>
        dependency>
        <dependency>
            <groupId>org.junit.jupitergroupId>
            <artifactId>junit-jupiter-engineartifactId>
            <version>5.5.2version>
            <scope>testscope>
        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.0modelVersion>

    <groupId>com.itranswarp.learnjavagroupId>
    <artifactId>parentartifactId>
    <version>1.0version>
    <packaging>pompackaging>

    <name>parentname>

    <properties>
        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
        <maven.compiler.source>11maven.compiler.source>
        <maven.compiler.target>11maven.compiler.target>
        <java.version>11java.version>
    properties>

    <dependencies>
        <dependency>
            <groupId>org.slf4jgroupId>
            <artifactId>slf4j-apiartifactId>
            <version>1.7.28version>
        dependency>
        <dependency>
            <groupId>ch.qos.logbackgroupId>
            <artifactId>logback-classicartifactId>
            <version>1.2.3version>
            <scope>runtimescope>
        dependency>
        <dependency>
            <groupId>org.junit.jupitergroupId>
            <artifactId>junit-jupiter-engineartifactId>
            <version>5.5.2version>
            <scope>testscope>
        dependency>
    dependencies>
project>

注意到parent的pom而不是jar,因为parent本身不含任何Java代码。编写parentpom.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.0modelVersion>

    <parent>
        <groupId>com.itranswarp.learnjavagroupId>
        <artifactId>parentartifactId>
        <version>1.0version>
        <relativePath>../parent/pom.xmlrelativePath>
    parent>

    <artifactId>module-aartifactId>
    <packaging>jarpackaging>
    <name>module-aname>
project>

模块B、模块C都可以直接从parent继承,大幅简化了pom.xml的编写。

如果模块A依赖模块B,则模块A需要模块B的jar包才能正常编译,我们需要在模块A中引入模块B:

 ...
    <dependencies>
        <dependency>
            <groupId>com.itranswarp.learnjavagroupId>
            <artifactId>module-bartifactId>
            <version>1.0version>
        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.0modelVersion>
    <groupId>com.itranswarp.learnjavagroupId>
    <artifactId>buildartifactId>
    <version>1.0version>
    <packaging>pompackaging>
    <name>buildname>

    <modules>
        <module>parentmodule>
        <module>module-amodule>
        <module>module-bmodule>
        <module>module-cmodule>
    modules>
project>

这样,在根目录执行mvn clean package时,Maven根据根目录的pom.xml找到包括parent在内的共4个,一次性全部编译。也可以把parent下的pom.xml提到根目录下,由根目录的pom.xml进行管理。

5.1 中央仓库

我们使用的大多数第三方模块都是这个用法,例如,我们使用commons logging、log4j这些第三方模块,就是第三方模块的开发者自己把编译好的jar包发布到Maven的中央仓库中。

5.2 私有仓库

私有仓库是指公司内部如果不希望把源码和jar包放到公网上,那么可以搭建私有仓库。私有仓库总是在公司内部使用,它只需要在本地的settings.xml中配置好,使用方式和中央仓位没有任何区别。

5.3 本地仓库

本地仓库是指把本地开发的项目“发布”在本地,这样其他项目可以通过本地仓库引用它。但是我们不推荐把自己的模块安装到Maven的本地仓库,因为每次修改某个模块的源码,都需要重新安装,非常容易出现版本不一致的情况。更好的方法是使用模块化编译,在编译的时候,告诉Maven几个模块之间存在依赖关系,需要一块编译,Maven就会自动按依赖顺序编译这些模块。

5.4 小结

Maven支持模块化管理,可以把一个大项目拆成几个模块:

  • 可以通过继承在parent的pom.xml统一定义重复配置;
  • 可以通过编译多个模块。
6、使用mvnw

mvnw是Maven Wrapper的缩写。因为我们安装Maven时,默认情况下,系统所有项目都会使用全局安装的这个Maven版本。但是,对于某些项目来说,它可能必须使用某个特定的Maven版本,这个时候,就可以使用Maven Wrapper,它可以负责给这个特定的项目安装指定版本的Maven,而其他项目不受影响。

简单地说,Maven Wrapper就是给一个项目提供一个独立的,指定版本的Maven给它使用。

6.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

发现多了mvnwmvnw.cmd.mvn目录,我们只需要把mvn命令改成mvnw就可以使用跟项目关联的Maven。例如:

mvnw clean package

在Linux或macOS下运行时需要加上./

./mvnw clean package

Maven Wrapper的另一个作用是把项目的mvnwmvnw.cmd.mvn提交到版本库中,可以使所有开发人员使用统一的Maven版本。

6.2 小结

使用Maven Wrapper,可以为一个项目指定特定的Maven版本。

7、发布Artifact

当我们使用commons-logging这些第三方开源库的时候,我们实际上是通过Maven自动下载它的jar包,并根据其pom.xml解析依赖,自动把相关依赖包都下载后加入到classpath。

那么问题来了:当我们自己写了一个牛逼的开源库时,非常希望别人也能使用,总不能直接放个jar包的链接让别人下载吧?

如果我们把自己的开源库放到Maven的repo中,那么,别人只需按标准引用groupId:artifactId:version,即可自动下载jar包以及相关依赖。因此,本节我们介绍如何发布一个库到Maven的repo中。

把自己的库发布到Maven的repo中有好几种方法,我们介绍3种最常用的方法。

7.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-repo        <-- Maven本地文件仓库
├── pom.xml           <-- 项目文件
├── src
│   ├── main
│   │   ├── java      <-- 源码目录
│   │   └── resources <-- 资源目录
│   └── test
│       ├── java      <-- 测试源码目录
│       └── resources <-- 测试资源目录
└── target            <-- 编译输出目录

pom.xml中添加如下内容:

<project ...>
    ...
    <distributionManagement>
        <repository>
            <id>local-repo-releaseid>
            <name>GitHub Releasename>
            <url>file://${project.basedir}/maven-repourl>
        repository>
    distributionManagement>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-source-pluginartifactId>
                <executions>
                    <execution>
                        <id>attach-sourcesid>
                        <phase>packagephase>
                        <goals>
                            <goal>jar-no-forkgoal>
                        goals>
                    execution>
                executions>
            plugin>
            <plugin>
                <artifactId>maven-javadoc-pluginartifactId>
                <executions>
                    <execution>
                        <id>attach-javadocsid>
                        <phase>packagephase>
                        <goals>
                            <goal>jargoal>
                        goals>
                    execution>
                executions>
            plugin>
        plugins>
    build>
project>

注意到,它指示了发布的软件包的位置,这里的是项目根目录下的maven-repo目录,在中定义的两个插件maven-source-pluginmaven-javadoc-plugin分别用来创建源码和javadoc,如果不想发布源码,可以把对应的插件去掉。

我们直接在项目根目录下运行Maven命令mvn clean package deploy,如果一切顺利,我们就可以在maven-repo目录下找到部署后的所有文件如下:

maven-repo
└── com
    └── itranswarp
        └── rich
            └── how-to-become-rich
                ├── 1.0.0
                │   ├── how-to-become-rich-1.0.0-javadoc.jar
                │   ├── how-to-become-rich-1.0.0-javadoc.jar.md5
                │   ├── how-to-become-rich-1.0.0-javadoc.jar.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
                │   ├── 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
                ├── maven-metadata.xml
                ├── maven-metadata.xml.md5
                └── maven-metadata.xml.sha1

最后一步,是把这个工程推到GitHub上,并选择Settings-GitHub Pages,选择master branch启用Pages服务:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TH4FprxY-1650692264462)(images/maven基础/github.PNG)]

这样,把全部内容推送至GitHub后,即可作为静态网站访问Maven的repo,它的地址是https://michaelliao.github.io/how-to-become-rich/maven-repo/。版本1.0.0对应的jar包地址是:

https://michaelliao.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.richgroupId>
    <artifactId>how-to-become-richartifactId>
    <version>1.0.0version>
dependency>

但是,除了正常导入依赖外,对方还需要再添加一个的声明,即使用方完整的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.0modelVersion>

    <groupId>examplegroupId>
    <artifactId>how-to-become-rich-usageartifactId>
    <version>1.0-SNAPSHOTversion>
    <packaging>jarpackaging>

    <properties>
        <maven.compiler.source>11maven.compiler.source>
        <maven.compiler.target>11maven.compiler.target>
        <java.version>11java.version>
    properties>

    <repositories>
        <repository>
            <id>github-rich-repoid>
            <name>The Maven Repository on Githubname>
            <url>https://michaelliao.github.io/how-to-become-rich/maven-repo/url>
        repository>
    repositories>

    <dependencies>
        <dependency>
            <groupId>com.itranswarp.richgroupId>
            <artifactId>how-to-become-richartifactId>
            <version>1.0.0version>
        dependency>
    dependencies>
project>

中,我们必须声明发布的Maven的repo地址,其中可以任意填写,填入GitHub Pages提供的地址+/maven-repo/后缀。现在,即可正常引用这个库并编写代码如下:

Millionaire millionaire = new Millionaire();
System.out.println(millionaire.howToBecomeRich());

为什么使用commons-logging等第三方库时,并不需要声明repo地址?这是因为这些库都是发布到Maven中央仓库的,发布到中央仓库后,不需要告诉Maven仓库地址,因为它知道中央仓库的地址默认是Central Repository: ,也可以通过~/.m2/settings.xml指定一个代理仓库地址以替代中央仓库来提高速度(参考依赖管理的Maven镜像)。

因为GitHub Pages并不会把我们发布的Maven包同步到中央仓库,所以自然使用方必须手动添加一个我们提供的仓库地址。

此外,通过GitHub Pages发布Maven repo时需要注意一点,即不要改动已发布的版本。因为Maven的仓库是不允许修改任何版本的,对一个库进行修改的唯一方法是发布一个新版本。但是通过静态文件的方式发布repo,实际上我们是可以修改jar文件的,但最好遵守规范,不要修改已发布版本。

7.2 通过Nexus发布到中央仓库

能不能把自己的开源库发布到Maven的中央仓库,这样用户就不需要声明repo地址,可以直接引用,显得更专业。

当然可以,但我们不能直接发布到Maven中央仓库,而是通过曲线救国的方式,发布到central.sonatype.org,它会定期自动同步到Maven的中央仓库。Nexus是一个支持Maven仓库的软件,由Sonatype开发,有免费版和专业版两个版本,很多大公司内部都使用Nexus作为自己的私有Maven仓库,而这个central.sonatype.org相当于面向开源的一个Nexus公共服务。

所以,第一步是在central.sonatype.org上注册一个账号,注册链接非常隐蔽,可以自己先找找,找半小时没找到点这里查看攻略。

如果注册顺利并审核通过,会得到一个登录账号,然后,通过这个页面一步一步 *** 作就可以成功地将自己的Artifact发布到Nexus上,再耐心等待几个小时后,你的Artifact就会出现在Maven的中央仓库中。

这里简单提一下发布重点与难点:

  • 必须正确创建GPG签名,Linux和Mac下推荐使用gnupg2;
  • 必须在~/.m2/settings.xml中配置好登录用户名和口令,以及GPG口令:
<settings ...>
    ...
    <servers>
        <server>
            <id>ossrhid>
            <username>OSSRH-USERNAMEusername>
            <password>OSSRH-PASSWORDpassword>
        server>
    servers>
    <profiles>
        <profile>
            <id>ossrhid>
            <activation>
                <activeByDefault>trueactiveByDefault>
            activation>
            <properties>
                <gpg.executable>gpg2gpg.executable>
                <gpg.passphrase>GPG-PASSWORDgpg.passphrase>
            properties>
        profile>
    profiles>
settings>

在待发布的Artifact的pom.xml中添加OSS的Maven repo地址,以及maven-jar-pluginmaven-source-pluginmaven-javadoc-pluginmaven-gpg-pluginnexus-staging-maven-plugin

<project ...>
    ...
    <distributionManagement>
        <snapshotRepository>
            <id>ossrhid>
            <url>https://oss.sonatype.org/content/repositories/snapshotsurl>
        snapshotRepository>

        <repository>
            <id>ossrhid>
            <name>Nexus Release Repositoryname>
            <url>http://oss.sonatype.org/service/local/staging/deploy/maven2/url>
        repository>
    distributionManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-jar-pluginartifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>jargoal>
                            <goal>test-jargoal>
                        goals>
                    execution>
                executions>
            plugin>
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-source-pluginartifactId>
                <executions>
                    <execution>
                        <id>attach-sourcesid>
                        <goals>
                            <goal>jar-no-forkgoal>
                        goals>
                    execution>
                executions>
            plugin>
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-javadoc-pluginartifactId>
                <executions>
                    <execution>
                        <id>attach-javadocsid>
                        <goals>
                            <goal>jargoal>
                        goals>
                        <configuration>
                            <additionalOption>
                                <additionalOption>-Xdoclint:noneadditionalOption>
                            additionalOption>
                        configuration>
                    execution>
                executions>
            plugin>
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-gpg-pluginartifactId>
                <executions>
                    <execution>
                        <id>sign-artifactsid>
                        <phase>verifyphase>
                        <goals>
                            <goal>signgoal>
                        goals>
                    execution>
                executions>
            plugin>
            <plugin>
                <groupId>org.sonatype.pluginsgroupId>
                <artifactId>nexus-staging-maven-pluginartifactId>
                <version>1.6.3version>
                <extensions>trueextensions>
                <configuration>
                    <serverId>ossrhserverId>
                    <nexusUrl>https://oss.sonatype.org/nexusUrl>
                    <autoReleaseAfterClose>trueautoReleaseAfterClose>
                configuration>
            plugin>
        plugins>
    build>
project>

最后执行命令mvn clean package deploy即可发布至central.sonatype.org。

此方法前期需要复杂的申请账号和项目的流程,后期需要安装调试GPG,但只要跑通流程,后续发布都只需要一行命令。

7.3 发布到私有仓库

通过nexus-staging-maven-plugin除了可以发布到central.sonatype.org外,也可以发布到私有仓库,例如,公司内部自己搭建的Nexus服务器。

如果没有私有Nexus服务器,还可以发布到GitHub Packages。GitHub Packages是GitHub提供的仓库服务,支持Maven、NPM、Docker等。使用GitHub Packages时,无论是发布Artifact,还是引用已发布的Artifact,都需要明确的授权Token,因此,GitHub Packages只能作为私有仓库使用。

在发布前,我们必须首先登录后在用户的Settings-Developer settings-Personal access tokens中创建两个Token,一个用于发布,一个用于使用。发布Artifact的Token必须有repowrite:packagesread:packages权限:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xSN3Fvc0-1650692264466)(images/maven基础/github1.PNG)]

使用Artifact的Token只需要read:packages权限。

在发布端,把GitHub的用户名和发布Token写入~/.m2/settings.xml配置中:

<settings ...>
    ...
    <servers>
        <server>
            <id>github-releaseid>
            <username>GITHUB-USERNAMEusername>
            <password>f052...c21fpassword>
        server>
    servers>
settings>

然后,在需要发布的Artifact的pom.xml中,添加一个声明:

<project ...>
    ...
    <distributionManagement>
        <repository>
            <id>github-releaseid>
            <name>GitHub Releasename>
            <url>https://maven.pkg.github.com/michaelliao/complexurl>
        repository>
    distributionManagement>
project>

注意到~/.m2/settings.xml配置中的要保持一致,因为发布时Maven根据id找到用于登录的用户名和Token,才能成功上传文件到GitHub。我们直接通过命令mvn clean package deploy部署,成功后,在GitHub用户页面可以看到该Artifact:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dEZ468cy-1650692264467)(images/maven基础/github2.PNG)]

完整的配置请参考complex项目,这是一个非常简单的支持复数运算的库。

使用该Artifact时,因为GitHub的Package只能作为私有仓库使用,所以除了在使用方的pom.xml中声明外:

<project ...>
    ...
    <repositories>
        <repository>
            <id>github-releaseid>
            <name>GitHub Releasename>
            <url>https://maven.pkg.github.com/michaelliao/complexurl>
        repository>
    repositories>

    <dependencies>
        <dependency>
            <groupId>com.itranswarpgroupId>
            <artifactId>complexartifactId>
            <version>1.0.0version>
        dependency>
    dependencies>
    ...
project>

还需要把有读权限的Token配置到~/.m2/settings.xml文件中。

7.4 小结

使用Maven发布一个Artifact时:

  • 可以发布到本地,然后推送到远程Git库,由静态服务器提供基于网页的repo服务,使用方必须声明repo地址;
  • 可以发布到central.sonatype.org,并自动同步到Maven中央仓库,需要前期申请账号以及本地配置;
  • 可以发布到GitHub Packages作为私有仓库使用,必须提供Token以及正确的权限才能发布和使用。



注意到``和`~/.m2/settings.xml`配置中的``要保持一致,因为发布时Maven根据id找到用于登录的用户名和Token,才能成功上传文件到GitHub。我们直接通过命令`mvn clean package deploy`部署,成功后,在GitHub用户页面可以看到该Artifact:

[外链图片转存中...(img-dEZ468cy-1650692264467)]

完整的配置请参考[complex](https://github.com/michaelliao/complex/)项目,这是一个非常简单的支持复数运算的库。

使用该Artifact时,因为GitHub的Package只能作为私有仓库使用,所以除了在使用方的`pom.xml`中声明``外:

```xml

    ...
    
        
            github-release
            GitHub Release
            https://maven.pkg.github.com/michaelliao/complex
        
    

    
        
            com.itranswarp
            complex
            1.0.0
        
    
    ...

还需要把有读权限的Token配置到~/.m2/settings.xml文件中。

7.4 小结

使用Maven发布一个Artifact时:

  • 可以发布到本地,然后推送到远程Git库,由静态服务器提供基于网页的repo服务,使用方必须声明repo地址;
  • 可以发布到central.sonatype.org,并自动同步到Maven中央仓库,需要前期申请账号以及本地配置;
  • 可以发布到GitHub Packages作为私有仓库使用,必须提供Token以及正确的权限才能发布和使用。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/724441.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-26
下一篇 2022-04-26

发表评论

登录后才能评论

评论列表(0条)

保存