Spring Boot多模块工程实战:从零搭建企业级项目骨架

📅 2026/8/9 3:20:46
Spring Boot多模块工程实战:从零搭建企业级项目骨架
最近在整理技术文档时发现很多开发者对如何构建一个结构清晰、可维护性强的项目骨架感到困惑。尤其是在处理多模块、多环境配置以及依赖管理时常常陷入配置文件的“泥潭”。本文将以一个模拟的“港综签到系统”后端项目为例手把手带你从零搭建一个Spring Boot多模块工程整合MyBatis-Plus、Apollo配置中心等常用组件并分享一套经过实战检验的工程化最佳实践。无论你是刚接触企业级开发的应届生还是希望优化现有项目结构的中级开发者都能从中获得可直接复用的代码模板和配置思路。1. 项目背景与核心需求分析在开始敲代码之前明确我们要解决什么问题至关重要。假设我们正在开发一个名为“港综签到系统”的后台服务其核心业务逻辑可能包括用户每日签到、积分累计、奖励兑换等。这类系统通常面临以下技术挑战配置繁杂数据库连接、Redis地址、第三方API密钥等配置项分散在各个application.properties文件中难以统一管理更别提多环境开发、测试、生产的隔离了。代码耦合所有功能代码堆在一个模块里随着业务增长代码库会变得臃肿不堪编译时间变长团队协作也容易冲突。依赖混乱第三方库的版本冲突是常见痛点一个模块升级了某个库可能导致另一个模块运行失败。缺乏规范代码风格、日志格式、异常处理方式不一影响代码可读性和后期维护。为了解决这些问题我们将采用以下技术栈和架构思想Spring Boot 2.7.x作为基础的快速开发框架。Maven进行多模块管理和依赖控制。MyBatis-Plus 3.5.x简化数据库操作。Apollo作为分布式配置中心实现配置的集中管理、实时推送。多模块架构按功能边界拆分模块实现高内聚、低耦合。2. 环境准备与版本说明在开始构建之前请确保你的本地开发环境已就绪。以下版本为本文撰写时的稳定版本你可以根据实际情况调整。操作系统Windows 10/11, macOS, 或 Linux (如 Ubuntu 20.04)。JavaJDK 8 或 JDK 11 (推荐 JDK 11 LTS版本长期支持)。通过java -version验证。Maven3.6.x 及以上版本。通过mvn -v验证。IDEIntelliJ IDEA (推荐) 或 Eclipse with STS。本文示例使用 IntelliJ IDEA。数据库MySQL 5.7 或 8.0。确保已安装并启动。Apollo配置中心需要提前部署或使用官方提供的Quick Start本地环境。本文会给出集成方式假设你已有一个可用的Apollo服务地址如http://localhost:8080。项目依赖核心版本(在父POM中统一定义)spring-boot.version2.7.18/spring-boot.version mybatis-plus.version3.5.5/mybatis-plus.version apollo-client.version2.1.0/apollo-client.version注意版本号在父POM的properties标签中定义子模块继承这是解决依赖冲突的关键第一步。3. 多模块项目结构设计与搭建一个清晰的项目结构是成功的一半。我们将项目拆分为以下几个模块gangzong-sign-system (父工程打包方式为pom) ├── gangzong-common -- 通用工具模块 ├── gangzong-dao -- 数据持久层模块 ├── gangzong-service -- 业务逻辑层模块 └── gangzong-web -- Web接口层模块 (可独立启动)3.1 创建父工程 (gangzong-sign-system)首先使用IDE或命令行创建一个Maven项目选择pom作为打包方式。父工程pom.xml核心部分?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion groupIdcom.gangzong/groupId artifactIdgangzong-sign-system/artifactId version1.0.0-SNAPSHOT/version packagingpom/packaging !-- 关键打包方式为pom -- modules modulegangzong-common/module modulegangzong-dao/module modulegangzong-service/module modulegangzong-web/module /modules parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent properties java.version11/java.version project.build.sourceEncodingUTF-8/project.build.sourceEncoding !-- 统一版本管理 -- mybatis-plus.version3.5.5/mybatis-plus.version apollo-client.version2.1.0/apollo-client.version lombok.version1.18.30/lombok.version /properties dependencyManagement dependencies !-- Spring Boot 基础依赖已在 parent 中管理 -- !-- MyBatis-Plus -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version${mybatis-plus.version}/version /dependency !-- Apollo Client -- dependency groupIdcom.ctrip.framework.apollo/groupId artifactIdapollo-client/artifactId version${apollo-client.version}/version /dependency !-- Lombok -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId version${lombok.version}/version optionaltrue/optional /dependency /dependencies /dependencyManagement dependencies !-- 所有子模块都会继承的通用依赖比如单元测试 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency /dependencies /project关键点packagingpom/packaging声明此为聚合模块不生成具体jar/war。modules列出了所有子模块。dependencyManagement这里是版本管理的核心。在此处声明依赖及其版本子模块引用时无需再写版本号确保了版本统一。父工程dependencies中的依赖会被所有子模块继承适合放spring-boot-starter-test这类通用依赖。3.2 创建通用模块 (gangzong-common)此模块存放项目全局使用的工具类、常量、枚举、通用DTO/VO、异常定义等。子模块pom.xml?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion parent groupIdcom.gangzong/groupId artifactIdgangzong-sign-system/artifactId version1.0.0-SNAPSHOT/version /parent artifactIdgangzong-common/artifactId dependencies !-- 常用工具包 -- dependency groupIdorg.apache.commons/groupId artifactIdcommons-lang3/artifactId /dependency dependency groupIdcom.google.guava/groupId artifactIdguava/artifactId version31.1-jre/version !-- 注意此版本在父pom未管理需单独指定 -- /dependency !-- JSON处理 -- dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId /dependency !-- Lombok -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies /project示例工具类src/main/java/com/gangzong/common/utils/DateUtil.javapackage com.gangzong.common.utils; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; /** * 日期时间工具类 */ public class DateUtil { private static final DateTimeFormatter DEFAULT_FORMATTER DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss); public static String formatNow() { return LocalDateTime.now().format(DEFAULT_FORMATTER); } // 其他工具方法... }3.3 创建数据层模块 (gangzong-dao)此模块专注于数据库交互包含实体类(Entity)、Mapper接口、以及可选的XML映射文件。子模块pom.xml?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion parent groupIdcom.gangzong/groupId artifactIdgangzong-sign-system/artifactId version1.0.0-SNAPSHOT/version /parent artifactIdgangzong-dao/artifactId dependencies !-- 依赖 common 模块 -- dependency groupIdcom.gangzong/groupId artifactIdgangzong-common/artifactId version${project.version}/version !-- 使用当前项目版本 -- /dependency !-- MyBatis-Plus -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId /dependency !-- MySQL驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency /dependencies /project示例实体类src/main/java/com/gangzong/dao/entity/UserSignRecord.javapackage com.gangzong.dao.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.time.LocalDate; Data TableName(t_user_sign_record) // 指定表名 public class UserSignRecord { /** * 主键ID */ TableId(type IdType.AUTO) private Long id; /** * 用户ID */ private Long userId; /** * 签到日期 */ private LocalDate signDate; /** * 连续签到天数 */ private Integer continuousDays; /** * 本次获得积分 */ private Integer earnedPoints; /** * 创建时间 */ private LocalDate createTime; }示例Mapper接口src/main/java/com/gangzong/dao/mapper/UserSignRecordMapper.javapackage com.gangzong.dao.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.gangzong.dao.entity.UserSignRecord; import org.apache.ibatis.annotations.Mapper; Mapper // 关键注解让Spring管理此接口的代理对象 public interface UserSignRecordMapper extends BaseMapperUserSignRecord { // 可以在此定义自定义的复杂SQL方法 // UserSignRecord selectLatestByUserId(Param(userId) Long userId); }注意gangzong-dao模块不包含任何Spring Boot启动类它只是一个被其他模块依赖的jar包。3.4 创建业务层模块 (gangzong-service)此模块包含具体的业务逻辑实现依赖于dao模块。子模块pom.xml?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion parent groupIdcom.gangzong/groupId artifactIdgangzong-sign-system/artifactId version1.0.0-SNAPSHOT/version /parent artifactIdgangzong-service/artifactId dependencies !-- 依赖 dao 模块 -- dependency groupIdcom.gangzong/groupId artifactIdgangzong-dao/artifactId version${project.version}/version /dependency !-- Spring Boot 基础Web支持用于事务管理等 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-aop/artifactId /dependency /dependencies /project示例Service接口与实现// 文件路径src/main/java/com/gangzong/service/SignService.java package com.gangzong.service; public interface SignService { /** * 用户签到 * param userId 用户ID * return 签到结果信息 */ SignResult sign(Long userId); } // 文件路径src/main/java/com/gangzong/service/impl/SignServiceImpl.java package com.gangzong.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.gangzong.dao.entity.UserSignRecord; import com.gangzong.dao.mapper.UserSignRecordMapper; import com.gangzong.service.SignService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; Slf4j Service RequiredArgsConstructor // Lombok注解为final字段生成构造函数 public class SignServiceImpl implements SignService { private final UserSignRecordMapper userSignRecordMapper; Override Transactional(rollbackFor Exception.class) // 声明式事务 public SignResult sign(Long userId) { LocalDate today LocalDate.now(); // 1. 检查今日是否已签到 LambdaQueryWrapperUserSignRecord wrapper new LambdaQueryWrapper(); wrapper.eq(UserSignRecord::getUserId, userId) .eq(UserSignRecord::getSignDate, today); Long count userSignRecordMapper.selectCount(wrapper); if (count 0) { return SignResult.fail(今日已签到请勿重复操作); } // 2. 查询昨日签到记录计算连续天数 LocalDate yesterday today.minusDays(1); wrapper.clear(); wrapper.eq(UserSignRecord::getUserId, userId) .eq(UserSignRecord::getSignDate, yesterday); UserSignRecord lastRecord userSignRecordMapper.selectOne(wrapper); int continuousDays (lastRecord ! null) ? lastRecord.getContinuousDays() 1 : 1; // 3. 根据连续天数计算本次积分 (简单规则连续天数即为积分) int earnedPoints continuousDays; // 4. 保存今日签到记录 UserSignRecord newRecord new UserSignRecord(); newRecord.setUserId(userId); newRecord.setSignDate(today); newRecord.setContinuousDays(continuousDays); newRecord.setEarnedPoints(earnedPoints); newRecord.setCreateTime(LocalDate.now()); userSignRecordMapper.insert(newRecord); log.info(用户[{}]签到成功连续签到[{}]天获得积分[{}], userId, continuousDays, earnedPoints); return SignResult.success(continuousDays, earnedPoints); } }3.5 创建Web接口层模块 (gangzong-web)这是可独立启动的Spring Boot应用模块提供RESTful API依赖于service模块。子模块pom.xml?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion parent groupIdcom.gangzong/groupId artifactIdgangzong-sign-system/artifactId version1.0.0-SNAPSHOT/version /parent artifactIdgangzong-web/artifactId !-- 注意这是唯一打包方式为jar/war的可启动模块 -- packagingjar/packaging dependencies !-- 依赖 service 模块 -- dependency groupIdcom.gangzong/groupId artifactIdgangzong-service/artifactId version${project.version}/version /dependency !-- Spring Boot Web -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- Apollo Client 配置中心 -- dependency groupIdcom.ctrip.framework.apollo/groupId artifactIdapollo-client/artifactId /dependency /dependencies build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin /plugins /build /project启动类与配置文件// 文件路径src/main/java/com/gangzong/web/GangzongSignApplication.java package com.gangzong.web; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; SpringBootApplication ComponentScan(basePackages {com.gangzong}) // 扫描整个项目下的组件 public class GangzongSignApplication { public static void main(String[] args) { SpringApplication.run(GangzongSignApplication.class, args); } }Apollo配置集成src/main/resources/application.yml# 应用基础配置 spring: application: name: gangzong-sign-web # Apollo中对应的AppId # Apollo配置中心设置 app: id: gangzong-sign-web # 必须与spring.application.name一致 apollo: bootstrap: enabled: true # 启用Apollo配置加载 namespaces: application # 加载的命名空间多个用逗号分隔 meta: http://localhost:8080 # Apollo Meta Server地址根据实际环境修改Controller示例// 文件路径src/main/java/com/gangzong/web/controller/SignController.java package com.gangzong.web.controller; import com.gangzong.common.model.ApiResponse; import com.gangzong.service.SignService; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; RestController RequestMapping(/api/sign) RequiredArgsConstructor public class SignController { private final SignService signService; PostMapping public ApiResponseSignResult doSign(RequestParam Long userId) { // 参数校验可以放在这里或使用Validated if (userId null || userId 0) { return ApiResponse.fail(用户ID无效); } SignResult result signService.sign(userId); return result.isSuccess() ? ApiResponse.success(result) : ApiResponse.fail(result.getMessage()); } }4. Apollo配置中心集成与使用为什么需要配置中心想象一下你的数据库密码变了或者要增加一个功能开关难道要重新打包部署所有服务吗配置中心就是为了实现配置的集中管理和实时生效。4.1 在Apollo中创建配置访问你的Apollo管理端如http://localhost:8070。创建一个名为gangzong-sign-web的应用对应app.id。在application命名空间下添加以下配置# 数据库配置 spring.datasource.url jdbc:mysql://localhost:3306/gangzong_sign?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai spring.datasource.username root spring.datasource.password your_password_here spring.datasource.driver-class-name com.mysql.cj.jdbc.Driver # MyBatis-Plus配置 mybatis-plus.configuration.log-impl org.apache.ibatis.logging.stdout.StdOutImpl # 开发环境输出SQL日志 mybatis-plus.global-config.db-config.id-type auto mybatis-plus.global-config.db-config.table-prefix t_ # 业务相关配置 sign.points.base 1 sign.points.continuous.bonus true发布配置。4.2 在代码中读取配置在gangzong-web模块中你可以通过Value注解或ConfigurationProperties来读取Apollo中的配置。// 文件路径src/main/java/com/gangzong/web/config/SignConfig.java package com.gangzong.web.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; Data Configuration ConfigurationProperties(prefix sign.points) public class SignConfig { private Integer base; private Boolean continuousBonus; }然后在Service中注入使用Service RequiredArgsConstructor public class SignServiceImpl implements SignService { private final UserSignRecordMapper userSignRecordMapper; private final SignConfig signConfig; // 注入配置类 Override public SignResult sign(Long userId) { // ... // 使用配置 int earnedPoints signConfig.getBase(); if (signConfig.getContinuousBonus() continuousDays 1) { earnedPoints (continuousDays - 1); // 连续签到额外奖励 } // ... } }4.3 配置动态刷新Apollo的优势在于配置修改后可以实时推送到应用。对于ConfigurationProperties绑定的类需要加上RefreshScope注解。RefreshScope // 添加此注解 Configuration ConfigurationProperties(prefix sign.points) public class SignConfig { // ... }这样当你在Apollo管理界面修改sign.points.base的值并发布后应用内对应的属性值会自动更新无需重启服务。5. 完整项目构建与运行编译整个项目在父工程根目录下执行mvn clean compile。打包执行mvn clean package会在gangzong-web/target目录下生成可执行的jar包。运行在IDE中直接运行GangzongSignApplication的main方法。或者使用命令java -jar gangzong-web/target/gangzong-web-1.0.0-SNAPSHOT.jar。验证启动后访问http://localhost:8080/api/sign?userId1(假设你的服务端口是8080)查看签到接口是否正常工作。可以通过查看控制台SQL日志和数据库记录来验证。6. 常见问题与排查思路在搭建和运行过程中你可能会遇到以下问题问题现象可能原因排查步骤与解决方案启动时报No qualifying bean of type ‘xxxMapper‘1. Mapper接口未被扫描到。2.Mapper注解缺失或扫描路径不对。1. 检查启动类上的MapperScan(“com.gangzong.dao.mapper”)或确保每个Mapper接口有Mapper。2. 检查gangzong-dao模块是否被正确依赖和打包。Apollo配置不生效1.app.id与Apollo中创建的应用名不一致。2. Apollo Meta Server地址 (apollo.meta) 错误或网络不通。3. 未在bootstrap.yml/properties中启用Apollo。1. 核对application.yml中的app.id。2. 检查Apollo服务状态和网络连接。3. 确保apollo.bootstrap.enabledtrue且配置在bootstrap.yml或application.yml的顶层。模块间依赖找不到类1. 子模块未正确安装到本地仓库。2. 依赖的模块版本号不对。1. 在父工程目录执行mvn clean install将模块安装到本地Maven仓库。2. 检查子模块pom.xml中依赖的版本号\${project.version}是否正确解析。事务Transactional不生效1. 方法非public。2. 异常被捕获未抛出。3. 数据库引擎不支持事务如MyISAM。1. 确保方法为public。2. 确保异常抛出或指定rollbackFor。3. 确认MySQL表引擎为InnoDB。配置文件优先级混乱Spring Boot加载配置文件的顺序问题。记住优先级bootstrap.ymlapplication.yml Apollo配置。Apollo配置具有最高优先级默认。7. 最佳实践与工程建议版本统一管理坚持在父POM的dependencyManagement中管理所有第三方依赖版本这是避免“Jar Hell”的最有效手段。模块职责单一common放工具dao只做数据操作service处理业务逻辑web负责交互。禁止跨层调用例如web层直接调用dao。配置外部化所有可能因环境而变的配置数据库、Redis、开关都必须放到配置中心如Apollo或外部配置文件绝对不要硬编码在代码中。日志规范使用SLF4J门面配合Logback或Log4j2。日志级别合理设置关键业务操作、异常必须记录日志并带上可追踪的请求ID。Slf4j // 使用Lombok注解简化 Service public class MyService { public void doBusiness(String param) { log.info(“业务开始参数: {}”, param); // 使用参数化占位符{}不要用字符串拼接 try { // ... } catch (Exception e) { log.error(“业务处理失败参数: {}”, param, e); // 异常必须作为最后一个参数传入 throw new BusinessException(“业务异常”, e); } } }异常处理定义清晰的业务异常体系在Web层使用ControllerAdvice或RestControllerAdvice进行全局异常处理返回统一的错误响应格式。接口设计RESTful风格使用HTTP状态码表达结果如200成功400客户端错误500服务端错误。响应体统一封装。数据库操作使用MyBatis-Plus提供的Lambda查询避免SQL注入。复杂查询写在XML中并做好注释。涉及删除和更新操作必须加WHERE条件并在测试环境充分验证。环境隔离使用Apollo的Namespace命名空间功能严格隔离开发、测试、生产环境的配置。生产环境的配置修改必须走严格的审批和灰度发布流程。代码质量集成Checkstyle、SpotBugs、Sonar等代码检查工具在CI/CD流水线中设置质量门禁。通过以上步骤你不仅搭建了一个可运行的多模块Spring Boot项目更掌握了一套企业级项目开发的工程化方法论。从清晰的模块划分到统一的依赖管理再到配置中心的集成这些实践能显著提升项目的可维护性、可扩展性和团队协作效率。接下来你可以在此基础上继续集成Redis缓存、消息队列、分布式链路追踪等更多中间件构建更健壮的业务系统。