Develop

Spring Boot 3 企业级开发实战:从自动配置到生产部署的完整指南

✎ -- 字 🕐 -- 分钟
字号

Spring Boot 3 封面

Spring Boot 3 已经发布两年多,依托 Jakarta EE 9+ 与原生镜像支持,成为 Java 后端开发的事实标准。这篇文章从零开始,带你走完项目初始化、核心机制理解、RESTful API 开发、数据访问、安全配置、Docker 部署与生产监控的完整链路,适合想系统掌握 Spring Boot 的开发者。文中所有代码片段都基于真实可运行的项目结构整理,你可以直接复制到本地工程里验证。

一、为什么选择 Spring Boot 3

在企业级 Java 开发里,Spring 生态一直是主力。但原生 Spring Framework 配置繁琐、依赖管理复杂,Spring Boot 的出现把“约定大于配置”推到了极致。Spring Boot 3 基于 Spring Framework 6,最低要求 JDK 17,带来了几个关键升级:

  • 全面迁移到 jakarta.* 命名空间,告别 javax.*
  • 支持 GraalVM 原生镜像,启动速度从秒级降到毫秒级
  • 内置 Micrometer 可观测性,Metrics、Tracing 接入更统一
  • Spring Security 6 默认配置更严格,安全模型更现代
  • HTTP interface 客户端,让声明式远程调用像本地方法一样简单
  • 可观测性增强,内置 Zipkin、Wavefront 等 Tracing 支持

如果你的新项目还在用 Spring Boot 2.x,建议直接上 3.x。长期支持、性能、安全都是更值得押注的方向。当然,老项目迁移需要评估 Jakarta 命名空间、Spring Security 配置方式以及部分已废弃的自动配置类,后文会专门给出迁移 Checklist。

二、环境准备与项目初始化

2.1 JDK 与构建工具

Spring Boot 3 要求 JDK 17 及以上。推荐用 SDKMAN 或手动安装 JDK 21 LTS。构建工具方面,Maven 和 Gradle 都可以,Gradle 在增量构建和大项目里更快,Maven 在国内文档和社区资源更足。如果你的团队对 Groovy/Kotlin DSL 不熟,Maven 仍然是更稳妥的选择。

# 检查 JDK 版本
java -version
# 输出应包含 17 或更高

# Maven 创建项目
mvn archetype:generate -DgroupId=com.example -DartifactId=demo \
  -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

2.2 用 Spring Initializr 快速起步

最省事的办法是访问 start.spring.io,选择 Maven、Java 17、Spring Boot 3.x,勾选 Web、JPA、H2、Redis、Security、Actuator 等依赖,下载压缩包解压即可。命令行党也可以直接用 HTTPie:

curl https://start.spring.io/starter.zip \
  -d dependencies=web,data-jpa,h2,data-redis,security,actuator \
  -d type=maven-project \
  -d bootVersion=3.3.0 \
  -d baseDir=demo \
  -o demo.zip

unzip demo.zip && cd demo

项目结构很清晰:src/main/java 放代码,src/main/resources 放配置和静态资源,pom.xml 管理依赖。主类上有 @SpringBootApplication,它其实是 @Configuration@EnableAutoConfiguration@ComponentScan 三个注解的组合。

2.3 第一个可运行的接口

写好主类后,加一个 Controller 就能跑起来:

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello(@RequestParam(defaultValue = "Spring Boot 3") String name) {
        return "Hello, " + name + "!";
    }
}

运行 mvn spring-boot:run 后访问 http://localhost:8080/hello,就能看到返回结果。到这里,你已经完成了最基础的环境验证。

三、核心机制解析

3.1 自动配置原理

Spring Boot 的自动配置不是魔法,而是靠 @EnableAutoConfiguration 扫描 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件,按条件注册 Bean。核心注解包括:

  • @ConditionalOnClass:类路径存在某类才生效
  • @ConditionalOnMissingBean:容器里没有该 Bean 才生效
  • @ConditionalOnProperty:配置文件开关控制
  • @ConditionalOnWebApplication:仅在 Web 环境下生效
