活动公告

系统通知
05-18 21:22
系统通知
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,资源失效请在帖子内回复要求补档,会尽快处理!
10-23 09:31

Maven父子项目详解从基础概念到实际应用全面掌握多模块项目构建与管理技巧提升开发效率

SunJu_FaceMall

3万

主题

2860

科技点

3万

积分

白金月票

碾压王

积分
32872

塔罗立华奏

<font color=白金月票" /> 发表于 2025-9-3 09:20:00 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
1. Maven父子项目基础概念

Maven是一个强大的项目管理和构建工具,它通过一个中央信息管理的方式管理项目的构建、报告和文档。在大型项目开发中,Maven的父子项目(也称为多模块项目)结构尤为重要。

1.1 什么是Maven父子项目

Maven父子项目是一种项目组织结构,其中一个父项目(Parent Project)可以包含多个子项目(Child Modules)。这种结构允许我们将一个大型项目分解为多个更小、更易于管理的模块,每个模块可以独立开发、测试和构建,同时又能共享配置和依赖。

1.2 父子项目的优势

使用Maven父子项目结构有以下优势:

1. 统一管理:父项目可以统一管理所有子模块的版本号、依赖项、插件配置等,确保一致性。
2. 简化构建:通过一次命令可以构建整个项目或特定模块,提高构建效率。
3. 代码复用:公共代码可以放在共享模块中,其他模块可以依赖这些共享模块,避免代码重复。
4. 模块化开发:不同功能可以分离到不同模块,使项目结构更清晰,便于团队协作。
5. 依赖管理:模块间的依赖关系清晰可见,避免循环依赖等问题。

1.3 父子项目的结构

典型的Maven父子项目结构如下:
  1. parent-project/
  2. ├── pom.xml (父POM)
  3. ├── module-a/
  4. │   └── pom.xml
  5. ├── module-b/
  6. │   └── pom.xml
  7. └── module-c/
  8.     └── pom.xml
复制代码

在这个结构中,parent-project是父项目,包含三个子模块:module-a、module-b和module-c。每个子模块都有自己的pom.xml文件,同时继承自父项目的pom.xml。

2. 创建和配置Maven父子项目

2.1 创建父项目

首先,我们需要创建一个父项目。父项目的pom.xml文件需要包含packaging类型为pom,以及modules元素来列出所有子模块。

以下是一个父项目pom.xml的示例:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3.          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5.     <modelVersion>4.0.0</modelVersion>
  6.     <groupId>com.example</groupId>
  7.     <artifactId>parent-project</artifactId>
  8.     <version>1.0.0</version>
  9.     <packaging>pom</packaging>
  10.     <name>Parent Project</name>
  11.     <description>Parent project for multi-module example</description>
  12.     <modules>
  13.         <module>module-a</module>
  14.         <module>module-b</module>
  15.         <module>module-c</module>
  16.     </modules>
  17.     <properties>
  18.         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  19.         <maven.compiler.source>1.8</maven.compiler.source>
  20.         <maven.compiler.target>1.8</maven.compiler.target>
  21.         <spring.boot.version>2.7.0</spring.boot.version>
  22.         <junit.version>5.8.2</junit.version>
  23.     </properties>
  24.     <dependencyManagement>
  25.         <dependencies>
  26.             <dependency>
  27.                 <groupId>org.springframework.boot</groupId>
  28.                 <artifactId>spring-boot-dependencies</artifactId>
  29.                 <version>${spring.boot.version}</version>
  30.                 <type>pom</type>
  31.                 <scope>import</scope>
  32.             </dependency>
  33.             <dependency>
  34.                 <groupId>org.junit.jupiter</groupId>
  35.                 <artifactId>junit-jupiter</artifactId>
  36.                 <version>${junit.version}</version>
  37.                 <scope>test</scope>
  38.             </dependency>
  39.         </dependencies>
  40.     </dependencyManagement>
  41.     <build>
  42.         <pluginManagement>
  43.             <plugins>
  44.                 <plugin>
  45.                     <groupId>org.springframework.boot</groupId>
  46.                     <artifactId>spring-boot-maven-plugin</artifactId>
  47.                     <version>${spring.boot.version}</version>
  48.                 </plugin>
  49.                 <plugin>
  50.                     <groupId>org.apache.maven.plugins</groupId>
  51.                     <artifactId>maven-compiler-plugin</artifactId>
  52.                     <version>3.8.1</version>
  53.                     <configuration>
  54.                         <source>${maven.compiler.source}</source>
  55.                         <target>${maven.compiler.target}</target>
  56.                     </configuration>
  57.                 </plugin>
  58.             </plugins>
  59.         </pluginManagement>
  60.     </build>
  61. </project>
复制代码

在这个父项目的pom.xml中:

1. packaging设置为pom,表示这是一个聚合项目,不产生实际的构建产物。
2. modules部分列出了所有子模块。
3. properties部分定义了一些全局属性,如源代码编码、Java版本、依赖版本等。
4. dependencyManagement部分用于管理依赖版本,子模块在使用这些依赖时无需指定版本。
5. pluginManagement部分用于管理插件配置,子模块可以继承这些配置。

2.2 创建子模块

接下来,我们创建子模块。每个子模块都是一个独立的Maven项目,但会继承父项目的配置。

