跳到主要内容

POM 详解

POM(Project Object Model,项目对象模型)是 Maven 的核心概念。每个 Maven 项目都有一个 pom.xml 文件,它定义了项目的基本信息、依赖关系、构建配置等内容。本章将深入讲解 POM 的各个方面。

POM 是什么?

POM 是一个 XML 文件,包含了 Maven 构建项目所需的所有配置信息。可以把 POM 理解为项目的"身份证"和"说明书",它告诉 Maven 这个项目是谁、需要什么、如何构建。

POM 采用声明式的方式来描述项目配置。与 Ant 的 build.xml 这种过程式配置不同,pom.xml 只是声明项目需要什么配置,具体的执行顺序由 Maven 的生命周期来控制。这意味着开发者只需要关注"配置什么",而不需要关心"如何执行"。

最简 POM

一个最简单的 POM 只需要包含三个元素:

<?xml version="1.0" encoding="UTF-8"?>
<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.example</groupId>
<artifactId>my-project</artifactId>
<version>1.0.0</version>

</project>

让我逐个解释这些元素:

  • modelVersion:POM 模型版本,目前固定为 4.0.0,这是 Maven 支持的唯一版本
  • groupId:组织或公司标识,用于区分不同组织
  • artifactId:项目标识,用于区分同一组织下的不同项目
  • version:项目版本号

Maven 坐标

Maven 使用坐标来唯一标识一个构件。坐标由三部分组成:groupId、artifactId、version。这三个元素组合起来形成了构件在全球 Maven 仓库中的唯一地址。

groupId(组织标识)

groupId 表示项目所属的组织或公司。命名规则通常采用域名的倒置形式,例如:

  • org.springframework:Spring 框架
  • org.apache.maven:Apache Maven
  • com.alibaba:阿里巴巴

在本地仓库中,groupId 中的点号会被转换为目录分隔符。例如 org.springframework 会存储为 org/springframework 目录。

artifactId(项目标识)

artifactId 是项目的名称,用于区分同一个 groupId 下的不同项目。命名通常使用小写字母和连字符,例如:

  • spring-core:Spring 核心模块
  • spring-webmvc:Spring Web MVC 模块
  • mybatis:MyBatis 框架

version(版本号)

版本号标识项目的具体版本。版本号的命名规范通常遵循语义化版本:

主版本.次版本.增量版本-里程碑版本

例如:
1.0.0
2.1.3-SNAPSHOT
3.0.0-RC1

版本号中的里程碑标识含义:

标识说明
SNAPSHOT开发中的快照版本,不稳定
ALPHA内部测试版本
BETA公开测试版本
RCRelease Candidate,候选发布版本
RELEASE 或 GA正式发布版本

坐标完整表示

完整的 Maven 坐标表示为:

groupId:artifactId:version[:packaging[:classifier]]

例如:
org.springframework:spring-core:5.3.23
org.apache.maven.plugins:maven-compiler-plugin:3.11.0:maven-plugin
org.junit.jupiter:junit-jupiter:5.9.0:jar:sources

packaging(打包类型)

packaging 元素定义项目的打包类型,决定了项目的构建方式和输出文件的类型。

<packaging>jar</packaging>

Maven 支持的打包类型:

类型说明输出文件
jar(默认)Java 应用程序.jar 文件
warWeb 应用程序.war 文件
pom父项目或聚合项目
ear企业应用程序.ear 文件
maven-pluginMaven 插件.jar 文件

不同的打包类型对应不同的生命周期绑定。例如,jar 打包会在 package 阶段绑定 jar:jar 目标,而 war 打包会绑定 war:war 目标。

项目信息元素

除了基本坐标,POM 还可以包含丰富的项目信息:

<project>
<!-- 基本信息 -->
<name>My Project</name>
<description>这是一个示例项目</description>
<url>https://example.com/my-project</url>
<inceptionYear>2024</inceptionYear>

<!-- 许可证 -->
<licenses>
<license>
<name>Apache License 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0</url>
<distribution>repo</distribution>
</license>
</licenses>

<!-- 组织信息 -->
<organization>
<name>Example Inc.</name>
<url>https://example.com</url>
</organization>

<!-- 开发者 -->
<developers>
<developer>
<id>dev1</id>
<name>张三</name>
<email>[email protected]</email>
<roles>
<role>architect</role>
<role>developer</role>
</roles>
</developer>
</developers>

<!-- 贡献者 -->
<contributors>
<contributor>
<name>李四</name>
<email>[email protected]</email>
</contributor>
</contributors>
</project>

这些信息主要用于生成项目站点文档,便于团队协作和项目展示。

properties(属性)

属性是 POM 中的变量,可以在其他地方引用。属性的主要作用是避免重复配置和便于统一管理。

属性定义

<properties>
<!-- 自定义属性 -->
<spring.version>5.3.23</spring.version>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<!-- 编译器配置 -->
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>

属性引用

使用 ${propertyName} 语法引用属性:

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>

内置属性

Maven 提供了丰富的内置属性:

