Spring Boot

上一章我们看了 Spring 的 IoC/AOP——强大但配置繁琐:要写一堆 XML 或 Java Config,要部署到 Tomcat,要管理一堆依赖版本。Spring Boot 就是来解放这一切的——“约定优于配置” + “开箱即用”,让一个 Spring 应用从”一周搭起来”变成”五分钟跑起来”。

这一章看 Spring Boot 的核心——自动配置、起步依赖、REST API、配置文件、数据访问整合、虚拟线程。

一、Spring Boot 解决了什么

传统 Spring 应用的痛点:

  1. 配置爆炸 —— 一个简单 Web 应用要写 web.xmlapplicationContext.xmlspring-mvc.xmldispatcher-servlet.xml……
  2. 依赖地狱 —— Spring 自己几十个模块,加上 Spring MVC、事务、ORM、JSON……版本要自己协调。
  3. 部署繁琐 —— 打成 WAR 包,部署到 Tomcat,配置数据源……
  4. 启动慢 —— Tomcat 启动 + Spring 上下文加载,几十秒。

Spring Boot 的解药:

  • 起步依赖(Starter) —— 一个 spring-boot-starter-web 拉齐 Web 开发所需的所有库,版本协调好。
  • 自动配置(Auto-Configuration) —— 引入数据库驱动,自动配数据源;引入 Thymeleaf,自动配视图解析器。
  • 内嵌 Tomcat —— java -jar app.jar 直接跑,不用部署。
  • Actuator —— 内置健康检查、监控端点。
  • 生产就绪 —— 默认配置已适合大多数场景。

二、起步依赖与自动配置

2.1 起步依赖 Starter

Spring Boot 把常用场景的依赖打包成”Starter”——一个依赖拉齐一整套:

Starter用途
spring-boot-starter核心基础
spring-boot-starter-webWeb + 内嵌 Tomcat + Spring MVC
spring-boot-starter-data-jpaSpring Data JPA + Hibernate
spring-boot-starter-data-redisRedis 集成
spring-boot-starter-securitySpring Security
spring-boot-starter-test测试(JUnit/Mockito/AssertJ)
spring-boot-starter-actuator监控端点
spring-boot-starter-validationBean Validation
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.0</version>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

spring-boot-starter-parent 是父 POM——管理所有 Spring Boot 相关依赖的版本,子项目不用写版本号。这就是”版本协调”。

2.2 自动配置原理

@SpringBootApplication 是组合注解:

@SpringBootApplication   // 等价于下面三个
@SpringBootConfiguration   // @Configuration, 配置类
@EnableAutoConfiguration   // 开启自动配置
@ComponentScan             // 扫描当前包及子包
public class App { ... }

@EnableAutoConfiguration 是自动配置的核心——它通过 spring.factories(Spring Boot 2.x)或 META-INF/spring/...imports(3.x)加载一堆自动配置类,每个配置类根据条件决定是否生效:

@AutoConfiguration
@ConditionalOnClass(DataSource.class)              // classpath 有 DataSource 类才生效
@ConditionalOnProperty(prefix = "spring.datasource",
                       name = "url")                // 配了 url 才生效
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean                       // 用户没自己定义才创建
    public DataSource dataSource(DataSourceProperties props) {
        return props.initializeDataSourceBuilder().build();
    }
}

@ConditionalOnXxx 是关键——条件装配:

  • @ConditionalOnClass(X.class) —— classpath 有 X 类才生效。
  • @ConditionalOnMissingBean —— 用户没自己定义才生效(用户的优先)。
  • @ConditionalOnProperty —— 配了某属性才生效。
  • @ConditionalOnWebApplication —— 是 Web 应用才生效。

效果——你引入 MySQL 驱动 + 配 spring.datasource.url,数据源自动配好;不引入就不配。“约定优于配置”——按约定的方式做事,零配置;偏离约定才需配置。

三、第一个 REST API