以下是一个子模块module-a的pom.xml示例:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3.          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5.     <modelVersion>4.0.0</modelVersion>
  6.     <!-- 继承父项目 -->
  7.     <parent>
  8.         <groupId>com.example</groupId>
  9.         <artifactId>parent-project</artifactId>
  10.         <version>1.0.0</version>
  11.         <relativePath>../pom.xml</relativePath>
  12.     </parent>
  13.     <artifactId>module-a</artifactId>
  14.     <packaging>jar</packaging>
  15.     <name>Module A</name>
  16.     <description>Module A for multi-module example</description>
  17.     <dependencies>
  18.         <dependency>
  19.             <groupId>org.springframework.boot</groupId>
  20.             <artifactId>spring-boot-starter-web</artifactId>
  21.         </dependency>
  22.         <dependency>
  23.             <groupId>org.junit.jupiter</groupId>
  24.             <artifactId>junit-jupiter</artifactId>
  25.             <scope>test</scope>
  26.         </dependency>
  27.     </dependencies>
  28.     <build>
  29.         <plugins>
  30.             <plugin>
  31.                 <groupId>org.springframework.boot</groupId>
  32.                 <artifactId>spring-boot-maven-plugin</artifactId>
  33.             </plugin>
  34.         </plugins>
  35.     </build>
  36. </project>
复制代码

在这个子模块的pom.xml中:

1. parent部分指定了父项目的坐标和相对路径,表示这个模块继承自父项目。
2. artifactId定义了当前模块的唯一标识。
3. packaging设置为jar,表示这个模块将打包成JAR文件。
4. dependencies部分列出了当前模块所需的依赖,无需指定版本,因为版本已在父项目的dependencyManagement中定义。
5. build部分可以包含特定于当前模块的插件配置。

2.3 模块间依赖

在多模块项目中,模块之间可能存在依赖关系。例如,module-b可能依赖于module-a。我们可以在module-b的pom.xml中添加对module-a的依赖:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3.          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5.     <modelVersion>4.0.0</modelVersion>
  6.     <parent>
  7.         <groupId>com.example</groupId>
  8.         <artifactId>parent-project</artifactId>
  9.         <version>1.0.0</version>
  10.         <relativePath>../pom.xml</relativePath>
  11.     </parent>
  12.     <artifactId>module-b</artifactId>
  13.     <packaging>jar</packaging>
  14.     <name>Module B</name>
  15.     <description>Module B for multi-module example</description>
  16.     <dependencies>
  17.         <dependency>
  18.             <groupId>com.example</groupId>
  19.             <artifactId>module-a</artifactId>
  20.             <version>${project.version}</version>
  21.         </dependency>
  22.         <dependency>
  23.             <groupId>org.springframework.boot</groupId>
  24.             <artifactId>spring-boot-starter-data-jpa</artifactId>
  25.         </dependency>
  26.         <dependency>
  27.             <groupId>org.junit.jupiter</groupId>
  28.             <artifactId>junit-jupiter</artifactId>
  29.             <scope>test</scope>
  30.         </dependency>
  31.     </dependencies>
  32. </project>
复制代码

在这个例子中,module-b依赖于module-a,使用${project.version}表示使用与当前项目相同的版本。

3. 多模块项目的构建与管理

3.1 构建多模块项目

Maven提供了多种方式来构建多模块项目:

1. 构建整个项目:在父项目目录下运行mvn clean install,Maven会按照模块间的依赖关系顺序构建所有模块。
2. 构建特定模块:可以使用-pl选项指定要构建的模块,例如:mvn clean install -pl module-a
3. 构建特定模块及其依赖:可以使用-am选项同时构建指定模块及其依赖的模块,例如:mvn clean install -pl module-b -am
4. 构建特定模块及依赖它的模块:可以使用-amd选项同时构建指定模块以及依赖它的模块,例如:mvn clean install -pl module-a -amd

构建整个项目:在父项目目录下运行mvn clean install,Maven会按照模块间的依赖关系顺序构建所有模块。

构建特定模块:可以使用-pl选项指定要构建的模块,例如:
  1. mvn clean install -pl module-a
复制代码

构建特定模块及其依赖:可以使用-am选项同时构建指定模块及其依赖的模块,例如:
  1. mvn clean install -pl module-b -am
复制代码

构建特定模块及依赖它的模块:可以使用-amd选项同时构建指定模块以及依赖它的模块,例如:
  1. mvn clean install -pl module-a -amd
复制代码

3.2 跳过特定模块

在某些情况下,我们可能需要在构建过程中跳过某些模块。可以使用-pl选项结合!或-来排除特定模块:
  1. mvn clean install -pl !module-c
复制代码

或者:
  1. mvn clean install -pl -module-c
复制代码

3.3 并行构建

Maven支持并行构建多模块项目,可以显著提高构建速度。可以使用-T选项启用并行构建:
  1. mvn clean install -T 4
复制代码

这个命令表示使用4个线程进行并行构建。也可以使用-T C表示使用CPU核心数相同的线程数:
  1. mvn clean install -T C
复制代码

3.4 模块排序

Maven在构建多模块项目时,会根据模块间的依赖关系自动确定构建顺序。如果模块间没有明确的依赖关系,则按照pom.xml中modules部分的顺序进行构建。

在某些情况下,我们可能需要显式指定模块的构建顺序,可以通过在父项目的pom.xml中使用<moduleOrder>元素来实现:
  1. <modules>
  2.     <moduleOrder>reactor</moduleOrder>
  3.     <module>module-a</module>
  4.     <module>module-b</module>
  5.     <module>module-c</module>
  6. </modules>
复制代码

moduleOrder可以设置为:

• reactor(默认):根据依赖关系确定顺序
• declaration:按照声明顺序
• none:不进行排序

4. 实际应用案例