属性说明示例值
${project.version}项目版本1.0.0
${project.basedir}项目根目录/home/user/my-project
${project.build.directory}构建目录target
${project.build.finalName}最终包名my-project-1.0.0
${project.name}项目名称My Project
${project.url}项目 URLhttps://example.com

环境变量和系统属性

<!-- 环境变量 -->
<java.home>${env.JAVA_HOME}</java.home>
<user.home>${env.HOME}</user.home>

<!-- 系统属性 -->
<line.separator>${line.separator}</line.separator>
<file.separator>${file.separator}</file.separator>

依赖配置

依赖配置是 POM 最重要的部分之一。详细内容在依赖管理章节已有讲解,这里补充一些关键点。

依赖的完整结构

<dependency>
<!-- 必需元素 -->
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.23</version>

<!-- 可选元素 -->
<type>jar</type> <!-- 依赖类型,默认 jar -->
<scope>compile</scope> <!-- 依赖范围 -->
<optional>false</optional> <!-- 是否可选 -->
<classifier>sources</classifier> <!-- 分类器 -->

<!-- 排除传递依赖 -->
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>

classifier 的使用

classifier 用于区分同一构件的不同变体。常见用途包括:

<!-- 源码包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.23</version>
<classifier>sources</classifier>
</dependency>

<!-- Javadoc 包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.23</version>
<classifier>javadoc</classifier>
</dependency>

<!-- 不同 JDK 版本 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>my-lib</artifactId>
<version>1.0.0</version>
<classifier>jdk11</classifier>
</dependency>

版本范围

Maven 支持灵活的版本范围声明:

<!-- 软性要求:推荐使用 1.0,但可被其他版本替代 -->
<version>1.0</version>

<!-- 硬性要求:必须使用 1.0 -->
<version>[1.0]</version>

<!-- 范围要求:1.0 到 2.0 之间(不包含 2.0) -->
<version>[1.0,2.0)</version>

<!-- 最小版本:大于等于 1.5 -->
<version>[1.5,)</version>

<!-- 最大版本:小于等于 1.0 -->
<version>(,1.0]</version>

<!-- 排除特定版本 -->
<version>(,1.1),(1.1,)</version>

实际开发中,建议使用具体版本号而非版本范围,以保证构建的可重复性。

dependencyManagement

dependencyManagement 用于统一管理依赖版本,常用于多模块项目。

版本集中管理

<!-- 父 POM -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>5.3.23</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
</dependencies>
</dependencyManagement>

子模块使用时无需指定版本:

<!-- 子模块 -->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<!-- 版本由父 POM 管理 -->
</dependency>
</dependencies>

import scope

import scope 用于导入其他 POM 的 dependencyManagement 配置:

<dependencyManagement>
<dependencies>
<!-- 导入 Spring Boot BOM -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.7.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

使用 import scope 时需要注意:

  1. 只能用于 dependencyManagement 中
  2. 目标 POM 的 packaging 必须是 pom
  3. type 必须指定为 pom
  4. scope 必须是 import

继承

POM 支持继承机制,子项目可以继承父项目的配置。

父 POM

<!-- 父项目 pom.xml -->
<project>
<groupId>com.example</groupId>
<artifactId>parent-project</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging> <!-- 父项目必须是 pom 类型 -->

<!-- 公共配置 -->
<properties>
<java.version>17</java.version>
</properties>

<dependencyManagement>
<!-- 依赖版本管理 -->
</dependencyManagement>

<dependencies>
<!-- 公共依赖 -->
</dependencies>
</project>

子 POM

<!-- 子项目 pom.xml -->
<project>
<parent>
<groupId>com.example</groupId>
<artifactId>parent-project</artifactId>
<version>1.0.0</version>
<relativePath>../parent-project</relativePath>
</parent>

<artifactId>child-project</artifactId>

<!-- 子项目特有的配置 -->
</project>

可继承的元素

以下元素可以被继承:

  • groupId、version
  • properties
  • dependencies、dependencyManagement
  • repositories、pluginRepositories
  • build(包括 plugins、pluginManagement)
  • reporting
  • description、url、organization
  • licenses、developers、contributors
  • scm、issueManagement、ciManagement

不可继承的元素

以下元素不能被继承:

  • artifactId(必须唯一)
  • name
  • prerequisites
  • profiles(但激活的 profile 效果会继承)

relativePath

relativePath 指定父 POM 的相对路径:

<parent>
<groupId>com.example</groupId>
<artifactId>parent-project</artifactId>
<version>1.0.0</version>
<relativePath>../pom.xml</relativePath>
</parent>

如果省略 relativePath,Maven 会按以下顺序查找父 POM:

  1. 当前目录的父目录
  2. 本地仓库
  3. 远程仓库

设置为空字符串可以禁用本地查找:

<relativePath/>

聚合

聚合允许在一个项目中构建多个模块。

聚合项目

<project>
<groupId>com.example</groupId>
<artifactId>parent-project</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>