// src/main/java/com/example/App.java
@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}
// src/main/java/com/example/controller/UserController.java
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;

    @GetMapping
    public List<User> list() {
        return userService.findAll();
    }

    @GetMapping("/{id}")
    public User getById(@PathVariable Long id) {
        return userService.findById(id);
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public User create(@Valid @RequestBody CreateUserRequest req) {
        return userService.create(req);
    }

    @PutMapping("/{id}")
    public User update(@PathVariable Long id, @RequestBody UpdateUserRequest req) {
        return userService.update(id, req);
    }

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

注解解析:

注解作用
@RestController@Controller + @ResponseBody,返回值自动转 JSON
@RequestMapping类级路径前缀
@GetMapping/@PostMapping/…HTTP 方法映射
@PathVariable取 URL 路径变量 /users/{id}
@RequestParam取查询参数 ?name=x
@RequestBody把请求体反序列化成对象
@Valid触发 Bean Validation 校验
@ResponseStatus指定响应状态码

启动后 curl http://localhost:8080/api/users 就能拿到 JSON——Spring Boot 自动配了 Jackson 序列化、内嵌 Tomcat、DispatcherServlet,零配置。

四、配置文件 application.yml

Spring Boot 支持 application.propertiesapplication.yml。yml 层次更清晰,主流:

server:
  port: 8080                    # 服务端口
  servlet:
    context-path: /api          # 上下文路径

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      maximum-pool-size: 10
      minimum-idle: 2

  jpa:
    hibernate:
      ddl-auto: update          # 自动建表
    show-sql: true
    properties:
      hibernate.format_sql: true

  redis:
    host: localhost
    port: 6379

  threads:
    virtual:
      enabled: true             # Spring Boot 3.2+ 启用虚拟线程

logging:
  level:
    com.example: DEBUG
    org.hibernate.SQL: DEBUG
  file:
    name: logs/app.log

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,env

Profile 多环境——application-dev.yml/application-prod.yml,激活用 spring.profiles.active=prod

绑定到字段:

@ConfigurationProperties(prefix = "app")
@Data
public class AppProperties {
    private String name;
    private int maxSize;
    private List<String> allowedOrigins;
}
app:
  name: 我的系统
  max-size: 100
  allowed-origins:
    - https://a.com
    - https://b.com

五、整合 MyBatis / MyBatis-Plus

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.5</version>
</dependency>
@Data
@TableName("users")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer age;
}

@Mapper
public interface UserMapper extends BaseMapper<User> {
    // BaseMapper 自带: insert/updateById/deleteById/selectById/selectList...
    // 复杂查询写 XML 或注解
    @Select("SELECT * FROM users WHERE age > #{age}")
    List<User> findOlderThan(int age);
}

@Service
@RequiredArgsConstructor
public class UserService {
    private final UserMapper userMapper;

    public List<User> findAll() {
        return userMapper.selectList(null);   // null = 无条件
    }

    public User findById(Long id) {
        return userMapper.selectById(id);
    }

    public User create(User u) {
        userMapper.insert(u);
        return u;   // u.id 已自动回填
    }

    // 条件构造器
    public List<User> findByNameLike(String keyword) {
        return userMapper.selectList(
            new LambdaQueryWrapper<User>()
                .like(User::getName, keyword)
                .ge(User::getAge, 18)
                .orderByDesc(User::getId)
        );
    }
}

MyBatis-Plus 的杀手锏——BaseMapper 自带 CRUD,复杂查询用 LambdaQueryWrapper 链式构造,零 SQL 搞定大多数场景。

六、整合 Spring Data JPA

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
@Entity
@Table(name = "users")
@Data
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private Integer age;
}

// Repository 接口, 不用写实现!
public interface UserRepository extends JpaRepository<User, Long> {
    List<User> findByAgeGreaterThan(int age);
    Optional<User> findByName(String name);

    @Query("SELECT u FROM User u WHERE u.age BETWEEN :min AND :max")
    List<User> findByAgeRange(@Param("min") int min, @Param("max") int max);
}

@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository repo;

    @Transactional
    public User create(User u) {
        return repo.save(u);
    }

    public List<User> findByAgeRange(int min, int max) {
        return repo.findByAgeRange(min, max);
    }
}

Spring Data JPA 的”魔法”——只声明接口方法,框架自动实现。findByAgeGreaterThan 这种方法名直接被解析成 SQL WHERE age > ?。复杂查询用 @Query 写 JPQL。

