62-Maven核心详解

Maven核心详解

本章导读

Maven作为Java生态中最主流的构建工具,是每个开发者必须掌握的核心技能。本章将带你深入理解Maven的依赖管理机制、构建生命周期、插件体系等核心原理,帮助你从"会用"到"精通",彻底解决日常开发中遇到的依赖冲突、构建优化等难题。

学习目标

  • 目标1:理解Maven项目结构、POM配置与坐标体系
  • 目标2:掌握依赖管理机制,包括依赖范围、传递性与冲突解决
  • 目标3:能够编写多模块项目配置和自定义Maven插件

前置知识:Java基础语法、IDE使用经验、基本的命令行操作能力

阅读时长:约 25 分钟

一、知识概述

Maven是Java生态系统中最流行的项目管理和构建工具,它不仅简化了构建过程,还提供了强大的依赖管理、项目标准化和插件扩展能力。掌握Maven是每个Java开发者的必备技能。

本文将深入分析Maven的核心原理,包括依赖管理机制、生命周期、插件开发、多模块项目构建等核心内容,并提供实战代码示例。

Maven的核心价值

  1. 标准化项目结构:统一的目录布局
  2. 依赖管理:自动下载和管理第三方库
  3. 构建生命周期:标准化的构建流程
  4. 插件扩展:丰富的插件生态
  5. 多模块支持:大型项目模块化管理

二、知识点详细讲解

2.1 Maven项目结构

标准目录布局
bash 复制代码
my-project/
├── pom.xml                          # 项目对象模型文件
├── src/
│   ├── main/
│   │   ├── java/                    # 主代码目录
│   │   │   └── com/example/app/
│   │   │       ├── App.java
│   │   │       └── service/
│   │   ├── resources/               # 主资源目录
│   │   │   ├── application.properties
│   │   │   └── logback.xml
│   │   ├── webapp/                  # Web应用目录(WAR项目)
│   │   │   ├── WEB-INF/
│   │   │   │   └── web.xml
│   │   │   └── index.jsp
│   │   └── filters/                 # 资源过滤文件
│   │       └── filter.properties
│   └── test/
│       ├── java/                    # 测试代码目录
│       │   └── com/example/app/
│       │       └── AppTest.java
│       ├── resources/               # 测试资源目录
│       │   └── test-application.properties
│       └── filters/                 # 测试过滤文件
├── target/                          # 构建输出目录
│   ├── classes/                     # 编译后的class文件
│   ├── test-classes/                # 测试class文件
│   └── my-project-1.0.jar           # 构建产物
└── README.md
POM文件详解
xml 复制代码
<?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 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    
    <!-- 模型版本(固定值) -->
    <modelVersion>4.0.0</modelVersion>
    
    <!-- ==================== 项目坐标 ==================== -->
    <groupId>com.example</groupId>           <!-- 组织ID -->
    <artifactId>my-project</artifactId>       <!-- 项目ID -->
    <version>1.0.0</version>                  <!-- 版本号 -->
    <packaging>jar</packaging>                <!-- 打包类型:jar/war/pom -->
    
    <!-- ==================== 项目信息 ==================== -->
    <name>My Project</name>                   <!-- 项目名称 -->
    <description>A sample Maven project</description>
    <url>https://www.example.com/my-project</url>
    <inceptionYear>2024</inceptionYear>
    
    <!-- 许可证 -->
    <licenses>
        <license>
            <name>Apache License, Version 2.0</name>
            <url>https://www.apache.org/licenses/LICENSE-2.0</url>
            <distribution>repo</distribution>
        </license>
    </licenses>
    
    <!-- 开发者 -->
    <developers>
        <developer>
            <id>john</id>
            <name>John Doe</name>
            <email>john@example.com</email>
            <organization>Example Inc.</organization>
            <roles>
                <role>architect</role>
                <role>developer</role>
            </roles>
        </developer>
    </developers>
    
    <!-- ==================== 属性定义 ==================== -->
    <properties>
        <!-- 编码 -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        
        <!-- Java版本 -->
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        
        <!-- 依赖版本 -->
        <spring.version>6.1.0</spring.version>
        <junit.version>5.10.0</junit.version>
        <lombok.version>1.18.30</lombok.version>
    </properties>
    
    <!-- ==================== 依赖管理 ==================== -->
    <dependencyManagement>
        <dependencies>
            <!-- Spring BOM -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>3.2.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
    <!-- ==================== 依赖配置 ==================== -->
    <dependencies>
        <!-- Spring Context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        
        <!-- Lombok(编译时依赖) -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>
        
        <!-- JUnit 5(测试依赖) -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <!-- ==================== 构建配置 ==================== -->
    <build>
        <!-- 默认目标 -->
        <defaultGoal>clean install</defaultGoal>
        
        <!-- 最终构建名称 -->
        <finalName>${project.artifactId}-${project.version}</finalName>
        
        <!-- 资源配置 -->
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                    <include>**/*.yml</include>
                </includes>
            </resource>
        </resources>
        
        <!-- 插件配置 -->
        <plugins>
            <!-- 编译插件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <source>${maven.compiler.source}</source>
                    <target>${maven.compiler.target}</target>
                    <encoding>${project.build.sourceEncoding}</encoding>
                    <compilerArgs>
                        <arg>-parameters</arg>
                    </compilerArgs>
                </configuration>
            </plugin>
            
            <!-- 测试插件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.1.2</version>
                <configuration>
                    <skipTests>false</skipTests>
                    <parallel>methods</parallel>
                    <threadCount>4</threadCount>
                </configuration>
            </plugin>
            
            <!-- JAR打包插件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.3.0</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>com.example.app.App</mainClass>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>lib/</classpathPrefix>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
        
        <!-- 插件管理 -->
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <version>3.2.0</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
    
    <!-- ==================== 环境配置 ==================== -->
    <profiles>
        <!-- 开发环境 -->
        <profile>
            <id>dev</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <env>dev</env>
            </properties>
        </profile>
        
        <!-- 生产环境 -->
        <profile>
            <id>prod</id>
            <properties>
                <env>prod</env>
            </properties>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-jar-plugin</artifactId>
                        <configuration>
                            <excludes>
                                <exclude>**/application-dev.*</exclude>
                            </excludes>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>
    
    <!-- ==================== 仓库配置 ==================== -->
    <repositories>
        <repository>
            <id>aliyun</id>
            <name>Aliyun Maven Repository</name>
            <url>https://maven.aliyun.com/repository/public</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
    
    <pluginRepositories>
        <pluginRepository>
            <id>aliyun-plugin</id>
            <name>Aliyun Maven Plugin Repository</name>
            <url>https://maven.aliyun.com/repository/public</url>
        </pluginRepository>
    </pluginRepositories>
    
    <!-- ==================== 发布配置 ==================== -->
    <distributionManagement>
        <repository>
            <id>releases</id>
            <name>Internal Releases</name>
            <url>http://nexus.example.com/repository/maven-releases/</url>
        </repository>
        <snapshotRepository>
            <id>snapshots</id>
            <name>Internal Snapshots</name>
            <url>http://nexus.example.com/repository/maven-snapshots/</url>
        </snapshotRepository>
    </distributionManagement>
    
