第一篇:入门篇 — 认识 Spring Boot 与基础开发

目标 :能独立创建 Spring Boot 项目,写出基础 REST 接口
学习时长 :1~2 周
前置要求:Java 基础、Maven/Gradle 基础、HTTP 协议了解


目录

  1. [Spring Boot 是什么](#Spring Boot 是什么)
  2. [Spring、Spring MVC、Spring Boot 的关系](#Spring、Spring MVC、Spring Boot 的关系)
  3. [第一个 Spring Boot 项目](#第一个 Spring Boot 项目)
  4. 项目结构说明
  5. [application.yml 配置文件](#application.yml 配置文件)
  6. [Controller、Service、Repository 分层](#Controller、Service、Repository 分层)
  7. [REST API 开发基础](#REST API 开发基础)
  8. 常用注解入门
  9. [接口测试工具 Postman](#接口测试工具 Postman)
  10. [简单 CRUD 实战](#简单 CRUD 实战)
  11. 面试高频题

1. Spring Boot 是什么

Spring Boot 是由 Pivotal 团队开发的开箱即用的 Spring 应用快速构建框架,于 2014 年发布。

核心设计哲学

哲学 说明
约定优于配置 提供合理默认值,减少配置量
开箱即用 Starter 依赖自动装配,无需手动配置
独立运行 内嵌 Tomcat/Jetty,打成 Jar 直接运行
生产就绪 Actuator 提供健康检查、监控、指标等

Spring Boot 解决了什么问题

传统 Spring 项目的痛点:

复制代码
- 繁重的 XML 配置(applicationContext.xml、web.xml)
- 依赖版本冲突
- 部署需要外部 Tomcat
- 环境搭建成本高

Spring Boot 的解决方案:

复制代码
✅ 注解替代 XML,自动配置替代手动配置
✅ Starter BOM 统一管理依赖版本
✅ 内嵌服务器,jar 包一键启动
✅ Spring Initializr 30 秒生成项目骨架

2. Spring、Spring MVC、Spring Boot 的关系

复制代码
┌─────────────────────────────────────────┐
│           Spring Boot                    │  ← 自动配置层(整合者)
│  ┌───────────────────────────────────┐  │
│  │         Spring MVC                │  │  ← Web 层框架
│  │  ┌─────────────────────────────┐ │  │
│  │  │       Spring Core           │ │  │  ← IoC/DI/AOP 核心
│  │  └─────────────────────────────┘ │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘
框架 定位 核心能力
Spring Core 基础框架 IoC 容器、DI 依赖注入、AOP 面向切面
Spring MVC Web 框架 DispatcherServlet、Controller、视图解析
Spring Boot 整合框架 自动配置、Starter、内嵌服务器、Actuator

📌 一句话记忆:Spring 是地基,Spring MVC 是楼,Spring Boot 是装修好的精装房。


3. 第一个 Spring Boot 项目

方式一:Spring Initializr(推荐)

访问 https://start.spring.io

复制代码
Project:Maven
Language:Java
Spring Boot:3.2.x
Java:17
Dependencies:Spring Web、Lombok

方式二:IDEA 内置向导

File → New → Project → Spring Initializr

最简启动类

java 复制代码
@SpringBootApplication  // = @Configuration + @EnableAutoConfiguration + @ComponentScan
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

Hello World Controller

java 复制代码
@RestController  // = @Controller + @ResponseBody
public class HelloController {

    @GetMapping("/hello")
    public String hello(@RequestParam(defaultValue = "World") String name) {
        return "Hello, " + name + "!";
    }
}

启动验证

bash 复制代码
mvn spring-boot:run
# 访问:http://localhost:8080/hello?name=SpringBoot
# 返回:Hello, SpringBoot!

4. 项目结构说明

复制代码
my-project/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/example/demo/
│   │   │       ├── DemoApplication.java      ← 启动类(放在最外层包)
│   │   │       ├── controller/               ← 接收请求,参数校验
│   │   │       │   └── UserController.java
│   │   │       ├── service/                  ← 业务逻辑
│   │   │       │   ├── UserService.java      ← 接口
│   │   │       │   └── impl/
│   │   │       │       └── UserServiceImpl.java ← 实现类
│   │   │       ├── repository/               ← 数据访问
│   │   │       │   └── UserRepository.java
│   │   │       ├── entity/                   ← 数据库实体
│   │   │       │   └── User.java
│   │   │       ├── dto/                      ← 数据传输对象(入参)
│   │   │       ├── vo/                       ← 视图对象(出参)
│   │   │       └── config/                   ← 配置类
│   │   └── resources/
│   │       ├── application.yml               ← 主配置文件
│   │       ├── application-dev.yml           ← 开发环境配置
│   │       ├── application-prod.yml          ← 生产环境配置
│   │       ├── static/                       ← 静态资源(css/js/img)
│   │       └── templates/                    ← 模板文件(Thymeleaf)
│   └── test/
│       └── java/                             ← 单元测试
└── pom.xml                                   ← Maven 依赖管理

⚠️ 注意 :启动类必须放在所有包的最外层 ,否则 @ComponentScan 扫描不到子包


5. application.yml 配置文件

基础配置

yaml 复制代码
server:
  port: 8080                    # 服务端口
  servlet:
    context-path: /api          # 应用根路径

spring:
  application:
    name: my-service            # 应用名(服务注册、链路追踪会用到)

  datasource:
    url: jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf8
    username: root
    password: root123
    driver-class-name: com.mysql.cj.jdbc.Driver

  jpa:
    hibernate:
      ddl-auto: update          # none/validate/update/create/create-drop
    show-sql: true

logging:
  level:
    com.example: DEBUG          # 包级别日志
    org.hibernate.SQL: DEBUG    # 打印 SQL

Properties vs YAML 对比

properties 复制代码
# application.properties(传统写法)
server.port=8080
spring.datasource.url=jdbc:mysql://localhost:3306/db
yaml 复制代码
# application.yml(层级结构,推荐)
server:
  port: 8080
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/db

@Value 读取配置

java 复制代码
@Value("${spring.application.name}")
private String appName;

@Value("${server.port:8080}")  // 有默认值
private int port;

@ConfigurationProperties 批量绑定(推荐)

java 复制代码
@Data
@Component
@ConfigurationProperties(prefix = "app.oss")
public class OssProperties {
    private String endpoint;
    private String accessKey;
    private String secretKey;
    private String bucket;
}
yaml 复制代码
app:
  oss:
    endpoint: https://oss-cn-hangzhou.aliyuncs.com
    access-key: LTAI5t...
    secret-key: abc123...
    bucket: my-bucket

6. Controller、Service、Repository 分层

三层架构职责

复制代码
请求 → Controller → Service → Repository → Database
                ↑               ↑
            参数校验         业务逻辑        数据访问
注解 职责 禁止事项
Controller @RestController 接收请求、参数校验、调用 Service 不写业务逻辑
Service @Service 业务逻辑、事务控制 不写 SQL
Repository @Repository 数据库访问 不写业务逻辑

标准代码结构

java 复制代码
// Controller 层:只做参数校验和调用 Service
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
    private final UserService userService;

    @PostMapping
    public Result<User> create(@Valid @RequestBody CreateUserDTO dto) {
        return Result.success(userService.createUser(dto));
    }
}

// Service 层:核心业务逻辑
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
    private final UserRepository userRepository;

    @Transactional
    public User createUser(CreateUserDTO dto) {
        // 业务校验
        if (userRepository.existsByEmail(dto.getEmail())) {
            throw new BusinessException("邮箱已存在");
        }
        // 转换 + 保存
        User user = BeanUtils.copyProperties(dto, User.class);
        return userRepository.save(user);
    }
}

// Repository 层:只做数据访问
public interface UserRepository extends JpaRepository<User, Long> {
    boolean existsByEmail(String email);
}

7. REST API 开发基础

HTTP 方法与 CRUD 对应关系

HTTP 方法 操作 示例 状态码
GET 查询 GET /users / GET /users/1 200
POST 新增 POST /users 201
PUT 全量更新 PUT /users/1 200
PATCH 部分更新 PATCH /users/1 200
DELETE 删除 DELETE /users/1 204

RESTful URL 设计规范

复制代码
✅ 正确
GET    /api/v1/users          查询用户列表
GET    /api/v1/users/1        查询单个用户
POST   /api/v1/users          创建用户
PUT    /api/v1/users/1        更新用户
DELETE /api/v1/users/1        删除用户
GET    /api/v1/users/1/orders 查询用户的订单

❌ 错误(动词放在 URL 中)
GET /api/getUser?id=1
POST /api/createUser
POST /api/deleteUser?id=1

参数接收方式

java 复制代码
// 1. 路径参数:/users/1
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) { ... }

// 2. 查询参数:/users?page=1&size=10
@GetMapping("/users")
public Page<User> listUsers(
    @RequestParam(defaultValue = "0") int page,
    @RequestParam(defaultValue = "10") int size) { ... }

// 3. 请求体(JSON):POST /users  Body: {...}
@PostMapping("/users")
public User createUser(@RequestBody @Valid CreateUserDTO dto) { ... }

// 4. 请求头
@GetMapping("/profile")
public User getProfile(@RequestHeader("Authorization") String token) { ... }

8. 常用注解入门

组件注解

注解 作用
@SpringBootApplication 启动类,组合注解(扫描+配置+自动装配)
@Component 通用组件,注册为 Bean
@Controller Web 控制器,返回视图
@RestController RESTful 控制器,返回 JSON
@Service 业务层组件
@Repository 数据访问层组件
@Configuration 配置类,等价于 XML 配置文件

注入注解

注解 作用
@Autowired 按类型注入(Spring 原生)
@Resource 按名称注入(JDK 标准)
@Qualifier 配合 @Autowired,指定 Bean 名称
@Value 注入配置文件的值

请求映射注解

注解 等效
@RequestMapping 通用映射
@GetMapping @RequestMapping(method = GET)
@PostMapping @RequestMapping(method = POST)
@PutMapping @RequestMapping(method = PUT)
@DeleteMapping @RequestMapping(method = DELETE)

9. 接口测试工具 Postman

基本使用

  1. 下载安装:https://www.postman.com
  2. 新建请求:New → HTTP Request
  3. 选择方法(GET/POST/PUT/DELETE)
  4. 输入 URL

发送 JSON 请求体

复制代码
Method: POST
URL: http://localhost:8080/api/v1/users
Headers:
  Content-Type: application/json
Body (raw JSON):
{
  "username": "zhangsan",
  "email": "zhangsan@example.com",
  "age": 25
}

使用环境变量

复制代码
Environment: dev
{{base_url}} = http://localhost:8080

请求 URL: {{base_url}}/api/v1/users

常用内置工具

  • Swagger UI (推荐):访问 http://localhost:8080/swagger-ui.html 在线测试
  • IntelliJ HTTP Client.http 文件,直接在 IDEA 中测试
  • curl:命令行测试
bash 复制代码
# GET 请求
curl http://localhost:8080/api/v1/users

# POST JSON
curl -X POST http://localhost:8080/api/v1/users \
  -H "Content-Type: application/json" \
  -d '{"username":"test","email":"test@example.com"}'

10. 简单 CRUD 实战

本章 Demo 对应 chapter01-quickstart 模块,实现了完整的用户 CRUD。

核心接口清单

方法 路径 描述
GET /api/v1/users?page=0&size=10 分页查询用户
GET /api/v1/users/{id} 查询单个用户
POST /api/v1/users 创建用户
PUT /api/v1/users/{id} 更新用户信息
DELETE /api/v1/users/{id} 删除用户(软删除)
GET /api/v1/users/search?keyword=xx 搜索用户

启动 Demo

bash 复制代码
cd springboot-demo/chapter01-quickstart
mvn spring-boot:run

# 访问 Swagger:http://localhost:8081/swagger-ui/index.html
# 访问 H2 控制台:http://localhost:8081/h2-console
#   JDBC URL: jdbc:h2:mem:chapter01db

11. 面试高频题

Q1:Spring Boot 和 Spring 的区别是什么?

Spring Boot 是对 Spring 的封装,提供自动配置、Starter 依赖管理和内嵌服务器,让开发者专注业务代码,无需写大量配置。

Q2:@SpringBootApplication 包含了哪些注解?

三个:@SpringBootConfiguration(等价于 @Configuration)、@EnableAutoConfiguration(开启自动装配)、@ComponentScan(包扫描)。

Q3:Spring Boot 支持哪些内嵌服务器?

默认 Tomcat,可以切换为 Jetty 或 Undertow,只需排除 Tomcat 依赖并引入对应 Starter。

Q4:@RestController@Controller 的区别?

@RestController = @Controller + @ResponseBody,前者直接返回 JSON,后者用于返回视图(如 Thymeleaf 模板)。

Q5:Spring Boot 的 application.yml 和 application.properties 有什么区别?

功能相同,YAML 支持层级结构、列表更简洁,可读性更好,推荐使用 YAML。

Q6:如何读取 application.yml 中的配置?

有三种方式:@Value 单个注入、@ConfigurationProperties 批量绑定、Environment.getProperty() 编程式读取。

Q7:Spring Boot 的启动流程是什么?

SpringApplication.run() → 加载配置 → 创建 ApplicationContext → 刷新容器(Bean 实例化、自动配置)→ 触发 ApplicationReadyEvent → 启动完成。

Q8:@ComponentScan 默认扫描哪个包?

扫描启动类所在包及其所有子包,这也是为什么启动类要放在最外层包的原因。

Q9:@Autowired@Resource 的区别?

@Autowired 是 Spring 注解,默认按类型注入;@Resource 是 JDK 标准注解,默认按名称注入。构造器注入推荐用 @RequiredArgsConstructor(Lombok)。

Q10:为什么推荐构造器注入而不是字段注入?

构造器注入:依赖不可变(final)、依赖不可为空、便于单元测试(不依赖 Spring 容器)、能发现循环依赖。字段注入隐藏了依赖关系,不利于测试。


下一篇:02_核心篇_SpringBoot常用开发能力.md


12. Spring Boot Actuator 详解(专家必知)

知识点 1:Actuator 是什么,能做什么

Spring Boot Actuator 提供了一套生产就绪的监控和管理端点,让运维和监控系统可以实时观察应用状态,无需改代码。

核心端点一览

端点 地址 说明
health /actuator/health 应用健康状态(含DB/Redis/MQ等组件)
info /actuator/info 应用信息(版本、构建时间等)
metrics /actuator/metrics 性能指标(JVM内存、GC、HTTP请求数)
env /actuator/env 当前所有环境变量和配置
loggers /actuator/loggers 动态修改日志级别(运行时无需重启)
threaddump /actuator/threaddump 线程快照(排查死锁/阻塞)
heapdump /actuator/heapdump 下载堆内存转储文件
prometheus /actuator/prometheus Prometheus格式指标(需引入依赖)

知识点 2:生产环境安全配置

⚠️ 默认只暴露 healthinfo,其余端点需显式配置。生产环境切勿全量暴露。

yaml 复制代码
management:
  endpoints:
    web:
      exposure:
        # 生产推荐:只暴露必要端点
        include: "health,info,metrics,prometheus,loggers"
        # 开发调试时可以全部暴露
        # include: "*"
  endpoint:
    health:
      show-details: when-authorized  # 认证后才显示详情
      show-components: always
    loggers:
      enabled: true
  # Actuator 端口独立(不对外暴露)
  server:
    port: 9090

安全配置原则

  • heapdumpenv 端点包含敏感信息,生产禁止对外暴露
  • Actuator 端口(如9090)只对内网/监控系统开放
  • 结合 Spring Security 对 Actuator 端点单独设置权限

知识点 3:自定义健康检查

java 复制代码
/**
 * 自定义健康检查:检查第三方依赖的可用性
 * 访问 /actuator/health 时会聚合所有 HealthIndicator 的结果
 */
@Component
public class ExternalServiceHealthIndicator implements HealthIndicator {

    private final ExternalServiceClient client;

    public ExternalServiceHealthIndicator(ExternalServiceClient client) {
        this.client = client;
    }

    @Override
    public Health health() {
        try {
            boolean available = client.ping();
            if (available) {
                return Health.up()
                    .withDetail("service", "external-payment")
                    .withDetail("status", "reachable")
                    .build();
            }
            return Health.down()
                .withDetail("service", "external-payment")
                .withDetail("reason", "ping timeout")
                .build();
        } catch (Exception e) {
            return Health.down(e)
                .withDetail("service", "external-payment")
                .build();
        }
    }
}

健康状态聚合规则

  • 所有组件 UP → 整体 UP(HTTP 200)
  • 任一组件 DOWN → 整体 DOWN(HTTP 503)
  • Kubernetes 的 readinessProbe 就是调用此接口

知识点 4:自定义 Info 端点

yaml 复制代码
# application.yml
info:
  app:
    name: "@project.artifactId@"    # 从 Maven pom.xml 读取
    version: "@project.version@"
    description: "订单服务"
  build:
    time: "@maven.build.timestamp@"
java 复制代码
// 动态 Info 贡献者(运行时添加信息)
@Component
public class AppInfoContributor implements InfoContributor {
    @Override
    public void contribute(Info.Builder builder) {
        builder.withDetail("startup-time",
            ManagementFactory.getRuntimeMXBean().getStartTime());
        builder.withDetail("active-profiles",
            Arrays.toString(environment.getActiveProfiles()));
    }
}

知识点 5:Prometheus 指标接入(生产标配)

xml 复制代码
<!-- 引入 Prometheus 端点支持 -->
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
yaml 复制代码
management:
  endpoints:
    web:
      exposure:
        include: "health,prometheus"
  metrics:
    tags:
      application: ${spring.application.name}  # 所有指标附带应用名标签
      environment: ${spring.profiles.active}

/actuator/prometheus 端点暴露后,Prometheus 可以定时采集,Grafana 订阅展示。

常用监控指标

指标 含义
jvm_memory_used_bytes JVM 堆/非堆内存使用
jvm_gc_pause_seconds GC停顿时间分布
http_server_requests_seconds HTTP 接口响应时间分布(含P99)
hikaricp_connections_active 连接池活跃连接数
process_cpu_usage 应用 CPU 使用率

13. Spring Boot 3.x 新特性与现代化实践

知识点 1:虚拟线程(JDK 21 + Spring Boot 3.2)

什么是虚拟线程

JDK 21 引入的轻量级线程,由 JVM 调度(不是 OS 线程)。创建成本极低,可以创建数百万个,用于解决传统线程模型在高并发 I/O 场景下的瓶颈。

传统线程 vs 虚拟线程对比

维度 传统平台线程 虚拟线程
创建成本 重(~1MB 栈内存) 轻(KB级别)
数量 通常数百个 可达数百万个
阻塞行为 阻塞OS线程(占资源) 仅挂起虚拟线程(不占OS线程)
适用场景 CPU密集型 I/O密集型(HTTP、数据库、文件)

Spring Boot 3.2 一行配置开启

yaml 复制代码
spring:
  threads:
    virtual:
      enabled: true  # 将 Tomcat/调度器切换到虚拟线程

开启后,每个 HTTP 请求都运行在独立的虚拟线程中,数据库调用阻塞时不占用平台线程,吞吐量显著提升。

注意事项

  • 虚拟线程不适合 CPU 密集型任务(如图像处理、加密运算),这类任务仍用平台线程池
  • ThreadLocal 使用无问题,但建议用 ScopedValue(JDK 21 引入)替代

知识点 2:GraalVM 原生镜像

Spring Boot 3.x 原生支持编译成 GraalVM Native Image(本地可执行文件):

bash 复制代码
# 编译为本地可执行文件(无需 JVM)
mvn -Pnative native:compile

# 编译结果:
# target/myapp(Linux 可执行文件,~50MB)
# 启动时间:< 100ms(对比 JVM 的 2-5秒)
# 内存占用:减少 50-80%

适用场景:Serverless / FaaS(函数计算)、容器化部署追求极速启动。

限制:不支持反射(需额外配置)、动态代理受限,Spring Boot 已内置大量 AOT 提示。

知识点 3:@HttpExchange 声明式 HTTP 客户端

Spring Boot 3.x 内置声明式 HTTP 客户端(类似 Feign,无需额外依赖):

java 复制代码
// 声明接口
@HttpExchange("https://api.github.com")
public interface GitHubClient {

    @GetExchange("/users/{username}")
    GitHubUser getUser(@PathVariable String username);

    @PostExchange("/repos/{owner}/{repo}/issues")
    Issue createIssue(
        @PathVariable String owner,
        @PathVariable String repo,
        @RequestBody CreateIssueRequest request
    );
}

// 注册为 Bean
@Configuration
public class HttpClientConfig {
    @Bean
    public GitHubClient gitHubClient() {
        WebClient webClient = WebClient.builder()
            .defaultHeader("Authorization", "Bearer " + token)
            .build();
        HttpServiceProxyFactory factory = HttpServiceProxyFactory
            .builderFor(WebClientAdapter.create(webClient))
            .build();
        return factory.createClient(GitHubClient.class);
    }
}

// 直接注入使用
@Service
@RequiredArgsConstructor
public class GitHubService {
    private final GitHubClient gitHubClient;

    public GitHubUser getUser(String username) {
        return gitHubClient.getUser(username);
    }
}

14. Spring Boot 启动流程深度解析

知识点 1:SpringApplication.run 做了什么

text 复制代码
SpringApplication.run(MyApp.class, args)
    │
    ├── 1. 创建 SpringApplication 对象
    │       推断应用类型(SERVLET / REACTIVE / NONE)
    │       加载 ApplicationContextInitializer(从 spring.factories)
    │       加载 ApplicationListener(从 spring.factories)
    │       推断 mainApplicationClass(打印启动日志用)
    │
    ├── 2. run() 方法执行
    │       ① 创建 SpringApplicationRunListeners(启动事件广播)
    │       ② 发布 ApplicationStartingEvent
    │       ③ 准备环境(ConfigurableEnvironment)
    │          加载 application.yml / application.properties
    │          解析命令行参数、环境变量
    │          发布 ApplicationEnvironmentPreparedEvent
    │       ④ 创建 ApplicationContext(AnnotationConfigServletWebServerApplicationContext)
    │       ⑤ prepareContext()
    │          执行 ApplicationContextInitializer
    │          注册 mainApplicationClass 为 BeanDefinition
    │       ⑥ refreshContext()  ← 核心!完整 Spring IoC 容器初始化
    │          扫描 @ComponentScan 下的所有类
    │          执行自动配置(AutoConfiguration)
    │          启动内嵌 Tomcat(onRefresh 阶段)
    │       ⑦ afterRefresh()
    │          执行 ApplicationRunner / CommandLineRunner
    │       ⑧ 发布 ApplicationStartedEvent
    │       ⑨ 发布 ApplicationReadyEvent(应用就绪,可接收流量)
    │
    └── 返回 ConfigurableApplicationContext

知识点 2:如何在启动完成后执行初始化逻辑

有三种方式,执行顺序和使用场景不同:

java 复制代码
// 方式1:@PostConstruct(Bean 级别,最早执行)
// 场景:单个 Bean 初始化自己的状态(如加载本地缓存)
@Component
@Slf4j
public class CacheInitializer {

    private final ProductRepository productRepository;
    private final Map<Long, Product> localCache = new ConcurrentHashMap<>();

    @PostConstruct  // 属性注入完成后立即执行
    public void init() {
        log.info("开始预热本地缓存...");
        productRepository.findAll().forEach(p -> localCache.put(p.getId(), p));
        log.info("本地缓存预热完成,共 {} 条", localCache.size());
    }
}

// 方式2:ApplicationRunner(应用就绪后执行)
// 场景:需要整个 Spring 容器就绪后才能执行的初始化
@Component
@Order(1)  // 多个 Runner 时控制顺序
@Slf4j
public class DatabaseHealthRunner implements ApplicationRunner {

    private final DataSource dataSource;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        // 应用完全启动后再检查
        try (Connection conn = dataSource.getConnection()) {
            log.info("数据库连接正常:{}", conn.getMetaData().getDatabaseProductName());
        }
        // 可以访问命令行参数
        if (args.containsOption("init-data")) {
            log.info("检测到 --init-data 参数,执行数据初始化...");
        }
    }
}

// 方式3:CommandLineRunner(更简单的命令行参数访问)
@Component
@Order(2)
public class ReportRunner implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        // args 是原始命令行参数数组
        for (String arg : args) {
            System.out.println("启动参数: " + arg);
        }
    }
}

执行顺序@PostConstructApplicationRunner/CommandLineRunner(按 @Order 排序)


15. 配置文件体系详解

知识点 1:YAML 高级语法

yaml 复制代码
# 基础类型
name: 张三
age: 25
salary: 10000.50
active: true

# 字符串注意事项
simple: hello world       # 不需要引号
with-colon: "包含:冒号"   # 包含特殊字符需要引号
multiline: |              # | 保留换行符
  第一行
  第二行
folded: >                 # > 折叠换行为空格
  这是一段很长的文本
  会被折叠成一行

# List 写法
hobbies:
  - 阅读
  - 编程
  - 游泳
# 也可以写成:hobbies: [阅读, 编程, 游泳]

# Map 写法
address:
  province: 广东
  city: 深圳
  district: 南山区

# 嵌套
servers:
  - host: 192.168.1.1
    port: 8080
    name: 主服务器
  - host: 192.168.1.2
    port: 8081
    name: 备用服务器

# 多文档块(同一文件多个配置,用 --- 分隔)
---
spring:
  profiles: dev
server:
  port: 8080
---
spring:
  profiles: prod
server:
  port: 80

知识点 2:配置绑定的三种方式对比

java 复制代码
// 方式1:@Value(简单值,支持 SpEL 表达式)
@RestController
public class DemoController {

    @Value("${server.port}")
    private int port;

    @Value("${app.name:默认应用名}")  // 有默认值
    private String appName;

    @Value("#{${app.servers}}")  // SpEL 解析 Map
    private Map<String, String> servers;

    @Value("${app.tags:}")  // 列表(逗号分隔)
    private List<String> tags;
}

// 方式2:@ConfigurationProperties(推荐,强类型,支持校验)
@Data
@Component
@ConfigurationProperties(prefix = "app")
@Validated  // 开启 JSR-303 校验
public class AppConfig {

    @NotEmpty
    private String name;

    @Min(1) @Max(65535)
    private int port = 8080;

    @NotNull
    private DatabaseConfig database = new DatabaseConfig();

    private List<String> admins = new ArrayList<>();

    @Data
    public static class DatabaseConfig {
        @NotBlank
        private String url;
        private int maxPoolSize = 10;
        private Duration timeout = Duration.ofSeconds(30);
    }
}

// 方式3:Environment(编程式获取,灵活但不推荐常用)
@Service
public class ConfigService {

    @Autowired
    private Environment env;

    public void printConfig() {
        String dbUrl = env.getProperty("spring.datasource.url");
        int port = env.getProperty("server.port", Integer.class, 8080);
        String[] profiles = env.getActiveProfiles();
    }
}

application.yml 配置示例

yaml 复制代码
app:
  name: 订单服务
  port: 9001
  admins:
    - admin@example.com
    - dev@example.com
  database:
    url: jdbc:mysql://localhost:3306/orders
    max-pool-size: 20
    timeout: 30s

知识点 3:配置优先级

text 复制代码
优先级从高到低(高优先级覆盖低优先级):

1. 命令行参数          java -jar app.jar --server.port=9090
2. 操作系统环境变量    SERVER_PORT=9090
3. JVM 系统属性       -Dserver.port=9090
4. application-{profile}.yml(激活的环境配置)
5. application.yml
6. @PropertySource 注解引入的配置文件
7. @SpringBootApplication 类所在包的默认配置

16. RESTful API 设计规范与最佳实践

知识点 1:REST 设计原则

text 复制代码
REST(Representational State Transfer)六大约束:

1. 资源标识(URI):
   ✅ GET /api/v1/users/123        ← 名词,复数
   ❌ GET /api/getUserById?id=123  ← 动词,不符合 REST

2. HTTP 方法语义:
   GET    → 查询(安全且幂等)
   POST   → 创建(非幂等)
   PUT    → 全量更新(幂等)
   PATCH  → 部分更新(幂等)
   DELETE → 删除(幂等)

3. 状态码语义:
   200 OK        → 成功
   201 Created   → 创建成功(POST 后返回)
   204 No Content → 成功但无响应体(DELETE 后返回)
   400 Bad Request → 参数错误
   401 Unauthorized → 未认证
   403 Forbidden → 已认证但无权限
   404 Not Found → 资源不存在
   409 Conflict  → 资源冲突(如用户名已存在)
   422 Unprocessable → 语义错误(校验失败)
   500 Internal Server Error → 服务端错误

4. API 版本管理:
   推荐方案:URI 路径版本  /api/v1/users  /api/v2/users
   次选方案:请求头版本   Accept: application/vnd.myapp.v2+json

知识点 2:统一返回结构

java 复制代码
// 统一响应体
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
    private int code;        // 业务状态码(200=成功,非200=失败)
    private String message;  // 描述信息
    private T data;          // 业务数据
    private long timestamp;  // 时间戳(便于排查问题)

    // 静态工厂方法
    public static <T> Result<T> success(T data) {
        return new Result<>(200, "success", data, System.currentTimeMillis());
    }

    public static <T> Result<T> success() {
        return success(null);
    }

    public static <T> Result<T> fail(int code, String message) {
        return new Result<>(code, message, null, System.currentTimeMillis());
    }

    public static <T> Result<T> fail(ResultCode resultCode) {
        return fail(resultCode.getCode(), resultCode.getMessage());
    }
}

// 业务状态码枚举
public enum ResultCode {
    SUCCESS(200, "操作成功"),
    BAD_REQUEST(400, "请求参数错误"),
    UNAUTHORIZED(401, "请先登录"),
    FORBIDDEN(403, "权限不足"),
    NOT_FOUND(404, "资源不存在"),
    CONFLICT(409, "数据冲突"),
    INTERNAL_ERROR(500, "服务器内部错误"),

    // 业务码(从1000开始,避免与HTTP状态码冲突)
    USER_NOT_FOUND(1001, "用户不存在"),
    USER_DISABLED(1002, "账号已被禁用"),
    PASSWORD_WRONG(1003, "密码错误"),
    STOCK_NOT_ENOUGH(2001, "库存不足"),
    ORDER_ALREADY_PAID(2002, "订单已支付");

    private final int code;
    private final String message;

    ResultCode(int code, String message) {
        this.code = code;
        this.message = message;
    }
    public int getCode() { return code; }
    public String getMessage() { return message; }
}

// 全局异常处理器
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    // 参数校验失败
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<Map<String, String>> handleValidationException(
            MethodArgumentNotValidException e) {
        Map<String, String> errors = new LinkedHashMap<>();
        e.getBindingResult().getFieldErrors().forEach(err ->
            errors.put(err.getField(), err.getDefaultMessage()));
        log.warn("参数校验失败: {}", errors);
        return Result.fail(400, "参数校验失败: " + errors);
    }

    // 业务异常
    @ExceptionHandler(BusinessException.class)
    public Result<Void> handleBusinessException(BusinessException e) {
        log.warn("业务异常: code={}, message={}", e.getCode(), e.getMessage());
        return Result.fail(e.getCode(), e.getMessage());
    }

    // 兜底异常
    @ExceptionHandler(Exception.class)
    public Result<Void> handleException(Exception e, HttpServletRequest request) {
        log.error("未知异常 [{}] {}", request.getMethod(), request.getRequestURI(), e);
        return Result.fail(500, "服务器内部错误,请稍后重试");
    }
}

