Spring Boot: 第一个 Spring Boot 应用

最后更新:2026-08-26

一个 @SpringBootApplication 注解就能启动整个应用——Spring Boot 的魔力背后是三个注解的精妙组合。

1. 你将学到


2. 一个新项目的真实故事

(1) 痛点:启动一个项目要配半天

Alice 回忆起刚入行时用传统 Spring MVC 的经历:创建一个项目需要配置 web.xmlapplicationContext.xmlspring-mvc.xml,还要安装 Tomcat、配置数据源 JNDI。光是让项目跑起来就要花半天,更不用说调试启动顺序和 Bean 加载问题了。

(2) Spring Boot 的解法

Spring Boot 只需一个注解 + 一个 main 方法:

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

(3) 收益

Alice 用 Spring Boot 创建 OrderFlow 项目,从零到第一个 API 响应只花了 5 分钟,无需任何 XML,无需外部容器,启动日志清晰可读。


3. @SpringBootApplication 注解拆解

(1) 三个注解的组合

@SpringBootApplication 是一个组合注解,等价于同时使用三个注解:

100%
graph TB
    A["@SpringBootApplication"] --> B["@SpringBootConfiguration"]
    A --> C["@EnableAutoConfiguration"]
    A --> D["@ComponentScan"]
    B --> B1["Marks class as<br/>Configuration bean source"]
    C --> C1["Triggers auto-configuration<br/>based on classpath"]
    D --> D1["Scans components<br/>in same package tree"]
注解 作用 等价传统写法
@SpringBootConfiguration 标记当前类是配置类 @Configuration
@EnableAutoConfiguration 启用自动配置 @EnableAutoConfiguration
@ComponentScan 组件扫描 <context:component-scan>

(2) @ComponentScan 的扫描规则

@ComponentScan 默认扫描主启动类所在包及其所有子包。

TEXT 📖 仅展示
com.orderflow                           ← 主启动类所在包
├── OrderFlowApplication.java           ← @SpringBootApplication
├── controller/                         ← 被 @ComponentScan 扫描到
│   └── OrderController.java
├── service/                            ← 被 @ComponentScan 扫描到
│   └── OrderService.java
└── repository/                         ← 被 @ComponentScan 扫描到
    └── OrderRepository.java
com.other                               ← ⚠️ 不在扫描范围内!
🔥 易错: 如果把 Controller 放在主启动类所在包的上级或平级包中,Spring Boot 不会扫描到它,导致 404 错误。


4. SpringApplication.run() 启动流程

(1) 启动时序

SpringApplication.run() 的执行过程是一个精心设计的流水线:

100%
sequenceDiagram
    participant Main as main()
    participant SA as SpringApplication
    participant Ctx as ApplicationContext
    participant Bean as Beans

    Main->>SA: new SpringApplication()
    SA->>SA: infer Primary Sources
    SA->>SA: check Web Application Type
    SA->>SA: load Initializers & Listeners
    Main->>SA: run(args)
    SA->>SA: create Bootstrap Context
    SA->>SA: prepare Environment
    SA->>SA: print Banner
    SA->>Ctx: create ApplicationContext
    SA->>Ctx: prepare Context (register sources)
    SA->>Ctx: refresh Context
    Ctx->>Bean: instantiate Beans
    Ctx->>Bean: auto-configure
    SA->>SA: call Runners
    SA->>Main: return ApplicationContext

(2) Web Application Type 判断

Spring Boot 自动判断应用类型:

条件 类型 使用的容器
classpath 有 spring-webmvc SERVLET Tomcat / Jetty / Undertow
classpath 有 spring-webflux 但无 spring-webmvc REACTIVE Netty
都没有 NONE 无内嵌容器

▶ 示例: 自定义 SpringApplication 启动

JAVA
@SpringBootApplication
public class OrderFlowApplication {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(OrderFlowApplication.class);
        app.setBannerMode(Banner.Mode.CONSOLE);
        app.setWebApplicationType(WebApplicationType.SERVLET);
        app.run(args);
    }
}

输出:

TEXT 📖 仅展示
// 执行成功

5. 配置文件格式

(1) Properties vs YAML

Spring Boot 支持两种配置文件格式,功能等价但写法不同。

▶ 示例: application.properties 写法

PROPERTIES
server.port=8080
spring.application.name=orderflow-service
spring.datasource.url=jdbc:mysql://localhost:3306/orderflow
spring.datasource.username=root
spring.datasource.password=secret
spring.jpa.hibernate.ddl-auto=update

输出:

TEXT 📖 仅展示
// 执行成功

▶ 示例: application.yml 写法

YAML
server:
  port: 8080

spring:
  application:
    name: orderflow-service
  datasource:
    url: jdbc:mysql://localhost:3306/orderflow
    username: root
    password: secret
  jpa:
    hibernate:
      ddl-auto: update

输出:

TEXT 📖 仅展示
配置文件已生效
维度 Properties YAML
层级表达 . 分隔 用缩进表示层级
可读性 简单键值对,扁平 层级清晰,适合嵌套
List 支持 list[0]=a - a 更直观
重复前缀 需重复写 同层级共享前缀
解析性能 更快 略慢(需解析层级)
优先级 相同 相同(同时存在时 properties 优先)
⚠️ 注意: YAML 文件对缩进非常敏感,必须用空格不能用 Tab,同一层级缩进必须一致。

(2) 常用配置项速查

配置项 默认值 说明
server.port 8080 应用监听端口
spring.application.name 应用名称
server.servlet.context-path / 上下文路径
spring.main.banner-mode console Banner 显示模式
spring.jpa.show-sql false 是否打印 SQL
logging.level.root INFO 全局日志级别