4.1 创建一个多模块的Spring Boot应用

让我们创建一个实际的多模块Spring Boot应用示例,包含以下模块:

1. parent-project:父项目
2. common:公共模块,包含共享的工具类和配置
3. model:模型模块,包含实体类和数据传输对象
4. repository:数据访问模块,包含数据库访问逻辑
5. service:服务模块,包含业务逻辑
6. web:Web模块,包含控制器和Web相关配置

首先,创建父项目的pom.xml:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3.          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5.     <modelVersion>4.0.0</modelVersion>
  6.     <groupId>com.example</groupId>
  7.     <artifactId>multi-module-spring-boot</artifactId>
  8.     <version>1.0.0</version>
  9.     <packaging>pom</packaging>
  10.     <name>Multi Module Spring Boot</name>
  11.     <description>Example of multi-module Spring Boot application</description>
  12.     <modules>
  13.         <module>common</module>
  14.         <module>model</module>
  15.         <module>repository</module>
  16.         <module>service</module>
  17.         <module>web</module>
  18.     </modules>
  19.     <properties>
  20.         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  21.         <maven.compiler.source>1.8</maven.compiler.source>
  22.         <maven.compiler.target>1.8</maven.compiler.target>
  23.         <spring.boot.version>2.7.0</spring.boot.version>
  24.         <spring.cloud.version>2021.0.3</spring.cloud.version>
  25.         <mysql.connector.version>8.0.29</mysql.connector.version>
  26.         <lombok.version>1.18.24</lombok.version>
  27.         <junit.version>5.8.2</junit.version>
  28.     </properties>
  29.     <dependencyManagement>
  30.         <dependencies>
  31.             <dependency>
  32.                 <groupId>org.springframework.boot</groupId>
  33.                 <artifactId>spring-boot-dependencies</artifactId>
  34.                 <version>${spring.boot.version}</version>
  35.                 <type>pom</type>
  36.                 <scope>import</scope>
  37.             </dependency>
  38.             <dependency>
  39.                 <groupId>org.springframework.cloud</groupId>
  40.                 <artifactId>spring-cloud-dependencies</artifactId>
  41.                 <version>${spring.cloud.version}</version>
  42.                 <type>pom</type>
  43.                 <scope>import</scope>
  44.             </dependency>
  45.             <dependency>
  46.                 <groupId>mysql</groupId>
  47.                 <artifactId>mysql-connector-java</artifactId>
  48.                 <version>${mysql.connector.version}</version>
  49.             </dependency>
  50.             <dependency>
  51.                 <groupId>org.projectlombok</groupId>
  52.                 <artifactId>lombok</artifactId>
  53.                 <version>${lombok.version}</version>
  54.                 <scope>provided</scope>
  55.             </dependency>
  56.             <dependency>
  57.                 <groupId>org.junit.jupiter</groupId>
  58.                 <artifactId>junit-jupiter</artifactId>
  59.                 <version>${junit.version}</version>
  60.                 <scope>test</scope>
  61.             </dependency>
  62.         </dependencies>
  63.     </dependencyManagement>
  64.     <build>
  65.         <pluginManagement>
  66.             <plugins>
  67.                 <plugin>
  68.                     <groupId>org.springframework.boot</groupId>
  69.                     <artifactId>spring-boot-maven-plugin</artifactId>
  70.                     <version>${spring.boot.version}</version>
  71.                 </plugin>
  72.                 <plugin>
  73.                     <groupId>org.apache.maven.plugins</groupId>
  74.                     <artifactId>maven-compiler-plugin</artifactId>
  75.                     <version>3.8.1</version>
  76.                     <configuration>
  77.                         <source>${maven.compiler.source}</source>
  78.                         <target>${maven.compiler.target}</target>
  79.                     </configuration>
  80.                 </plugin>
  81.             </plugins>
  82.         </pluginManagement>
  83.     </build>
  84. </project>
复制代码

创建common模块的pom.xml:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3.          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5.     <modelVersion>4.0.0</modelVersion>
  6.     <parent>
  7.         <groupId>com.example</groupId>
  8.         <artifactId>multi-module-spring-boot</artifactId>
  9.         <version>1.0.0</version>
  10.         <relativePath>../pom.xml</relativePath>
  11.     </parent>
  12.     <artifactId>common</artifactId>
  13.     <packaging>jar</packaging>
  14.     <name>Common</name>
  15.     <description>Common module with shared utilities and configurations</description>
  16.     <dependencies>
  17.         <dependency>
  18.             <groupId>org.projectlombok</groupId>
  19.             <artifactId>lombok</artifactId>
  20.         </dependency>
  21.         <dependency>
  22.             <groupId>org.springframework.boot</groupId>
  23.             <artifactId>spring-boot-starter</artifactId>
  24.         </dependency>
  25.         <dependency>
  26.             <groupId>org.junit.jupiter</groupId>
  27.             <artifactId>junit-jupiter</artifactId>
  28.             <scope>test</scope>
  29.         </dependency>
  30.     </dependencies>
  31. </project>
复制代码