七、Spring Boot 与虚拟线程

Spring Boot 3.2+ 支持虚拟线程(Java 21)——一行配置让每个 HTTP 请求跑在虚拟线程上:

spring:
  threads:
    virtual:
      enabled: true

效果——Tomcat 不再受平台线程池(默认 200)限制,每个请求一个虚拟线程,能轻松撑 10 万并发。虚拟线程在 IO 时自动让出 CPU,几乎不耗资源。

传统模型(平台线程):

请求1 → 线程1 (IO 阻塞时线程1 干等)
请求2 → 线程2
...
请求200 → 线程200 (满了, 后续请求排队)

虚拟线程模型:

请求1 → 虚拟线程1 (IO 时挂起, 平台线程去跑别的虚拟线程)
请求2 → 虚拟线程2
...
请求100000 → 虚拟线程100000 (都跑在几十个平台线程上)

注意——虚拟线程不适合 CPU 密集任务(它解决 IO 阻塞问题)。同步阻塞代码(JDBC、HTTP 调用)直接受益,无需改代码。

八、Actuator 监控

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,env,loggers
  endpoint:
    health:
      show-details: always

常用端点:

端点用途
/actuator/health健康检查(数据库、Redis 状态)
/actuator/info应用信息
/actuator/metrics指标(JVM、HTTP 请求)
/actuator/env环境变量
/actuator/loggers动态调日志级别
/actuator/beans所有 Bean
/actuator/mappings所有 URL 映射
curl localhost:8080/actuator/health
# {"status":"UP","components":{"db":{"status":"UP"},...}}

# 动态调日志级别 (不用重启)
curl -X POST localhost:8080/actuator/loggers/com.example \
  -H 'Content-Type: application/json' \
  -d '{"configuredLevel":"DEBUG"}'

生产环境配合 Prometheus + Grafana 做监控——/actuator/prometheus 暴露 Prometheus 格式指标。

九、实战:模拟 Spring Boot 自动配置

由于 Spring Boot 需要完整运行环境,Piston 跑不了。下面用纯 Java SE 模拟自动配置的核心思想——根据 classpath 上有什么类、配置文件有什么属性,决定创建哪些 Bean。

Java · 在线运行

观察重点

  • DataSourceAutoConfiguration 生效,RedisAutoConfiguration 跳过——因为 classpath 没有 RedisClient(模拟没引入 redis 依赖)。
  • 第二轮引入 RedisClient 后,RedisAutoConfiguration 也生效——这就是”引入依赖即获得配置”。
  • @ConditionalOnProperty 检查配置项——没配 url 就不创建 DataSource。
  • 用户自定义 Bean 优先——@ConditionalOnMissingBean 让用户的 Bean 覆盖默认。

十、本章小结

概念核心要点
Starter起步依赖,一个拉齐整套库
自动配置@ConditionalOnXxx 条件装配
@SpringBootApplication配置 + 自动配置 + 组件扫描
@RestController@Controller + @ResponseBody
application.yml配置文件,支持 Profile
MyBatis-PlusBaseMapper 自带 CRUD + 条件构造器
Spring Data JPA方法名解析成 SQL,零实现
虚拟线程Spring Boot 3.2+ 一行启用
Actuator监控端点,健康检查/指标

记忆口诀

  • Starter 拉齐依赖——spring-boot-starter-web 一行起 Web。
  • 自动配置条件装配——@ConditionalOnClass/Property/MissingBean
  • 内嵌 Tomcat——java -jar 直接跑。
  • @RestController 返回 JSON——自动 Jackson 序列化。
  • MyBatis-Plus BaseMapper——CRUD 零代码。
  • JPA 方法名即 SQL——findByAgeGreaterThan 自动解析。
  • 虚拟线程一行开——spring.threads.virtual.enabled=true

结语:从 Spring 到 Spring Boot

这一章我们看了 Spring Boot——把 Spring 的强大包装成开箱即用。下一章深入数据访问层——MyBatis 的动态 SQL、MyBatis-Plus 增强、Spring Data JPA、数据库迁移工具。