// 一个简化的自定义自动配置示例
@Configuration
@ConditionalOnClass(RedisOperations.class)
@ConditionalOnProperty(prefix = "demo.cache", name = "enabled", havingValue = "true")
public class DemoCacheAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public DemoCacheManager demoCacheManager(RedisTemplate<String, Object> redisTemplate) {
        return new DemoCacheManager(redisTemplate);
    }
}

想看哪些自动配置生效、哪些被跳过,可以在运行时加 --debug 参数,或者在配置文件里设置 debug=true。Spring Boot 会输出一份 Condition Evaluation Report,排查“为什么我的配置没生效”时非常有用。

3.2 起步依赖与 Starter

Spring Boot 用 Starter 把一组相关依赖打包。比如 spring-boot-starter-web 会引入 Tomcat、Spring MVC、Jackson 等。你不需要记每个依赖的版本,spring-boot-starter-parent 已经做了兼容锁定。

Starter用途关键依赖
spring-boot-starter-webWeb 应用Spring MVC, Tomcat, Jackson
spring-boot-starter-webflux响应式 WebNetty, Reactor
spring-boot-starter-data-jpa关系型数据库访问Spring Data JPA, Hibernate
spring-boot-starter-data-redisRedis 操作Lettuce, Spring Data Redis
spring-boot-starter-security安全认证Spring Security 6
spring-boot-starter-actuator监控端点Micrometer, Prometheus
spring-boot-starter-test测试JUnit 5, Mockito, Testcontainers
spring-boot-starter-validation参数校验Jakarta Bean Validation

3.3 内嵌容器与可执行 Jar

传统 Java Web 应用要打成 War 包丢到 Tomcat 里。Spring Boot 直接内嵌 Tomcat/Jetty/Undertow,通过 spring-boot-maven-plugin 打包成可执行 Jar:

mvn clean package
java -jar target/demo-0.0.1-SNAPSHOT.jar

Jar 包里包含所有依赖和内嵌容器,java -jar 就能跑。生产环境推荐用 layertools 模式分层构建 Docker 镜像,提升构建缓存命中率。分层构建会把依赖、Spring Boot 加载器、快照依赖、应用代码拆到不同层,只有代码变更时才重新构建最后一层。

3.4 配置绑定与类型安全

Spring Boot 鼓励用 @ConfigurationProperties 把配置绑定到强类型对象,而不是到处用 @Value。这样既能享受 IDE 补全,又能做统一校验。

@ConfigurationProperties(prefix = "demo.storage")
public record StorageProperties(
    @NotBlank String bucket,
    @Min(1) @Max(365) int retentionDays,
    @NotEmpty List<String> allowedTypes
) {}

@Configuration
@EnableConfigurationProperties(StorageProperties.class)
public class AppConfig {
}

配置文件对应:

demo:
  storage:
    bucket: prod-assets
    retention-days: 30
    allowed-types:
      - image/jpeg
      - image/png
      - application/pdf

配合 spring-boot-configuration-processor 还能在 application.yml 里生成配置元数据提示,团队新成员上手会快很多。

四、实战:构建一个 RESTful 服务

4.1 分层架构设计

一个可维护的后端项目通常分四层:Controller 接收请求、Service 处理业务、Repository 访问数据、Entity/Domain 定义模型。不要直接在 Controller 里写 SQL,也不要让 Service 层处理 HTTP 细节。

Spring Boot 分层架构

建议额外引入 DTO/VO 层做数据 outward 转换,尤其不要用 JPA 实体直接当接口返回对象。否则一次懒加载没处理好,就可能触发 N+1 查询,或者把敏感字段暴露出去。

4.2 实体与 Repository

用 JPA 定义用户实体,Hibernate 会自动建表:

@Entity
@Table(name = "tb_user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true, length = 32)
    private String username;

    @Column(nullable = false)
    private String email;

    @Column(name = "created_at", nullable = false, updatable = false)
    private LocalDateTime createdAt = LocalDateTime.now();

    // getters / setters / constructors 省略
}

public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByUsername(String username);

    @Query("SELECT u FROM User u WHERE u.email LIKE %:domain")
    List<User> findByEmailDomain(@Param("domain") String domain);
}

4.3 Service 与 Controller

Service 层处理业务逻辑,Controller 只负责路由和参数校验:

@Service
@Transactional
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public User createUser(String username, String email) {
        if (userRepository.findByUsername(username).isPresent()) {
            throw new BusinessException("用户名已存在");
        }
        return userRepository.save(new User(username, email));
    }

    public User getUser(Long id) {
        return userRepository.findById(id)
            .orElseThrow(() -> new ResourceNotFoundException("用户不存在"));
    }
}

@RestController
@RequestMapping("/api/users")
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping
    public ResponseEntity<ApiResult<User>> create(@RequestBody @Valid UserCreateRequest request) {
        User user = userService.createUser(request.getUsername(), request.getEmail());
        return ResponseEntity.ok(ApiResult.success(user));
    }

    @GetMapping("/{id}")
    public ResponseEntity<ApiResult<User>> get(@PathVariable Long id) {
        return ResponseEntity.ok(ApiResult.success(userService.getUser(id)));
    }
}

4.4 统一响应与全局异常

接口返回统一格式,异常集中处理:

public record ApiResult<T>(int code, String message, T data) {
    public static <T> ApiResult<T> success(T data) {
        return new ApiResult<>(0, "ok", data);
    }
    public static <T> ApiResult<T> error(int code, String message) {
        return new ApiResult<>(code, message, null);
    }
}

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ApiResult<?>> handleNotFound(ResourceNotFoundException e) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
            .body(ApiResult.error(404, e.getMessage()));
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ApiResult<?>> handleValidation(MethodArgumentNotValidException e) {
        String msg = e.getBindingResult().getFieldErrors().stream()
            .map(error -> error.getField() + ":" + error.getDefaultMessage())
            .collect(Collectors.joining("; "));
        return ResponseEntity.badRequest().body(ApiResult.error(400, msg));
    }
}

五、数据访问与缓存

5.1 多环境数据源配置

application-{profile}.yml 管理不同环境。开发用 H2,生产用 MySQL:

# application-dev.yml
spring:
  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver
  jpa:
    hibernate:
      ddl-auto: create-drop
    show-sql: true

# application-prod.yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/demo?useSSL=false&serverTimezone=Asia/Shanghai
    username: demo
    password: ${DB_PASSWORD}
  jpa:
    hibernate:
      ddl-auto: validate

5.2 Redis 缓存实战

在 Service 方法上加 @Cacheable,Spring Boot 会自动把结果缓存到 Redis:

@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(10))
            .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
        return RedisCacheManager.builder(factory).cacheDefaults(config).build();
    }
}

@Service
public class OrderService {

    @Cacheable(value = "orders", key = "#id")
    public Order getOrder(Long id) {
        return orderRepository.findById(id).orElseThrow();
    }

    @CacheEvict(value = "orders", key = "#order.id")
    public Order updateOrder(Order order) {
        return orderRepository.save(order);
    }
}

缓存记得设置 TTL,否则内存会无限增长。高并发下还要注意缓存穿透、击穿和雪崩,这些在之前的 Redis 文章里有详细方案,这里不再展开。

六、安全与认证

6.1 Spring Security 6 基础配置