<!-- 聚合的子模块 -->
<modules>
<module>common</module>
<module>service</module>
<module>web</module>
</modules>
</project>

继承与聚合的关系

继承和聚合是两个独立的概念,可以单独使用,也可以组合使用:

  • 继承:子项目继承父项目的配置
  • 聚合:父项目可以一起构建多个子模块

通常,我们会将两者结合使用:父项目既是聚合项目,也是父 POM。

build 配置

build 元素包含项目构建相关的配置。

基础配置

<build>
<!-- 构建输出目录 -->
<directory>${project.basedir}/target</directory>

<!-- 最终包名 -->
<finalName>${project.artifactId}-${project.version}</finalName>

<!-- 源码目录 -->
<sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>

<!-- 测试源码目录 -->
<testSourceDirectory>${project.basedir}/src/test/java</testSourceDirectory>

<!-- 资源目录 -->
<resources>
<resource>
<directory>${project.basedir}/src/main/resources</directory>
</resource>
</resources>
</build>

资源过滤

资源过滤允许在资源文件中使用变量,构建时自动替换:

<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*.properties</include>
<include>**/*.yml</include>
</includes>
</resource>
</resources>
</build>

资源文件中使用变量:

# application.properties
app.name=${project.name}
app.version=${project.version}
db.url=${db.url}

插件配置

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
<encoding>UTF-8</encoding>
</configuration>
<executions>
<execution>
<id>default-compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

pluginManagement

类似于 dependencyManagement,pluginManagement 用于统一管理插件版本:

<!-- 父 POM -->
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>

子模块使用时无需指定版本和配置:

<!-- 子模块 -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<!-- 版本和配置由父 POM 管理 -->
</plugin>
</plugins>
</build>

仓库配置

repositories

配置项目的依赖仓库:

<repositories>
<repository>
<id>aliyun</id>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
<updatePolicy>daily</updatePolicy>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</snapshots>
</repository>
</repositories>

pluginRepositories

配置插件仓库:

<pluginRepositories>
<pluginRepository>
<id>aliyun-plugin</id>
<name>阿里云插件仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</pluginRepository>
</pluginRepositories>

distributionManagement

distributionManagement 配置项目的部署目标:

<distributionManagement>
<!-- 发布版本仓库 -->
<repository>
<id>nexus-releases</id>
<name>Nexus Release Repository</name>
<url>http://localhost:8081/repository/maven-releases/</url>
</repository>

<!-- 快照版本仓库 -->
<snapshotRepository>
<id>nexus-snapshots</id>
<name>Nexus Snapshot Repository</name>
<url>http://localhost:8081/repository/maven-snapshots/</url>
</snapshotRepository>

<!-- 项目站点部署 -->
<site>
<id>website</id>
<url>scp://www.example.com/www/docs/project/</url>
</site>
</distributionManagement>

Super POM

所有 POM 都隐式继承自 Super POM。Super POM 定义了 Maven 的默认配置,包括:

  • 默认的目录结构
  • 默认的仓库配置(中央仓库)
  • 默认的插件版本

可以通过以下命令查看 Super POM 与当前 POM 合并后的有效配置:

mvn help:effective-pom

Super POM 的主要默认配置:

<project>
<modelVersion>4.0.0</modelVersion>

<repositories>
<repository>
<id>central</id>
<name>Central Repository</name>
<url>https://repo.maven.apache.org/maven2</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>

<build>
<directory>${project.basedir}/target</directory>
<outputDirectory>${project.build.directory}/classes</outputDirectory>
<finalName>${project.artifactId}-${project.version}</finalName>
<sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/java</testSourceDirectory>
</build>
</project>

POM 最佳实践

1. 使用 properties 管理版本

将依赖版本集中定义在 properties 中,便于统一管理和升级:

<properties>
<spring.version>5.3.23</spring.version>
<mybatis.version>3.5.11</mybatis.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>

2. 合理使用 dependencyManagement

在多模块项目中,使用父 POM 的 dependencyManagement 统一管理依赖版本。

3. 避免重复配置

利用继承机制,将公共配置放在父 POM 中。

4. 显式声明直接使用的依赖

即使某个依赖会通过传递性依赖引入,如果项目直接使用了它,也应该显式声明。

5. 使用 BOM 管理框架依赖

对于 Spring Boot、Jackson 等框架,使用官方 BOM 确保版本兼容性。

6. 保持 POM 整洁

  • 使用有意义的 groupId 和 artifactId
  • 添加适当的项目描述信息
  • 定期清理不再使用的依赖

小结

本章详细讲解了 POM 的各个方面:

  1. POM 的概念和作用
  2. Maven 坐标的组成和含义
  3. 打包类型的分类和用途
  4. 属性的定义和使用
  5. 依赖配置的完整结构
  6. dependencyManagement 的使用
  7. POM 继承和聚合机制
  8. build 配置的详细选项
  9. 仓库和部署配置
  10. Super POM 的概念
  11. POM 的最佳实践

理解 POM 是掌握 Maven 的关键,通过合理配置 POM,可以实现项目的规范化管理。