// 业务异常基类
@Getter
public class BusinessException extends RuntimeException {
    private final int code;

    public BusinessException(ResultCode resultCode) {
        super(resultCode.getMessage());
        this.code = resultCode.getCode();
    }

    public BusinessException(int code, String message) {
        super(message);
        this.code = code;
    }
}

// Controller 使用示例
@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
@Slf4j
public class UserController {

    private final UserService userService;

    @GetMapping("/{id}")
    public Result<UserDTO> getUser(@PathVariable Long id) {
        return Result.success(userService.findById(id));
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Result<UserDTO> createUser(@RequestBody @Valid CreateUserRequest request) {
        return Result.success(userService.create(request));
    }

    @PutMapping("/{id}")
    public Result<UserDTO> updateUser(@PathVariable Long id,
                                       @RequestBody @Valid UpdateUserRequest request) {
        return Result.success(userService.update(id, request));
    }

    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void deleteUser(@PathVariable Long id) {
        userService.delete(id);
    }

    @GetMapping
    public Result<Page<UserDTO>> listUsers(
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") @Max(100) int size,
            @RequestParam(required = false) String keyword) {
        return Result.success(userService.list(page, size, keyword));
    }
}

17. 单元测试与集成测试

知识点 1:Spring Boot Test 分层测试

text 复制代码
测试分层策略:

单元测试(速度最快):
  Mockito 模拟依赖,只测 Service 层业务逻辑
  不启动 Spring 容器

切片测试(中等速度):
  @WebMvcTest    → 只启动 MVC 层(Controller、Filter)
  @DataJpaTest   → 只启动 JPA 层(Repository,内存数据库)
  @JsonTest      → 只测试 JSON 序列化

集成测试(最慢):
  @SpringBootTest → 启动完整 Spring 容器
  配合 Testcontainers 使用真实数据库

知识点 2:Service 层单元测试(Mockito)

java 复制代码
@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @Mock
    private PasswordEncoder passwordEncoder;

