Maven核心详解
本章导读
Maven作为Java生态中最主流的构建工具,是每个开发者必须掌握的核心技能。本章将带你深入理解Maven的依赖管理机制、构建生命周期、插件体系等核心原理,帮助你从"会用"到"精通",彻底解决日常开发中遇到的依赖冲突、构建优化等难题。
学习目标:
- 目标1:理解Maven项目结构、POM配置与坐标体系
- 目标2:掌握依赖管理机制,包括依赖范围、传递性与冲突解决
- 目标3:能够编写多模块项目配置和自定义Maven插件
前置知识:Java基础语法、IDE使用经验、基本的命令行操作能力
阅读时长:约 25 分钟
一、知识概述
Maven是Java生态系统中最流行的项目管理和构建工具,它不仅简化了构建过程,还提供了强大的依赖管理、项目标准化和插件扩展能力。掌握Maven是每个Java开发者的必备技能。
本文将深入分析Maven的核心原理,包括依赖管理机制、生命周期、插件开发、多模块项目构建等核心内容,并提供实战代码示例。
Maven的核心价值
- 标准化项目结构:统一的目录布局
- 依赖管理:自动下载和管理第三方库
- 构建生命周期:标准化的构建流程
- 插件扩展:丰富的插件生态
- 多模块支持:大型项目模块化管理
二、知识点详细讲解
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 最佳实践清单
-
版本管理
- 使用dependencyManagement统一管理版本
- 使用BOM简化依赖管理
- 定期更新依赖版本
-
多模块设计
- 按功能垂直拆分
- 公共模块提取
- 单向依赖避免循环
-
构建优化
- 合理配置跳过测试
- 使用并行构建
- 配置本地镜像加速
-
规范约束
- 统一编码规范
- 配置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
- 资源过滤动态注入
参考资料
- 《Maven实战》
- Maven官方文档:maven.apache.org/
- Spring Boot Maven Plugin文档
- 《Maven权威指南》
六、思考与练习
思考题
- 基础题:Maven的依赖范围(scope)有哪些?它们分别作用于什么阶段?
- 进阶题:当项目中出现依赖冲突时,Maven是如何决策选择哪个版本的?请说明"最短路径优先"和"先声明优先"两种策略的区别。
- 实战题:如何设计一个多模块Maven项目?父子POM如何配置才能实现版本统一管理和模块间依赖?
编程练习
练习:创建一个多模块Maven项目,包含common(公共模块)、service(业务模块)、web(启动模块)三个子模块,实现模块间的依赖关系配置,并配置统一的版本管理和公共依赖。
章节关联
- 前置章节:Java基础、面向对象编程
- 后续章节:Gradle构建详解、依赖冲突解决
- 扩展阅读:《Maven实战》、《Maven权威指南》
📝 下一章预告
Gradle作为新一代构建工具,以其灵活的DSL语法和强大的增量构建能力,正逐渐成为主流选择。下一章我们将深入探索Gradle的核心原理与实战技巧。
本章完