Maven基础

1.Maven介绍

1.1 Maven是什么

概念

Maven是一个标准化的java项目管理和构建工具

主要功能

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

1.2 为什么需要Maven

Java项目通常由多个模块组成,每个模块都有许多依赖项。手动管理这些依赖项及其版本可能是非常困难和耗时的。而Maven可以自动解决这些问题,它可以帮助开发人员管理项目依赖关系,自动下载所需的库文件,并处理编译、测试和打包等操作。

使用Maven还有以下好处:

  1. 管理依赖关系:Maven可以自动下载并安装项目所需的所有库文件,并确保每个库文件的版本都是与其他依赖项兼容的。
  2. 构建项目:Maven提供了一种易于使用的方式来构建项目,开发人员只需要执行一条命令即可完成整个构建过程。
  3. 统一构建过程:Maven可以确保所有开发人员都遵循相同的构建流程,从而提高生产力和代码质量。
  4. 自动生成文档:Maven可以根据项目的注释自动生成文档,降低了文档编写的工作量。
  5. 支持多种IDE:Maven可以集成到多种IDE中,例如Eclipse、NetBeans和IntelliJ IDEA,方便开发人员使用。

1.2 Maven项目结构

a-maven-project
├── pom.xml   ---> 项目描述文件
├── src
│   ├── main
│   │   ├── java   ---> 存放Java源码
│   │   └── resources   ---> 存放资源文件
│   └── test
│       ├── java   ---> 存放测试源码
│       └── resources   ---> 存放测试资源
└── target   ---> 存放所有编译、打包、生成的文件

pom.xml

<project ...>
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.itranswarp.learnjava</groupId>
	<artifactId>hello</artifactId>
	<version>1.0</version>
	<packaging>jar</packaging>
	<properties>
        ...
	</properties>
	<dependencies>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>
	</dependencies>
</project>

groupId: 类似于Java的包名,通常是公司或组织的名称

artifactId:类似于Java的类名,通常是项目名称

一个Maven工程就是由groupIdartifactIdversion作为唯一标识。我们在引用其他第三方库的时候,也是通过 这三个变量确定的。例如,依赖commons-loggin

<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
</dependency>

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

2.依赖管理

依赖管理是Maven的一个重要功能。当我们的一个项目需要依赖另一个或多个jar包时,Maven会自动下载这些依赖包,并且如果这些包又依赖于其他的一些包,Maven也会自动进行解析并且下载。这极大方便了Java项目依赖第三方包时的操作。

2.1 依赖关系

Maven定义了四种依赖关系,分别是:compile,test,runtime,provided

scope description
compile 编译时需要用到该jar包(默认)
test 编译test时需要用到该jar包
runtime 编译时不需要用到,但运行时需要用到
provided 编译时需要用到,但运行时由JDK或某个服务器提供
// 示例
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.3.2</version>
    <scope>test</scope>
</dependency>

2.2 jar包从哪里获取

Maven维护了一个中央仓库,所有的第三方库将自身的jar和相关信息上传至中央仓库,这样Maven就可以从中央仓库根据提供的依赖信息来进行下载。

但是对于同一个依赖包,Maven不会每次都从中央仓库进行下载。一个jar包一旦被下载之后,就会被Maven自动缓存在本地目录中(用户目录下的.m2目录),下一次再使用时,只需要从本地目录导入即可。

2.3 镜像仓库

中国区用户可以使用阿里云提供的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>

3.构建流程

Maven不仅提供了一套标准的项目结构,同时也提供了一套标准的项目构建流程,可以自动实现编译、打包、发布等等。

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

3.1 lifecycle和phase

default

Maven的lifecycle(生命周期)是一套完整的操作流程,由一系列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生命周期,它会从validate一直运行到package这个phase为止。

clean

clean生命周期由3个phase构成

- pre-clean
- clean (注意这个clean不是lifecycle而是phase)
- post-clean

当我们执行mvn clean package,Maven会先执行clean生命周期并运行到clean这个phase,然后再执行default生命周期并运行到package这个phase。

在实际开发过程中,经常使用的命令有:

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.2 Goal

执行一个phase又会触发一个或多个goal,goal是最小的任务单元。也就是说,执行一个生命周期在底层就是在执行多个goal。通常情况,我们总是执行phase默认绑定的goal,因此不必指定goal。

执行的Phase 对应执行的Goal
compile compiler:compile
test compiler:testCompile surefire:test

goal的命名总是abc:xyz这种形式。

3.3 插件管理

Maven在执行phase时,都会通过对应的插件来执行关联的goal。Maven本身其实并不知道如何执行phase,它只是负责找到对应的插件,然后执行默认的goal来完成编译。

例如:Maven将执行compile这个phase,这个phase会调用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-shade-plugin:打包所有依赖包并生成可执行jar;
  • cobertura-maven-plugin:生成单元测试覆盖率报告;
  • findbugs-maven-plugin:对Java源码进行静态分析以找出潜在问题。

4.模块管理

在java软件开发过程中,对于一个大型的项目,通常会将其拆分成多个模块来进行开发,而通过Maven可以很方便的对这些模块进行管理。

例如:对于以下Maven工程来说,原来是一个大项目:

single-project
├── pom.xml
└── src

现在可以分拆成3个模块:每一个模块都可以当作一个独立的maven项目,都有各自独立的pom.xml。

mutiple-project
├── module-a
│   ├── pom.xml
│   └── src
├── module-b
│   ├── pom.xml
│   └── src
└── module-c
    ├── pom.xml
    └── src

但是,此时module-a中的pom.xml与b、c中的pom.xml存在相当程度的重复,因此,我们可以提取出共同的部分来作为parent,简化代码并提高复用性。

// 示例,将a,b,c中共用的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>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代码。编写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、模块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>,一次性全部编译。

5.mvnw

我们使用Maven时,基本上只会用到mvn这一个命令。有些童鞋可能听说过mvnw,这个是啥?

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

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

安装与使用

安装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版本。

posted @ 2023-04-07 15:41  Xiao·Tong  阅读(44)  评论(0编辑  收藏  举报