
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-web | Web 应用 | Spring MVC, Tomcat, Jackson |
| spring-boot-starter-webflux | 响应式 Web | Netty, Reactor |
| spring-boot-starter-data-jpa | 关系型数据库访问 | Spring Data JPA, Hibernate |
| spring-boot-starter-data-redis | Redis 操作 | 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 细节。
建议额外引入 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
迁移不是改个版本号那么简单,重点关注以下几点:
- Jakarta EE 命名空间:把
javax.servlet、javax.persistence等替换成jakarta.*。 - Spring Security 配置:废弃
WebSecurityConfigurerAdapter,改用SecurityFilterChainBean。 - 配置文件变更:部分属性已废弃或重命名,启动时会有迁移报告提示。
- Actuator 端点:默认只暴露 health,需要显式配置其他端点。
- Java EE 依赖:移除
javax.validation,改用jakarta.validation。
建议先用 Spring Boot 提供的 spring-boot-properties-migrator 跑一遍,它会自动提示哪些配置需要调整。
十四、常见陷阱与最佳实践
- 混淆 JDK 版本:Spring Boot 3 必须 JDK 17+,CI/CD 镜像别用 JDK 8。
- 自动配置被覆盖:自定义 Bean 后原自动配置失效,搞不清楚时用
--debug启动查看 Condition 报告。 - 懒加载误用:
spring.main.lazy-initialization=true能加快启动,但会延迟暴露配置错误,生产慎用。 - DTO 与实体混用:Controller 直接返回 JPA 实体容易触发懒加载异常,坚持用 DTO/Record 做层间隔离。
- 日志配置随意:生产用
logback-spring.xml,按级别和包拆分,JSON 格式方便日志平台解析。 - 忽略优雅停机:K8s 滚动更新时不配 preStop,会导致正在处理的请求被强制中断。
- 事务范围过大:在 Controller 或整个 Service 类上加
@Transactional,容易把数据库连接持有过久,应该精确到方法。 - 滥用 @Async:线程池配置不当会导致任务堆积,建议自定义
ThreadPoolTaskExecutor并监控队列长度。
十五、总结
Spring Boot 3 的价值不只是“少写配置”,它把依赖管理、内嵌容器、自动配置、监控、安全打包成了一套完整的企业级开发体验。本文从项目初始化到 Docker 部署,覆盖了一个后端服务从 0 到 1 的关键环节。掌握这些之后,再深入 Spring Cloud、微服务、云原生会轻松很多。
如果你正在从 2.x 迁移到 3.x,建议先做一份依赖兼容性扫描,重点检查 javax 命名空间、Spring Security 配置方式以及已弃用的属性。迁移虽然有点工作量,但 JDK 17 和原生镜像带来的收益,绝对值得。