Java(九)

37 阅读8分钟

01 Spring Boot 项目创建

JDK 17、Spring Boot 3.3.x、包名 com.example.demo。


1. 本章目标与前置阅读

本章目标

学完本章,你应该能够:

  • 使用 start.spring.io 或 IDEA 创建 Spring Boot 3 项目
  • 读懂 pom.xml 关键段落,理解 Starter 机制
  • 认识标准 Maven 目录与 src/main/resources
  • 编写启动类 @SpringBootApplication 并理解组件扫描范围
  • 配置 application.yml(端口、应用名、profile 入门)
  • 理解 Maven 目录与 Java 包的结构划分,按统一规范落地骨架,编写第一个 HelloController
  • 配置本地敏感配置与 .gitignore,避免密钥入库

2. 为什么项目里需要它

Spring Boot 解决的是「传统 Spring 配置繁重、嵌入式服务器与依赖版本难对齐」的问题。在团队里,统一的建项方式意味着:

  • 新人 clone 后一条命令能跑起来
  • 依赖版本由 BOM(Bill of Materials)管理,减少冲突
  • 约定优于配置:缺省内嵌 Tomcat、自动装配数据源(后续章节启用)

没有标准骨架,后面的 MyBatis、Result 封装、全局异常将无处安放。

flowchart TB
  subgraph 项目骨架
    POM[pom.xml 依赖与版本]
    APP[DemoApplication 启动入口]
    YML[application.yml 配置]
    PKG[包结构 module/common/config]
    CTRL[HelloController 第一个接口]
  end
  POM --> APP
  YML --> APP
  APP --> PKG
  PKG --> CTRL

3. 依赖与配置

3.1 使用 start.spring.io 生成项目

访问 start.spring.io/ ,建议选项:

项值
ProjectMaven
LanguageJava
Spring Boot3.3.x(与 README 表一致)
PackagingJar
Java17
Groupcom.example
Artifactdemo
Namedemo

Dependencies(本章最小集):

  • Spring Web(spring-boot-starter-web)

生成后解压,用 IDEA Open 该目录。

Gradle 用户:结构类似,依赖写在 build.gradle;本书主线以 Maven 为例,Gradle 仅作对照。

3.2 pom.xml 关键解读

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!-- 继承 Spring Boot 父 POM,统一管理依赖版本 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.5</version><!-- 与 README 表 3.3.x 对齐,patch 可随官方更新 -->
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Spring Boot beginner demo</description>

    <properties>
        <java.version>17</java.version>
    </properties>

    <dependencies>
        <!-- Web:Spring MVC + 内嵌 Tomcat + Jackson JSON -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

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

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

读懂这三点即可跟做后续章节:

  1. <parent> 引入 Boot 依赖版本清单,子依赖往往不写 version
  2. spring-boot-starter-web 是一个 Starter,一次性拉齐 Web 开发常用库
  3. spring-boot-maven-plugin 支持 mvn spring-boot:run 与打可执行 fat jar

3.3 标准目录结构

demo/
├── pom.xml
├── src/
│   ├── main/
│   │   ├── java/com/example/demo/
│   │   │   └── DemoApplication.java
│   │   └── resources/
│   │       ├── application.yml
│   │       └── application-local.yml   # 本地专用,不提交 Git
│   └── test/
│       └── java/com/example/demo/
│           └── DemoApplicationTests.java
└── .gitignore

3.4 application.yml

src/main/resources/application.yml:

spring:
  application:
    name: demo
  # 默认激活 local profile(本地开发);生产环境由部署平台覆盖
  profiles:
    active: local

server:
  port: 8080

src/main/resources/application-local.yml(本地密码放这里):

# 示例:后续 02 章数据源会用到,此处先占位
# spring:
#   datasource:
#     url: jdbc:mysql://localhost:3306/demo?useSSL=false&serverTimezone=Asia/Shanghai
#     username: root
#     password: your-password

3.5 .gitignore 与本地配置

在项目根 .gitignore 中增加(若 IDEA 模板未包含):

# 本地敏感配置 —— 切勿提交真实密码
application-local.yml
**/application-local.yml

target/
.idea/
*.iml
.DS_Store

团队约定:仓库内只保留 application.yml + application-dev.yml 等无真实密钥的示例;个人机器差异放 application-local.yml。


4. 最小可运行示例

4.1 启动类

src/main/java/com/example/demo/DemoApplication.java:

package com.example.demo;

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

@SpringBootApplication
public class DemoApplication {

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

@SpringBootApplication 是组合注解,等价于:

  • @Configuration:当前类可作为配置源
  • @EnableAutoConfiguration:启用 Boot 自动配置
  • @ComponentScan:默认扫描 当前包及其子包(com.example.demo.**)

因此,com.example.demo.module.user 下的 Controller 会被自动发现;若把类放在 com.other 包则扫描不到。

4.2 结构划分(Maven 目录 + Java 包)详解

Spring Boot 项目有两层「结构」要同时搞清:

  1. Maven 标准目录:源码、配置、测试各放哪
  2. Java 包划分:业务类按域与分层怎么拆
4.2.1 Maven 工程目录
demo/                              # 项目根(与 artifact 同名)
├── pom.xml                        # 依赖与构建
├── .gitignore
└── src
    ├── main
    │   ├── java                   # 业务与配置 Java 源码
    │   │   └── com/example/demo/
    │   └── resources              # 非 Java 资源(yml、XML、静态文件)
    │       ├── application.yml
    │       ├── application-local.yml   # 本地敏感配置,勿提交
    │       ├── mapper/            # MyBatis XML(02 起)
    │       │   └── user/
    │       └── static/            # 可选:静态资源
    └── test
        └── java                   # 单元/集成测试
路径放什么常见误区
src/main/java所有 .java不要把 yml / XML 塞进 java 包目录
src/main/resourcesyml、MyBatis XML、schema.sql 等不要把业务类放进 resources
src/test/java测试类测试包名通常与被测类镜像
项目根 pom.xmlMaven 坐标与依赖不要在源码里硬编码依赖 jar 路径
4.2.2 Java 包划分

在 com.example.demo 下按「公共能力 / 配置 / 业务域 / 安全」拆:

com.example.demo
├── DemoApplication.java      # 启动类:必须在根包,才能扫到子包
├── common/                   # 跨模块公共
│   ├── result/               # 03 Result<T>
│   ├── error/                # 错误码
│   └── exception/            # 04 BizException、Advice
├── config/                   # Redis / MQ / Mail / Knife4j / WebSocket 等
├── security/                 # 09 JWT(Filter、签发校验)
└── module/                   # 按业务域拆分
    ├── user/
    │   ├── controller/       # HTTP 入口
    │   ├── service/          # 业务编排、事务
    │   ├── mapper/           # 02 MyBatis 接口
    │   ├── entity/           # 与表对应的实体
    │   └── dto/              # 请求/响应/消息体
    └── order/                # 结构与 user 对称
flowchart TB
  subgraph root[&#34;com.example.demo&#34;]
    APP[DemoApplication]
    COM[common]
    CFG[config]
    SEC[security]
    MOD[module]
  end
  MOD --> USER[user]
  MOD --> ORDER[order]
  USER --> C[controller]
  USER --> S[service]
  USER --> M[mapper]
  C --> S
  S --> M
  S --> COM
  S --> CFG
  C --> SEC
4.2.3 每个包解决什么问题
包一句话职责后续哪一章填充
DemoApplication启动入口;组件扫描根本章
common.*与具体业务无关的横切能力03、04、13
config「怎么连中间件」,无业务 if/else05–08、10、11
module.{域}.controller收参、校验入口、返回统一响应01 起
module.{域}.service业务编排、事务边界02 起
module.{域}.mapper + resources/mapperSQL / 数据访问02、12
module.{域}.entity / dto持久化模型 / 传输对象02、07
security认证鉴权,与业务域解耦09
4.2.4 分层约定(域内再拆层)

同一业务域内固定 Controller → Service → Mapper:

层可以做禁止做
Controller参数绑定、调用 Service、包装 Result写 SQL;堆砌 Redis/MQ 细节
Service编排、@Transactional、调 Mapper/发消息直接拼 HTTP 协议细节
MapperSQL、结果映射业务判断、发邮件

新功能先问:「属于哪个 域(user/order)?落在哪一层?」再决定建哪个包下的类。完整空包骨架 + package-info 说明见 examples/01-structure/。

4.3 HelloController

src/main/java/com/example/demo/module/user/controller/HelloController.java:

package com.example.demo.module.user.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@RestController
@RequestMapping("/api/hello")
public class HelloController {

    @GetMapping
    public Map<String, String> hello() {
        return Map.of(
                "message", "Hello, Spring Boot!",
                "app", "demo"
        );
    }
}
  • @RestController = @Controller + @ResponseBody,返回值序列化为 JSON
  • @RequestMapping("/api/hello") 为类级别路径前缀

4.4 运行与验证

IDEA:右键 DemoApplication → Run。

命令行:

mvn spring-boot:run

预期结果:

  • 控制台出现 Started DemoApplication,监听 8080
  • 浏览器或 curl 访问:
curl http://localhost:8080/api/hello

响应示例:

{"message":"Hello, Spring Boot!","app":"demo"}

5. 使用规范 / 团队约定

DoDon't
包名统一 com.example.demo,业务按 module/{domain} 拆分把所有类堆在根包或 controller 一个包
API 路径统一前缀 /api/有的接口带前缀、有的不带
配置分环境:默认 yml + application-local.yml在 yml 里写真实生产密码并 commit
启动类放在根包,保证扫描子包启动类放进 module.user 导致漏扫 common
新依赖加在 pom.xml 后刷新 Maven手动拷贝 jar 到 lib
端口、应用名写在 yml,不写死在代码new Server(8080) 这类硬编码

6. 原理简述

嵌入式容器:spring-boot-starter-web 引入 Tomcat(默认),main 方法启动即启动 Web 服务器,无需外部部署 WAR。

自动配置:Classpath 存在 Spring MVC 相关类时,Boot 自动注册 DispatcherServlet、JSON 转换器等。条件装配由 @ConditionalOnClass 等实现——你只需加 Starter,不必手写 XML。

组件扫描与 Bean:带 @Component / @Service / @RestController 等的类被注册为 Bean,由 IoC 容器管理生命周期与依赖注入(DI)。

sequenceDiagram
  participant Main as main()
  participant Boot as SpringApplication
  participant Ctx as ApplicationContext
  participant Tomcat as Embedded Tomcat

  Main->>Boot: run(DemoApplication)
  Boot->>Ctx: 扫描 @SpringBootApplication 包
  Ctx->>Ctx: 注册 Bean(Controller 等)
  Boot->>Tomcat: 启动 Web 容器
  Tomcat-->>Main: 监听 server.port

7. 常见错误与排查

现象可能原因排查
启动报 Port 8080 was already in use端口被占用改 server.port 或结束占用进程
访问 404路径写错或 Controller 未被扫描检查 @RequestMapping;启动类包是否在 com.example.demo 根
访问 404应用未启动完成看日志是否 Started
YAML 启动失败 DuplicateKeyException同一文件重复 key合并重复的 spring: 块
YAML 解析失败Tab 缩进或冒号后缺空格改用空格缩进,格式 key: value
改 yml 不生效改错文件或 profile 未激活确认 spring.profiles.active 与文件名一致
Maven 依赖下载失败网络或镜像配置国内 Maven 镜像;IDEA Reload
Java 版本报错本地 JDK 非 17IDEA Project Structure → SDK 17

8. 练习题

  1. 改端口:将应用改为 9090,用 curl 验证 Hello 接口。
  2. 新接口:新增 GET /api/hello/version,返回 {"version":"0.0.1"}。
  3. 包结构:创建 module/order/controller 空包,写注释说明 order 模块将来放什么。
  4. profile:新增 application-dev.yml,设置 server.port: 8081;切换 spring.profiles.active 观察端口变化。
  5. Git:确认 application-local.yml 已被 .gitignore 忽略(git status 不应出现该文件)。

完整例子

1. 改端口为 9090

src/main/resources/application.yml:

spring:
  application:
    name: demo
  profiles:
    active: local

server:
  port: 9090

验证:

curl http://localhost:9090/api/hello
2. 新增 GET /api/hello/version

src/main/java/com/example/demo/module/user/controller/HelloController.java:

package com.example.demo.module.user.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@RestController
@RequestMapping("/api/hello")
public class HelloController {

    @GetMapping
    public Map<String, String> hello() {
        return Map.of(
                "message", "Hello, Spring Boot!",
                "app", "demo"
        );
    }

    @GetMapping("/version")
    public Map<String, String> version() {
        return Map.of("version", "0.0.1");
    }
}

验证:curl http://localhost:9090/api/hello/version → {"version":"0.0.1"}。

3. order 模块空包说明

src/main/java/com/example/demo/module/order/controller/package-info.java:

/**
 * 订单模块 Controller 包。
 * <p>
 * 后续放置:订单 CRUD、下单接口、订单查询/取消等 HTTP 入口。
 * 配套目录:service / mapper / entity / dto。
 */
package com.example.demo.module.order.controller;
4. profile:application-dev.yml 端口 8081

src/main/resources/application-dev.yml:

server:
  port: 8081

主配置切换 profile(application.yml):

spring:
  application:
    name: demo
  profiles:
    active: dev

server:
  port: 8080

启动后日志应出现 Tomcat started on port(s): 8081(dev 覆盖主配置端口)。

5. .gitignore 忽略本地敏感配置

项目根 .gitignore:

# 本地敏感配置 —— 切勿提交真实密码
application-local.yml
**/application-local.yml

target/
.idea/
*.iml
.DS_Store

本地创建 src/main/resources/application-local.yml 后执行:

git status

输出中不应出现 application-local.yml。


9.源码

源码地址 01-structure