</project>

2.2 依赖管理机制

依赖范围(Scope)
java 复制代码
/**
 * Maven依赖范围详解
 * 
 * scope取值说明:
 * 
 * 1. compile(默认)
 *    - 编译、测试、运行都有效
 *    - 打包时包含
 * 
 * 2. provided
 *    - 编译、测试有效,运行无效
 *    - 打包时不包含(由容器提供)
 *    - 典型应用:servlet-api、lombok
 * 
 * 3. runtime
 *    - 测试、运行有效,编译无效
 *    - 打包时包含
 *    - 典型应用:JDBC驱动
 * 
 * 4. test
 *    - 仅测试有效
 *    - 打包时不包含
 *    - 典型应用:JUnit、Mockito
 * 
 * 5. system
 *    - 与provided类似,但需要指定本地路径
 *    - 不推荐使用
 * 
 * 6. import
 *    - 仅用于dependencyManagement
 *    - 导入BOM(Bill of Materials)
 */
依赖范围与classpath关系
sql 复制代码
┌─────────────┬──────────────┬──────────────┬──────────────┐
│    Scope    │   Compile    │     Test     │    Runtime   │
├─────────────┼──────────────┼──────────────┼──────────────┤
│  compile    │      ✓       │      ✓       │      ✓       │
│  provided   │      ✓       │      ✓       │      ✗       │
│  runtime    │      ✗       │      ✓       │      ✓       │
│    test     │      ✗       │      ✓       │      ✗       │
│   system    │      ✓       │      ✓       │      ✗       │
└─────────────┴──────────────┴──────────────┴──────────────┘
依赖传递性
java 复制代码
/**
 * 依赖传递性规则:
 * 
 * 假设 A -> B -> C(A依赖B,B依赖C)
 * 
 * C能否传递到A,取决于:
 * 1. B对C的scope
 * 2. A对B的scope
 * 
 * 传递性矩阵:
 * 
 *           B的scope
 *          compile  provided  runtime   test
 * A的scope
 * compile    ✓         ✗        ✓        ✗
 * provided   ✗         ✗        ✗        ✗
 * runtime    ✓         ✗        ✓        ✗
 *   test     ✓         ✗        ✓        ✗
 * 
 * 示例:
 * 项目A -> 项目B(compile)-> 项目C(compile)
 * 结果:A可以使用C
 * 
 * 项目A -> 项目B(compile)-> 项目C(provided)
 * 结果:A不能使用C
 */
