SpringBoot项目在初期用单个工程就能快速启动,但业务模块增多后,包目录会越来越深,公共工具类散落各处,一次全量构建也可能拖慢开发节奏。Maven多模块父子工程正是针对这类膨胀问题的结构优化方案:用父工程统一管理依赖版本和插件,用子模块划分职责边界,再通过模块依赖完成组装。本文会从工程结构、创建步骤、优势场景和避坑要点几个方面展开。

一、先理解聚合与继承:多模块工程的核心机制
Maven多模块父子工程并不是一个单独的新项目类型,而是Maven聚合(aggregation)与继承(inheritance)两种能力组合后的工程组织方式。聚合强调一个父工程可以同时管理多个子模块,父工程的<packaging>必须设置为pom,并在<modules>中声明所有子模块。Maven执行mvn install时会按照依赖关系构建整个模块集合。
继承则解决版本管理问题。子模块通过<parent>指向父工程后,可以继承父工程中定义的<dependencyManagement>和<pluginManagement>。子模块引入依赖时不用重复写版本号,版本提升或降级只需要改父工程一处。SpringBoot官方提供的spring-boot-starter-parent本质上就是一个父POM,项目里如果已经有自己的父工程,可以在父工程的<dependencyManagement>中导入spring-boot-dependencies,同样能达到统一管理SpringBoot依赖版本的效果。
常见的模块划分方式是:parent负责版本与聚合,common存放工具类、异常、常量、DTO等公共代码,service负责业务逻辑,web只负责接口暴露和请求参数处理。依赖方向保持web依赖service,service依赖common,不要反向引用。这样的单向依赖可以让模块边界清晰,也能减少循环依赖风险。
二、从零创建SpringBoot多模块父子工程
下面以一个demo-parent工程为例,创建common、service、web三个子模块。先建一个空目录,再创建父工程的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>
<groupId>com.demo</groupId>
<artifactId>demo-parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
<module>common</module>
<module>service</module>
<module>web</module>
</modules>
<properties>
<java.version>17</java.version>
<spring-boot.version>3.2.5</spring-boot.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
接着创建common模块,目录为common,并新建它的pom.xml。该模块的parent指向demo-parent,通常只需要引入spring-boot-starter以获取基础能力,如果只是纯工具模块,也可以不引入Spring依赖。
<?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">
<parent>
<groupId>com.demo</groupId>
<artifactId>demo-parent</artifactId>
<version>1.0.0</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>common</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
service模块需要依赖common,这样它可以复用工具类和公共DTO。业务接口可以定义在service模块,也可以拆成api和impl两个模块,这里先保持简单。
<?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">
<parent>
<groupId>com.demo</groupId>
<artifactId>demo-parent</artifactId>
<version>1.0.0</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>service</artifactId>
<dependencies>
<dependency>
<groupId>com.demo</groupId>
<artifactId>common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
web模块是最终可运行模块,它需要依赖service,并引入spring-boot-starter-web。启动类放在com.demo.web包下,同时配置组件扫描范围覆盖com.demo,这样才能加载到service和common中的Spring组件。
<?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">
<parent>
<groupId>com.demo</groupId>
<artifactId>demo-parent</artifactId>
<version>1.0.0</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>web</artifactId>
<dependencies>
<dependency>
<groupId>com.demo</groupId>
<artifactId>service</artifactId>
<version>${project.version}</version>
</dependency>
<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>
</plugin>
</plugins>
</build>
</project>
启动类的示例代码如下:
package com.demo.web;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan(basePackages = {"com.demo"})
public class WebApplication {
public static void main(String[] args) {
SpringApplication.run(WebApplication.class, args);
}
}
整个工程的目录结构可以规划为下面这样,所有子模块都在父工程目录下,pom文件与src目录分离:
demo-parent
pom.xml
common/pom.xml
common/src/main/java/com/demo/common
service/pom.xml
service/src/main/java/com/demo/service
web/pom.xml
web/src/main/java/com/demo/web
三、多模块父子工程带来的实际优势
版本管理集中是最直接的好处。单体项目里如果每个模块都各自写依赖版本,升级SpringBoot时需要全局搜索替换,容易漏改。多模块工程把spring-boot-dependencies或spring-boot-starter-parent放在父POM中,子模块只声明groupId和artifactId,版本冲突概率大幅降低。遇到需要统一升级安全补丁时,通常只改父POM一处即可。
第二个优势是构建和复用边界更清晰。公共模块可以单独打成jar供其他系统使用,职责边界也通过目录和依赖关系固定下来。比如common模块不应该依赖业务模块,如果误引入,Maven构建时会因为找不到依赖而提前暴露问题。团队协作时,每个人负责不同模块,代码冲突范围更小。
此外,多模块结构对日后向微服务演进也比较友好。虽然多模块不等于微服务,但清晰的模块边界会让拆分服务时工作量更小。CI/CD流水线可以按模块选择构建任务,不必每次提交都全量编译,开发期反馈速度会更快。
四、适用场景与常见避坑点
并不是所有项目都适合一开始就拆多模块。如果一个内部管理系统只有两三个接口,或者项目体量很小,多模块反而增加目录层级和POM维护成本。通常当项目出现明显的公共代码沉淀、多人并行开发、多个可复用组件,或者计划拆分服务时,再切换到多模块结构收益更高。
实际搭建时有几个坑要避开。第一,父工程的<packaging>必须是pom,否则Maven不会把它当作聚合工程。第二,子模块的<relativePath>默认会向上找父POM,如果目录结构与标准Maven多模块不一致,需要显式指定相对路径。第三,模块之间要避免循环依赖,例如common依赖service,service又依赖common,Maven reactor构建时会报错。
SpringBoot启动类所在的包名也要格外注意。默认@SpringBootApplication只扫描启动类所在包及其子包,如果启动类在com.demo.web,而Service在com.demo.service,就必须通过@ComponentScan扩大扫描范围,或者把所有模块的根包统一为com.demo再放置启动类在根包下。最后,子模块依赖版本尽量使用${project.version}统一管理,而不是在各模块写死不同版本,否则会出现同一个父工程下版本漂移的问题。
SpringBootMaven多模块父子工程修改时间:2026-09-22 23:09:02