Maven的pom管理规则

文章目录

  • 学习目的
  • 内容
    • 1. 概述
    • 2. 依赖管理
    • 3. 构建流程
    • 4. 模块管理
    • 5. 思考

学习目的

工欲善其事,必先利其器。熟悉标准化的 Java 项目管理和构建工具原理,切勿知其已然,而不知其所以然,有助于今后处理jar包版本冲突,促进成长。

内容

1. 概述

  • 假如我们项目中用到 log4j,就需要把 log4j 相关的 jar 包都放到 classpath 中,既是依赖包的管理流程;
  • 用 IDE 进行编译好,我们还必须能通过命令行工具进行编译,才能够让项目在一个独立的服务器上编译、测试、部署,既是构建流程;
  • 这些工作难度不大,但是非常琐碎且耗时。如果每一个项目都自己搞一套配置,肯定会一团糟。我们需要的是一个标准化的 Java 项目管理和构建工具 —— Maven。

Maven主要功能:

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

一个基本的maven管理的 Java项目目录结构如下:

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

注: 目录结构都是约定好的标准结构,千万不要随意修改目录结构;项目描述文件 pom.xml ,通过 groupId,artifactId 和 version 可以定位 唯一jar包 坐标。

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

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

  1. Maven 使用 pom.xml 定义项目内容,并使用预设的目录结构;
  2. 在 Maven 中声明一个依赖项可以自动下载并导入 classpath;
  3. Maven 使用 groupId,artifactId 和 version 唯一定位一个依赖。

2. 依赖管理

原理说明
我们的项目依赖 abc 这个 jar 包,而 abc 又依赖 xyz 这个 jar 包:

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

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

看一个复杂依赖示例:

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

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

scope 说明 示例

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

test 依赖表示仅在测试时使用,正常运行时并不需要

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.3.2</version>
    <scope>test</scope>
</dependency>

runtime 依赖表示编译时不需要,但运行时需要, 经典依赖是 JDBC 驱动,例如 MySQL 驱动

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.48</version>
    <scope>runtime</scope>
</dependency>

最典型的 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 包。
  • 一个 jar 包一旦被下载过,就会被 Maven 自动缓存在本地目录(用户主目录的.m2 目录)
  • 除了第一次编译时因为下载需要时间会比较慢,后续过程因为有本地缓存,并不会重复下载相同的 jar 包

唯一jar 包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。

小结:

  1. Maven 通过解析依赖关系确定项目所需的 jar 包,常用的 4 种 scope 有:compile(默认),test,runtime 和 provided;
  2. Maven 从中央仓库下载所需的 jar 包并缓存在本地;

3. 构建流程

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 clean:清理所有生成的 class 和 jar;

  • mvn clean compile:先清理,再执行到 compile;

  • mvn clean test:先清理,再执行到 test,因为执行 test 前必须执行 compile,所以这里不必指定 compile;

  • mvn clean package:先清理,再执行到 package。

大多数 phase 在执行过程中,因为我们通常没有在 pom.xml 中配置相关的设置,所以这些 phase 什么事情都不做。

goal

执行一个 phase 又会触发一个或多个 goal,goal 的命名总是 abc:xyz 这种形式。

执行的 phase 对应执行的 goal
compile compiler:compile
test compiler:testCompile surefile:test

为了方便理解:

  • lifecycle 相当于 Java 的 package,它包含一个或多个 phase;

  • phase 相当于 Java 的 class, 它包含一个或多个 goal;

  • goal 相当于 class 的 method, 它其实才是真正干活的。

大多数情况,我们只要指定 phase,就默认执行这些 phase 默认绑定的 goal,只有少数情况,我们可以直接指定运行一个 goal,例如,

启动 Tomcat 服务器:mvn tomcat:run

小结:

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

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

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

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

4. 模块管理

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

single-project
├── pom.xml  
          

i. 拆分成3个模块

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

ii. 对于高度相似的的模块om.xml文件,可以提取出共同部分作为 parent

parent 的 是 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
                 iii. 例:如果模块 A 和模块 B 的 pom.xml 高度相似
  1. 我们可以提取出共同部分作为 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>
  1. 这样模块 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>
  1. 如果模块 A 依赖模块 B,则模块 A 需要模块 B 的 jar 包才能正常编译,我们需要在模块 A 中引入模块 B:
<dependencies>
<dependency> 
<groupId>com.itranswarp.learnjava</groupId>
<artifactId>module-b</artifactId>
<version>1.0</version> 
</dependency> 
</dependencies>
  1. 最后,在编译的时候,需要在根目录创建一个 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 个 ,一次性全部编译

小结:

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

  2. 可以通过继承在 parent 的 pom.xml 统一定义重复配置;
    可以通过 modules 编译多个模块。

5. 思考

Maven是如何加载的Jar包的呢?

Maven是怎么处理jar包冲突的呢?

答:我会在下期 Java classloader 加载源码解析说明。

你可能感兴趣的:(java,maven)