Spring Boot 3 配套 Spring Security 6,配置方式从 WebSecurityConfigurerAdapter 改成了 SecurityFilterChain Bean:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable())
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/auth/**").permitAll()
                .requestMatchers(HttpMethod.GET, "/api/users/**").authenticated()
                .anyRequest().authenticated()
            )
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .addFilterBefore(jwtAuthFilter(), UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

安全无小事。生产环境尽量别关 CSRF,如果是前后端分离项目再用 Stateless JWT;内部管理系统建议保留 Cookie + Session。另外,requestMatchers 在 Spring Security 6 里替换了旧的 antMatchers,迁移时记得改。

七、异步任务与定时任务

7.1 异步执行

Spring Boot 里用 @Async 把方法变成异步执行,适合发送邮件、生成报表这类非实时任务。但一定要自定义线程池,否则用的是 SimpleAsyncTaskExecutor,每次新建线程,高并发下会打爆系统。

@Configuration
@EnableAsync
public class AsyncConfig {

    @Bean("taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(4);
        executor.setMaxPoolSize(16);
        executor.setQueueCapacity(200);
        executor.setThreadNamePrefix("async-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

@Service
public class NotificationService {

    @Async("taskExecutor")
    public CompletableFuture<Void> sendEmail(String to, String subject) {
        // 模拟发邮件
        return CompletableFuture.completedFuture(null);
    }
}

7.2 定时任务

@Scheduled 实现定时任务,配合 @EnableScheduling 开启:

@Component
public class CleanupJob {

    @Scheduled(cron = "0 0 3 * * ?")
    public void cleanExpiredLogs() {
        // 每天凌晨 3 点清理过期日志
    }
}

单机定时任务简单场景够用,但分布式环境下多个实例会重复执行。生产环境建议用 ShedLock、XXL-JOB 或 Quartz 集群方案。

八、生产部署

8.1 多环境打包

# 指定 profile 打包
mvn clean package -DskipTests -Pprod

# 运行
java -jar -Dspring.profiles.active=prod target/demo.jar

8.2 Docker 多阶段构建

用 Maven 镜像编译,JRE 镜像运行,镜像体积能从几百 MB 压到一百 MB 以内:

# 阶段一:编译
FROM maven:3.9-eclipse-temurin-21-alpine AS builder
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn clean package -DskipTests

# 阶段二:运行
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=builder /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75.0", "-Djava.security.egd=file:/dev/./urandom", "-jar", "app.jar"]

8.3 JVM 参数与优雅停机

容器里跑 Java 最容易踩的坑是内存限制没被识别。JDK 10 之后加 -XX:+UseContainerSupport,配合 MaxRAMPercentage 让 JVM 按容器内存自动调整堆大小。优雅停机用 -Dgraceful.shutdown=true 或 Kubernetes 的 preStop hook:

lifecycle:
  preStop:
    exec:
      command: ["/bin/sh", "-c", "sleep 15"]

sleep 15 秒是为了让 Kubernetes 先把 Pod 从 Endpoint 里摘掉,现有请求处理完再真正退出。

九、日志与可观测性

9.1 生产级日志配置

生产环境不要用默认控制台彩色日志,建议用 logback-spring.xml 做结构化输出。JSON 格式方便 ELK、Loki、Grafana 等日志平台解析和检索。

<configuration>
    <appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="net.logstash.logback.encoder.LogstashEncoder">
            <includeContext>true</includeContext>
            <includeMdc>true</includeMdc>
        </encoder>
    </appender>

    <logger name="com.example.demo" level="INFO"/>
    <logger name="org.hibernate.SQL" level="DEBUG"/>

    <root level="INFO">
        <appender-ref ref="JSON"/>
    </root>
</configuration>

同时养成在请求入口打印 trace_id 的习惯,排查问题时可以把整条链路串起来。配合 MDC 和分布式追踪,定位线上问题会快很多。

9.2 分布式追踪接入

Spring Boot 3 通过 Micrometer Tracing 统一了追踪抽象,可以很方便地接入 Zipkin 或 Wavefront:

<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-exporter-zipkin</artifactId>
</dependency>

开启后,RestTemplate、WebClient、JDBC 等组件会自动生成 Span,你也能用 @NewSpan 在业务方法上手动埋点。

十、性能监控

10.1 Actuator + Prometheus

Spring Boot Actuator 提供 /actuator/health/actuator/metrics 等端点。配合 Micrometer 可以把指标暴露成 Prometheus 格式:

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  endpoint:
    health:
      show-details: when_authorized
  metrics:
    tags:
      application: ${spring.application.name}

然后在 Prometheus 里配置 scrape:

scrape_configs:
  - job_name: 'spring-boot-demo'
    metrics_path: '/actuator/prometheus'
    static_configs:
      - targets: ['app:8080']

10.2 关键指标

指标Micrometer 名称用途
JVM 堆内存jvm_memory_used_bytes排查内存泄漏、GC 压力
HTTP 请求耗时http_server_requests_seconds接口性能基线
Tomcat 线程tomcat_threads_busy_threads连接池是否饱和
JDBC 连接池hikaricp_connections_active数据库连接是否够用
业务自定义counter/order_created_total订单量等业务指标

十一、测试策略

11.1 分层测试

Spring Boot 项目建议做三层测试:单元测试用 JUnit 5 + Mockito,集成测试用 @SpringBootTest,数据层测试用 @DataJpaTest。不要所有测试都启动完整应用上下文,那样跑起来太慢。

@DataJpaTest
class UserRepositoryTest {

    @Autowired
    private UserRepository userRepository;

    @Test
    void shouldFindByUsername() {
        userRepository.save(new User("alice", "alice@example.com"));
        Optional<User> user = userRepository.findByUsername("alice");
        assertThat(user).isPresent();
    }
}

11.2 Testcontainers 集成测试

对于依赖 MySQL、Redis、Kafka 的场景,用 Testcontainers 在测试时启动真实服务,比内存模拟更接近生产行为:

@SpringBootTest
@Testcontainers
class OrderServiceIntegrationTest {

    @Container
    static GenericContainer<?> redis = new GenericContainer<>(DockerImageName.parse("redis:7-alpine"))
        .withExposedPorts(6379);

    @DynamicPropertySource
    static void redisProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.data.redis.host", redis::getHost);
        registry.add("spring.data.redis.port", redis::getFirstMappedPort);
    }

    @Autowired
    private OrderService orderService;

    @Test
    void shouldCacheOrder() {
        // 测试缓存逻辑
    }
}

十二、GraalVM 原生镜像

Spring Boot 3 最重要的新能力之一是对 GraalVM 原生镜像的一等支持。原生镜像会把 Java 应用编译成机器码,启动时间从秒级降到几十毫秒,内存占用也大幅减少。

# 需要 GraalVM 或 Oracle GraalVM JDK
mvn -Pnative native:compile
./target/demo

原生镜像的限制是反射、动态代理、资源文件都需要显式配置。Spring Boot 的 AOT 处理会帮你生成大部分元数据,但如果项目里大量用了反射,还是要仔细检查 reflect-config.json

十三、从 Spring Boot 2.x 迁移到 3.x

迁移不是改个版本号那么简单,重点关注以下几点:

  1. Jakarta EE 命名空间:把 javax.servletjavax.persistence 等替换成 jakarta.*
  2. Spring Security 配置:废弃 WebSecurityConfigurerAdapter,改用 SecurityFilterChain Bean。
  3. 配置文件变更:部分属性已废弃或重命名,启动时会有迁移报告提示。
  4. Actuator 端点:默认只暴露 health,需要显式配置其他端点。
  5. Java EE 依赖:移除 javax.validation,改用 jakarta.validation

建议先用 Spring Boot 提供的 spring-boot-properties-migrator 跑一遍,它会自动提示哪些配置需要调整。

十四、常见陷阱与最佳实践

  1. 混淆 JDK 版本:Spring Boot 3 必须 JDK 17+,CI/CD 镜像别用 JDK 8。
  2. 自动配置被覆盖:自定义 Bean 后原自动配置失效,搞不清楚时用 --debug 启动查看 Condition 报告。
  3. 懒加载误用spring.main.lazy-initialization=true 能加快启动,但会延迟暴露配置错误,生产慎用。
  4. DTO 与实体混用:Controller 直接返回 JPA 实体容易触发懒加载异常,坚持用 DTO/Record 做层间隔离。
  5. 日志配置随意:生产用 logback-spring.xml,按级别和包拆分,JSON 格式方便日志平台解析。
  6. 忽略优雅停机:K8s 滚动更新时不配 preStop,会导致正在处理的请求被强制中断。
  7. 事务范围过大:在 Controller 或整个 Service 类上加 @Transactional,容易把数据库连接持有过久,应该精确到方法。
  8. 滥用 @Async:线程池配置不当会导致任务堆积,建议自定义 ThreadPoolTaskExecutor 并监控队列长度。

十五、总结

Spring Boot 3 的价值不只是“少写配置”,它把依赖管理、内嵌容器、自动配置、监控、安全打包成了一套完整的企业级开发体验。本文从项目初始化到 Docker 部署,覆盖了一个后端服务从 0 到 1 的关键环节。掌握这些之后,再深入 Spring Cloud、微服务、云原生会轻松很多。

如果你正在从 2.x 迁移到 3.x,建议先做一份依赖兼容性扫描,重点检查 javax 命名空间、Spring Security 配置方式以及已弃用的属性。迁移虽然有点工作量,但 JDK 17 和原生镜像带来的收益,绝对值得。