在common模块中,我们可以创建一些共享的工具类,例如:
  1. package com.example.common.utils;
  2. import lombok.extern.slf4j.Slf4j;
  3. import java.time.LocalDateTime;
  4. import java.time.format.DateTimeFormatter;
  5. @Slf4j
  6. public class DateUtils {
  7.    
  8.     private static final DateTimeFormatter DEFAULT_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
  9.    
  10.     public static String formatNow() {
  11.         return formatNow(DEFAULT_FORMATTER);
  12.     }
  13.    
  14.     public static String formatNow(DateTimeFormatter formatter) {
  15.         return LocalDateTime.now().format(formatter);
  16.     }
  17.    
  18.     public static LocalDateTime parse(String dateStr) {
  19.         return parse(dateStr, DEFAULT_FORMATTER);
  20.     }
  21.    
  22.     public static LocalDateTime parse(String dateStr, DateTimeFormatter formatter) {
  23.         try {
  24.             return LocalDateTime.parse(dateStr, formatter);
  25.         } catch (Exception e) {
  26.             log.error("Failed to parse date: {}", dateStr, e);
  27.             return null;
  28.         }
  29.     }
  30. }
复制代码

创建model模块的pom.xml:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3.          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5.     <modelVersion>4.0.0</modelVersion>
  6.     <parent>
  7.         <groupId>com.example</groupId>
  8.         <artifactId>multi-module-spring-boot</artifactId>
  9.         <version>1.0.0</version>
  10.         <relativePath>../pom.xml</relativePath>
  11.     </parent>
  12.     <artifactId>model</artifactId>
  13.     <packaging>jar</packaging>
  14.     <name>Model</name>
  15.     <description>Model module with entities and DTOs</description>
  16.     <dependencies>
  17.         <dependency>
  18.             <groupId>com.example</groupId>
  19.             <artifactId>common</artifactId>
  20.             <version>${project.version}</version>
  21.         </dependency>
  22.         <dependency>
  23.             <groupId>org.projectlombok</groupId>
  24.             <artifactId>lombok</artifactId>
  25.         </dependency>
  26.         <dependency>
  27.             <groupId>javax.persistence</groupId>
  28.             <artifactId>javax.persistence-api</artifactId>
  29.             <version>2.2</version>
  30.         </dependency>
  31.         <dependency>
  32.             <groupId>org.junit.jupiter</groupId>
  33.             <artifactId>junit-jupiter</artifactId>
  34.             <scope>test</scope>
  35.         </dependency>
  36.     </dependencies>
  37. </project>
复制代码

在model模块中,我们可以定义实体类和DTO,例如:
  1. package com.example.model.entity;
  2. import lombok.Data;
  3. import javax.persistence.Entity;
  4. import javax.persistence.GeneratedValue;
  5. import javax.persistence.GenerationType;
  6. import javax.persistence.Id;
  7. import javax.persistence.Table;
  8. import java.time.LocalDateTime;
  9. @Data
  10. @Entity
  11. @Table(name = "users")
  12. public class User {
  13.    
  14.     @Id
  15.     @GeneratedValue(strategy = GenerationType.IDENTITY)
  16.     private Long id;
  17.    
  18.     private String username;
  19.    
  20.     private String password;
  21.    
  22.     private String email;
  23.    
  24.     private LocalDateTime createdAt;
  25.    
  26.     private LocalDateTime updatedAt;
  27. }
复制代码
  1. package com.example.model.dto;
  2. import lombok.Data;
  3. import javax.validation.constraints.Email;
  4. import javax.validation.constraints.NotBlank;
  5. import javax.validation.constraints.Size;
  6. @Data
  7. public class UserDto {
  8.    
  9.     private Long id;
  10.    
  11.     @NotBlank(message = "Username is required")
  12.     @Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters")
  13.     private String username;
  14.    
  15.     @NotBlank(message = "Password is required")
  16.     @Size(min = 6, max = 100, message = "Password must be between 6 and 100 characters")
  17.     private String password;
  18.    
  19.     @NotBlank(message = "Email is required")
  20.     @Email(message = "Email should be valid")
  21.     private String email;
  22. }
复制代码

创建repository模块的pom.xml:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3.          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5.     <modelVersion>4.0.0</modelVersion>
  6.     <parent>
  7.         <groupId>com.example</groupId>
  8.         <artifactId>multi-module-spring-boot</artifactId>
  9.         <version>1.0.0</version>
  10.         <relativePath>../pom.xml</relativePath>
  11.     </parent>
  12.     <artifactId>repository</artifactId>
  13.     <packaging>jar</packaging>
  14.     <name>Repository</name>
  15.     <description>Repository module with data access logic</description>
  16.     <dependencies>
  17.         <dependency>
  18.             <groupId>com.example</groupId>
  19.             <artifactId>common</artifactId>
  20.             <version>${project.version}</version>
  21.         </dependency>
  22.         <dependency>
  23.             <groupId>com.example</groupId>
  24.             <artifactId>model</artifactId>
  25.             <version>${project.version}</version>
  26.         </dependency>
  27.         <dependency>
  28.             <groupId>org.springframework.boot</groupId>
  29.             <artifactId>spring-boot-starter-data-jpa</artifactId>
  30.         </dependency>
  31.         <dependency>
  32.             <groupId>mysql</groupId>
  33.             <artifactId>mysql-connector-java</artifactId>
  34.         </dependency>
  35.         <dependency>
  36.             <groupId>org.projectlombok</groupId>
  37.             <artifactId>lombok</artifactId>
  38.         </dependency>
  39.         <dependency>
  40.             <groupId>org.junit.jupiter</groupId>
  41.             <artifactId>junit-jupiter</artifactId>
  42.             <scope>test</scope>
  43.         </dependency>
  44.     </dependencies>
  45. </project>
复制代码