依赖冲突解决
xml 复制代码
<!-- ==================== 依赖冲突场景 ==================== -->

<!-- 
场景:项目同时依赖A和B,A依赖C-1.0,B依赖C-2.0
Maven的解决策略:
1. 最短路径优先
2. 先声明优先
-->

<!-- 示例1:路径优先 -->
<!-- 
  项目 -> A -> C-1.0(路径长度2)
  项目 -> B -> D -> C-2.0(路径长度3)
  
  结果:使用C-1.0(路径更短)
-->

<!-- 示例2:声明优先 -->
<!-- 
  项目 -> A -> C-1.0(路径长度2)
  项目 -> B -> C-2.0(路径长度2)
  
  结果:谁先声明使用谁
-->

<dependencies>
    <!-- 先声明A,使用C-1.0 -->
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>A</artifactId>
        <version>1.0</version>
    </dependency>
    
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>B</artifactId>
        <version>1.0</version>
    </dependency>
</dependencies>

<!-- ==================== 排除依赖 ==================== -->
<dependencies>
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>A</artifactId>
        <version>1.0</version>
        <exclusions>
            <!-- 排除A传递过来的C依赖 -->
            <exclusion>
                <groupId>com.example</groupId>
                <artifactId>C</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    
    <!-- 显式声明需要的版本 -->
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>C</artifactId>
        <version>2.0</version>
    </dependency>
</dependencies>

<!-- ==================== 强制版本 ==================== -->
<!-- 在dependencyManagement中强制指定版本 -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>C</artifactId>
            <version>2.0</version>
        </dependency>
    </dependencies>
</dependencyManagement>
依赖分析命令
bash 复制代码
# 查看依赖树
mvn dependency:tree

# 查看依赖树(指定范围)
mvn dependency:tree -Dscope=compile

# 分析依赖
mvn dependency:analyze

# 查看有效POM
mvn help:effective-pom

# 查看依赖列表
mvn dependency:list

# 解析依赖
mvn dependency:resolve

# 查看插件依赖
mvn dependency:resolve-plugins

2.3 构建生命周期

三套生命周期
java 复制代码
/**
 * Maven有三套相互独立的生命周期:
 * 
 * 1. Clean Lifecycle(清理)
 *    pre-clean    -> 执行清理前的工作
 *    clean        -> 清理上一次构建生成的文件
 *    post-clean   -> 执行清理后的工作
 * 
 * 2. Default Lifecycle(构建)
 *    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       -> 部署到远程仓库
 * 
 * 3. Site Lifecycle(站点文档)
 *    pre-site     -> 生成站点前的工作
 *    site         -> 生成项目站点文档
 *    post-site    -> 生成站点后的工作
 *    site-deploy  -> 发布站点到服务器
 */
生命周期执行顺序
bash 复制代码
# 执行clean生命周期
mvn clean

# 执行default生命周期到compile阶段
mvn compile

# 执行default生命周期到package阶段
mvn package

# 执行clean和default两个生命周期
mvn clean package

# 执行多个阶段
mvn clean test package

# 跳过测试
mvn package -DskipTests

# 跳过测试编译和测试执行
mvn package -Dmaven.test.skip=true

# 指定环境
mvn package -P prod

# 离线模式(使用本地缓存)
mvn package -o

# 调试模式
mvn package -X

2.4 插件机制

