Spring Boot 复习学习:从配置文件到接口打通(附知识点归纳)

前沿

梳理的话 还是要 优雅一下去归纳 , 本文整理 Spring Boot 后端开发的核心知识,

包括配置文件、Controller 接口创建、统一返回结果、DTO 参数校验、全局异常处理、MVC 分层结构等

并且有代码附又, 把每个注解背后的原理讲清楚,让你"知其然,也知其所以然"。

 

一、整体架构:一张图看懂请求流转

在写代码之前,先建立全局视角。一个典型的 Spring Boot Web 请求,会经过下面这些层:

前端 HTTP 请求
      │
      ▼
┌─────────────────┐
│   Controller    │  ← 接收参数、参数校验(@Valid)
│  (接口层)      │
└────────┬────────┘
         │ 调用
         ▼
┌─────────────────┐
│    Service      │  ← 业务逻辑(推荐算法、事务)
│  (业务层)      │
└────────┬────────┘
         │ 调用
         ▼
┌─────────────────┐
│    Model /      │  ← 持久化交互(MyBatis / JPA)
│   Repository    │
└────────┬────────┘
         │
         ▼
      数据库

      ▲
      │ 异常向上抛
┌─────────────────┐
│ GlobalException │  ← 全局捕获,统一返回
│    Handler       │
└─────────────────┘

每一层的职责:

image

 

二、配置文件: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 会按以下优先级加载(后面的覆盖前面的):

1. jar 包内的 application.yml
2. jar 包内的 application-{profile}.yml   ← 如 application-dev.yml
3. jar 包外的 application.yml
4. jar 包外的 application-{profile}.yml
5. 命令行参数 --server.port=9090

通过 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) vs new 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;
}

 

知识点:常用校验注解速查

0622febb-f48e-414b-97dd-b21870be0016

 


⚠️ 关键坑点: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 启动时通过这个构造器完成依赖注入,实现构造器注入。

三种注入方式对比:

8ec223a9-7858-4d3b-9a14-18326f948854

 

 

构造器注入的好处:

  1. 依赖不可变(final);

  2. 依赖不为 null(启动即报错);

  3. 便于单元测试(直接 new);

  4. 避免循环依赖。

知识点:@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。

常用请求映射注解

e14cf08f-c294-4681-a638-8bc8594cc4b2

 


知识点:参数接收注解

1d8a71dd-ef3d-406c-a17f-82f7c61da7a2

 

 

七、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

57ab9000-85b7-4b82-ae98-04dffae7dc9e

 


为什么不直接用 Entity 接收参数、返回结果?

  1. Entity 字段多(如 createTime、deleted),前端传了会有安全隐患;

  2. Entity 改了会影响接口契约,DTO/VO 可以独立演进;

  3. 不同接口需要的字段不同,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             // 扫描当前包及子包的组件

 

自动配置原理(简版):

  1. Spring Boot 启动时读取 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件;

  2. 根据 classpath 中存在的依赖,按条件(@ConditionalOnClass 等)加载对应配置类;

  3. 例如引入 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 注解全景

f5704c1f-8f44-49d5-acf1-51d40dfb8645

de249e63-8cbc-4509-8883-a45b01c8c8fc

 

 

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

 

好好加油!!

 
 
 
 
posted @ 2026-09-24 00:15  -鹿-  阅读(6)  评论(0)    收藏  举报