在repository模块中,我们可以定义数据访问接口,例如:
  1. package com.example.repository;
  2. import com.example.model.entity.User;
  3. import org.springframework.data.jpa.repository.JpaRepository;
  4. import org.springframework.stereotype.Repository;
  5. import java.util.Optional;
  6. @Repository
  7. public interface UserRepository extends JpaRepository<User, Long> {
  8.    
  9.     Optional<User> findByUsername(String username);
  10.    
  11.     Optional<User> findByEmail(String email);
  12.    
  13.     boolean existsByUsername(String username);
  14.    
  15.     boolean existsByEmail(String email);
  16. }
复制代码

创建service模块的pom.xml:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3.          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5.     <modelVersion>4.0.0</modelVersion>
  6.     <parent>
  7.         <groupId>com.example</groupId>
  8.         <artifactId>multi-module-spring-boot</artifactId>
  9.         <version>1.0.0</version>
  10.         <relativePath>../pom.xml</relativePath>
  11.     </parent>
  12.     <artifactId>service</artifactId>
  13.     <packaging>jar</packaging>
  14.     <name>Service</name>
  15.     <description>Service module with business logic</description>
  16.     <dependencies>
  17.         <dependency>
  18.             <groupId>com.example</groupId>
  19.             <artifactId>common</artifactId>
  20.             <version>${project.version}</version>
  21.         </dependency>
  22.         <dependency>
  23.             <groupId>com.example</groupId>
  24.             <artifactId>model</artifactId>
  25.             <version>${project.version}</version>
  26.         </dependency>
  27.         <dependency>
  28.             <groupId>com.example</groupId>
  29.             <artifactId>repository</artifactId>
  30.             <version>${project.version}</version>
  31.         </dependency>
  32.         <dependency>
  33.             <groupId>org.springframework.boot</groupId>
  34.             <artifactId>spring-boot-starter</artifactId>
  35.         </dependency>
  36.         <dependency>
  37.             <groupId>org.springframework.boot</groupId>
  38.             <artifactId>spring-boot-starter-validation</artifactId>
  39.         </dependency>
  40.         <dependency>
  41.             <groupId>org.projectlombok</groupId>
  42.             <artifactId>lombok</artifactId>
  43.         </dependency>
  44.         <dependency>
  45.             <groupId>org.junit.jupiter</groupId>
  46.             <artifactId>junit-jupiter</artifactId>
  47.             <scope>test</scope>
  48.         </dependency>
  49.         <dependency>
  50.             <groupId>org.springframework.boot</groupId>
  51.             <artifactId>spring-boot-starter-test</artifactId>
  52.             <scope>test</scope>
  53.         </dependency>
  54.     </dependencies>
  55. </project>
复制代码

在service模块中,我们可以定义业务逻辑,例如:
  1. package com.example.service;
  2. import com.example.model.dto.UserDto;
  3. import com.example.model.entity.User;
  4. import com.example.repository.UserRepository;
  5. import com.example.common.utils.DateUtils;
  6. import lombok.RequiredArgsConstructor;
  7. import lombok.extern.slf4j.Slf4j;
  8. import org.springframework.security.crypto.password.PasswordEncoder;
  9. import org.springframework.stereotype.Service;
  10. import org.springframework.transaction.annotation.Transactional;
  11. import java.util.List;
  12. import java.util.stream.Collectors;
  13. @Slf4j
  14. @Service
  15. @RequiredArgsConstructor
  16. public class UserService {
  17.    
  18.     private final UserRepository userRepository;
  19.     private final PasswordEncoder passwordEncoder;
  20.    
  21.     @Transactional
  22.     public UserDto createUser(UserDto userDto) {
  23.         if (userRepository.existsByUsername(userDto.getUsername())) {
  24.             throw new RuntimeException("Username is already taken");
  25.         }
  26.         
  27.         if (userRepository.existsByEmail(userDto.getEmail())) {
  28.             throw new RuntimeException("Email is already in use");
  29.         }
  30.         
  31.         User user = new User();
  32.         user.setUsername(userDto.getUsername());
  33.         user.setPassword(passwordEncoder.encode(userDto.getPassword()));
  34.         user.setEmail(userDto.getEmail());
  35.         user.setCreatedAt(DateUtils.parse(DateUtils.formatNow()));
  36.         user.setUpdatedAt(DateUtils.parse(DateUtils.formatNow()));
  37.         
  38.         User savedUser = userRepository.save(user);
  39.         log.info("Created new user: {}", savedUser.getUsername());
  40.         
  41.         return convertToDto(savedUser);
  42.     }
  43.    
  44.     public UserDto getUserById(Long id) {
  45.         return userRepository.findById(id)
  46.                 .map(this::convertToDto)
  47.                 .orElseThrow(() -> new RuntimeException("User not found with id: " + id));
  48.     }
  49.    
  50.     public UserDto getUserByUsername(String username) {
  51.         return userRepository.findByUsername(username)
  52.                 .map(this::convertToDto)
  53.                 .orElseThrow(() -> new RuntimeException("User not found with username: " + username));
  54.     }
  55.    
  56.     public List<UserDto> getAllUsers() {
  57.         return userRepository.findAll().stream()
  58.                 .map(this::convertToDto)
  59.                 .collect(Collectors.toList());
  60.     }
  61.    
  62.     @Transactional
  63.     public UserDto updateUser(Long id, UserDto userDto) {
  64.         User user = userRepository.findById(id)
  65.                 .orElseThrow(() -> new RuntimeException("User not found with id: " + id));
  66.         
  67.         if (!user.getUsername().equals(userDto.getUsername()) && userRepository.existsByUsername(userDto.getUsername())) {
  68.             throw new RuntimeException("Username is already taken");
  69.         }
  70.         
  71.         if (!user.getEmail().equals(userDto.getEmail()) && userRepository.existsByEmail(userDto.getEmail())) {
  72.             throw new RuntimeException("Email is already in use");
  73.         }
  74.         
  75.         user.setUsername(userDto.getUsername());
  76.         if (userDto.getPassword() != null && !userDto.getPassword().isEmpty()) {
  77.             user.setPassword(passwordEncoder.encode(userDto.getPassword()));
  78.         }
  79.         user.setEmail(userDto.getEmail());
  80.         user.setUpdatedAt(DateUtils.parse(DateUtils.formatNow()));
  81.         
  82.         User updatedUser = userRepository.save(user);
  83.         log.info("Updated user: {}", updatedUser.getUsername());
  84.         
  85.         return convertToDto(updatedUser);
  86.     }
  87.    
  88.     @Transactional
  89.     public void deleteUser(Long id) {
  90.         User user = userRepository.findById(id)
  91.                 .orElseThrow(() -> new RuntimeException("User not found with id: " + id));
  92.         
  93.         userRepository.delete(user);
  94.         log.info("Deleted user: {}", user.getUsername());
  95.     }
  96.    
  97.     private UserDto convertToDto(User user) {
  98.         UserDto userDto = new UserDto();
  99.         userDto.setId(user.getId());
  100.         userDto.setUsername(user.getUsername());
  101.         userDto.setEmail(user.getEmail());
  102.         return userDto;
  103.     }
  104. }