    @InjectMocks  // 将 @Mock 注入到此对象
    private UserService userService;

    @Test
    @DisplayName("创建用户:正常流程")
    void createUser_success() {
        // Given(准备数据)
        CreateUserRequest request = new CreateUserRequest("test@email.com", "password123");
        User savedUser = new User(1L, "test@email.com", "encodedPwd");

        when(userRepository.existsByEmail(request.getEmail())).thenReturn(false);
        when(passwordEncoder.encode(request.getPassword())).thenReturn("encodedPwd");
        when(userRepository.save(any(User.class))).thenReturn(savedUser);

        // When(执行)
        UserDTO result = userService.create(request);

        // Then(断言)
        assertNotNull(result);
        assertEquals("test@email.com", result.getEmail());
        verify(userRepository).save(argThat(user ->
            user.getEmail().equals("test@email.com") &&
            user.getPassword().equals("encodedPwd")));
    }

    @Test
    @DisplayName("创建用户:邮箱已存在抛出异常")
    void createUser_emailExists_throwsException() {
        CreateUserRequest request = new CreateUserRequest("exists@email.com", "password");
        when(userRepository.existsByEmail(request.getEmail())).thenReturn(true);

        assertThrows(BusinessException.class, () -> userService.create(request));
        verify(userRepository, never()).save(any());  // 确保没有调用 save
    }
}

