Spring Boot 项目初始化与工程结构
上一篇文章我把 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 注入失败的问题,到时候再细说。
- 点赞
- 收藏
- 关注作者
评论(0)