复制代码

创建web模块的pom.xml:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3.          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5.     <modelVersion>4.0.0</modelVersion>
  6.     <parent>
  7.         <groupId>com.example</groupId>
  8.         <artifactId>multi-module-spring-boot</artifactId>
  9.         <version>1.0.0</version>
  10.         <relativePath>../pom.xml</relativePath>
  11.     </parent>
  12.     <artifactId>web</artifactId>
  13.     <packaging>jar</packaging>
  14.     <name>Web</name>
  15.     <description>Web module with controllers and web configurations</description>
  16.     <dependencies>
  17.         <dependency>
  18.             <groupId>com.example</groupId>
  19.             <artifactId>common</artifactId>
  20.             <version>${project.version}</version>
  21.         </dependency>
  22.         <dependency>
  23.             <groupId>com.example</groupId>
  24.             <artifactId>model</artifactId>
  25.             <version>${project.version}</version>
  26.         </dependency>
  27.         <dependency>
  28.             <groupId>com.example</groupId>
  29.             <artifactId>service</artifactId>
  30.             <version>${project.version}</version>
  31.         </dependency>
  32.         <dependency>
  33.             <groupId>org.springframework.boot</groupId>
  34.             <artifactId>spring-boot-starter-web</artifactId>
  35.         </dependency>
  36.         <dependency>
  37.             <groupId>org.springframework.boot</groupId>
  38.             <artifactId>spring-boot-starter-security</artifactId>
  39.         </dependency>
  40.         <dependency>
  41.             <groupId>org.projectlombok</groupId>
  42.             <artifactId>lombok</artifactId>
  43.         </dependency>
  44.         <dependency>
  45.             <groupId>org.junit.jupiter</groupId>
  46.             <artifactId>junit-jupiter</artifactId>
  47.             <scope>test</scope>
  48.         </dependency>
  49.         <dependency>
  50.             <groupId>org.springframework.boot</groupId>
  51.             <artifactId>spring-boot-starter-test</artifactId>
  52.             <scope>test</scope>
  53.         </dependency>
  54.         <dependency>
  55.             <groupId>org.springframework.security</groupId>
  56.             <artifactId>spring-security-test</artifactId>
  57.             <scope>test</scope>
  58.         </dependency>
  59.     </dependencies>
  60.     <build>
  61.         <plugins>
  62.             <plugin>
  63.                 <groupId>org.springframework.boot</groupId>
  64.                 <artifactId>spring-boot-maven-plugin</artifactId>
  65.                 <configuration>
  66.                     <mainClass>com.example.web.WebApplication</mainClass>
  67.                 </configuration>
  68.                 <executions>
  69.                     <execution>
  70.                         <goals>
  71.                             <goal>repackage</goal>
  72.                         </goals>
  73.                     </execution>
  74.                 </executions>
  75.             </plugin>
  76.         </plugins>
  77.     </build>
  78. </project>
复制代码

在web模块中,我们可以定义控制器和应用程序入口,例如:
  1. package com.example.web.controller;
  2. import com.example.model.dto.UserDto;
  3. import com.example.service.UserService;
  4. import lombok.RequiredArgsConstructor;
  5. import org.springframework.http.HttpStatus;
  6. import org.springframework.http.ResponseEntity;
  7. import org.springframework.web.bind.annotation.*;
  8. import javax.validation.Valid;
  9. import java.util.List;
  10. @RestController
  11. @RequestMapping("/api/users")
  12. @RequiredArgsConstructor
  13. public class UserController {
  14.    
  15.     private final UserService userService;
  16.    
  17.     @PostMapping
  18.     public ResponseEntity<UserDto> createUser(@Valid @RequestBody UserDto userDto) {
  19.         UserDto createdUser = userService.createUser(userDto);
  20.         return new ResponseEntity<>(createdUser, HttpStatus.CREATED);
  21.     }
  22.    
  23.     @GetMapping("/{id}")
  24.     public ResponseEntity<UserDto> getUserById(@PathVariable Long id) {
  25.         UserDto user = userService.getUserById(id);
  26.         return ResponseEntity.ok(user);
  27.     }
  28.    
  29.     @GetMapping("/username/{username}")
  30.     public ResponseEntity<UserDto> getUserByUsername(@PathVariable String username) {
  31.         UserDto user = userService.getUserByUsername(username);
  32.         return ResponseEntity.ok(user);
  33.     }
  34.    
  35.     @GetMapping
  36.     public ResponseEntity<List<UserDto>> getAllUsers() {
  37.         List<UserDto> users = userService.getAllUsers();
  38.         return ResponseEntity.ok(users);
  39.     }
  40.    
  41.     @PutMapping("/{id}")
  42.     public ResponseEntity<UserDto> updateUser(@PathVariable Long id, @Valid @RequestBody UserDto userDto) {
  43.         UserDto updatedUser = userService.updateUser(id, userDto);
  44.         return ResponseEntity.ok(updatedUser);
  45.     }
  46.    
  47.     @DeleteMapping("/{id}")
  48.     public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
  49.         userService.deleteUser(id);
  50.         return ResponseEntity.noContent().build();
  51.     }
  52. }