常用插件配置
xml 复制代码
<!-- ==================== 编译插件 ==================== -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.11.0</version>
    <configuration>
        <!-- Java版本 -->
        <source>17</source>
        <target>17</target>
        <release>17</release>
        
        <!-- 编码 -->
        <encoding>UTF-8</encoding>
        
        <!-- 编译参数 -->
        <compilerArgs>
            <arg>-parameters</arg>          <!-- 保留参数名 -->
            <arg>-Xlint:unchecked</arg>     <!-- 未检查警告 -->
        </compilerArgs>
        
        <!-- 注解处理器 -->
        <annotationProcessorPaths>
            <path>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.30</version>
            </path>
            <path>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct-processor</artifactId>
                <version>1.5.5.Final</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>

<!-- ==================== 测试插件 ==================== -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>3.1.2</version>
    <configuration>
        <!-- 是否跳过测试 -->
        <skipTests>false</skipTests>
        
        <!-- 并行执行 -->
        <parallel>methods</parallel>
        <threadCount>4</threadCount>
        
        <!-- 包含/排除测试 -->
        <includes>
            <include>**/*Test.java</include>
        </includes>
        <excludes>
            <exclude>**/*IntegrationTest.java</exclude>
        </excludes>
        
        <!-- JVM参数 -->
        <argLine>
            -Xmx512m
            -Djava.security.egd=file:/dev/./urandom
        </argLine>
        
        <!-- 系统属性 -->
        <systemPropertyVariables>
            <spring.profiles.active>test</spring.profiles.active>
        </systemPropertyVariables>
    </configuration>
</plugin>

<!-- ==================== JAR打包插件 ==================== -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <version>3.3.0</version>
    <configuration>
        <archive>
            <!-- MANIFEST.MF配置 -->
            <manifest>
                <mainClass>com.example.app.App</mainClass>
                <addClasspath>true</addClasspath>
                <classpathPrefix>lib/</classpathPrefix>
            </manifest>
            
            <!-- 自定义manifest条目 -->
            <manifestEntries>
                <Build-Time>${maven.build.timestamp}</Build-Time>
                <Built-By>${user.name}</Built-By>
                <Implementation-Version>${project.version}</Implementation-Version>
            </manifestEntries>
        </archive>
        
        <!-- 排除文件 -->
        <excludes>
            <exclude>**/application-dev.*</exclude>
        </excludes>
    </configuration>
</plugin>

<!-- ==================== 源码打包插件 ==================== -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-source-plugin</artifactId>
    <version>3.3.0</version>
    <executions>
        <execution>
            <id>attach-sources</id>
            <phase>package</phase>
            <goals>
                <goal>jar-no-fork</goal>
            </goals>
        </execution>
    </executions>
</plugin>

<!-- ==================== Javadoc插件 ==================== -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-javadoc-plugin</artifactId>
    <version>3.6.2</version>
    <configuration>
        <encoding>UTF-8</encoding>
        <charset>UTF-8</charset>
        <docencoding>UTF-8</docencoding>
        <doclint>none</doclint>
        <show>private</show>
        <nohelp>true</nohelp>
    </configuration>
    <executions>
        <execution>
            <id>attach-javadocs</id>
            <phase>package</phase>
            <goals>
                <goal>jar</goal>
            </goals>
        </execution>
    </executions>
</plugin>

<!-- ==================== 资源处理插件 ==================== -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-resources-plugin</artifactId>
    <version>3.3.1</version>
    <configuration>
        <encoding>UTF-8</encoding>
        <!-- 启用过滤 -->
        <useDefaultDelimiters>true</useDefaultDelimiters>
        <delimiters>
            <delimiter>@</delimiter>
            <delimiter>${*}</delimiter>
        </delimiters>
    </configuration>
</plugin>

<!-- ==================== 依赖拷贝插件 ==================== -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <version>3.6.1</version>
    <executions>
        <execution>
            <id>copy-dependencies</id>
            <phase>package</phase>
            <goals>
                <goal>copy-dependencies</goal>
            </goals>
            <configuration>
                <outputDirectory>${project.build.directory}/lib</outputDirectory>
                <includeScope>runtime</includeScope>
            </configuration>
        </execution>
    </executions>
</plugin>

<!-- ==================== Spring Boot打包插件 ==================== -->
<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <version>3.2.0</version>
    <configuration>
        <mainClass>com.example.app.Application</mainClass>
        <!-- 排除依赖 -->
        <excludes>
            <exclude>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </exclude>
        </excludes>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>repackage</goal>
            </goals>
        </execution>
    </executions>
