Spring Boot 项目初始化与工程结构

举报
哈基米 发表于 2026/11/01 19:32:32 2026/11/01
【摘要】 上一篇文章我把 JDK21、MySQL、RabbitMQ 全装好了,今天直接上手 Spring Boot 项目初始化。我见过太多新手(包括当年的我)在 IDEA 里新建一个普通 Maven 项目,然后手动写十几个依赖,最

上一篇文章我把 JDK21、MySQL、RabbitMQ 全装好了,今天直接上手 Spring Boot 项目初始化。我见过太多新手(包括当年的我)在 IDEA 里新建一个普通 Maven 项目,然后手动写十几个依赖,最后启动不了。其实 Spring Initializr 已经把 90% 的脏活累活替你做了。

用 Spring Initializr 踩的第一个坑

我第一次用 Initializr 时,图快直接在 start.spring.io 上选了 Java 21 和 Spring Boot 3.x,然后点了 Generate。下载解压后 IDEA 打开,一切正常——直到我加了 spring-boot-starter-data-redis 之后,启动时报 NoClassDefFoundError: jakarta.servlet.ServletException。

后来我才知道,Spring Boot 3 已经切到 Jakarta EE,而有些旧教程还在用 javax.servlet。我当时手滑加了一个 javax.servlet 的依赖,版本冲突直接把类加载搞崩了。解决方式是在 pom.xml 里加 <exclusion> 把旧包踢掉,或者干脆不用那个依赖。

工程结构:我早期项目的「Sicily」架构

刚工作时我做过一个订单系统,当时不懂约定优于配置,硬生生把包名搞成了 com.sicily.controller、com.sicily.service、com.sicily.dao。项目小的时候还好,后来加了支付模块和物流模块,@SpringBootApplication 默认只扫描当前包及子包,结果控制器 bean 死活注入不进来。

那天我盯着 IDEA 的报错看了半小时,最后发现启动类在 com.sicily,而支付控制器在 com.sicily.payment.controller——按理说也是子包啊?哦,原来我把启动类不小心写到了 com.sicily.config 下面。所以这里分享一个我现在的标准布局:

src/main/java/com/example/mall/
├── MallApplication.java          # 启动类
├── controller/                   # 控制器
│   ├── ProductController.java
│   └── OrderController.java
├── service/                      # 业务逻辑
│   ├── ProductService.java
│   └── OrderService.java
├── repository/                   # 数据访问(JPA/MyBatis)
│   └── ProductRepository.java
├── entity/                       # 实体类
│   └── Product.java
├── dto/                          # 请求/响应封装
│   ├── ProductDTO.java
│   └── ProductVO.java
└── config/                       # 配置类
    └── WebMvcConfig.java

application.yml 的分层配置

配置文件也是新手重灾区。我见过有人把所有配置(数据库、Redis、RabbitMQ、第三方密钥)全写在 application.yml 里,然后 dev/test/prod 三个环境靠手动注释切换。这种做法在本地没问题,一到服务器必翻车。

我现在统一按环境拆分:

# application.yml (公共配置)
spring:
  profiles:
    active: dev

---
# application-dev.yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mall?useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root
  data:
    redis:
      host: localhost
      port: 6379
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

这里有个坑:spring.profiles.active 写在 application.yml 里是公共配置,但如果你的项目用 Docker 部署,最佳实践是不要把 active 写死,而是通过启动参数 --spring.profiles.active=prod 注入,这样同一份 jar 包能跑所有环境。

第一个接口:别把 Controller 放错包

结构理清楚后,我写了一个最简单的商品列表接口。这里最容易出现的错误是 @RestController 和 @RequestMapping 的路径写死全大写,或者 method 没指定导致 GET/POST 混用。

我的建议是,接口路径全小写,用名词复数,RESTful 风格统一:

@RestController
@RequestMapping("/api/v1/products")
@RequiredArgsConstructor
public class ProductController {

    private final ProductService productService;

    @GetMapping
    public List<ProductVO> list(@RequestParam(defaultValue = "1") Integer page,
                                @RequestParam(defaultValue = "10") Integer size) {
        return productService.list(page, size)
                .stream()
                .map(ProductVO::from)
                .toList();
    }
}

注意 @RequiredArgsConstructor 是 Lombok 的,比 @Autowired 字段注入安全得多——至少它强制你声明 final,编译期就能发现没注入的依赖。

启动类与扫描范围的隐形陷阱

最后说一个最隐蔽的问题。如果你把启动类 @SpringBootApplication 放在了一个子包里,而你的实体类(@Entity)又在另一个子包,JPA 扫描不到实体,启动时不报错,但查询的时候会抛 MappingException: No entity class for SomeEntity。

我当年在一个微服务里踩过这个坑。解决方式要么是把启动类提到最外层,要么是用 @SpringBootApplication(scanBasePackages = {"com.sicily"}) 手动指定。不过我更推荐前者——约定俗成的目录结构能减少 80% 的脑力消耗。

---

今天把项目初始化和工程结构聊完了。明天开始写 MyBatis-Plus 做第一张表的 CRUD,到时候会碰到 typeAliasesPackage 没配导致 mapper 注入失败的问题,到时候再细说。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。