复制代码
  1. package com.example.web;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.context.annotation.ComponentScan;
  5. @SpringBootApplication
  6. @ComponentScan(basePackages = "com.example")
  7. public class WebApplication {
  8.    
  9.     public static void main(String[] args) {
  10.         SpringApplication.run(WebApplication.class, args);
  11.     }
  12. }
复制代码

在web模块的src/main/resources目录下,创建application.yml配置文件:
  1. server:
  2.   port: 8080
  3. spring:
  4.   application:
  5.     name: multi-module-spring-boot
  6.   
  7.   datasource:
  8.     url: jdbc:mysql://localhost:3306/demo_db?useSSL=false&serverTimezone=UTC&useLegacyDatetimeCode=false
  9.     username: root
  10.     password: password
  11.     driver-class-name: com.mysql.cj.jdbc.Driver
  12.   
  13.   jpa:
  14.     hibernate:
  15.       ddl-auto: update
  16.     properties:
  17.       hibernate:
  18.         dialect: org.hibernate.dialect.MySQL8Dialect
  19.         format_sql: true
  20.     show-sql: true
  21. logging:
  22.   level:
  23.     com.example: DEBUG
  24.     org.springframework.web: INFO
复制代码

4.2 构建和运行项目

要构建整个项目,可以在父项目目录下运行以下命令:
  1. mvn clean install
复制代码

要运行应用程序,可以在web模块目录下运行以下命令:
  1. mvn spring-boot:run
复制代码

或者,先构建整个项目,然后运行生成的JAR文件:
  1. mvn clean package
  2. java -jar web/target/web-1.0.0.jar
复制代码

5. 高级技巧和最佳实践

5.1 统一版本管理

在多模块项目中,统一版本管理是非常重要的。我们可以通过以下几种方式实现:

1. 使用父项目的dependencyManagement:如前面示例所示,在父项目的dependencyManagement部分声明所有依赖的版本,子模块在使用时无需指定版本。
2. 使用属性定义版本:在父项目的properties部分定义版本号,然后在dependencyManagement中引用这些属性。这样可以方便地统一升级版本。
3. 使用BOM(Bill of Materials):对于一些框架(如Spring Boot、Spring Cloud),它们提供了BOM依赖,可以通过importscope导入,统一管理相关依赖的版本。

使用父项目的dependencyManagement:如前面示例所示,在父项目的dependencyManagement部分声明所有依赖的版本,子模块在使用时无需指定版本。

使用属性定义版本:在父项目的properties部分定义版本号,然后在dependencyManagement中引用这些属性。这样可以方便地统一升级版本。

使用BOM(Bill of Materials):对于一些框架(如Spring Boot、Spring Cloud),它们提供了BOM依赖,可以通过importscope导入,统一管理相关依赖的版本。

5.2 依赖范围优化

合理使用依赖范围(scope)可以避免不必要的依赖传递,减小最终构建产物的大小。常见的依赖范围有:

1. compile:默认范围,依赖在编译、测试和运行时都需要。
2. provided:依赖在编译和测试时需要,但在运行时由容器提供(如Servlet API)。
3. runtime:依赖在测试和运行时需要,但在编译时不需要(如JDBC驱动)。
4. test:依赖仅在测试时需要(如JUnit)。
5. system:类似于provided,但需要显式指定依赖的JAR文件路径。

5.3 可选依赖

使用可选依赖(optional dependencies)可以避免传递依赖带来的问题。当一个模块依赖另一个模块,但这个依赖不是所有使用场景都需要的,可以将其标记为可选:
  1. <dependency>
  2.     <groupId>com.example</groupId>
  3.     <artifactId>optional-module</artifactId>
  4.     <version>${project.version}</version>
  5.     <optional>true</optional>
  6. </dependency>
复制代码

5.4 资源过滤

Maven支持资源过滤,可以在构建过程中替换资源文件中的占位符。这在多模块项目中特别有用,可以为不同环境(开发、测试、生产)提供不同的配置。

在父项目的pom.xml中配置资源过滤:
  1. <build>
  2.     <resources>
  3.         <resource>
  4.             <directory>src/main/resources</directory>
  5.             <filtering>true</filtering>
  6.         </resource>
  7.     </resources>
  8. </build>
复制代码

然后,在资源文件中使用占位符:
  1. server:
  2.   port: ${server.port}
复制代码

在pom.xml中定义属性:
  1. <properties>
  2.     <server.port>8080</server.port>
  3. </properties>
