Spring Boot 复习学习:从配置文件到接口打通(附知识点归纳)
前沿
梳理的话 还是要 优雅一下去归纳 , 本文整理 Spring Boot 后端开发的核心知识,
包括配置文件、Controller 接口创建、统一返回结果、DTO 参数校验、全局异常处理、MVC 分层结构等
并且有代码附又, 把每个注解背后的原理讲清楚,让你"知其然,也知其所以然"。
一、整体架构:一张图看懂请求流转
在写代码之前,先建立全局视角。一个典型的 Spring Boot Web 请求,会经过下面这些层:
前端 HTTP 请求 │ ▼ ┌─────────────────┐ │ Controller │ ← 接收参数、参数校验(@Valid) │ (接口层) │ └────────┬────────┘ │ 调用 ▼ ┌─────────────────┐ │ Service │ ← 业务逻辑(推荐算法、事务) │ (业务层) │ └────────┬────────┘ │ 调用 ▼ ┌─────────────────┐ │ Model / │ ← 持久化交互(MyBatis / JPA) │ Repository │ └────────┬────────┘ │ ▼ 数据库 ▲ │ 异常向上抛 ┌─────────────────┐ │ GlobalException │ ← 全局捕获,统一返回 │ Handler │ └─────────────────┘
每一层的职责:

二、配置文件:application.yml
Spring Boot 支持 application.properties 和 application.yml,推荐使用 yml,层级清晰、可读性好。
它相当于前端的 .env + config 文件,用来集中管理端口、数据库连接、第三方服务等配置。
server: port: 8080 servlet: context-path: / # 全局路径前缀 spring: application: name: travel-java-server # 数据库配置 datasource: url: jdbc:mysql://localhost:3306/travel_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver # JSON 序列化配置 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 default-property-inclusion: non_null # null 字段不返回 # MyBatis-Plus 配置(如果使用) mybatis-plus: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true # user_name -> userName log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印 SQL # 自定义配置(可配合 @ConfigurationProperties 读取) travel: api: key: your_api_key timeout: 5000
知识点:配置文件加载顺序
Spring Boot 会按以下优先级加载(后面的覆盖前面的):
通过 spring.profiles.active=dev 可切换环境,实现"一套代码,多环境部署"。
三、统一响应封装:Result<T>
前后端交互,需要一个统一的响应格式,方便前端处理。核心思路:用泛型 T 承载业务数据。
package org.example.traveljavaserver.vo; import lombok.Data; @Data public class Result<T> { private Boolean success; private Integer code; private String message; private T data; // 成功 public static <T> Result<T> ok(T data) { Result<T> result = new Result<>(); result.setSuccess(true); result.setCode(200); result.setMessage("操作成功"); result.setData(data); return result; } // 成功(无数据) public static <T> Result<T> ok() { return ok(null); } // 失败(默认) public static <T> Result<T> fail() { Result<T> result = new Result<>(); result.setSuccess(false); result.setCode(500); result.setMessage("操作失败"); return result; } // 失败(自定义消息) public static <T> Result<T> fail(String message) { Result<T> result = fail(); result.setMessage(message); return result; } // 失败(自定义 code + message) ← 全局异常处理会用到 public static <T> Result<T> fail(Integer code, String message) { Result<T> result = fail(); result.setCode(code); result.setMessage(message); return result; } }
知识点:为什么要用泛型 Result<T>?
-
Result<String>、Result<TravelRecommendVO>、Result<Void>可以复用同一套模板; -
编译期类型安全,避免运行时强转;
-
前端拿到 JSON 后,只需判断
success和code,data结构按接口约定解析。
知识点:为什么用静态工厂方法(ok/fail)?
-
调用处更简洁:
Result.ok(data)vsnew Result<>(true, 200, "操作成功", data); -
统一默认值,避免每次手动 set;
-
符合建造者/工厂模式思想,后续想改默认行为只改一处。
四、DTO 与参数校验
DTO(Data Transfer Object)用来接收前端参数,与数据库 Entity 分离,避免 Entity 被污染。
补全:pom.xml 依赖
<!-- Web 启动器(含 Spring MVC、Tomcat、Jackson) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 参数校验(Jakarta Validation API + Hibernate Validator 实现) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Lombok:自动生成 getter/setter/构造器 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- MyBatis-Plus(可选) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.5</version>
</dependency>
DTO 定义
java package org.example.traveljavaserver.dto; import jakarta.validation.constraints.*; import lombok.Data; @Data public class TravelRequestDTO { @NotNull(message = "城市不能为空") private String city; @NotNull(message = "天数不能为空") @Min(value = 1, message = "天数不能小于1") @Max(value = 30, message = "天数不能大于30") private Integer days; @NotNull(message = "预算不能为空") @DecimalMin(value = "100", message = "预算不能小于100") private Double budget; }
知识点:常用校验注解速查

⚠️ 关键坑点:DTO 上加了校验注解只是"声明",必须在 Controller 方法参数前加
@Valid(或@Validated),Spring 才会真正触发校验。否则注解形同虚设!
知识点:@Valid 与 @Validated 的区别
-
@Valid:JSR-303 标准注解,支持嵌套对象校验; -
@Validated:Spring 扩展,支持分组校验(如新增时校验、更新时校验不同规则); -
一般场景用
@Valid就够。
五、全局异常处理:GlobalExceptionHandler
参数校验失败时,Spring 会抛出 MethodArgumentNotValidException。默认返回的是一大坨 Spring 错误信息,对前端很不友好。全局异常处理可以把它们统一转换成我们的 Result 格式。
package org.example.traveljavaserver.exception; import org.example.traveljavaserver.vo.Result; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import java.util.stream.Collectors; @RestControllerAdvice public class GlobalExceptionHandler { /** * 处理 @Valid 参数校验失败异常 */ @ExceptionHandler(MethodArgumentNotValidException.class) public Result<Void> handleValidException(MethodArgumentNotValidException e) { // 把所有字段的错误信息用逗号拼接 String message = e.getBindingResult().getFieldErrors().stream() .map(FieldError::getDefaultMessage) .collect(Collectors.joining(", ")); return Result.fail(400, message); } /** * 处理业务自定义异常 */ @ExceptionHandler(BusinessException.class) public Result<Void> handleBusinessException(BusinessException e) { return Result.fail(e.getCode(), e.getMessage()); } /** * 兜底:处理所有未被捕获的异常 */ @ExceptionHandler(Exception.class) public Result<Void> handleException(Exception e) { e.printStackTrace(); // 生产环境应打日志 return Result.fail(500, "服务器内部错误:" + e.getMessage()); } }
配套自定义异常类:
java package org.example.traveljavaserver.exception; import lombok.Getter; @Getter public class BusinessException extends RuntimeException { private final Integer code; public BusinessException(String message) { this(500, message); } public BusinessException(Integer code, String message) { super(message); this.code = code; } }
知识点:@RestControllerAdvice 拆解
-
@ControllerAdvice:Spring 3.2 引入,声明一个"控制器增强"类,可以全局拦截 Controller 的异常、数据绑定、参数预处理; -
@ResponseBody:让方法返回值自动序列化为 JSON; -
两者组合 =
@RestControllerAdvice。
知识点:异常处理的优先级
当同一个异常能被多个 @ExceptionHandler 匹配时,Spring 会优先选择最具体的异常类型:
text
BusinessException(具体)
↑
RuntimeException
↑
Exception(兜底)
所以 handleBusinessException 会先于 handleException 被命中
六、Controller:接口层
java package org.example.traveljavaserver.controller; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.example.traveljavaserver.dto.TravelRequestDTO; import org.example.traveljavaserver.service.TravelService; import org.example.traveljavaserver.vo.Result; import org.example.traveljavaserver.vo.TravelRecommendVO; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/travel") @RequiredArgsConstructor // Lombok:为 final 字段生成构造器 public class TravelController { private final TravelService travelService; @GetMapping("/hello") public Result<String> hello() { return Result.ok("hello world"); } @PostMapping("/recommend") public Result<TravelRecommendVO> recommend(@Valid @RequestBody TravelRequestDTO dto) { TravelRecommendVO vo = travelService.recommend( dto.getCity(), dto.getDays(), dto.getBudget()); return Result.ok(vo); } @PostMapping("/create") public Result<String> createTravel(@Valid @RequestBody TravelRequestDTO requestDTO) { // 能执行到这里,说明参数已通过所有校验 travelService.createTravel(requestDTO); return Result.ok("创建成功"); } }
知识点:@RequiredArgsConstructor 为什么能替代手写构造器?
-
Spring 4.3 之后,如果类只有一个构造器,
@Autowired可以省略; -
Lombok 的
@RequiredArgsConstructor会为所有final字段(和@NonNull字段)生成一个构造器; -
Spring 启动时通过这个构造器完成依赖注入,实现构造器注入。
三种注入方式对比:

构造器注入的好处:
-
依赖不可变(
final); -
依赖不为 null(启动即报错);
-
便于单元测试(直接 new);
-
避免循环依赖。
知识点:@RestController 拆解
java
@RestController = @Controller + @ResponseBody
核心注解说明
@RestController
-
这是一个组合注解(包含
@Controller+@ResponseBody)。 -
作用:告诉 Spring 这是一个处理 Web 请求的控制器,并且该类中所有方法的返回值都会直接作为 HTTP 响应体返回(通常是 JSON),而不是跳转到 HTML 页面。
@RequestMapping("/api/travel")
-
作用:定义该控制器下所有接口的基础路径(父路径)。
-
这意味着,这个类里面所有接口的完整 URL 都会以
http://你的域名:端口/api/travel开头。
-
@Controller:标识为控制器,返回值默认当视图名处理(跳 HTML); -
@ResponseBody:方法返回值直接作为响应体(JSON/文本); -
组合后,写 REST API 就不用每个方法都加
@ResponseBody。
常用请求映射注解

知识点:参数接收注解

七、Service 层与 VO
Service 接口
java package org.example.traveljavaserver.service; import org.example.traveljavaserver.dto.TravelRequestDTO; import org.example.traveljavaserver.vo.TravelRecommendVO; public interface TravelService { TravelRecommendVO recommend(String city, Integer days, Double budget); void createTravel(TravelRequestDTO dto); }
Service 实现
java package org.example.traveljavaserver.service.impl; import lombok.RequiredArgsConstructor; import org.example.traveljavaserver.dto.TravelRequestDTO; import org.example.traveljavaserver.service.TravelService; import org.example.traveljavaserver.vo.TravelRecommendVO; import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor public class TravelServiceImpl implements TravelService { // private final TravelMapper travelMapper; // 数据库操作 @Override public TravelRecommendVO recommend(String city, Integer days, Double budget) { // 1. 可以查数据库、调外部 API、跑推荐算法 TravelRecommendVO vo = new TravelRecommendVO(); vo.setCity(city); vo.setDays(days); vo.setBudget(budget); vo.setSummary("为您推荐 " + city + " " + days + " 天行程,预算 " + budget + " 元"); return vo; } @Override public void createTravel(TravelRequestDTO dto) { // travelMapper.insert(...); } }
VO 定义(返回给前端的视图对象)
java package org.example.traveljavaserver.vo; import lombok.Data; @Data public class TravelRecommendVO { private String city; private Integer days; private Double budget; private String summary; }
知识点:DTO vs VO vs Entity

为什么不直接用 Entity 接收参数、返回结果?
-
Entity 字段多(如
createTime、deleted),前端传了会有安全隐患; -
Entity 改了会影响接口契约,DTO/VO 可以独立演进;
-
不同接口需要的字段不同,DTO/VO 更灵活。
八、Model 层:数据库交互
以 MyBatis-Plus 为例,补全最小可用代码。
Entity
java package org.example.traveljavaserver.entity; import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import java.time.LocalDateTime; @Data @TableName("travel_plan") public class TravelPlan { @TableId(type = IdType.AUTO) private Long id; private String city; private Integer days; private Double budget; @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; @TableLogic private Integer deleted; // 逻辑删除 }
Mapper
java package org.example.traveljavaserver.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; import org.example.traveljavaserver.entity.TravelPlan; @Mapper public interface TravelPlanMapper extends BaseMapper<TravelPlan> { }
启动类
java package org.example.traveljavaserver; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @MapperScan("org.example.traveljavaserver.mapper") public class TravelJavaServerApplication { public static void main(String[] args) { SpringApplication.run(TravelJavaServerApplication.class, args); } }
知识点:@SpringBootApplication 拆解
java @SpringBootApplication = @SpringBootConfiguration // 标识为配置类 + @EnableAutoConfiguration // 开启自动配置(核心) + @ComponentScan // 扫描当前包及子包的组件
自动配置原理(简版):
-
Spring Boot 启动时读取
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件; -
根据 classpath 中存在的依赖,按条件(
@ConditionalOnClass等)加载对应配置类; -
例如引入
spring-boot-starter-web,就自动配置好 Tomcat、DispatcherServlet、Jackson 等。
九、完整测试
启动项目
bash
mvn spring-boot:run
测试成功场景
bash curl -X POST http://localhost:8080/api/travel/recommend \ -H "Content-Type: application/json" \ -d '{"city":"杭州","days":3,"budget":2000}'
响应:
json { "success": true, "code": 200, "message": "操作成功", "data": { "city": "杭州", "days": 3, "budget": 2000.0, "summary": "为您推荐 杭州 3 天行程,预算 2000.0 元" } }
测试校验失败场景
bash curl -X POST http://localhost:8080/api/travel/recommend \ -H "Content-Type: application/json" \ -d '{"city":"","days":0,"budget":50}'
响应:
json { "success": false, "code": 400, "message": "天数不能小于1, 预算不能小于100", "data": null }
十、知识点归纳(速查表)
10.1 注解全景


10.2 分层职责速记
text Controller → 管"进出":参数校验、组装响应 Service → 管"逻辑":业务规则、事务 Mapper → 管"数据":CRUD、SQL DTO → 管"入" :前端传参 VO → 管"出" :返回前端 Entity → 管"库" :数据库映射 Exception → 管"错" :统一兜底
10.3 最佳实践清单
✅ DTO 加校验注解,Controller 加 @Valid; ✅ 统一响应 Result<T>,统一异常 @RestControllerAdvice; ✅ 使用构造器注入(@RequiredArgsConstructor + final); ✅ DTO / VO / Entity 分离,不要混用; ✅ Service 接口 + 实现分离,便于扩展和测试; ✅ 配置文件分环境(application-dev.yml / application-prod.yml); ✅ 生产环境日志替代 System.out.println; ✅ 敏感配置(密码、密钥)走环境变量或配置中心。
项目结构参考
travel-java-server/ ├── src/main/java/org/example/traveljavaserver/ │ ├── TravelJavaServerApplication.java # 启动类 │ ├── controller/ # 接口层 │ │ └── TravelController.java │ ├── service/ # 业务层 │ │ ├── TravelService.java │ │ └── impl/TravelServiceImpl.java │ ├── mapper/ # 持久层 │ │ └── TravelPlanMapper.java │ ├── entity/ # 数据库实体 │ │ └── TravelPlan.java │ ├── dto/ # 入参 │ │ └── TravelRequestDTO.java │ ├── vo/ # 出参 │ │ ├── Result.java │ │ └── TravelRecommendVO.java │ └── exception/ # 异常 │ ├── GlobalExceptionHandler.java │ └── BusinessException.java └── src/main/resources/ ├── application.yml ├── application-dev.yml └── mapper/ └── TravelPlanMapper.xml
好好加油!!

浙公网安备 33010602011771号