6. Banner 自定义与启动日志

(1) 自定义 Banner

src/main/resources/banner.txt 中放置自定义 Banner,Spring Boot 启动时自动显示。

▶ 示例: OrderFlow 自定义 Banner

TEXT 📖 仅展示
  ____              _         ____  __
 / ___| _ __   __ _| |_ ___  / ___||  |  ___  _ __
| |    | '_ \ / _` | __/ _ \ \___ \|  |/ _ \| '_ \
| |___ | | | | (_| | ||  __/  ___) |  | (_) | | | |
 \____||_| |_|\__,_|\__\___| |____/|_|\___/|_| |_|

:: OrderFlow Service ::  v${application.version:1.0.0}
:: Spring Boot ${spring-boot.version} ::

输出:

TEXT 📖 仅展示
执行成功

(2) 启动日志解读

TEXT 📖 仅展示
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_  __ _   \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` |  \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  / / / /
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.2.5)

2024-01-15 10:00:01.123  INFO 12345 --- [main] c.o.OrderFlowApplication : Starting OrderFlowApplication
2024-01-15 10:00:03.456  INFO 12345 --- [main] o.s.b.w.e.t.TomcatWebServer : Tomcat initialized with port 8080 (http)
2024-01-15 10:00:04.789  INFO 12345 --- [main] o.s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories
2024-01-15 10:00:06.012  INFO 12345 --- [main] c.o.OrderFlowApplication : Started in 5.123 seconds
日志关键词 含义
Starting OrderFlowApplication 应用开始启动
Tomcat initialized with port 内嵌 Tomcat 启动
Bootstrapping Spring Data JPA 自动配置 JPA 仓库
Started in X seconds 启动完成,耗时

7. 运行第一个 Hello OrderFlow 接口

▶ 示例: Hello OrderFlow Controller

JAVA
package com.orderflow.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;

@RestController
public class HelloController {

    @GetMapping("/api/hello")
    public Map<String, String> hello() {
        return Map.of(
            "message", "Hello, OrderFlow!",
            "timestamp", java.time.Instant.now().toString()
        );
    }
}

输出:

TEXT 📖 仅展示
HTTP 200 OK
Content-Type: application/json

{"status":"success","data":{}}
💻 输出:

TEXT 📖 仅展示
$ curl http://localhost:8080/api/hello
{"message":"Hello, OrderFlow!","timestamp":"2024-01-15T10:00:00Z"}

▶ 示例: 使用 @Value 注入配置

JAVA
@RestController
public class HelloController {

    @Value("${spring.application.name}")
    private String appName;

    @GetMapping("/api/info")
    public Map<String, String> info() {
        return Map.of(
            "application", appName,
            "javaVersion", System.getProperty("java.version")
        );
    }
}

输出:

TEXT 📖 仅展示
// 执行成功

8. 综合示例:完整的 OrderFlow 启动配置

JAVA
// src/main/java/com/orderflow/OrderFlowApplication.java
package com.orderflow;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

// src/main/java/com/orderflow/controller/OrderFlowController.java
package com.orderflow.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.Instant;
import java.util.Map;

@RestController
public class OrderFlowController {

    @Value("${spring.application.name:orderflow}")
    private String appName;

    @GetMapping("/api/health")
    public Map<String, Object> health() {
        return Map.of(
            "status", "UP",
            "application", appName,
            "timestamp", Instant.now()
        );
    }
}
YAML
# src/main/resources/application.yml
server:
  port: 8080

spring:
  application:
    name: orderflow-service
💻 输出:

TEXT 📖 仅展示
$ curl http://localhost:8080/api/health
{"status":"UP","application":"orderflow-service","timestamp":"2024-01-15T10:00:00Z"}

❓ 常见问题

Q @SpringBootApplication 可以放在任意类上吗?
A 技术上可以,但强烈建议放在根包下。因为 @ComponentScan 默认扫描主启动类所在包及其子包,放错位置会导致其他 Bean 无法被发现。
Q properties 和 yml 可以同时存在吗?
A 可以,但相同属性以 properties 为准。建议只选一种,避免混乱。推荐 YAML,层级更清晰。
Q 如何关闭 Banner?
A 在配置文件中设置 spring.main.banner-mode=off,或在代码中 app.setBannerMode(Banner.Mode.OFF)
Q 启动报 "Port 8080 already in use" 怎么办?
A 修改 server.port=8081,或找到占用端口的进程 kill 掉:lsof -i :8080(Linux/Mac)或 netstat -ano | findstr 8080(Windows)。
Q 启动很慢是什么原因?
A 常见原因:1)类路径太大,扫描耗时;2)Hibernate ddl-auto=validate 连接远程数据库慢;3)未排除不需要的自动配置。可用 --debug 模式查看详情。
Q 如何指定主启动类?
A Maven 插件通过 <mainClass> 配置;IDE 中直接运行含 main 方法的类;打包时 Spring Boot Maven Plugin 会自动识别。

📖 小节


📝 作业

  1. 基础题(难度⭐):创建 Spring Boot 项目,自定义 banner.txt,将服务端口改为 9090,启动后验证 /api/hello 接口可访问。

  2. 进阶题(难度⭐⭐):使用 SpringApplicationBuilder 替代 SpringApplication.run(),关闭 Banner,设置日志级别为 DEBUG,并自定义一个 /api/app-info 接口返回应用名称和 Java 版本。

  3. 挑战题(难度⭐⭐⭐):实现一个 StartupListener 实现 ApplicationListener<ApplicationStartedEvent>,记录应用启动耗时并打印到日志,思考 Spring Boot 事件机制的设计意图。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