复制代码

或者通过命令行参数指定:
  1. mvn clean install -Dserver.port=8081
复制代码

5.5 多环境构建

Maven支持通过Profile来支持多环境构建。可以为不同的环境(开发、测试、生产)定义不同的配置,然后在构建时指定激活的Profile。

在父项目的pom.xml中定义Profile:
  1. <profiles>
  2.     <profile>
  3.         <id>dev</id>
  4.         <activation>
  5.             <activeByDefault>true</activeByDefault>
  6.         </activation>
  7.         <properties>
  8.             <environment>dev</environment>
  9.             <server.port>8080</server.port>
  10.             <datasource.url>jdbc:mysql://localhost:3306/dev_db</datasource.url>
  11.             <datasource.username>dev_user</datasource.username>
  12.             <datasource.password>dev_password</datasource.password>
  13.         </properties>
  14.     </profile>
  15.     <profile>
  16.         <id>test</id>
  17.         <properties>
  18.             <environment>test</environment>
  19.             <server.port>8080</server.port>
  20.             <datasource.url>jdbc:mysql://test-db:3306/test_db</datasource.url>
  21.             <datasource.username>test_user</datasource.username>
  22.             <datasource.password>test_password</datasource.password>
  23.         </properties>
  24.     </profile>
  25.     <profile>
  26.         <id>prod</id>
  27.         <properties>
  28.             <environment>prod</environment>
  29.             <server.port>80</server.port>
  30.             <datasource.url>jdbc:mysql://prod-db:3306/prod_db</datasource.url>
  31.             <datasource.username>prod_user</datasource.username>
  32.             <datasource.password>prod_password</datasource.password>
  33.         </properties>
  34.     </profile>
  35. </profiles>
复制代码

构建时指定Profile:
  1. mvn clean install -Pprod
复制代码

5.6 模块聚合的最佳实践

在设计多模块项目结构时,应该遵循一些最佳实践:

1. 合理划分模块:根据功能或层次划分模块,每个模块应该有明确的职责。
2. 避免循环依赖:模块间的依赖关系应该是单向的,避免循环依赖。
3. 最小化依赖范围:模块应该只依赖它真正需要的模块,避免不必要的依赖。
4. 使用接口隔离:模块间通过接口交互,而不是直接依赖实现类。
5. 考虑部署需求:根据部署需求设计模块结构,例如,可能需要将Web层和业务层分开部署。

5.7 持续集成与多模块项目

在持续集成环境中构建多模块项目时,可以考虑以下策略:

1. 增量构建:当只有部分模块的代码发生变化时,只构建这些模块及其依赖的模块。
2. 并行构建:使用Maven的并行构建功能,充分利用多核CPU提高构建速度。
3. 构建缓存:使用构建缓存(如Maven的本地仓库或远程缓存)避免重复构建未变化的模块。
4. 构建分析:使用Maven的构建分析工具(如maven-dependency-plugin)分析依赖关系,发现潜在问题。

6. 常见问题与解决方案

6.1 循环依赖问题

循环依赖是指两个或多个模块之间相互依赖,例如:模块A依赖模块B,同时模块B也依赖模块A。这种情况会导致Maven无法确定构建顺序,从而构建失败。

解决方案:

1. 重构代码:将共同依赖的代码提取到一个新的模块中,使原来的模块都依赖这个新模块。
2. 使用接口:定义接口模块,实现模块依赖接口模块,而不是直接相互依赖。
3. 使用事件机制:通过事件机制实现模块间的通信,而不是直接调用。

6.2 版本冲突问题

在多模块项目中,不同模块可能依赖同一个库的不同版本,导致版本冲突。

解决方案:

1. 使用父项目的dependencyManagement:在父项目中统一管理依赖版本。
2. 使用mvn dependency:tree:分析依赖树,找出冲突的依赖。
3. 使用<exclusions>:排除传递依赖中不需要的版本。
4. 使用<dependencyManagement>:在子模块中覆盖父项目定义的版本。

6.3 构建速度慢问题

随着模块数量的增加,多模块项目的构建时间可能会变长。

解决方案:

1. 使用并行构建:通过-T选项启用并行构建。
2. 增量构建:只构建发生变化的模块及其依赖的模块。
3. 优化测试:减少不必要的测试,或者将测试分类,只运行必要的测试。
4. 使用构建缓存:使用Maven的本地仓库或远程缓存。
5. 优化插件配置:禁用不必要的插件,或者优化插件配置。

6.4 模块间通信问题

在多模块项目中,模块间的通信可能会变得复杂,特别是在分布式部署的情况下。

解决方案:

1. 使用API模块:定义专门的API模块,包含接口和数据传输对象。
2. 使用消息队列:通过消息队列实现异步通信。
3. 使用REST API:通过HTTP REST API实现模块间通信。
4. 使用服务注册与发现:在微服务架构中,使用服务注册与发现机制。

7. 总结

Maven父子项目(多模块项目)是一种强大的项目组织结构,特别适合大型复杂项目的开发和管理。通过合理地划分模块,统一管理依赖和配置,可以显著提高开发效率和代码质量。

在实际应用中,我们应该根据项目的具体需求,合理设计模块结构,遵循最佳实践,避免常见问题。同时,充分利用Maven提供的各种功能,如依赖管理、并行构建、多环境构建等,优化构建过程,提高开发效率。

通过本文的介绍,相信读者已经对Maven父子项目有了深入的了解,并能够在实际项目中灵活应用这些知识和技巧,构建高效、可维护的多模块项目。
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则