</plugin>

<!-- ==================== 版本管理插件 ==================== -->
<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>versions-maven-plugin</artifactId>
    <version>2.16.2</version>
    <configuration>
        <generateBackupPoms>false</generateBackupPoms>
    </configuration>
</plugin>

<!-- ==================== 代码格式化插件 ==================== -->
<plugin>
    <groupId>net.revelc.code.formatter</groupId>
    <artifactId>formatter-maven-plugin</artifactId>
    <version>2.23.0</version>
    <configuration>
        <configFile>${project.basedir}/formatter.xml</configFile>
        <lineEnding>LF</lineEnding>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>format</goal>
            </goals>
        </execution>
    </executions>
</plugin>

<!-- ==================== Checkstyle插件 ==================== -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-checkstyle-plugin</artifactId>
    <version>3.3.1</version>
    <configuration>
        <configLocation>checkstyle.xml</configLocation>
        <encoding>UTF-8</encoding>
        <consoleOutput>true</consoleOutput>
        <failsOnError>true</failsOnError>
    </configuration>
    <executions>
        <execution>
            <id>validate</id>
            <phase>validate</phase>
            <goals>
                <goal>check</goal>
            </goals>
        </execution>
    </executions>
</plugin>

2.5 多模块项目

父POM配置
xml 复制代码
<?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 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    
    <modelVersion>4.0.0</modelVersion>
    
    <!-- 父项目坐标 -->
    <groupId>com.example</groupId>
    <artifactId>parent-project</artifactId>
    <version>1.0.0</version>
    <packaging>pom</packaging>
    
    <!-- 子模块 -->
    <modules>
        <module>common</module>
        <module>api</module>
        <module>service</module>
        <module>web</module>
    </modules>
    
    <!-- 属性 -->
    <properties>
        <java.version>17</java.version>
        <spring.boot.version>3.2.0</spring.boot.version>
        <mybatis.plus.version>3.5.5</mybatis.plus.version>
    </properties>
    
    <!-- 依赖管理 -->
    <dependencyManagement>
        <dependencies>
            <!-- Spring Boot BOM -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring.boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            
            <!-- 子模块依赖 -->
            <dependency>
                <groupId>com.example</groupId>
                <artifactId>common</artifactId>
                <version>${project.version}</version>
            </dependency>
            
            <dependency>
                <groupId>com.example</groupId>
                <artifactId>api</artifactId>
                <version>${project.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
    <!-- 公共依赖 -->
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    
    <!-- 构建配置 -->
    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <version>${spring.boot.version}</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>
子模块POM
xml 复制代码
<!-- common模块 -->
<?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 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    
    <modelVersion>4.0.0</modelVersion>
    
    <!-- 父项目 -->
    <parent>
        <groupId>com.example</groupId>
        <artifactId>parent-project</artifactId>
        <version>1.0.0</version>
    </parent>
    
    <artifactId>common</artifactId>
    <packaging>jar</packaging>
    
    <dependencies>
        <!-- 工具类依赖 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.24</version>
        </dependency>
    </dependencies>
</project>

<!-- service模块 -->
<?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 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    
    <modelVersion>4.0.0</modelVersion>
    
    <parent>
        <groupId>com.example</groupId>
        <artifactId>parent-project</artifactId>
        <version>1.0.0</version>
    </parent>
    
    <artifactId>service</artifactId>
    <packaging>jar</packaging>
    
    <dependencies>
        <!-- 依赖common模块 -->
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>common</artifactId>
        </dependency>
        
        <!-- Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        
        <!-- MyBatis Plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis.plus.version}</version>
        </dependency>
    </dependencies>
</project>

<!-- web模块(启动模块) -->
<?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 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    
    <modelVersion>4.0.0</modelVersion>
    
    <parent>
        <groupId>com.example</groupId>
        <artifactId>parent-project</artifactId>
        <version>1.0.0</version>
    </parent>
    
    <artifactId>web</artifactId>
    <packaging>jar</packaging>
    
    <dependencies>
        <!-- 依赖service模块 -->
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>service</artifactId>
        </dependency>
        
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
模块依赖关系
markdown 复制代码
web(启动模块)
  └── service(业务逻辑)
        └── common(公共模块)
              └── api(接口定义)