知识点 3:Controller 层切片测试(MockMvc)

java 复制代码
@WebMvcTest(UserController.class)  // 只启动 MVC 层,不启动完整容器
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean  // 在 Spring 容器中注册 Mock Bean
    private UserService userService;

    @Autowired
    private ObjectMapper objectMapper;

    @Test
    @DisplayName("GET /api/v1/users/{id} - 用户存在返回200")
    void getUser_exists_returns200() throws Exception {
        UserDTO user = new UserDTO(1L, "test@email.com");
        when(userService.findById(1L)).thenReturn(user);

        mockMvc.perform(get("/api/v1/users/1")
                .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.code").value(200))
            .andExpect(jsonPath("$.data.email").value("test@email.com"))
            .andDo(print());  // 打印请求/响应(调试用)
    }

    @Test
    @DisplayName("POST /api/v1/users - 参数校验失败返回400")
    void createUser_invalidRequest_returns400() throws Exception {
        CreateUserRequest request = new CreateUserRequest("invalid-email", ""); // 无效邮箱,空密码

        mockMvc.perform(post("/api/v1/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(request)))
            .andExpect(status().isBadRequest())
            .andExpect(jsonPath("$.code").value(400));
    }
}

知识点 4:集成测试(Testcontainers)

java 复制代码
// 使用真实 MySQL 容器进行集成测试
@SpringBootTest
@Testcontainers
@Transactional  // 每个测试后回滚
class UserRepositoryIntegrationTest {

    @Container
    static MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0")
        .withDatabaseName("test_db")
        .withUsername("test")
        .withPassword("test");

    // 动态注入容器的连接信息
    @DynamicPropertySource
    static void configureDataSource(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", mysql::getJdbcUrl);
        registry.add("spring.datasource.username", mysql::getUsername);
        registry.add("spring.datasource.password", mysql::getPassword);
    }

    @Autowired
    private UserRepository userRepository;

    @Test
    void findByEmail_exists_returnsUser() {
        User user = new User("test@email.com", "password");
        userRepository.save(user);

        Optional<User> found = userRepository.findByEmail("test@email.com");
        assertTrue(found.isPresent());
        assertEquals("test@email.com", found.get().getEmail());
    }
}
相关推荐
栈位迁移中1 小时前
二十一、Spring Framework 详细知识点文档
后端
马艳泽1 小时前
Maven 编译时生成、纯静态文档、不能调试、零侵入、不用运行项目的api文档
后端
RainCity1 小时前
Java Swing 自定义组件库分享(三)
java·笔记
泰式大师1 小时前
从“记忆”到“项目 Wiki”:我在 SkillLite 里实现了一套 Markdown-only LLM Wiki 自动维护机制
后端
凤凰院凶涛QAQ1 小时前
《C++转java快速入手系列》类与对象篇
java·开发语言·c++
Devin~Y1 小时前
大厂Java面试实录:Spring Boot/Cloud + Redis/Kafka + JWT + RAG/Agent(小Y翻车版)
java·spring boot·redis·spring cloud·kafka·spring security·jwt
渐儿1 小时前
案例2:内存管理与性能优化
后端
一叶之政1 小时前
C++ 系统学习日记・第 09 天|指针全解:定义 + 内存 + 空 / 野指针 + const 修饰 + 数组 + 函数
后端
风曦Kisaki1 小时前
# Linux服务Day1:模板机制作、FTP与NTP服务配置全解析
后端