三、可运行Java代码示例

Maven插件开发示例

java 复制代码
package com.example.maven.plugin;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * 自定义Maven插件:代码统计
 * 使用:mvn code-stat:stat
 */
@Mojo(name = "stat", defaultPhase = LifecyclePhase.COMPILE)
public class CodeStatMojo extends AbstractMojo {
    
    /**
     * 源代码目录
     */
    @Parameter(defaultValue = "${project.build.sourceDirectory}", readonly = true)
    private File sourceDirectory;
    
    /**
     * 项目对象
     */
    @Parameter(defaultValue = "${project}", readonly = true)
    private MavenProject project;
    
    /**
     * 是否显示详细信息
     */
    @Parameter(property = "showDetail", defaultValue = "false")
    private boolean showDetail;
    
    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        getLog().info("开始统计代码...");
        
        CodeStat stat = new CodeStat();
        stat.analyze(sourceDirectory.toPath());
        
        getLog().info("====== 代码统计结果 ======");
        getLog().info("Java文件数: " + stat.getJavaFileCount());
        getLog().info("总行数: " + stat.getTotalLines());
        getLog().info("代码行数: " + stat.getCodeLines());
        getLog().info("注释行数: " + stat.getCommentLines());
        getLog().info("空白行数: " + stat.getBlankLines());
        
        if (showDetail) {
            getLog().info("====== 文件详情 ======");
            stat.getFileStats().forEach((file, s) -> {
                getLog().info(file + ": " + s.getCodeLines() + "行代码");
            });
        }
    }
    
    /**
     * 代码统计类
     */
    static class CodeStat {
        private int javaFileCount;
        private int totalLines;
        private int codeLines;
        private int commentLines;
        private int blankLines;
        private Map<String, FileStat> fileStats = new HashMap<>();
        
        public void analyze(Path directory) {
            try {
                Files.walk(directory)
                    .filter(p -> p.toString().endsWith(".java"))
                    .forEach(this::analyzeFile);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        
        private void analyzeFile(Path file) {
            try {
                javaFileCount++;
                
                FileStat fileStat = new FileStat();
                boolean inBlockComment = false;
                
                for (String line : Files.readAllLines(file)) {
                    totalLines++;
                    String trimmed = line.trim();
                    
                    if (trimmed.isEmpty()) {
                        blankLines++;
                        fileStat.blankLines++;
                    } else if (inBlockComment) {
                        commentLines++;
                        fileStat.commentLines++;
                        if (trimmed.contains("*/")) {
                            inBlockComment = false;
                        }
                    } else if (trimmed.startsWith("//")) {
                        commentLines++;
                        fileStat.commentLines++;
                    } else if (trimmed.startsWith("/*")) {
                        commentLines++;
                        fileStat.commentLines++;
                        if (!trimmed.contains("*/")) {
                            inBlockComment = true;
                        }
                    } else {
                        codeLines++;
                        fileStat.codeLines++;
                    }
                }
                
                fileStats.put(file.getFileName().toString(), fileStat);
                
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        
        // getters...
    }
    
    static class FileStat {
        int codeLines;
        int commentLines;
        int blankLines;
        
        public int getCodeLines() {
            return codeLines;
        }
    }
    
    // getters...
}

插件POM配置

xml 复制代码
<?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 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    
    <modelVersion>4.0.0</modelVersion>
    
    <groupId>com.example.maven.plugins</groupId>
    <artifactId>code-stat-maven-plugin</artifactId>
    <version>1.0.0</version>
    <packaging>maven-plugin</packaging>
    
    <name>Code Stat Maven Plugin</name>
    
    <dependencies>
        <dependency>
            <groupId>org.apache.maven</groupId>
            <artifactId>maven-plugin-api</artifactId>
            <version>3.9.5</version>
            <scope>provided</scope>
        </dependency>
        
        <dependency>
            <groupId>org.apache.maven.plugin-tools</groupId>
            <artifactId>maven-plugin-annotations</artifactId>
            <version>3.10.2</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-plugin-plugin</artifactId>
                <version>3.10.2</version>
                <configuration>
                    <goalPrefix>code-stat</goalPrefix>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

四、实战应用场景

场景一:Spring Boot项目POM模板

xml 复制代码
<?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>
    
    <!-- Spring Boot父项目 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
        <relativePath/>
    </parent>
    
    <groupId>com.example</groupId>
    <artifactId>spring-boot-demo</artifactId>
    <version>1.0.0</version>
    <name>Spring Boot Demo</name>
    
    <properties>
        <java.version>17</java.version>
        <mybatis-plus.version>3.5.5</mybatis-plus.version>
    </properties>
    
    <dependencies>
        <!-- Spring Boot Starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        
        <!-- MyBatis Plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        
        <!-- MySQL -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        
        <!-- Test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

五、总结与最佳实践

5.1 最佳实践清单

  1. 版本管理

    • 使用dependencyManagement统一管理版本
    • 使用BOM简化依赖管理
    • 定期更新依赖版本
  2. 多模块设计

    • 按功能垂直拆分
    • 公共模块提取
    • 单向依赖避免循环
  3. 构建优化

    • 合理配置跳过测试
    • 使用并行构建
    • 配置本地镜像加速
  4. 规范约束

    • 统一编码规范
    • 配置Checkstyle
    • 使用Formatter插件

5.2 常用命令速查

bash 复制代码
# 清理构建
mvn clean

# 编译
mvn compile

# 打包
mvn package

# 安装到本地仓库
mvn install

# 部署到远程仓库
mvn deploy

# 查看依赖树
mvn dependency:tree

# 分析依赖
mvn dependency:analyze

# 查看有效POM
mvn help:effective-pom

# 更新依赖版本
mvn versions:display-dependency-updates

# 更新插件版本
mvn versions:display-plugin-updates

5.3 常见问题

Q1: 依赖冲突如何解决?

  • 使用dependency:tree分析
  • 使用exclusion排除冲突依赖
  • 在dependencyManagement强制版本

Q2: 如何加速构建?

  • 配置阿里云镜像
  • 使用离线模式-o
  • 并行构建-T 1C

Q3: 如何管理多环境配置?

  • 使用Maven Profile
  • 配合Spring Boot Profile
  • 资源过滤动态注入

参考资料

  1. 《Maven实战》
  2. Maven官方文档:maven.apache.org/
  3. Spring Boot Maven Plugin文档
  4. 《Maven权威指南》

六、思考与练习

思考题

  1. 基础题:Maven的依赖范围(scope)有哪些?它们分别作用于什么阶段?
  2. 进阶题:当项目中出现依赖冲突时,Maven是如何决策选择哪个版本的?请说明"最短路径优先"和"先声明优先"两种策略的区别。
  3. 实战题:如何设计一个多模块Maven项目?父子POM如何配置才能实现版本统一管理和模块间依赖?

编程练习

练习:创建一个多模块Maven项目,包含common(公共模块)、service(业务模块)、web(启动模块)三个子模块,实现模块间的依赖关系配置,并配置统一的版本管理和公共依赖。

章节关联

  • 前置章节:Java基础、面向对象编程
  • 后续章节:Gradle构建详解、依赖冲突解决
  • 扩展阅读:《Maven实战》、《Maven权威指南》

📝 下一章预告

Gradle作为新一代构建工具,以其灵活的DSL语法和强大的增量构建能力,正逐渐成为主流选择。下一章我们将深入探索Gradle的核心原理与实战技巧。


本章完

相关推荐
tcsunrise1 小时前
在线程任务中如何正确处理异常和中断?
后端
方也_arkling1 小时前
【Java-Day16】API篇-Math类/System类/Object类/包装类
java·开发语言
x***r1511 小时前
burpsuite-1.4.07.jar 使用步骤详解(附Java环境配置与Burp Suite抓包教程)
java·开发语言·jar
Cosmoshhhyyy1 小时前
《Effective Java》解读第54条:返回零长度的数组或者集合,而不是null
java·开发语言·python
沐一的blog1 小时前
Java 并发 100 问:从面试到生产(二)
后端·面试
jsl_jsl_jsl1 小时前
☕ Java 高并发进阶(二):无锁并发与数据隔离——CAS、Unsafe 与 ThreadLocal 深度内核解密
java
kTR2hD1qb1 小时前
Keepalived 学习总结
java·服务器·学习
用户713874229001 小时前
ASP.NET Core .NET 10 错误响应体系全景:从 BadRequest 到编译器基础设施
后端
土狗TuGou1 小时前
SQL内功笔记 · 第9篇:UPDATE FROM 进阶——告别逐行子查询,拥抱集合更新
java·数据库·笔记·sql·mysql