1.1、Spring Boot和MVC架构
Spring Boot 和 MVC 架构是 Java 开发中两个不同层面的概念,它们的定位、目标和应用场景有显著区别,下面从多个维度进行对比:
1. 本质与定位
-
MVC 架构
是一种软件设计模式(Model-View-Controller),用于分离应用程序的业务逻辑、数据展示和用户交互。
- Model:处理数据和业务逻辑
- View:负责数据展示(如页面)
- Controller:接收用户请求,协调 Model 和 View 完成响应
MVC 是一种思想,不依赖特定技术,可用于任何语言或框架(如 Java 的 Spring MVC、前端的 Angular 等)。
-
Spring Boot
是基于 Spring 框架的快速开发工具 ,本质是一套脚手架 ,用于简化 Spring 应用的配置和部署。
它内置了自动配置、依赖管理、嵌入式服务器等特性,让开发者无需手动配置大量 XML 或注解就能快速构建应用。
2. 核心目标
- MVC :解决应用程序的代码结构混乱问题,通过分离关注点提高代码复用性和可维护性。
- Spring Boot :解决 Spring 框架的配置繁琐、部署复杂问题,实现"开箱即用",加速开发流程。
3. 技术层级
- MVC 属于架构设计层面,是组织代码的方法论。
- Spring Boot 属于开发工具层面,是简化 Spring 应用开发的技术栈。
关系:Spring Boot 可以集成 Spring MVC(MVC 模式的一种实现)作为 Web 层框架,即 Spring Boot 是容器,Spring MVC 是其中的一个组件。
4. 功能范围
- MVC:仅关注请求处理流程(接收请求→处理业务→返回响应),不涉及应用部署、依赖管理等。
- Spring Boot :涵盖更广泛的功能:
- 自动配置 Spring 及第三方库(如默认配置数据源、Web 服务器)
- 内置 Tomcat、Jetty 等服务器,无需单独部署
- 提供 Actuator 监控应用健康状态
- 支持快速集成各种组件(如 MyBatis、Redis 等)
5. 使用场景
- MVC:适用于所有需要清晰分离业务逻辑和展示层的应用,尤其是 Web 应用。
- Spring Boot:适用于需要快速开发、简化配置的 Spring 应用,特别是微服务架构(可配合 Spring Cloud 使用)。
6. 典型示例对比
-
传统 Spring MVC 应用
需要手动配置
web.xml、DispatcherServlet、组件扫描等,且需单独部署到外部服务器:xml<!-- web.xml 配置 DispatcherServlet --> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> </servlet> -
Spring Boot 应用
无需 XML 配置,通过注解和
main方法即可启动嵌入式服务器:java@SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } } -
MVC 是架构模式 ,解决代码组织问题;Spring Boot 是开发工具,解决 Spring 应用的效率问题。
-
实际开发中,两者常结合使用:用 Spring Boot 简化配置,用 Spring MVC(MVC 模式的实现)处理 Web 请求。
-
选择依据:若需快速开发 Spring 应用,优先用 Spring Boot;若仅需规范代码结构,MVC 模式是基础原则。
1.2、Spring Boot的特征
Spring Boot 作为简化 Spring 应用开发的框架,具有以下核心特征,这些特征使其成为快速构建 Java 应用的首选工具:
1. 自动配置(Auto-configuration)
- 核心特性:根据类路径中的依赖自动配置 Spring 应用,减少手动配置。
- 举例 :若项目依赖
spring-boot-starter-web,Spring Boot 会自动配置 Tomcat 服务器、DispatcherServlet(Spring MVC 核心)等 Web 组件,无需手动编写 XML 或注解配置。 - 灵活性 :可通过
@EnableAutoConfiguration控制自动配置范围,或用@Configuration自定义配置覆盖默认行为。
2. 起步依赖(Starter Dependencies)
- 核心特性:将常用依赖打包成" starter ",简化 Maven/Gradle 依赖管理。
- 举例 :引入
spring-boot-starter-data-jpa即可自动包含 Hibernate、Spring Data JPA 及相关数据库依赖,无需手动添加多个 jar 包。 - 优势:避免版本冲突(Starter 内部已协调依赖版本),降低依赖管理复杂度。
3. 嵌入式服务器(Embedded Servers)
- 核心特性:内置 Tomcat、Jetty、Undertow 等服务器,无需单独部署应用到外部服务器。
- 效果 :应用可通过
java -jar直接运行,简化开发、测试和部署流程(如微服务场景)。 - 自定义 :可通过配置文件修改服务器端口(如
server.port=8081)或切换服务器类型。
4. 无代码生成与 XML 配置
- 核心特性 :通过注解(如
@SpringBootApplication)和约定优于配置(Convention over Configuration)原则,替代传统 XML 配置。 - 优势:减少模板代码和配置文件,专注业务逻辑开发。
- 例外 :若需特殊配置,仍可通过 Java 配置类(
@Configuration)或少量 YAML/Properties 文件实现。
5. Actuator 监控
- 核心特性:提供应用监控和管理功能,无需额外开发。
- 功能 :通过 HTTP 端点暴露应用健康状态(
/health)、内存使用(/metrics)、Bean 信息(/beans)等,便于运维和问题排查。 - 扩展:支持集成 Prometheus、Grafana 等工具实现可视化监控。
6. 命令行界面(CLI,可选)
- 核心特性:通过命令行快速创建和运行 Spring Boot 应用,支持 Groovy 脚本(无需编译即可执行)。
- 场景 :适合快速原型开发,例如通过
spring run app.groovy直接启动应用。
7. 与 Spring 生态无缝集成
- 核心特性:完美兼容 Spring 框架的所有功能(如 DI、AOP、Spring Security 等),并简化其使用。
- 扩展:可轻松集成 Spring Cloud 实现微服务(服务注册、配置中心等),或与 MyBatis、Redis 等第三方库协作。
8. 开发工具支持
- 核心特性 :集成开发者友好工具,提升开发效率。
- 热部署 :通过
spring-boot-devtools实现代码修改后自动重启,减少手动重启时间。 - IDE 集成:支持 IntelliJ IDEA、Eclipse 等主流 IDE 的一键创建和运行。
- 热部署 :通过
Spring Boot 的核心设计理念是"简化开发 "和"开箱即用",通过自动配置、起步依赖和嵌入式服务器等特性,大幅降低 Spring 应用的入门门槛,同时保持灵活性和扩展性。它特别适合快速开发微服务、RESTful API、企业级应用等场景,是当前 Java 开发领域的主流框架之一。
1.3、Maven简介及pom文件构成
Maven简介
Maven 是 Apache 推出的项目管理和构建自动化工具,主要用于 Java 项目,通过标准化的项目结构、依赖管理和构建流程,解决了传统项目中"jar 包冲突""构建步骤繁琐"等问题。
其核心功能包括:
- 依赖管理:自动下载、管理项目所需的第三方库(jar 包),并处理依赖间的版本冲突。
- 标准化构建 :提供编译、测试、打包、部署等统一的生命周期命令(如
mvn clean package)。 - 项目结构标准化 :规定了固定的目录结构(如
src/main/java存放源代码),避免团队开发中的结构混乱。
POM 文件(Project Object Model)
POM 是 Maven 的核心配置文件,以 XML 格式存在(命名为 pom.xml),位于项目根目录,用于描述项目信息、依赖、构建规则等。其核心构成如下:
1. 基本项目信息
定义项目的基本元数据,是 POM 的基础。
xml
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<!-- POM 模型版本(固定为 4.0.0) -->
<modelVersion>4.0.0</modelVersion>
<!-- 项目坐标(唯一标识项目,用于依赖引用) -->
<groupId>com.example</groupId> <!-- 组织/公司标识(如域名反转) -->
<artifactId>demo-project</artifactId> <!-- 项目名称 -->
<version>1.0.0</version> <!-- 版本号 -->
<name>Demo Project</name> <!-- 项目显示名称(可选) -->
<description>A sample Maven project</description> <!-- 项目描述(可选) -->
</project>
2. 依赖管理(dependencies)
声明项目所需的第三方库或模块,Maven 会自动从中央仓库(默认 https://repo.maven.apache.org/maven2)下载并添加到类路径。
xml
<dependencies>
<!-- 单个依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId> <!-- 依赖的组织标识 -->
<artifactId>spring-boot-starter-web</artifactId> <!-- 依赖的名称 -->
<version>2.7.0</version> <!-- 依赖的版本号 -->
<!-- 可选配置 -->
<scope>compile</scope> <!-- 依赖范围(默认 compile,其他如 test、provided 等) -->
<optional>false</optional> <!-- 是否可选依赖(避免传递依赖) -->
<exclusions> <!-- 排除依赖中的子依赖(解决冲突) -->
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
- 依赖范围(scope) :控制依赖在不同构建阶段(编译、测试、运行)的可见性,常见值:
compile:默认值,编译、测试、运行阶段均有效。test:仅测试阶段有效(如 JUnit)。provided:编译和测试阶段有效,运行时由容器提供(如 Servlet API)。
3. 依赖管理(dependencyManagement)
用于统一管理依赖版本,避免子模块重复声明版本号(常用于多模块项目)。
xml
<dependencyManagement>
<dependencies>
<!-- 声明 Spring Boot 版本,子模块引用时无需指定 version -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.0</version>
</dependency>
</dependencies>
</dependencyManagement>
4. 构建配置(build)
自定义项目的构建流程,如指定源代码目录、插件配置等。
xml
<build>
<!-- 最终构建产物的名称(默认 ${artifactId}-${version}) -->
<finalName>my-project</finalName>
<!-- 插件配置(扩展 Maven 功能) -->
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.7.0</version>
<executions>
<execution>
<goals>
<goal>repackage</goal> <!-- 将 jar 包转为可执行 jar -->
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
5. 父项目(parent)
用于继承父项目的配置(如依赖管理、插件),实现配置复用(典型如 Spring Boot 项目继承 spring-boot-starter-parent)。
xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
<relativePath/> <!-- 从仓库查找父项目,而非本地目录 -->
</parent>
6. 模块(modules)
多模块项目中,声明当前项目包含的子模块(子模块需在 pom.xml 中声明 parent 指向当前项目)。
xml
<modules>
<module>module-1</module> <!-- 子模块目录名称 -->
<module>module-2</module>
</modules>
pom.xml 是 Maven 项目的"灵魂",通过项目坐标 唯一标识项目,通过依赖管理 简化第三方库引用,通过构建配置定制编译、打包等流程。掌握 POM 文件的结构,是使用 Maven 进行高效开发的基础。
1.4、Spring Boot的文件目录
Spring Boot 项目遵循 Maven/Gradle 的标准化目录结构,同时结合自身特点进行了优化,使项目结构清晰、职责明确。以下是典型的 Spring Boot 项目文件目录结构及各部分的作用:
一、基本目录结构(Maven 为例)
project-root/ # 项目根目录
├── src/
│ ├── main/ # 主程序目录(生产环境代码)
│ │ ├── java/ # Java 源代码目录
│ │ │ └── com/
│ │ │ └── example/
│ │ │ └── demo/ # 项目包路径(通常为域名反转+项目名)
│ │ │ ├── DemoApplication.java # 启动类(入口)
│ │ │ ├── controller/ # 控制器(处理HTTP请求)
│ │ │ ├── service/ # 业务逻辑层
│ │ │ ├── repository/ # 数据访问层(如DAO)
│ │ │ ├── model/ # 数据模型(实体类)
│ │ │ └── config/ # 配置类(自定义Bean等)
│ │ └── resources/ # 资源文件目录
│ │ ├── application.properties # 主配置文件(默认)
│ │ ├── application.yml # 主配置文件(YAML格式,推荐)
│ │ ├── static/ # 静态资源(CSS、JS、图片等)
│ │ ├── templates/ # 模板文件(如Thymeleaf、Freemarker)
│ │ └── META-INF/ # 元数据文件(如spring.factories)
│ └── test/ # 测试目录
│ ├── java/ # 测试代码(对应main/java的包结构)
│ │ └── com/
│ │ └── example/
│ │ └── demo/
│ │ └── controller/
│ │ └── UserControllerTest.java # 测试类
│ └── resources/ # 测试资源文件(如测试专用配置)
├── pom.xml # Maven 依赖配置文件
├── .gitignore # Git 忽略文件配置
└── README.md # 项目说明文档
二、核心目录/文件详解
1. src/main/java
存放项目的核心 Java 源代码,遵循包结构规范:
- 启动类(如
DemoApplication.java) :带有@SpringBootApplication注解,是项目入口,通过main方法启动 Spring Boot 应用。 controller/:存放控制器类(带@Controller或@RestController注解),负责接收 HTTP 请求、调用业务逻辑并返回响应。service/:存放业务逻辑类(带@Service注解),实现核心业务功能,被控制器调用。repository/:数据访问层(如带@Repository注解的类),负责与数据库交互(通常结合 MyBatis、JPA 等框架)。model/:存放实体类(如User.java),映射数据库表或封装数据。config/:存放配置类(带@Configuration注解),用于定义自定义 Bean、配置第三方组件等。
2. src/main/resources
存放项目资源文件,包括配置、静态资源、模板等:
- 配置文件 :
application.properties或application.yml:Spring Boot 主配置文件,用于配置端口(server.port)、数据库连接、日志级别等。支持多环境配置(如application-dev.yml对应开发环境)。
static/:存放静态资源,如 CSS、JavaScript、图片、HTML 等,Spring Boot 会自动映射访问路径(例如static/index.html可通过http://localhost:8080/index.html直接访问)。templates/:存放模板引擎文件(如 Thymeleaf 的.html、Freemarker 的.ftl),用于动态生成 HTML 页面(需配合控制器返回逻辑视图名)。META-INF/:存放元数据文件,例如spring.factories用于自动配置扩展(Spring Boot 自动配置的核心机制之一)。
3. src/test/java
存放单元测试和集成测试代码,通常与 src/main/java 保持一致的包结构,使用 JUnit、Mockito 等框架编写测试用例。例如 UserControllerTest 测试 UserController 的接口功能。
4. 根目录文件
pom.xml:Maven 项目的核心配置文件,声明项目依赖(如spring-boot-starter-web)、插件(如spring-boot-maven-plugin)等。.gitignore:指定 Git 版本控制中需要忽略的文件(如target/编译目录、IDE 配置文件等)。
三、其他常见目录(视项目需求而定)
src/main/java/com/example/demo/exception/:存放自定义异常类及全局异常处理器(带@RestControllerAdvice注解)。src/main/java/com/example/demo/util/:存放工具类(如日期处理、加密解密等)。src/main/resources/db/migration/:若使用 Flyway/Liquibase 进行数据库版本管理,存放 SQL 迁移脚本。
Spring Boot 目录结构遵循"约定优于配置"原则,通过标准化的目录划分,使开发者能快速定位代码位置,降低团队协作成本。核心原则是:
- 源代码与资源文件分离(
java/vsresources/) - 主程序与测试代码分离(
main/vstest/) - 业务逻辑按分层职责划分(控制器、服务、数据访问等)
这种结构在微服务、RESTful API 等场景中尤为适用,是 Spring Boot 提高开发效率的重要基础。
1.5、Spring Boot的两种运行方式
Spring Boot 应用有两种主要的部署和运行方式:JAR 包方式和 WAR 包方式,它们适用于不同的场景,各有特点。以下是两种方式的详细介绍:
一、JAR 包运行方式(推荐)
Spring Boot 默认支持将应用打包为可执行 JAR(Java ARchive)文件,这是最常用的方式,尤其适合微服务架构。
1. 打包为 JAR 包
-
配置基础 :Spring Boot 项目的
pom.xml中默认包含spring-boot-maven-plugin插件,无需额外配置即可打包为可执行 JAR。xml<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> -
打包命令 :在项目根目录执行 Maven 命令
bashmvn clean package执行成功后,JAR 包会生成在
target/目录下,命名格式通常为[项目名]-[版本号].jar(如demo-0.0.1-SNAPSHOT.jar)。
2. 运行 JAR 包
直接通过 java -jar 命令启动,无需依赖外部 Web 服务器:
bash
java -jar target/demo-0.0.1-SNAPSHOT.jar
3. 特点与优势
- 内置服务器:JAR 包中包含嵌入式 Tomcat(默认)、Jetty 或 Undertow 服务器,无需额外部署到外部容器。
- 独立运行:一个 JAR 包就是一个独立的应用,可直接在任何安装了 JRE 的环境中运行。
- 简化部署:适合 CI/CD 流程和容器化部署(如 Docker),只需复制 JAR 包即可。
- 默认选择:Spring Boot 官方推荐,大多数场景(如微服务、REST API)优先使用。
二、WAR 包运行方式
WAR(Web Application Archive)包是传统 Java Web 应用的打包格式,适用于需要部署到外部 Web 服务器(如 Tomcat、JBoss)的场景。
1. 配置为 WAR 包
需要修改项目配置以支持 WAR 打包:
-
步骤 1 :在
pom.xml中修改打包类型为warxml<packaging>war</packaging> -
步骤 2 :排除内置服务器依赖(避免与外部服务器冲突)
xml<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <!-- 排除内置 Tomcat --> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <!-- 添加 Servlet API 依赖(由外部服务器提供) --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <scope>provided</scope> </dependency> </dependencies> -
步骤 3 :修改启动类,继承
SpringBootServletInitializer并重写configure方法java@SpringBootApplication public class DemoApplication extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { return application.sources(DemoApplication.class); } public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
2. 打包与运行 WAR 包
-
打包命令 :同 JAR 包,执行后在
target/目录生成 WAR 包bashmvn clean package -
运行方式 :将 WAR 包复制到外部 Web 服务器(如 Tomcat)的
webapps/目录下,启动服务器后自动部署,访问路径通常为http://localhost:8080/[WAR包名]。
3. 特点与适用场景
- 依赖外部服务器:需要部署到独立的 Web 服务器,由外部服务器管理生命周期。
- 兼容性:适合需要集成到传统 Java EE 环境或依赖特定服务器功能的场景。
- 灵活性低:部署流程比 JAR 包复杂,需手动管理服务器配置。
三、两种方式的核心区别
| 对比维度 | JAR 包方式 | WAR 包方式 |
|---|---|---|
| 服务器依赖 | 内置嵌入式服务器 | 依赖外部 Web 服务器 |
| 启动方式 | java -jar 直接运行 |
部署到外部服务器后启动 |
| 适用场景 | 微服务、独立应用、容器化 | 传统 Java Web 环境、服务器集群 |
| 部署复杂度 | 简单(复制 JAR 包即可) | 较复杂(需配置外部服务器) |
| 官方推荐程度 | 推荐(默认方式) | 按需使用(兼容传统场景) |
- 优先选择 JAR 包方式:适合大多数 Spring Boot 应用,尤其是微服务、云原生场景,简化部署流程。
- 必要时使用 WAR 包方式:当需要集成到现有外部 Web 服务器或依赖特定服务器功能时采用。
两种方式均能正常运行 Spring Boot 应用,选择时主要根据部署环境和项目需求决定。
2.1、Spring Boot的自动化配置
Spring Boot 的自动化配置(Auto-configuration)是其核心特性之一,它通过"约定优于配置"的原则,自动完成 Spring 应用的各项配置,大幅减少了传统 Spring 应用中繁琐的 XML 或注解配置。以下从实现原理、核心机制、自定义配置等方面详细介绍:
一、自动化配置的核心目标
- 消除重复配置:对于常见场景(如 Web 开发、数据库连接),Spring Boot 预定义了一套默认配置,无需开发者手动编写。
- 按需激活配置 :仅当特定依赖存在于类路径时,才激活对应的自动配置(例如引入
spring-boot-starter-web才会自动配置 Tomcat 和 Spring MVC)。 - 保持灵活性:允许开发者通过自定义配置覆盖默认行为,平衡"自动"与"可控"。
二、自动化配置的实现原理
Spring Boot 的自动配置基于 Spring 框架的 条件注解(Condition) 和 SPI(Service Provider Interface) 机制,核心流程如下:
1. 扫描自动配置类
-
Spring Boot 启动时,会通过
@EnableAutoConfiguration注解(被@SpringBootApplication间接包含)触发自动配置。 -
该注解会扫描类路径下
META-INF/spring.factories文件中声明的自动配置类(全类名),这些类是 Spring Boot 预定义的配置模板。示例
spring.factories内容(来自spring-boot-autoconfigure包):propertiesorg.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
2. 条件筛选有效配置
自动配置类通过 条件注解 决定是否生效,确保配置仅在满足特定条件时被激活。常见条件注解包括:
-
@ConditionalOnClass:当类路径中存在指定类时生效(如WebMvcAutoConfiguration依赖DispatcherServlet.class)。 -
@ConditionalOnMissingClass:当类路径中不存在指定类时生效。 -
@ConditionalOnBean:当 Spring 容器中存在指定 Bean 时生效。 -
@ConditionalOnMissingBean:当 Spring 容器中不存在指定 Bean 时生效(允许开发者自定义 Bean 覆盖默认配置)。 -
@ConditionalOnProperty:当配置文件中存在指定属性且值匹配时生效(如@ConditionalOnProperty(name = "spring.mvc.enabled", havingValue = "true"))。示例:
WebMvcAutoConfiguration的条件判断java@Configuration @ConditionalOnWebApplication(type = Type.SERVLET) // 仅在 Servlet Web 应用中生效 @ConditionalOnClass({ Servlet.class, DispatcherServlet.class }) // 存在相关类时生效 @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) public class WebMvcAutoConfiguration { // 自动配置内容(如 DispatcherServlet、视图解析器等) }
3. 加载默认配置与绑定属性
-
生效的自动配置类会向 Spring 容器中注册默认 Bean(如
DispatcherServlet、DataSource等)。 -
这些 Bean 的配置参数会通过
@ConfigurationProperties注解与application.yml/application.properties中的属性绑定,实现配置定制。示例:
DataSourceAutoConfiguration绑定数据库配置java@ConfigurationProperties(prefix = "spring.datasource") // 绑定配置文件中 spring.datasource 前缀的属性 public class DataSourceProperties { private String url; private String username; private String password; // getter/setter }对应的配置文件(
application.yml):yamlspring: datasource: url: jdbc:mysql://localhost:3306/test username: root password: 123456
4. 允许自定义配置覆盖默认值
Spring Boot 遵循"用户配置优先"原则:
- 若开发者手动定义了某个 Bean(如自定义
DataSource),则自动配置类中通过@ConditionalOnMissingBean声明的默认 Bean 会失效。 - 配置文件中的属性会覆盖自动配置类的默认参数(如通过
server.port=8081修改默认端口)。
三、核心组件与注解
@SpringBootApplication:组合注解,包含@EnableAutoConfiguration(开启自动配置)、@ComponentScan(扫描组件)、@Configuration(标识配置类)。@EnableAutoConfiguration:触发自动配置的核心注解,通过AutoConfigurationImportSelector加载spring.factories中的配置类。@Conditional系列注解:用于条件筛选,决定自动配置是否生效。@ConfigurationProperties:将配置文件中的属性绑定到 Java 类,实现配置参数注入。spring-boot-autoconfigure包:包含大量预定义的自动配置类(如 Web、数据库、缓存等场景),是自动化配置的核心依赖。
四、自定义自动化配置(扩展场景)
若需为自定义组件实现自动配置,可遵循以下步骤:
-
创建自动配置类 :使用
@Configuration和条件注解定义配置逻辑。java@Configuration @ConditionalOnClass(MyService.class) // 当类路径存在 MyService 时生效 public class MyAutoConfiguration { @Bean @ConditionalOnMissingBean // 若用户未定义 MyService,则使用默认实现 public MyService myService() { return new MyService(); } } -
注册自动配置类 :在项目的
src/main/resources/META-INF/spring.factories中声明:propertiesorg.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.example.MyAutoConfiguration -
绑定配置属性 (可选):通过
@ConfigurationProperties允许用户自定义参数。java@ConfigurationProperties(prefix = "my.service") public class MyServiceProperties { private boolean enabled = true; // getter/setter }在自动配置类中引入:
java@EnableConfigurationProperties(MyServiceProperties.class) public class MyAutoConfiguration { // 使用 MyServiceProperties 配置 MyService }
五、调试与排查自动配置
若需查看自动配置的生效情况,可通过以下方式调试:
-
开启 debug 日志 :在配置文件中添加
debug: true,启动时会输出自动配置报告,显示哪些配置生效(Positive matches)、哪些未生效(Negative matches)。 -
排除特定自动配置 :若某个自动配置不符合需求,可通过
@SpringBootApplication的exclude属性排除:java@SpringBootApplication(exclude = DataSourceAutoConfiguration.class) public class DemoApplication { ... }或在配置文件中排除:
yamlspring: autoconfigure: exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
Spring Boot 的自动化配置通过 SPI 扫描 、条件筛选 、属性绑定 三大机制,实现了"按需配置"和"用户优先"的核心逻辑。它既简化了开发流程(无需手动配置常见组件),又保留了足够的灵活性(允许覆盖默认配置),是 Spring Boot 实现"开箱即用"的关键所在。理解自动配置的原理,有助于开发者更好地掌控 Spring Boot 应用的配置逻辑,解决配置冲突等问题。
2.2、Spring Boot的全局配置
Spring Boot 的全局配置是管理应用行为的核心方式,通过配置文件可以统一设置应用端口、数据库连接、日志级别等各种参数,无需修改代码即可调整应用特性。以下从配置文件类型、核心配置项、多环境配置、配置原理等方面详细介绍:
一、全局配置文件的类型与位置
Spring Boot 支持两种格式的全局配置文件,用于设置应用的各种参数:
1. 配置文件类型
-
application.properties:传统的键值对格式,语法为key=value示例:
propertiesserver.port=8080 spring.datasource.url=jdbc:mysql://localhost:3306/test -
application.yml:YAML 格式(推荐),采用缩进表示层级关系,语法更简洁示例:
yamlserver: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/test
两种格式功能完全一致,YAML 更适合复杂层级配置,Properties 适合简单键值对场景。
2. 配置文件存放位置(优先级从高到低)
Spring Boot 会按以下顺序加载配置文件,高优先级配置会覆盖低优先级:
file:./config/:项目根目录下的config文件夹(适合生产环境外部配置)。file:./:项目根目录(如pom.xml同级目录)。classpath:/config/:src/main/resources/config目录(适合开发环境)。classpath:/:src/main/resources目录(默认存放位置)。
注意 :若多个位置存在配置文件,Spring Boot 会合并所有配置,冲突时以高优先级为准。
二、核心全局配置项分类
全局配置项覆盖应用开发的各个方面,以下是常用分类及示例:
1. 服务器配置(server 前缀)
yaml
server:
port: 8081 # 应用端口(默认 8080)
servlet:
context-path: /api # 应用上下文路径(默认 /)
tomcat:
max-threads: 200 # Tomcat 最大线程数
2. 数据源配置(spring.datasource 前缀)
yaml
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver # 数据库驱动(多数情况可自动推断)
hikari: # 连接池配置(HikariCP 是默认连接池)
maximum-pool-size: 10 # 最大连接数
idle-timeout: 300000 # 连接空闲超时(毫秒)
3. 日志配置(logging 前缀)
yaml
logging:
level:
root: INFO # 全局日志级别(ERROR/WARN/INFO/DEBUG/TRACE)
com.example.demo.controller: DEBUG # 特定包的日志级别
file:
name: ./logs/app.log # 日志文件路径
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n" # 控制台日志格式
4. Spring MVC 配置(spring.mvc 前缀)
yaml
spring:
mvc:
view:
prefix: /templates/ # 视图前缀(如 Thymeleaf 模板路径)
suffix: .html # 视图后缀
format:
date: yyyy-MM-dd # 日期格式化
5. 静态资源配置(spring.web.resources 前缀)
yaml
spring:
web:
resources:
static-locations: classpath:/static/,classpath:/public/ # 静态资源目录(默认已包含)
6. 自定义配置(无固定前缀)
可自定义配置项,通过 @Value 或 @ConfigurationProperties 注入到代码中:
yaml
app:
name: "我的应用"
version: "1.0.0"
features:
- "用户管理"
- "订单系统"
三、配置参数的注入方式
全局配置中的参数可通过以下方式注入到 Java 代码中使用:
1. @Value 注解(适合简单参数)
直接注入单个配置项,支持 SpEL 表达式:
java
@RestController
public class MyController {
@Value("${server.port}")
private int serverPort; // 注入服务器端口
@Value("${app.name:默认名称}") // 冒号后为默认值(配置不存在时使用)
private String appName;
@GetMapping("/info")
public String info() {
return "端口:" + serverPort + ",应用名:" + appName;
}
}
2. @ConfigurationProperties 注解(适合复杂参数)
将一组相关配置绑定到 Java 类,适合多个参数的场景:
java
// 绑定前缀为 "app" 的配置
@ConfigurationProperties(prefix = "app")
@Component
public class AppConfig {
private String name;
private String version;
private List<String> features;
// getter 和 setter(必须存在,否则无法绑定)
}
使用时直接注入该类:
java
@Service
public class MyService {
@Autowired
private AppConfig appConfig;
public void printInfo() {
System.out.println("应用名:" + appConfig.getName());
System.out.println("功能列表:" + appConfig.getFeatures());
}
}
注意 :需在启动类添加 @EnableConfigurationProperties 或在配置类添加 @Component 使绑定生效。
四、多环境配置
实际开发中,应用需要在开发(dev)、测试(test)、生产(prod) 等不同环境运行,配置参数可能不同(如数据库地址)。Spring Boot 支持多环境配置,无需修改主配置文件即可切换环境。
1. 多环境配置文件命名规则
创建多个配置文件,命名格式为:
application-{profile}.yml(或 .properties),其中 {profile} 为环境标识(如 dev、prod)。
示例:
application-dev.yml:开发环境配置application-test.yml:测试环境配置application-prod.yml:生产环境配置
2. 环境配置内容示例
application-dev.yml(开发环境):
yaml
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/dev_db # 开发库
application-prod.yml(生产环境):
yaml
server:
port: 80 # 生产环境用 80 端口
spring:
datasource:
url: jdbc:mysql://prod-server:3306/prod_db # 生产库
3. 激活指定环境
通过以下方式指定当前环境(优先级从高到低):
-
方式 1:在主配置文件中指定
application.yml:yamlspring: profiles: active: dev # 激活开发环境 -
方式 2:启动命令参数指定
运行 JAR 包时通过
--spring.profiles.active指定:bashjava -jar app.jar --spring.profiles.active=prod # 激活生产环境 -
方式 3:IDE 启动参数
在 IDE 中配置启动参数(如 VM options):
-Dspring.profiles.active=test
五、配置的优先级
当同一配置项在多个地方被定义时,Spring Boot 按以下优先级(从高到低)生效:
- 命令行参数(如
--server.port=8081) - 系统环境变量(如操作系统的环境变量)
application-{profile}.yml(激活的环境配置)application.yml(主配置文件)- 类路径下的默认配置(如
spring-boot-autoconfigure中的默认值)
示例 :若命令行指定 --server.port=8082,同时 application-dev.yml 中设置 server.port=8080,最终端口为 8082(命令行优先级更高)。
六、外部化配置扩展
除了默认配置文件,Spring Boot 还支持多种外部化配置方式,适合生产环境动态调整参数:
-
外部配置文件 :将
application.yml放在应用外部(如/etc/app/),通过启动参数指定路径:bashjava -jar app.jar --spring.config.location=/etc/app/application.yml -
配置中心:结合 Spring Cloud Config 或 Nacos 等工具,实现分布式环境下的配置集中管理和动态刷新。
-
操作系统环境变量:适合容器化部署(如 Docker),通过环境变量注入配置:
bash# 启动 Docker 容器时注入环境变量 docker run -e "SPRING_PROFILES_ACTIVE=prod" -e "SERVER_PORT=80" myapp:latest(注意:环境变量名需将配置中的
.改为_,并大写,如server.port对应SERVER_PORT)
Spring Boot 的全局配置通过标准化的配置文件和灵活的注入方式,实现了应用参数的集中管理和动态调整。核心优势包括:
- 支持多格式(Properties/YAML)和多环境配置,适应不同开发阶段需求。
- 提供多种配置注入方式,满足简单和复杂场景。
- 灵活的优先级机制和外部化配置支持,便于生产环境部署。
掌握全局配置是使用 Spring Boot 开发的基础,合理使用可大幅提升应用的可配置性和可维护性。
2.3、Spring Boot的自定义配置
在 Spring Boot 中,除了使用全局配置文件管理默认参数外,还可以通过"自定义配置"灵活开发者根据业务需求定义配置项,并在代码中灵活使用。这种方式能让配置与业务逻辑解耦,便于维护和扩展。以下从自定义配置的定义、注入方式、高级特性等方面详细介绍:
一、自定义配置的基本定义
自定义配置指开发者在全局配置文件(application.yml 或 application.properties)中添加业务相关的配置项,例如应用名称、功能开关、第三方 API 密钥等。
1. 配置项格式
-
YAML 格式(推荐):
yaml# 自定义应用信息 app: name: "电商平台" version: "2.1.0" enabled: true timeout: 3000 contact: email: "support@example.com" phone: "123456789" features: # 列表类型 - "支付功能" - "会员系统" - "优惠券" -
Properties 格式:
properties# 自定义应用信息 app.name=电商平台 app.version=2.1.0 app.enabled=true app.timeout=3000 app.contact.email=support@example.com app.contact.phone=123456789 app.features[0]=支付功能 app.features[1]=会员系统 app.features[2]=优惠券
二、自定义配置的注入方式
定义配置项后,需将其注入到 Java 代码中使用。Spring Boot 提供了多种注入方式,适用于不同场景:
1. @Value 注解(简单配置项)
@Value 适合注入单个简单类型的配置项(如字符串、数字、布尔值),支持直接指定默认值。
示例:
java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AppController {
// 注入字符串类型(带默认值,配置不存在时使用)
@Value("${app.name:默认应用}")
private String appName;
// 注入数字类型
@Value("${app.version}")
private String appVersion;
// 注入布尔类型
@Value("${app.enabled}")
private boolean isEnabled;
// 注入嵌套配置项(contact.email)
@Value("${app.contact.email}")
private String contactEmail;
@GetMapping("/app/info")
public String getAppInfo() {
return String.format(
"应用名称:%s,版本:%s,状态:%s,联系邮箱:%s",
appName, appVersion, isEnabled ? "启用" : "禁用", contactEmail
);
}
}
注意:
- 若配置项不存在且未指定默认值,启动时会抛出
IllegalArgumentException。 @Value不适合注入复杂类型(如列表、对象),需配合 SpEL 表达式,较繁琐。
2. @ConfigurationProperties 注解(复杂配置项)
@ConfigurationProperties 适合注入一组相关的配置项(尤其是包含对象、列表的复杂结构),通过"前缀匹配"将配置项绑定到 Java 类的属性中,更清晰且类型安全。
步骤 1:定义配置类并绑定前缀
java
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
// 绑定前缀为 "app" 的所有配置项
@ConfigurationProperties(prefix = "app")
@Component // 注册为 Spring 组件,使其被扫描并注入
public class AppConfig {
// 基本类型
private String name;
private String version;
private boolean enabled;
private int timeout;
// 嵌套对象(对应 app.contact 配置)
private Contact contact = new Contact(); // 初始化避免空指针
// 列表类型(对应 app.features 配置)
private List<String> features;
// 内部类:对应嵌套配置 app.contact
public static class Contact {
private String email;
private String phone;
// getter 和 setter(必须存在,否则无法绑定)
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
}
// 外部类的 getter 和 setter(必须存在)
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getVersion() { return version; }
public void setVersion(String version) { this.version = version; }
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
public int getTimeout() { return timeout; }
public void setTimeout(int timeout) { this.timeout = timeout; }
public Contact getContact() { return contact; }
public void setContact(Contact contact) { this.contact = contact; }
public List<String> getFeatures() { return features; }
public void setFeatures(List<String> features) { this.features = features; }
}
步骤 2:在代码中注入并使用配置类
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AppController {
// 注入自定义配置类
@Autowired
private AppConfig appConfig;
@GetMapping("/app/detail")
public String getAppDetail() {
StringBuilder info = new StringBuilder();
info.append("应用详情:").append("\n")
.append("名称:").append(appConfig.getName()).append("\n")
.append("版本:").append(appConfig.getVersion()).append("\n")
.append("超时时间:").append(appConfig.getTimeout()).append("ms\n")
.append("联系电话:").append(appConfig.getContact().getPhone()).append("\n")
.append("支持功能:").append(appConfig.getFeatures());
return info.toString();
}
}
注意:
- 配置类必须包含 getter 和 setter,否则 Spring 无法绑定配置值。
- 若配置类未使用
@Component,需在启动类或配置类上添加@EnableConfigurationProperties(AppConfig.class)手动激活绑定。
3. 两种注入方式的对比
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
@Value |
简单直观,适合单个参数 | 不适合复杂类型,配置项多时代码冗余 | 少量简单配置(如单个密钥、端口) |
@ConfigurationProperties |
支持复杂类型,配置与代码解耦,类型安全 | 需定义专门的配置类,稍显繁琐 | 多个相关配置(如应用信息、第三方服务配置) |
三、自定义配置的高级特性
1. 配置校验(@Validated)
通过 JSR-303 注解(如 @NotBlank、@Min)对配置项进行校验,确保配置值符合业务规则,避免无效配置导致的问题。
步骤:
-
引入校验依赖(Spring Boot 已包含在
spring-boot-starter-web中):xml<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> -
在配置类上添加
@Validated并使用校验注解:javaimport org.springframework.validation.annotation.Validated; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Min; import javax.validation.constraints.Email; @ConfigurationProperties(prefix = "app") @Component @Validated // 开启校验 public class AppConfig { @NotBlank(message = "应用名称不能为空") // 非空校验 private String name; @Min(value = 1000, message = "超时时间不能小于 1000ms") // 最小值校验 private int timeout; public static class Contact { @Email(message = "邮箱格式不正确") // 邮箱格式校验 private String email; // ... } // ... getter/setter }若配置不符合规则(如
app.timeout=500),应用启动时会抛出ConstraintViolationException,明确提示错误原因。
2. 配置提示(IDE 友好)
当使用 @ConfigurationProperties 时,IDE(如 IntelliJ IDEA)默认不会提示自定义配置项的名称和类型。通过添加依赖可生成配置元数据,让 IDE 支持自动补全和文档提示。
步骤:
-
引入配置处理器依赖:
xml<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> <!-- 打包时排除,仅开发时使用 --> </dependency> -
重新编译项目,处理器会在
target/classes/META-INF下生成spring-configuration-metadata.json,包含配置项的名称、类型、描述等信息。 -
可通过
@ConfigurationProperties的description属性或@Deprecated注解添加文档:java@ConfigurationProperties(prefix = "app", description = "应用基础配置") public class AppConfig { /** * 应用名称,会显示在页面标题中 */ private String name; @Deprecated // 标记为过时配置 private String oldProperty; // ... }之后在配置文件中输入
app.时,IDE 会自动提示配置项及说明,提升开发效率。
3. 多环境下的自定义配置
结合 Spring Boot 的多环境配置(application-{profile}.yml),可针对不同环境(开发、测试、生产)定义不同的自定义配置值。
示例:
-
application-dev.yml(开发环境):yamlapp: name: "电商平台-开发版" timeout: 2000 # 开发环境超时时间较短 -
application-prod.yml(生产环境):yamlapp: name: "电商平台-正式版" timeout: 5000 # 生产环境超时时间较长
启动时通过 spring.profiles.active=dev 或 prod 激活对应环境,配置类会自动加载当前环境的配置值。
4. 外部化自定义配置
除了内置配置文件,自定义配置还支持从外部源加载(如命令行、环境变量、配置中心),适合生产环境动态调整。
-
命令行参数 :启动时通过
--传递自定义配置,覆盖文件中的值:bashjava -jar app.jar --app.name="临时测试版" --app.timeout=1000 -
环境变量 :在操作系统或容器中设置环境变量,Spring Boot 会自动识别(变量名需将
.改为_并大写):bash# Linux 环境 export APP_NAME="环境变量配置" export APP_TIMEOUT=4000 java -jar app.jar -
配置中心:结合 Spring Cloud Config 或 Nacos,将自定义配置集中管理,支持动态刷新(无需重启应用)。
四、自定义配置的最佳实践
- 命名规范 :配置项前缀使用项目或模块标识(如
app.、pay.、redis.),避免与 Spring 内置配置冲突。 - 分层管理 :将不同业务的配置分类(如
pay.对应支付配置,cache.对应缓存配置),提高可读性。 - 必选配置校验 :通过
@NotBlank等注解强制校验必选配置,避免应用启动后因缺少配置而报错。 - 敏感配置加密 :对于密码、密钥等敏感配置,使用 Spring Boot 加密工具(如
jasypt-spring-boot)加密存储,避免明文泄露。
Spring Boot 的自定义配置通过灵活的注入方式和丰富的高级特性,让开发者能够轻松管理业务相关的配置项。无论是简单的单个参数还是复杂的嵌套结构,都能通过 @Value 或 @ConfigurationProperties 优雅地注入到代码中。结合配置校验、多环境支持和外部化配置,可进一步提升配置的可靠性和灵活性,是 Spring Boot 应用开发中的核心实践之一。
3.1、Thymeleaf的基本语法
Thymeleaf 是一款适用于 Web 和独立环境的现代服务器端 Java 模板引擎,它支持 HTML5 原型,能无缝集成 Spring Boot 应用。其语法简洁且贴近自然 HTML,以下是 Thymeleaf 的基本语法详解:
一、命名空间与基本表达式
在 HTML 页面中使用 Thymeleaf,需先声明命名空间:
html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Thymeleaf 示例</title>
</head>
<body>
<!-- 内容 -->
</body>
</html>
Thymeleaf 通过 th: 前缀标识其属性,核心表达式语法如下:
1. 变量表达式(${...})
用于获取模型(Model)中的变量,等价于 Spring MVC 的 request.getAttribute()。
示例 :
后端控制器添加变量:
java
@GetMapping("/user")
public String user(Model model) {
User user = new User("张三", 25, "zhangsan@example.com");
model.addAttribute("user", user);
return "user"; // 指向 user.html 模板
}
前端模板使用:
html
<!-- 获取对象属性 -->
<p>姓名:<span th:text="${user.name}">默认姓名</span></p>
<p>年龄:<span th:text="${user.age}">默认年龄</span></p>
<!-- 支持链式调用(需对象有对应 getter 方法) -->
<p>邮箱:<span th:text="${user.email}">默认邮箱</span></p>
th:text:替换标签内的文本内容("默认姓名" 会被${user.name}的值替换)。- 若变量为
null,则显示空字符串(而非默认文本)。
2. 选择变量表达式(*{...})
用于简化对象属性的访问,需配合 th:object 指定上下文对象。
示例:
html
<!-- 先通过 th:object 指定对象 -->
<div th:object="${user}">
<p>姓名:<span th:text="*{name}">默认姓名</span></p> <!-- 等价于 ${user.name} -->
<p>年龄:<span th:text="*{age}">默认年龄</span></p> <!-- 等价于 ${user.age} -->
</div>
3. 链接表达式(@{...})
用于生成 URL 路径,自动处理上下文路径(Context Path),支持绝对路径和相对路径。
示例:
html
<!-- 相对路径(自动拼接上下文路径) -->
<a th:href="@{/home}">首页</a>
<a th:href="@{/user/detail(id=1, name='张三')}">用户详情</a> <!-- 带参数 -->
<!-- 绝对路径 -->
<a th:href="@{https://www.example.com}">外部链接</a>
<!-- 资源路径(CSS/JS/图片) -->
<link th:href="@{/static/css/style.css}" rel="stylesheet">
<script th:src="@{/static/js/app.js}"></script>
<img th:src="@{/static/images/logo.png}" alt="logo">
- 若应用上下文路径为
/app,@{/home}会生成/app/home。 - 参数通过
(key=value)形式添加,多个参数用逗号分隔。
4. 片段表达式(~{...})
用于引用模板片段(可复用的 HTML 片段),配合 th:insert 或 th:replace 使用。
示例 :
定义片段(footer.html):
html
<footer th:fragment="copyright">
<p>© 2023 示例网站 版权所有</p>
</footer>
引用片段(在其他页面中):
html
<!-- 插入片段(保留当前标签,片段作为子元素) -->
<div th:insert="~{footer :: copyright}"></div>
<!-- 替换片段(用片段替换当前标签) -->
<div th:replace="~{footer :: copyright}"></div>
footer :: copyright表示引用footer.html中名为copyright的片段。
二、常用属性
Thymeleaf 提供了丰富的属性用于动态控制页面元素,以下是常用属性:
1. 文本与属性操作
th:text:设置标签内文本(会转义特殊字符,防止 XSS)。th:utext:设置标签内文本(不转义特殊字符,适合输出 HTML 内容)。th:attr:设置标签的任意属性(如th:attr="src=@{/img.png}, title='图片'")。- 简化属性:
th:src、th:href、th:title、th:class等(直接对应 HTML 属性)。
示例:
html
<!-- 转义文本(< 会变为 <) -->
<p th:text="${htmlContent}">包含 HTML 的文本</p>
<!-- 不转义文本(直接渲染 HTML) -->
<p th:utext="${htmlContent}">包含 HTML 的文本</p>
<!-- 动态设置图片和样式 -->
<img th:src="@{/images/{filename}(filename=${imageName})}" th:alt="${imageDesc}">
<div th:class="${isActive ? 'active' : 'inactive'}">状态样式</div>
2. 条件判断
th:if:当表达式为true时显示标签。th:unless:当表达式为false时显示标签(与th:if相反)。th:switch+th:case:多条件判断(类似switch-case)。
示例:
html
<!-- th:if 条件 -->
<p th:if="${user.age >= 18}">已成年</p>
<!-- th:unless 条件(等价于 th:if="${!(user.age >= 18)}") -->
<p th:unless="${user.age >= 18}">未成年</p>
<!-- switch-case -->
<div th:switch="${user.role}">
<p th:case="'ADMIN'">管理员</p>
<p th:case="'USER'">普通用户</p>
<p th:case="*">未知角色</p> <!-- * 表示默认 case -->
</div>
- 表达式结果为
null时,th:if视为false;非null的数值/字符串/布尔值按实际值判断。
3. 循环遍历
th:each:用于遍历集合(List、Map、数组等),语法为 变量名, 状态变量 : ${集合}。
示例 :
后端传递集合:
java
model.addAttribute("users", Arrays.asList(
new User("张三", 25),
new User("李四", 30)
));
前端遍历:
html
<!-- 遍历用户列表 -->
<table>
<tr>
<th>序号</th>
<th>姓名</th>
<th>年龄</th>
</tr>
<!-- status 是状态变量,包含 index(索引,从 0 开始)、count(计数,从 1 开始)等 -->
<tr th:each="user, status : ${users}">
<td th:text="${status.count}">1</td>
<td th:text="${user.name}">姓名</td>
<td th:text="${user.age}">年龄</td>
</tr>
</table>
状态变量常用属性:
index:当前元素索引(0 开始)。count:当前元素计数(1 开始)。size:集合总大小。even/odd:是否为偶/奇数行(布尔值)。first/last:是否为第一个/最后一个元素(布尔值)。
4. 表单处理
Thymeleaf 提供表单相关属性,与 Spring MVC 表单绑定配合使用:
th:action:表单提交地址(结合@{...}表达式)。th:object:绑定表单对象(后端@ModelAttribute对应的对象)。th:field:绑定对象属性(自动生成name和value属性)。
示例:
html
<form th:action="@{/user/save}" th:object="${user}" method="post">
<!-- 等价于 name="name" value="${user.name}" -->
<input type="text" th:field="*{name}" placeholder="姓名">
<!-- 等价于 name="age" value="${user.age}" -->
<input type="number" th:field="*{age}" placeholder="年龄">
<button type="submit">提交</button>
</form>
th:field会自动处理表单回显(如提交失败后保留用户输入)。
三、内置对象
Thymeleaf 提供了多个内置对象,简化常见操作:
1. Web 相关对象
#request:HttpServletRequest 对象(如#request.getContextPath())。#session:HttpSession 对象(如#session.getAttribute('user'))。#servletContext:ServletContext 对象。
示例:
html
<p>上下文路径:<span th:text="${#request.getContextPath()}"></span></p>
<p>Session 中的用户:<span th:text="${#session.getAttribute('username')}"></span></p>
2. 工具类对象
#strings:字符串工具类(isEmpty、toUpperCase、substring等)。#numbers:数字工具类(格式化数字、百分比等)。#dates/#calendars:日期工具类(格式化日期、获取年/月/日等)。#lists:集合工具类(size、contains、sort等)。
示例:
html
<!-- 字符串处理 -->
<p>大写姓名:<span th:text="${#strings.toUpperCase(user.name)}"></span></p>
<p>是否为空:<span th:text="${#strings.isEmpty(user.email)}"></span></p>
<!-- 日期格式化 -->
<p>注册时间:<span th:text="${#dates.format(user.regTime, 'yyyy-MM-dd HH:mm:ss')}"></span></p>
<!-- 集合操作 -->
<p>用户总数:<span th:text="${#lists.size(users)}"></span></p>
四、注释与内联表达式
1. Thymeleaf 注释
html
<!-- 标准 HTML 注释(会被浏览器解析) -->
<!--/* Thymeleaf 解析注释(仅模板引擎可见,不会输出到页面) */-->
<!--/*
多行 Thymeleaf 注释
不会被渲染到最终 HTML 中
*/-->
2. 内联表达式
在 HTML 文本或 JavaScript 中直接使用表达式,无需通过 th:text 等属性:
- 文本内联 :
[[${表达式}]](等价于th:text,会转义),[(${表达式})](等价于th:utext,不转义)。 - JavaScript 内联 :
/*[[${表达式}]]*/(在 JS 中注入变量)。
示例:
html
<!-- 文本内联 -->
<p>欢迎您:[[${user.name}]]</p> <!-- 等价于 <p th:text="${user.name}"></p> -->
<p>简介:[(${user.intro})]</p> <!-- 渲染 HTML 内容 -->
<!-- JavaScript 内联 -->
<script th:inline="javascript">
var username = /*[[${user.name}]]*/ '默认名称';
console.log("当前用户:" + username);
</script>
- 使用 JavaScript 内联时,需在
<script>标签添加th:inline="javascript"。
Thymeleaf 的核心语法围绕 表达式 和 属性 展开,通过 th: 前缀实现模板动态渲染。其特点是:
- 语法贴近 HTML,易于理解和维护。
- 支持丰富的表达式(变量、链接、片段等)和操作(条件、循环、表单绑定)。
- 内置工具类简化常见处理(字符串、日期、集合等)。
掌握这些基本语法后,可轻松实现动态页面渲染,结合 Spring Boot 开发高效的 Web 应用。
3.2、Spring Boot实现页面国际化
Spring Boot 实现页面国际化(i18n)可以让应用根据不同语言环境显示对应的文本内容,核心是通过配置消息源(MessageSource)和切换Locale(语言环境)来实现。以下是详细实现步骤:
一、准备工作:创建国际化资源文件
首先在 src/main/resources 目录下创建国际化消息文件,命名规则为 messages_{语言}_{国家}.properties,默认文件为 messages.properties。
1. 文件结构
resources/
├── i18n/ # 建议统一放在i18n目录下(可选)
│ ├── messages.properties # 默认(未指定语言时使用)
│ ├── messages_en_US.properties # 英文(美国)
│ └── messages_zh_CN.properties # 中文(中国)
2. 资源文件内容
-
messages.properties(默认):propertieswelcome=欢迎访问网站 username=用户名 password=密码 login=登录 -
messages_en_US.properties(英文):propertieswelcome=Welcome to the website username=Username password=Password login=Login -
messages_zh_CN.properties(中文):propertieswelcome=欢迎访问网站 username=用户名 password=密码 login=Login
二、配置Spring Boot支持国际化
需要配置消息源位置和Locale解析器,告诉Spring Boot如何加载国际化资源和切换语言。
1. 配置application.yml
指定国际化资源文件的位置:
yaml
spring:
messages:
basename: i18n/messages # 资源文件路径(无需扩展名)
encoding: UTF-8 # 编码格式(解决中文乱码)
2. 自定义Locale解析器(可选)
默认情况下,Spring Boot通过请求头的 Accept-Language 字段判断Locale。若需要通过URL参数(如 ?lang=en_US)切换语言,需自定义LocaleResolver:自定义Locale解析器配置类:
java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
import java.util.Locale;
@Configuration
public class LocaleConfig implements WebMvcConfigurer {
// 配置Locale解析器(存储用户选择的语言)
@Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver resolver = new SessionLocaleResolver();
// 设置默认语言(中文)
resolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
return resolver;
}
// 配置语言切换拦截器(通过URL参数lang切换语言)
@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
// 指定切换语言的参数名(如 ?lang=en_US)
interceptor.setParamName("lang");
return interceptor;
}
// 注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor());
}
}
SessionLocaleResolver:将用户选择的Locale存储在Session中,保持会话内语言一致。LocaleChangeInterceptor:拦截URL中包含lang参数的请求,动态切换Locale(如http://localhost:8080?lang=en_US切换为英文)。
三、在页面中使用国际化文本
以Thymeleaf模板为例,通过 #{key} 表达式引用国际化资源文件中的key:
html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>国际化示例</title>
</head>
<body>
<!-- 引用国际化文本 -->
<h1 th:text="#{welcome}">欢迎信息</h1>
<form>
<label th:text="#{username}">用户名</label>
<input type="text" name="username"><br>
<label th:text="#{password}">密码</label>
<input type="password" name="password"><br>
<button type="submit" th:text="#{login}">登录</button>
</form>
<!-- 语言切换链接 -->
<div>
<a th:href="@{/?lang=zh_CN}">中文</a> |
<a th:href="@{/?lang=en_US}">English</a>
</div>
</body>
</html>
#{welcome}会根据当前Locale自动匹配messages_*.properties中对应的value。- 点击"中文"或"English"链接时,通过
lang参数触发语言切换。
四、在Java代码中使用国际化文本
通过 MessageSource 接口在Service或Controller中获取国际化文本:
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Service;
import java.util.Locale;
@Service
public class HelloService {
// 注入消息源
@Autowired
private MessageSource messageSource;
// 获取国际化消息
public String getWelcomeMessage() {
// LocaleContextHolder.getLocale() 获取当前Locale(由LocaleResolver管理)
Locale locale = LocaleContextHolder.getLocale();
// 第一个参数:资源文件中的key;第二个参数:占位符参数(可选);第三个参数:Locale
return messageSource.getMessage("welcome", null, locale);
}
}
LocaleContextHolder.getLocale():获取当前请求的Locale(自动适配自定义的LocaleResolver)。messageSource.getMessage():第一个参数为资源文件的key,第二个参数用于填充带占位符的消息(如welcome=欢迎 {0} 访问,可传递new Object[]{"张三"})。
五、测试与验证
- 启动应用,访问页面默认显示中文。
- 点击"English"链接(URL变为
?lang=en_US),页面切换为英文。 - 刷新页面或访问其他路由,语言设置会保持(因使用Session存储)。
关键知识点总结
- 资源文件命名 :遵循
messages_{语言}_{国家}.properties规则,默认文件messages.properties必须存在。 - Locale解析 :
- 默认通过请求头
Accept-Language解析。 - 自定义
SessionLocaleResolver+LocaleChangeInterceptor支持URL参数切换。
- 默认通过请求头
- 文本引用 :
- 页面(Thymeleaf)用
#{key}。 - 代码中用
MessageSource.getMessage(key, params, locale)。
- 页面(Thymeleaf)用
通过以上步骤,Spring Boot应用即可实现灵活的页面国际化,满足多语言场景需求。
3.3、Spring Boot集成Spring MVC
Spring Boot 对 Spring MVC 进行了深度集成和自动化配置,让开发者无需手动搭建 Spring MVC的基础架构,就能能快速开发基于基于 MVC 模式的 Web 应用。以下从集成原理、核心配置、组件使用等方面详细讲解:
一、集成原理与自动配置
Spring Boot 通过 spring-boot-starter-web starter 实现对 Spring MVC 的自动集成,底层依赖 Spring 框架的 Web 模块,并通过自动配置类简化传统 Spring MVC 的繁琐配置。
1. 核心依赖
在 pom.xml 中引入 Web starter 即可自动集成 Spring MVC:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
该依赖包含:
- Spring MVC 核心组件(
DispatcherServlet、控制器、视图解析器等) - 嵌入式服务器(默认 Tomcat)
- 常用 Web 依赖(如
javax.servlet-api)
2. 自动配置类
Spring Boot 通过 WebMvcAutoConfiguration 自动配置 Spring MVC 核心组件,主要包括:
DispatcherServlet:前端控制器,自动注册并映射到/路径- 视图解析器 :默认配置
InternalResourceViewResolver,支持 JSP 等视图 - 静态资源处理器 :映射
/static、/public等目录的静态资源 - 类型转换:注册默认的类型转换器和格式化器
- 消息转换器 :配置
HttpMessageConverter,支持 JSON 等数据格式(默认集成 Jackson)
二、核心组件与使用方式
Spring Boot 集成 Spring MVC 后,保留了 MVC 模式的核心组件(控制器、服务、视图等),使用方式与传统 Spring MVC 一致,但配置更简化。
1. 控制器(Controller)
通过 @Controller 或 @RestController 定义控制器,处理 HTTP 请求:
Spring MVC控制器示例:
java
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
// @RestController = @Controller + @ResponseBody(返回JSON/文本,不跳转视图)
@Controller
@RequestMapping("/users") // 类级别的URL映射
public class UserController {
// 处理GET请求,返回视图
@GetMapping("/{id}")
public String getUser(@PathVariable("id") Long id, Model model) {
// 模拟查询用户(实际应调用Service)
User user = new User(id, "张三", 25);
model.addAttribute("user", user); // 向视图传递数据
return "user/detail"; // 跳转至templates/user/detail.html视图
}
// 处理POST请求,返回JSON(@ResponseBody单独使用)
@PostMapping("/save")
@ResponseBody
public Result saveUser(@RequestBody User user) {
// 模拟保存用户
return Result.success("保存成功", user);
}
}
关键注解说明:
@RequestMapping:定义请求路径和方法(可指定method = RequestMethod.GET等)@GetMapping/@PostMapping:简化的 HTTP 方法注解(分别对应 GET/POST)@PathVariable:获取 URL 路径参数(如/users/1中的1)@RequestBody:接收请求体中的 JSON 数据并绑定到对象@ResponseBody:将返回值直接转换为 JSON/XML 等响应体(无需视图解析)
2. 服务层(Service)
通过 @Service 定义业务逻辑层,被控制器调用:
Spring MVC服务层示例:
java
import org.springframework.stereotype.Service;
@Service
public class UserService {
// 模拟查询用户
public User getUserById(Long id) {
// 实际应调用DAO层操作数据库
return new User(id, "张三", 25);
}
// 模拟保存用户
public boolean saveUser(User user) {
// 实际应包含事务管理和业务校验
return true;
}
}
3. 视图层(View)
Spring Boot 推荐使用 Thymeleaf 作为视图模板引擎(默认集成),替代传统 JSP。
步骤 1 :引入 Thymeleaf 依赖(已包含在 spring-boot-starter-web 中,如需单独引入):
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
步骤 2 :创建 Thymeleaf 视图(src/main/resources/templates/user/detail.html):
html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>用户详情</title>
</head>
<body>
<h1>用户详情</h1>
<p>ID:<span th:text="${user.id}"></span></p>
<p>姓名:<span th:text="${user.name}"></span></p>
<p>年龄:<span th:text="${user.age}"></span></p>
</body>
</html>
4. 静态资源处理
Spring Boot 自动映射以下目录的静态资源(CSS、JS、图片等):
classpath:/static/classpath:/public/classpath:/resources/classpath:/META-INF/resources/
访问方式:直接通过资源路径访问,例如 http://localhost:8080/css/style.css 对应 static/css/style.css。
三、自定义 Spring MVC 配置
Spring Boot 允许通过两种方式自定义 Spring MVC 配置,覆盖默认行为:
1. 实现 WebMvcConfigurer 接口(推荐)
通过重写接口方法自定义配置,不会覆盖自动配置:
自定义Spring MVC配置:
java
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
// 配置视图控制器(无需编写Controller即可映射URL到视图)
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index"); // 访问/直接跳转index.html
registry.addViewController("/login").setViewName("login");
}
// 配置拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册自定义拦截器,排除静态资源
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") // 拦截所有请求
.excludePathPatterns("/login", "/css/**", "/js/**"); // 排除登录页和静态资源
}
// 配置跨域资源共享(CORS)
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**") // 对/api/*路径允许跨域
.allowedOrigins("http://localhost:3000") // 允许的源
.allowedMethods("GET", "POST") // 允许的方法
.allowCredentials(true); // 允许携带Cookie
}
}
2. 使用 @EnableWebMvc 注解(谨慎使用)
在配置类上添加 @EnableWebMvc 会完全禁用 Spring Boot 的 MVC 自动配置,需手动配置所有组件(如视图解析器、消息转换器等),适用于需要完全自定义的场景。
四、常用功能配置
1. 全局异常处理
通过 @ControllerAdvice 定义全局异常处理器,统一处理请求中的异常:
java
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice // 全局控制器增强
public class GlobalExceptionHandler {
// 处理自定义异常
@ExceptionHandler(UserNotFoundException.class)
@ResponseBody
public Result handleUserNotFound(UserNotFoundException e) {
return Result.error(404, e.getMessage());
}
// 处理所有未捕获的异常
@ExceptionHandler(Exception.class)
@ResponseBody
public Result handleException(Exception e) {
return Result.error(500, "服务器内部错误:" + e.getMessage());
}
}
2. 数据校验
结合 JSR-303 注解(如 @NotNull、@Size)实现请求参数校验:
java
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;
public class User {
private Long id;
@NotBlank(message = "姓名不能为空")
private String name;
@NotNull(message = "年龄不能为空")
@Positive(message = "年龄必须为正数")
private Integer age;
// getter/setter
}
java
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@PostMapping("/users")
public Result addUser(@Validated @RequestBody User user) {
// 若校验失败,会抛出MethodArgumentNotValidException,被全局异常处理器捕获
return Result.success("添加成功", user);
}
}
五、集成优势总结
- 零 XML 配置 :自动配置核心组件(
DispatcherServlet、视图解析器等),无需手动配置。 - 简化依赖管理:通过 starter 一键引入所需依赖,避免版本冲突。
- 开箱即用:默认支持 JSON 转换、静态资源处理、类型转换等功能。
- 灵活扩展 :通过
WebMvcConfigurer接口轻松自定义配置,不破坏自动配置。 - 与 Spring 生态无缝衔接:可直接集成 Spring Security、Spring Data 等组件。
通过 Spring Boot 集成 Spring MVC,开发者能专注于业务逻辑实现,大幅提升 Web 应用开发效率。
3.4、Spring Boot返回JSON数据
在 Spring Boot 中,返回 JSON 数据是 Web 开发(尤其是 RESTful API)的常见需求。得益于 Spring Boot 的自动配置,无需额外依赖即可实现 JSON 数据的序列化与返回,核心依赖 Jackson 会被自动引入并配置。以下是详细实现方式:
一、默认 JSON 支持原理
Spring Boot 的 spring-boot-starter-web 依赖中默认包含 jackson-databind 库,它会自动配置 MappingJackson2HttpMessageConverter 消息转换器,负责将 Java 对象与 JSON 数据互相转换。
-
依赖自动引入 :引入
spring-boot-starter-web即可(无需额外配置):xml<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
二、基本使用:返回 JSON 数据
通过 @ResponseBody 注解(或 @RestController 组合注解)即可将 Java 对象自动转换为 JSON 并返回。
1. 使用 @RestController 注解(推荐)
@RestController 是 @Controller + @ResponseBody 的组合注解,类中所有方法的返回值都会被自动转换为 JSON:
使用@RestController返回JSON:
java
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
// @RestController = @Controller + @ResponseBody
@RestController
public class UserController {
// 返回单个对象
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
// 模拟查询用户(实际应从数据库获取)
return new User(id, "张三", 25, "zhangsan@example.com");
}
// 返回集合
@GetMapping("/users")
public List<User> getUsers() {
List<User> users = new ArrayList<>();
users.add(new User(1L, "张三", 25, "zhangsan@example.com"));
users.add(new User(2L, "李四", 30, "lisi@example.com"));
return users;
}
// 返回自定义响应体(通用格式)
@GetMapping("/api/users/{id}")
public Result<User> getUserApi(@PathVariable Long id) {
User user = new User(id, "张三", 25, "zhangsan@example.com");
return Result.success("查询成功", user);
}
}
2. 相关实体类定义
-
User 实体类:
javapublic class User { private Long id; private String name; private Integer age; private String email; // 构造方法、getter、setter(必须存在,否则JSON序列化会丢失字段) public User(Long id, String name, Integer age, String email) { this.id = id; this.name = name; this.age = age; this.email = email; } // getter 和 setter 省略... } -
通用响应体 Result 类(统一 API 响应格式):
javapublic class Result<T> { private Integer code; // 状态码(如 200 成功,400 失败) private String message; // 提示信息 private T data; // 数据本体 // 静态方法:快速构建成功/失败响应 public static <T> Result<T> success(String message, T data) { Result<T> result = new Result<>(); result.code = 200; result.message = message; result.data = data; return result; } public static <T> Result<T> error(Integer code, String message) { Result<T> result = new Result<>(); result.code = code; result.message = message; return result; } // getter 和 setter 省略... }
3. 访问效果
-
访问
http://localhost:8080/users/1会返回 JSON 格式的用户对象:json{ "id": 1, "name": "张三", "age": 25, "email": "zhangsan@example.com" } -
访问
http://localhost:8080/api/users/1会返回统一格式的响应:json{ "code": 200, "message": "查询成功", "data": { "id": 1, "name": "张三", "age": 25, "email": "zhangsan@example.com" } }
三、JSON 序列化自定义配置
默认情况下,Jackson 会按实体类的字段(通过 getter 方法)序列化 JSON,但实际开发中常需要自定义格式(如日期格式化、忽略 null 值等)。
1. 局部配置(通过注解)
在实体类或字段上使用 Jackson 注解自定义序列化规则:
使用注解自定义JSON序列化:
java
import com.fasterxml.jackson.annotation.*;
import java.util.Date;
public class User {
private Long id;
// 序列化时指定字段别名(JSON中显示为"username")
@JsonProperty("username")
private String name;
private Integer age;
// 忽略该字段(不序列化到JSON)
@JsonIgnore
private String email;
// 日期格式化(默认会序列化为时间戳)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
// 忽略值为null的字段
@JsonInclude(JsonInclude.Include.NON_NULL)
private String remark;
// 构造方法、getter、setter 省略...
}
常用注解说明:
@JsonProperty("alias"):指定 JSON 字段的别名。@JsonIgnore:序列化时忽略该字段。@JsonFormat(pattern = "...", timezone = "GMT+8"):格式化日期/时间(解决时区问题)。@JsonInclude(JsonInclude.Include.NON_NULL):忽略值为null的字段(也可全局配置)。
2. 全局配置(通过配置类)
通过自定义 Jackson2ObjectMapperBuilderCustomizer 实现全局 JSON 序列化规则:
全局JSON序列化配置:
java
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.TimeZone;
@Configuration
public class JacksonConfig {
// 全局配置Jackson
@Bean
public Jackson2ObjectMapperBuilderCustomizer customizer() {
return builder -> {
// 全局设置时区
builder.timeZone(TimeZone.getTimeZone("GMT+8"));
// 全局日期格式化(LocalDateTime)
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
// 忽略值为null的字段
builder.serializationInclusion(JsonInclude.Include.NON_NULL);
// 禁用序列化空对象(避免返回{"empty":false}等)
builder.featuresToDisable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
// 反序列化时忽略未知字段(避免前端传递多余字段时报错)
builder.featuresToEnable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
};
}
}
四、接收 JSON 数据(请求体)
除了返回 JSON,Spring Boot 也能自动将请求体中的 JSON 数据反序列化为 Java 对象,通过 @RequestBody 注解实现:
java
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
// 接收JSON请求体并转换为User对象
@PostMapping("/users")
public Result<User> addUser(@RequestBody User user) {
// user 对象已自动填充请求体中的JSON数据
System.out.println("接收用户:" + user.getName());
return Result.success("添加成功", user);
}
}
请求示例 (使用 POST 方法,Content-Type 为 application/json):
json
// 请求体
{
"name": "王五",
"age": 28,
"email": "wangwu@example.com"
}
Spring Boot 会自动将上述 JSON 转换为 User 对象,无需手动解析。
五、常见问题解决
-
中文乱码 :Spring Boot 默认已配置 UTF-8 编码,若出现乱码,检查请求头
Content-Type是否为application/json;charset=utf-8。 -
日期序列化问题 :通过
@JsonFormat注解或全局配置指定日期格式和时区(GMT+8),避免时间戳或时区偏移问题。 -
循环引用报错 :若实体类存在循环引用(如 User 包含 Order,Order 包含 User),可在关联字段添加
@JsonIgnore忽略一方,或配置builder.featuresToDisable(SerializationFeature.FAIL_ON_SELF_REFERENCES)。
Spring Boot 对 JSON 的支持基于 Jackson 实现,通过 @RestController 或 @ResponseBody 可轻松返回 JSON 数据。核心优势:
- 自动配置:无需手动配置消息转换器,开箱即用。
- 灵活定制:通过注解或全局配置自定义序列化规则(日期、null 值、字段别名等)。
- 双向转换:既支持返回 JSON,也支持接收 JSON 请求体并自动反序列化为对象。
这种方式满足了绝大多数 RESTful API 开发需求,是 Spring Boot 构建 Web 应用的核心能力之一。
3.5、Spring Boot实现RESTful风格的Web应用
Spring Boot 非常适合构建 RESTful 风格的 Web 应用提供了天然支持,通过简洁的注解、自动化配置和内置功能,可快速实现符合 REST 规范的 API 设计。以下是详细实现方法:
一、RESTful 核心原则
RESTful 是一种软件设计风格,核心是通过 HTTP 方法(GET/POST/PUT/DELETE 等)和 URI 路径表示资源的操作,特点包括:
- 资源导向 :URI 表示资源(如
/users表示用户集合),而非动作(避免/getUser)。 - HTTP 方法语义 :用不同方法表达操作类型:
GET:查询资源(幂等,无副作用)POST:创建资源PUT:全量更新资源(幂等)PATCH:部分更新资源DELETE:删除资源(幂等)
- 状态码表示结果:用 HTTP 状态码(200/201/400/404 等)表示请求结果。
- 无状态:每次请求独立,服务器不存储客户端状态。
二、Spring Boot 实现 RESTful API 的核心组件
1. 依赖准备
引入 spring-boot-starter-web 即可,包含 RESTful 开发所需的所有组件:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2. 控制器(Controller)设计
使用 @RestController 定义 REST 控制器(自动返回 JSON),结合 HTTP 方法注解(@GetMapping/@PostMapping 等)映射请求。
RESTful风格控制器示例:
java
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
@RestController
@RequestMapping("/api/users") // 资源路径(用户集合)
public class UserController {
// 模拟数据库(线程安全)
private final ConcurrentMap<Long, User> userMap = new ConcurrentHashMap<>();
private final AtomicLong idGenerator = new AtomicLong(1);
// 1. 查询所有用户(GET /api/users)
@GetMapping
public List<User> getAllUsers() {
return new ArrayList<>(userMap.values());
}
// 2. 查询单个用户(GET /api/users/{id})
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
User user = userMap.get(id);
if (user == null) {
// 资源不存在返回404
return ResponseEntity.notFound().build();
}
// 成功返回200 + 资源
return ResponseEntity.ok(user);
}
// 3. 创建用户(POST /api/users)
@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) {
// 生成ID
Long id = idGenerator.incrementAndGet();
user.setId(id);
userMap.put(id, user);
// 成功创建返回201 + 资源 + Location头(新资源路径)
return ResponseEntity
.status(HttpStatus.CREATED)
.header("Location", "/api/users/" + id)
.body(user);
}
// 4. 全量更新用户(PUT /api/users/{id})
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(
@PathVariable Long id,
@RequestBody User updatedUser) {
if (!userMap.containsKey(id)) {
return ResponseEntity.notFound().build();
}
// 全量更新(覆盖原有字段)
updatedUser.setId(id);
userMap.put(id, updatedUser);
return ResponseEntity.ok(updatedUser);
}
// 5. 部分更新用户(PATCH /api/users/{id})
@PatchMapping("/{id}")
public ResponseEntity<User> patchUser(
@PathVariable Long id,
@RequestBody User partialUser) {
User existingUser = userMap.get(id);
if (existingUser == null) {
return ResponseEntity.notFound().build();
}
// 部分更新(仅更新非null字段)
if (partialUser.getName() != null) {
existingUser.setName(partialUser.getName());
}
if (partialUser.getAge() != null) {
existingUser.setAge(partialUser.getAge());
}
userMap.put(id, existingUser);
return ResponseEntity.ok(existingUser);
}
// 6. 删除用户(DELETE /api/users/{id})
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
if (!userMap.containsKey(id)) {
return ResponseEntity.notFound().build();
}
userMap.remove(id);
// 成功删除返回204(无内容)
return ResponseEntity.noContent().build();
}
}
3. 实体类(Resource)
定义资源模型,对应业务实体:
java
public class User {
private Long id;
private String name;
private Integer age;
private String email;
// 无参构造器(反序列化需要)
public User() {}
// getter、setter
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
4. 统一响应体(可选)
为了规范响应格式,可定义通用响应类(包含状态码、消息、数据):
RESTful统一响应体:
java
import org.springframework.http.HttpStatus;
public class ApiResponse<T> {
private int code; // 业务状态码
private String message; // 提示消息
private T data; // 响应数据
// 成功响应(带数据)
public static <T> ApiResponse<T> success(T data) {
ApiResponse<T> response = new ApiResponse<>();
response.code = HttpStatus.OK.value();
response.message = "success";
response.data = data;
return response;
}
// 成功响应(无数据)
public static <T> ApiResponse<T> success() {
return success(null);
}
// 错误响应
public static <T> ApiResponse<T> error(int code, String message) {
ApiResponse<T> response = new ApiResponse<>();
response.code = code;
response.message = message;
return response;
}
// getter、setter
public int getCode() { return code; }
public void setCode(int code) { this.code = code; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public T getData() { return data; }
public void setData(T data) { this.data = data; }
}
修改控制器方法返回统一响应体(以查询为例):
java
@GetMapping("/{id}")
public ApiResponse<User> getUserById(@PathVariable Long id) {
User user = userMap.get(id);
if (user == null) {
return ApiResponse.error(404, "用户不存在");
}
return ApiResponse.success(user);
}
三、RESTful 高级特性实现
1. 分页与排序
查询集合时,通过 Pageable 实现分页和排序:
RESTful分页与排序:
java
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/articles")
public class ArticleController {
private final ArticleService articleService;
// 构造器注入服务
public ArticleController(ArticleService articleService) {
this.articleService = articleService;
}
// 分页查询文章(默认第0页,每页10条,按创建时间降序)
@GetMapping
public Page<Article> getArticles(
@PageableDefault(
page = 0,
size = 10,
sort = "createTime",
direction = Sort.Direction.DESC
) Pageable pageable) {
return articleService.findArticles(pageable);
}
}
前端请求示例:
GET /api/articles?page=1&size=20&sort=title,asc // 第2页,每页20条,按标题升序
2. 异常处理
通过 @ControllerAdvice 统一处理 REST 接口异常,返回标准错误响应:
RESTful异常处理:
java
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.NoHandlerFoundException;
@ControllerAdvice
public class GlobalExceptionHandler {
// 资源未找到异常(404)
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ApiResponse<Void>> handleResourceNotFound(ResourceNotFoundException e) {
ApiResponse<Void> error = ApiResponse.error(404, e.getMessage());
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}
// 请求参数错误(400)
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ApiResponse<Void>> handleIllegalArgument(IllegalArgumentException e) {
ApiResponse<Void> error = ApiResponse.error(400, e.getMessage());
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
// 未匹配的路由(404)
@ExceptionHandler(NoHandlerFoundException.class)
public ResponseEntity<ApiResponse<Void>> handleNoHandlerFound() {
ApiResponse<Void> error = ApiResponse.error(404, "接口不存在");
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}
// 服务器内部错误(500)
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<Void>> handleGenericException(Exception e) {
ApiResponse<Void> error = ApiResponse.error(500, "服务器内部错误:" + e.getMessage());
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
3. 跨域支持
前后端分离场景下,通过配置允许跨域请求:
RESTful跨域配置:
java
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**") // 对/api/*路径允许跨域
.allowedOrigins("http://localhost:3000") // 允许的前端域名
.allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH") // 允许的HTTP方法
.allowedHeaders("*") // 允许的请求头
.allowCredentials(true) // 允许携带Cookie
.maxAge(3600); // 预检请求缓存时间(秒)
}
}
四、RESTful API 测试
可通过工具测试接口是否符合 REST 规范:
- Postman:手动发送 HTTP 请求,验证响应状态码和数据。
- Swagger/OpenAPI :自动生成 API 文档,支持在线测试(需引入
springdoc-openapi-ui依赖)。 - JUnit:编写单元测试验证接口逻辑:
java
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerTest {
@Autowired
private MockMvc mockMvc;
// 测试创建用户
@Test
public void testCreateUser() throws Exception {
String userJson = "{\"name\":\"测试用户\",\"age\":20,\"email\":\"test@example.com\"}";
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(userJson))
.andExpect(status().isCreated()) // 验证状态码201
.andExpect(jsonPath("$.name").value("测试用户"))
.andExpect(header().exists("Location")); // 验证Location头
}
// 测试查询用户
@Test
public void testGetUser() throws Exception {
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk()) // 验证状态码200
.andExpect(jsonPath("$.id").value(1));
}
}
五、最佳实践总结
-
URI 设计:
- 用名词表示资源(复数形式,如
/users),避免动词(/getUsers)。 - 子资源用嵌套路径(如
/users/1/orders表示用户1的订单)。 - 使用查询参数过滤资源(如
/users?role=admin)。
- 用名词表示资源(复数形式,如
-
状态码使用:
200 OK:GET/PUT/PATCH 请求成功。201 Created:POST 请求创建资源成功。204 No Content:DELETE 请求成功。400 Bad Request:请求参数错误。404 Not Found:资源不存在。500 Internal Server Error:服务器内部错误。
-
版本控制:
- 在 URI 中包含版本(如
/api/v1/users),便于兼容旧版本。
- 在 URI 中包含版本(如
-
安全性:
- 对敏感接口添加认证授权(如 Spring Security)。
- 验证请求参数,防止注入攻击。
通过以上方式,Spring Boot 可快速构建规范、可扩展的 RESTful Web 应用,满足前后端分离、微服务等场景需求。
3.6、Spring Boot实现文件的上传和下载
Spring Boot 实现文件上传和下载功能非常便捷,通过内置的 Multipart 支持和资源处理能力,可以可快速开发相关功能。以下是详细的开发流程:
一、准备工作:配置文件上传参数
首先在 application.yml 中配置文件上传的相关参数(如大小限制):
yaml
spring:
servlet:
multipart:
max-file-size: 10MB # 单个文件最大大小
max-request-size: 100MB # 单次请求最大文件总大小
max-file-size:限制单个上传文件的大小(默认 1MB)。max-request-size:限制单次请求中所有文件的总大小(默认 10MB)。
二、实现文件上传功能
文件上传需要处理 multipart/form-data 类型的请求,通过 MultipartFile 接收上传的文件。
1. 编写文件上传控制器
java
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.UUID;
@RestController
public class FileController {
// 上传文件保存路径(实际项目中建议配置在application.yml中)
private static final String UPLOAD_DIR = "uploads/";
// 单文件上传
@PostMapping("/upload/single")
public ResponseEntity<String> uploadSingleFile(
@RequestParam("file") MultipartFile file) {
// 检查文件是否为空
if (file.isEmpty()) {
return new ResponseEntity<>("请选择文件", HttpStatus.BAD_REQUEST);
}
try {
// 确保上传目录存在
File uploadDir = new File(UPLOAD_DIR);
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}
// 生成唯一文件名(避免重名覆盖)
String originalFilename = file.getOriginalFilename();
String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
String uniqueFilename = UUID.randomUUID().toString() + extension;
// 保存文件到本地磁盘
Path filePath = Paths.get(UPLOAD_DIR + uniqueFilename);
Files.write(filePath, file.getBytes());
return new ResponseEntity<>("文件上传成功:" + uniqueFilename, HttpStatus.OK);
} catch (IOException e) {
return new ResponseEntity<>("文件上传失败:" + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
// 多文件上传
@PostMapping("/upload/multiple")
public ResponseEntity<String> uploadMultipleFiles(
@RequestParam("files") MultipartFile[] files) {
// 检查是否有文件
if (files == null || files.length == 0) {
return new ResponseEntity<>("请选择文件", HttpStatus.BAD_REQUEST);
}
StringBuilder result = new StringBuilder();
for (MultipartFile file : files) {
if (file.isEmpty()) {
result.append("跳过空文件;");
continue;
}
try {
// 生成唯一文件名
String originalFilename = file.getOriginalFilename();
String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
String uniqueFilename = UUID.randomUUID().toString() + extension;
// 保存文件
Path filePath = Paths.get(UPLOAD_DIR + uniqueFilename);
Files.write(filePath, file.getBytes());
result.append("文件上传成功:").append(uniqueFilename).append(";");
} catch (IOException e) {
result.append("文件上传失败(").append(file.getOriginalFilename()).append("):").append(e.getMessage()).append(";");
}
}
return new ResponseEntity<>(result.toString(), HttpStatus.OK);
}
}
2. 核心代码说明
@RequestParam("file") MultipartFile file:接收前端上传的文件,"file"需与前端表单的name属性一致。- 文件重命名 :使用
UUID生成唯一文件名,避免因文件名重复导致文件被覆盖。 - 目录创建 :通过
mkdirs()确保上传目录存在,防止保存文件时出现路径不存在的错误。 - 多文件处理 :通过
MultipartFile[]接收多个文件,循环处理每个文件的上传逻辑。
3. 前端上传页面(可选)
如果需要网页上传界面,可创建一个简单的 HTML 页面(放在 src/main/resources/static 目录下):
html
<!DOCTYPE html>
<html>
<head>
<title>文件上传</title>
</head>
<body>
<h3>单文件上传</h3>
<form action="/upload/single" method="post" enctype="multipart/form-data">
<input type="file" name="file" accept="*/*">
<button type="submit">上传</button>
</form>
<h3>多文件上传</h3>
<form action="/upload/multiple" method="post" enctype="multipart/form-data">
<input type="file" name="files" multiple accept="*/*">
<button type="submit">上传</button>
</form>
</body>
</html>
- 表单必须设置
enctype="multipart/form-data"才能支持文件上传。 multiple属性允许选择多个文件。
三、实现文件下载功能
文件下载需要读取服务器上的文件,通过 ResponseEntity<Resource> 或 HttpServletResponse 输出文件流。
1. 编写文件下载控制器
java
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.io.File;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
// 继续扩展上面的FileController类
public class FileController {
// 上传文件保存路径(与上传功能保持一致)
private static final String UPLOAD_DIR = "uploads/";
// 文件下载
@GetMapping("/download/{filename:.+}") // .+ 确保能匹配带扩展名的文件名
public ResponseEntity<Resource> downloadFile(@PathVariable String filename) {
// 构建文件路径
Path filePath = Paths.get(UPLOAD_DIR + filename);
Resource resource = new FileSystemResource(filePath);
// 检查文件是否存在
if (!resource.exists()) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
try {
// 构建响应头(指定文件名和下载方式)
String encodedFilename = URLEncoder.encode(filename, StandardCharsets.UTF_8.name())
.replaceAll("\\+", "%20"); // 处理空格编码问题
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename*=UTF-8''" + encodedFilename); // 支持中文文件名
return new ResponseEntity<>(resource, headers, HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
2. 核心代码说明
@GetMapping("/download/{filename:.+}::.+用于匹配带扩展名的文件名(如test.jpg)。FileSystemResource:用于访问本地文件系统中的资源。- 响应头设置 :
Content-Disposition: attachment告诉浏览器以附件形式下载文件(而非直接打开)。- 通过
URLEncoder编码文件名,解决中文文件名乱码问题。
3. 前端下载链接(可选)
在页面中添加下载链接,指向下载接口:
html
<!-- 在上传页面中添加 -->
<h3>文件下载</h3>
<!-- 假设已知文件名,实际项目中通常从数据库获取文件名列表 -->
<a href="/download/xxx-xxx-xxx.jpg">下载图片</a><br>
<a href="/download/xxx-xxx-xxx.pdf">下载文档</a>
四、优化与最佳实践
-
文件存储路径配置 :
建议将上传路径配置在
application.yml中,而非硬编码:yamlapp: upload: dir: /var/www/uploads/ # 生产环境建议使用绝对路径在代码中通过
@Value注入:java@Value("${app.upload.dir}") private String uploadDir; -
文件类型限制 :
上传时校验文件类型,防止恶意文件上传:
java// 允许的文件类型 private static final List<String> ALLOWED_EXTENSIONS = Arrays.asList("jpg", "jpeg", "png", "pdf"); // 校验文件类型 private boolean isAllowed(String filename) { String extension = filename.substring(filename.lastIndexOf(".") + 1).toLowerCase(); return ALLOWED_EXTENSIONS.contains(extension); } -
使用云存储 :
生产环境中,建议将文件存储到云存储服务(如 AWS S3、阿里云 OSS),而非本地磁盘,避免服务器存储压力和文件丢失风险。
-
数据库记录文件信息 :
上传文件后,将文件名、原文件名、大小、上传时间等信息存入数据库,便于管理和查询文件列表。
-
大文件分片上传 :
对于超大文件(如超过 100MB),可实现分片上传功能,将文件分割成小块依次上传,最后合并。
五、测试流程
- 启动应用 :访问
http://localhost:8080打开上传页面。 - 上传文件:选择文件并提交,查看上传结果。
- 下载文件 :通过返回的文件名或数据库记录的文件名,访问
/download/文件名下载文件。
通过以上流程,Spring Boot 可快速实现文件上传和下载功能,结合实际需求进行适当优化后,即可应用于生产环境。
3.7、Spring Boot异常统一处理方式
在 Spring Boot 应用中,异常统一处理是保证接口健壮性和用户体验的关键机制。通过集中处理各类异常,可以避免代码中充斥大量重复的 try-catch 块,同时返回格式统一的错误响应。以下是 Spring Boot 实现异常统一处理的常用方式:
一、核心组件:@ControllerAdvice 与 @ExceptionHandler
Spring Boot 提供 @ControllerAdvice 注解(控制器增强),配合 @ExceptionHandler 注解(异常处理器),可实现全局异常捕获与处理。
@ControllerAdvice:标识一个类为全局异常处理器,会拦截所有控制器(@Controller/@RestController)抛出的异常。@ExceptionHandler:定义方法处理特定类型的异常,通过参数指定异常类型。
二、基础实现:统一异常处理类
1. 定义统一响应体
首先创建通用的错误响应类,规范异常返回格式:
java
import java.time.LocalDateTime;
public class ErrorResponse {
private int code; // 错误状态码(建议与HTTP状态码一致或自定义业务码)
private String message; // 错误提示信息
private String details; // 错误详情(可选,如堆栈信息简化版)
private LocalDateTime timestamp; // 错误发生时间
// 构造方法
public ErrorResponse(int code, String message, String details) {
this.code = code;
this.message = message;
this.details = details;
this.timestamp = LocalDateTime.now();
}
// getter和setter
public int getCode() { return code; }
public void setCode(int code) { this.code = code; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public String getDetails() { return details; }
public void setDetails(String details) { this.details = details; }
public LocalDateTime getTimestamp() { return timestamp; }
}
2. 创建全局异常处理器
通过 @ControllerAdvice 定义全局异常处理类,针对不同异常类型编写处理逻辑:
java
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.NoHandlerFoundException;
@ControllerAdvice // 标识为全局控制器增强
public class GlobalExceptionHandler {
// 1. 处理自定义业务异常(最常用)
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponse> handleBusinessException(
BusinessException ex, WebRequest request) {
ErrorResponse error = new ErrorResponse(
ex.getCode(), // 自定义业务状态码
ex.getMessage(), // 异常消息
request.getDescription(false) // 请求详情(如URI)
);
// 返回自定义状态码和错误响应
return new ResponseEntity<>(error, HttpStatus.valueOf(ex.getCode()));
}
// 2. 处理参数校验异常(如@Valid注解触发)
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationExceptions(
MethodArgumentNotValidException ex, WebRequest request) {
// 提取校验失败的字段和消息
String errorMessage = ex.getBindingResult().getFieldError().getDefaultMessage();
ErrorResponse error = new ErrorResponse(
HttpStatus.BAD_REQUEST.value(), // 400
"参数校验失败:" + errorMessage,
request.getDescription(false)
);
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
// 3. 处理资源未找到异常(如404)
@ExceptionHandler(NoHandlerFoundException.class)
public ResponseEntity<ErrorResponse> handleNoHandlerFoundException(
NoHandlerFoundException ex, WebRequest request) {
ErrorResponse error = new ErrorResponse(
HttpStatus.NOT_FOUND.value(), // 404
"请求的资源不存在:" + ex.getRequestURL(),
request.getDescription(false)
);
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}
// 4. 处理所有未捕获的异常(兜底处理)
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGlobalException(
Exception ex, WebRequest request) {
ErrorResponse error = new ErrorResponse(
HttpStatus.INTERNAL_SERVER_ERROR.value(), // 500
"服务器内部错误:" + ex.getMessage(),
// 生产环境建议隐藏详细堆栈,开发环境可开启
request.getDescription(false)
);
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
3. 自定义业务异常(可选但推荐)
实际开发中,建议定义业务相关的异常类,便于区分不同业务场景的错误:
java
public class BusinessException extends RuntimeException {
private int code; // 业务状态码(如400/403/500等)
// 构造方法
public BusinessException(int code, String message) {
super(message);
this.code = code;
}
// 快捷构造方法(如通用的400错误)
public static BusinessException badRequest(String message) {
return new BusinessException(400, message);
}
public static BusinessException forbidden(String message) {
return new BusinessException(403, message);
}
// getter
public int getCode() {
return code;
}
}
三、使用场景与示例
1. 在业务代码中抛出异常
在 Service 或 Controller 中主动抛出异常,由全局处理器统一捕获:
java
import org.springframework.stereotype.Service;
@Service
public class UserService {
public User getUserById(Long id) {
// 模拟查询用户,若不存在则抛出异常
if (id == null || id <= 0) {
throw BusinessException.badRequest("用户ID必须为正数");
}
User user = userRepository.findById(id).orElse(null);
if (user == null) {
throw new BusinessException(404, "用户不存在:" + id);
}
return user;
}
}
2. 触发参数校验异常
使用 @Valid 注解校验请求参数,不符合规则时会抛出 MethodArgumentNotValidException:
java
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@RestController
public class UserController {
@PostMapping("/users")
public User createUser(@Valid @RequestBody User user) {
// @Valid 会触发参数校验,失败则抛出MethodArgumentNotValidException
return userService.save(user);
}
}
// User实体类(含校验注解)
class User {
private Long id;
@NotBlank(message = "用户名不能为空")
private String name;
@Min(value = 18, message = "年龄不能小于18岁")
private Integer age;
// getter/setter
}
3. 404异常处理
默认情况下,Spring Boot 会返回内置的 404 页面。若需自定义 404 响应,需配置 throwExceptionIfNoHandlerFound=true:
yaml
spring:
mvc:
throw-exception-if-no-handler-found: true
web:
resources:
add-mappings: false # 禁用默认静态资源映射(避免掩盖404)
此时访问不存在的接口会抛出 NoHandlerFoundException,被全局处理器捕获并返回统一格式的 404 响应。
四、高级特性与最佳实践
1. 区分环境显示错误详情
开发环境可显示详细堆栈信息,生产环境隐藏敏感信息,通过 @Profile 实现:
java
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGlobalException(Exception ex, WebRequest request) {
String details;
// 根据环境决定是否显示堆栈信息
if (environment.acceptsProfiles(Profiles.of("dev"))) {
details = Arrays.toString(ex.getStackTrace()); // 开发环境显示堆栈
} else {
details = request.getDescription(false); // 生产环境仅显示请求信息
}
ErrorResponse error = new ErrorResponse(500, "服务器内部错误", details);
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
2. 异常日志记录
在异常处理方法中添加日志记录,便于问题排查:
java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException ex) {
logger.warn("业务异常:{}", ex.getMessage()); // 警告级别日志
// ... 构建响应
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGlobalException(Exception ex) {
logger.error("未捕获异常", ex); // 错误级别日志,记录堆栈
// ... 构建响应
}
}
3. 结合 AOP 实现更细粒度的异常处理
对于需要在异常处理前后执行额外逻辑(如统计异常次数、发送告警)的场景,可结合 AOP 实现:
java
@Aspect
@Component
public class ExceptionAspect {
@AfterThrowing(pointcut = "within(@org.springframework.web.bind.annotation.RestController *)",
throwing = "ex")
public void afterThrowing(Exception ex) {
// 异常发生后执行:如发送邮件告警、记录 metrics
System.out.println("捕获到异常:" + ex.getMessage());
}
}
Spring Boot 异常统一处理的核心是通过 @ControllerAdvice + @ExceptionHandler 实现全局拦截,优势包括:
- 代码简洁 :避免在控制器中重复编写
try-catch。 - 响应统一:所有异常返回格式一致的 JSON,便于前端解析。
- 可扩展性:轻松添加新的异常类型处理逻辑。
实际开发中,建议结合自定义业务异常、参数校验和日志记录,构建完整的异常处理体系,提升应用的可靠性和可维护性。
4.1、Spring Boot整合JDBC
Spring Boot 对 JDBC 的整合进行了简化,通过 spring-boot-starter-jdbc starter 实现自动配置,无需手动编写繁琐的数据库连接代码。以下是详细的整合流程和使用方式:
一、准备工作:引入依赖
在 pom.xml 中添加 JDBC starter 和数据库驱动(以 MySQL 为例):
xml
<!-- JDBC 自动配置 starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- MySQL 驱动(根据数据库类型选择) -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
spring-boot-starter-jdbc包含:spring-jdbc核心库、数据源自动配置、事务管理等。- 数据库驱动需根据实际使用的数据库(MySQL、PostgreSQL 等)选择。
二、配置数据库连接信息
在 application.yml 中配置数据库连接参数,Spring Boot 会自动根据配置创建数据源:
yaml
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC&characterEncoding=utf8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver # MySQL 8.x 驱动类
hikari: # HikariCP 连接池配置(默认使用 HikariCP)
maximum-pool-size: 10 # 最大连接数
minimum-idle: 5 # 最小空闲连接数
idle-timeout: 300000 # 连接空闲超时时间(毫秒)
connection-timeout: 20000 # 连接超时时间(毫秒)
- 连接池:Spring Boot 2.x 后默认使用 HikariCP(性能最优),无需额外配置即可使用。
- 时区配置 :
serverTimezone=UTC解决时区偏移问题(MySQL 8.x 必需)。
三、核心组件与使用方式
Spring Boot 整合 JDBC 后,主要通过 JdbcTemplate 操作数据库,它封装了 JDBC 的繁琐操作(如获取连接、释放资源等)。
1. JdbcTemplate 注入与基本使用
JdbcTemplate 已由 Spring 自动配置,可直接在 Service 或 Controller 中注入使用:JdbcTemplate基本使用:UserService.java
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
@Service
public class UserService {
// 注入JdbcTemplate(Spring自动配置)
@Autowired
private JdbcTemplate jdbcTemplate;
// 1. 查询单个用户
public User getUserById(Long id) {
String sql = "SELECT id, name, age, email FROM user WHERE id = ?";
// RowMapper:将ResultSet映射为User对象
return jdbcTemplate.queryForObject(sql, new RowMapper<User>() {
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(rs.getLong("id"));
user.setName(rs.getString("name"));
user.setAge(rs.getInt("age"));
user.setEmail(rs.getString("email"));
return user;
}
}, id); // 最后一个参数是SQL中的?占位符值
}
// 2. 查询所有用户
public List<User> getAllUsers() {
String sql = "SELECT id, name, age, email FROM user";
return jdbcTemplate.query(sql, (rs, rowNum) -> {
// 使用Lambda简化RowMapper
User user = new User();
user.setId(rs.getLong("id"));
user.setName(rs.getString("name"));
user.setAge(rs.getInt("age"));
user.setEmail(rs.getString("email"));
return user;
});
}
// 3. 新增用户
public int addUser(User user) {
String sql = "INSERT INTO user (name, age, email) VALUES (?, ?, ?)";
// 返回影响行数
return jdbcTemplate.update(sql,
user.getName(),
user.getAge(),
user.getEmail());
}
// 4. 更新用户
public int updateUser(User user) {
String sql = "UPDATE user SET name = ?, age = ?, email = ? WHERE id = ?";
return jdbcTemplate.update(sql,
user.getName(),
user.getAge(),
user.getEmail(),
user.getId());
}
// 5. 删除用户
public int deleteUser(Long id) {
String sql = "DELETE FROM user WHERE id = ?";
return jdbcTemplate.update(sql, id);
}
// 6. 统计用户总数
public Integer countUsers() {
String sql = "SELECT COUNT(*) FROM user";
return jdbcTemplate.queryForObject(sql, Integer.class);
}
}
2. 核心方法说明
JdbcTemplate 提供了丰富的方法简化数据库操作:
- 查询方法 :
queryForObject(sql, rowMapper, params):查询单个对象。query(sql, rowMapper, params):查询集合。queryForObject(sql, Class<T>, params):查询单个值(如数量、总和)。
- 增删改方法 :
update(sql, params):执行 INSERT/UPDATE/DELETE,返回影响行数。
- 批量操作 :
batchUpdate(sql, batchArgs):批量执行 SQL(如批量插入)。
3. 批量操作示例
JdbcTemplate批量操作:UserService.java
java
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;
// 在UserService中添加批量插入方法
public int batchAddUsers(List<User> users) {
String sql = "INSERT INTO user (name, age, email) VALUES (?, ?, ?)";
// 批量设置参数
return jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
User user = users.get(i);
ps.setString(1, user.getName());
ps.setInt(2, user.getAge());
ps.setString(3, user.getEmail());
}
@Override
public int getBatchSize() {
return users.size(); // 批量大小
}
}).length; // 返回成功执行的数量
}
四、事务管理
Spring Boot 整合 JDBC 时,可通过 @Transactional 注解轻松实现事务管理:
JDBC事物管理:OrderService.java
java
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class OrderService {
@Autowired
private JdbcTemplate jdbcTemplate;
// 声明事务:发生异常时自动回滚
@Transactional
public void createOrder(Order order, List<OrderItem> items) {
// 1. 插入订单
String orderSql = "INSERT INTO order (order_no, user_id, total) VALUES (?, ?, ?)";
jdbcTemplate.update(orderSql, order.getOrderNo(), order.getUserId(), order.getTotal());
// 2. 插入订单项(若此处抛出异常,订单插入会自动回滚)
String itemSql = "INSERT INTO order_item (order_id, product_id, quantity, price) VALUES (?, ?, ?, ?)";
for (OrderItem item : items) {
jdbcTemplate.update(itemSql, order.getId(), item.getProductId(), item.getQuantity(), item.getPrice());
}
}
}
@Transactional注解添加在方法上,当方法内抛出未捕获的异常时,所有数据库操作会自动回滚。- 可通过
rollbackFor指定需要回滚的异常类型(默认只回滚RuntimeException)。
五、初始化数据库(可选)
Spring Boot 支持通过 SQL 脚本自动初始化数据库(建表、插入初始数据):
1. 创建初始化脚本
在 src/main/resources 目录下创建:
schema.sql:数据库表结构脚本(建表语句)。data.sql:初始数据脚本(插入语句)。
示例 schema.sql:
sql
CREATE TABLE IF NOT EXISTS user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
age INT,
email VARCHAR(100),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
示例 data.sql:
sql
INSERT INTO user (name, age, email) VALUES
('张三', 25, 'zhangsan@example.com'),
('李四', 30, 'lisi@example.com');
2. 配置初始化参数
在 application.yml 中配置脚本执行规则:
yaml
spring:
sql:
init:
mode: always # 始终执行初始化脚本(可选:always/embedded/never)
schema-locations: classpath:schema.sql # 表结构脚本路径
data-locations: classpath:data.sql # 数据脚本路径
encoding: utf-8 # 脚本编码
六、高级配置:自定义数据源
默认使用 HikariCP 数据源,若需切换为其他数据源(如 Druid),需添加对应依赖并配置:
1. 引入 Druid 依赖
xml
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
2. 配置 Druid 数据源
yaml
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/mydb
username: root
password: 123456
druid:
initial-size: 5 # 初始连接数
max-active: 20 # 最大连接数
min-idle: 5 # 最小空闲连接数
# 其他配置(如监控、过滤等)
Spring Boot 整合 JDBC 的核心优势:
- 自动配置 :引入 starter 后,无需手动创建
DataSource、JdbcTemplate等组件。 - 简化操作 :
JdbcTemplate封装了 JDBC 繁琐的样板代码,专注于 SQL 逻辑。 - 事务支持 :通过
@Transactional轻松实现事务管理。 - 灵活扩展:支持多种数据源(HikariCP、Druid 等)和数据库类型。
适合场景:简单的数据库操作、需要直接控制 SQL 的场景。若项目复杂度较高,建议进一步整合 MyBatis 或 JPA 框架。
5.1、Spring Boot整合MyBatis
Spring Boot 整合 MyBatis 可以充分利用 MyBatis 的 SQL 灵活性和 Spring Boot 的自动配置能力,简化数据访问层开发。以下是详细的整合流程和使用方式:
一、准备工作:引入依赖
在 pom.xml 中添加 MyBatis Starter 和数据库驱动(以 MySQL 为例):
xml
<!-- MyBatis 整合 Spring Boot 的 Starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.1</version> <!-- 版本需与 Spring Boot 兼容 -->
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
mybatis-spring-boot-starter包含:MyBatis 核心、Spring 整合支持、自动配置等。- 版本说明:2.3.x 版本适用于 Spring Boot 2.7+,3.x 版本适用于 Spring Boot 3.0+。
二、配置数据库和 MyBatis
在 application.yml 中配置数据库连接信息和 MyBatis 相关参数:
yaml
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC&characterEncoding=utf8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
# Mapper XML 文件路径(若使用注解方式可省略)
mapper-locations: classpath:mybatis/mappers/*.xml
# 实体类包路径(别名配置,可选)
type-aliases-package: com.example.entity
configuration:
# 开启驼峰命名自动映射(如数据库字段 user_name 映射到 Java 属性 userName)
map-underscore-to-camel-case: true
# 日志输出 SQL 语句(开发环境使用)
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
三、核心组件与实现方式
Spring Boot 整合 MyBatis 有两种主流方式:注解方式 (适合简单 SQL)和 XML 映射方式(适合复杂 SQL)。
1. 注解方式(推荐简单场景)
通过 @Mapper 注解标识接口,并使用 MyBatis 注解编写 SQL。
步骤 1:定义实体类
java
package com.example.entity;
public class User {
private Long id;
private String name;
private Integer age;
private String email;
// 构造方法、getter、setter
public User() {}
// getter 和 setter 省略...
}
步骤 2:创建 Mapper 接口(使用注解)
java
package com.example.mapper;
import com.example.entity.User;
import org.apache.ibatis.annotations.*;
import java.util.List;
// @Mapper 标识这是MyBatis的Mapper接口(无需手动配置,Spring会自动扫描)
@Mapper
public interface UserMapper {
// 1. 根据ID查询
@Select("SELECT id, name, age, email FROM user WHERE id = #{id}")
User selectById(Long id);
// 2. 查询所有
@Select("SELECT id, name, age, email FROM user")
List<User> selectAll();
// 3. 新增用户
@Insert("INSERT INTO user (name, age, email) VALUES (#{name}, #{age}, #{email})")
// 回填自增ID到实体类(若主键是自增)
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);
// 4. 更新用户
@Update("UPDATE user SET name = #{name}, age = #{age}, email = #{email} WHERE id = #{id}")
int update(User user);
// 5. 删除用户
@Delete("DELETE FROM user WHERE id = #{id}")
int delete(Long id);
}
步骤 3:创建 Service 层调用 Mapper
java
package com.example.service;
import com.example.entity.User;
import com.example.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {
// 注入Mapper接口(Spring自动生成实现类)
@Autowired
private UserMapper userMapper;
public User getUserById(Long id) {
return userMapper.selectById(id);
}
public List<User> getAllUsers() {
return userMapper.selectAll();
}
public int addUser(User user) {
return userMapper.insert(user);
}
public int updateUser(User user) {
return userMapper.update(user);
}
public int deleteUser(Long id) {
return userMapper.delete(id);
}
}
2. XML 映射方式(推荐复杂场景)
将 SQL 写在 XML 文件中,Mapper 接口仅定义方法签名,适合 SQL 逻辑复杂或需要动态 SQL 的场景。
步骤 1:创建 Mapper 接口(无注解)
java
package com.example.mapper;
import com.example.entity.User;
import java.util.List;
import java.util.Map;
@Mapper
public interface UserXmlMapper {
// 方法签名需与XML中的id一致
User selectById(Long id);
List<User> selectByCondition(Map<String, Object> condition); // 动态条件查询
int insert(User user);
}
步骤 2:创建 Mapper XML 文件
在 src/main/resources/mybatis/mappers 目录下创建 UserXmlMapper.xml(路径需与 application.yml 中 mapper-locations 配置一致):
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace需与Mapper接口全类名一致 -->
<mapper namespace="com.example.mapper.UserXmlMapper">
<!-- 定义结果集映射(可选,若开启驼峰命名可省略) -->
<resultMap id="BaseResultMap" type="com.example.entity.User">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="age" property="age"/>
<result column="email" property="email"/>
</resultMap>
<!-- 根据ID查询(id需与接口方法名一致) -->
<select id="selectById" parameterType="java.lang.Long" resultMap="BaseResultMap">
SELECT id, name, age, email FROM user WHERE id = #{id}
</select>
<!-- 动态条件查询(使用if标签) -->
<select id="selectByCondition" parameterType="map" resultMap="BaseResultMap">
SELECT id, name, age, email FROM user
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
</select>
<!-- 新增用户 -->
<insert id="insert" parameterType="com.example.entity.User"
useGeneratedKeys="true" keyProperty="id">
INSERT INTO user (name, age, email)
VALUES (#{name}, #{age}, #{email})
</insert>
</mapper>
四、扫描 Mapper 接口的两种方式
-
在每个 Mapper 接口上添加
@Mapper注解(如上述示例),适合 Mapper 数量较少的情况。 -
在启动类上添加
@MapperScan注解(批量扫描),适合 Mapper 数量较多的情况:批量扫描Mapper接口
java
package com.example;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
// 扫描指定包下的所有Mapper接口(无需在每个接口加@Mapper)
@MapperScan("com.example.mapper")
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
五、事务管理
与 JDBC 一样,MyBatis 整合 Spring Boot 时可通过 @Transactional 注解实现事务管理:
java
package com.example.service;
import com.example.entity.Order;
import com.example.entity.OrderItem;
import com.example.mapper.OrderItemMapper;
import com.example.mapper.OrderMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private OrderItemMapper orderItemMapper;
// 声明事务:发生异常时自动回滚
@Transactional
public void createOrder(Order order, List<OrderItem> items) {
// 插入订单
orderMapper.insert(order);
// 插入订单项(若抛出异常,订单插入会回滚)
for (OrderItem item : items) {
item.setOrderId(order.getId());
orderItemMapper.insert(item);
}
}
}
六、高级特性:分页查询
MyBatis 本身不支持分页,需整合 pagehelper-spring-boot-starter 实现分页功能:
1. 引入分页插件依赖
xml
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.6</version>
</dependency>
2. 使用分页查询
java
package com.example.service;
import com.example.entity.User;
import com.example.mapper.UserMapper;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
// 分页查询用户(页码从1开始)
public PageInfo<User> getUsersByPage(int pageNum, int pageSize) {
// 在查询前调用PageHelper.startPage,自动拦截SQL添加分页
PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.selectAll();
// 包装查询结果为PageInfo,包含分页信息(总条数、总页数等)
return new PageInfo<>(users);
}
}
七、整合优势与最佳实践
- 零 XML 配置:MyBatis 的核心配置(如数据源、映射器)由 Spring Boot 自动完成。
- 两种 SQL 编写方式:简单 SQL 用注解,复杂 SQL 用 XML,灵活适配不同场景。
- 动态 SQL 支持 :通过 XML 中的
<if>、<where>、<foreach>等标签编写动态 SQL,无需拼接字符串。 - 最佳实践 :
- 保持 Mapper 接口与 XML 文件的目录结构一致(便于维护)。
- 开发环境开启 SQL 日志(
log-impl: StdOutImpl),方便调试。 - 复杂业务逻辑使用事务管理,确保数据一致性。
Spring Boot 整合 MyBatis 简化了传统 MyBatis 的繁琐配置,通过自动扫描 Mapper 接口、内置数据源等特性,让开发者专注于 SQL 编写和业务逻辑。无论是简单的 CRUD 操作还是复杂的动态 SQL 场景,这种整合方式都能提供高效、灵活的解决方案,是企业级应用开发的常用选择。
6.1、Spring Boot整合JPA
Spring Boot 整合 JPA(Java Persistence API)可以极大简化数据访问层开发,通过面向对象的方式操作数据库,无需编写大量 SQL 语句。以下是详细的整合流程和使用方式:
一、核心概念
JPA 是 Java 持久化规范,Hibernate 是其最流行的实现。Spring Boot 通过 spring-boot-starter-data-jpa 实现自动配置,底层默认使用 Hibernate 作为 JPA 实现。
二、准备工作:引入依赖
在 pom.xml 中添加 JPA Starter 和数据库驱动(以 MySQL 为例):
xml
<!-- Spring Data JPA Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
三、配置数据库和 JPA 参数
在 application.yml 中配置数据库连接和 JPA 相关属性:
yaml
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC&characterEncoding=utf8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
# 数据库方言(根据使用的数据库选择)
database-platform: org.hibernate.dialect.MySQL8Dialect
# 是否自动生成数据库表结构
hibernate:
ddl-auto: update # 可选值:create(每次重建)/create-drop(退出时删除)/update(更新)/validate(校验)/none(不操作)
# 显示 SQL 语句(开发环境)
show-sql: true
# 格式化 SQL 输出
properties:
hibernate:
format_sql: true
# 指定实体类包扫描(可选,默认扫描所有)
packages-to-scan: com.example.entity
ddl-auto: update:最常用配置,会根据实体类自动更新表结构(不会删除已有数据)。show-sql: true:开发时开启,便于调试生成的 SQL 语句。
四、核心实现步骤
1. 定义实体类(Entity)
使用 JPA 注解映射实体类与数据库表:
java
package com.example.entity;
import lombok.Data;
import javax.persistence.*;
import java.time.LocalDateTime;
@Data // Lombok注解,自动生成getter/setter
@Entity // 标识为JPA实体
@Table(name = "t_user") // 指定映射的表名(默认类名小写)
public class User {
@Id // 主键
@GeneratedValue(strategy = GenerationType.IDENTITY) // 自增策略(依赖数据库)
private Long id;
@Column(name = "username", length = 50, nullable = false) // 映射表字段
private String name;
@Column(nullable = false)
private Integer age;
@Column(unique = true) // 唯一约束
private String email;
@Column(name = "create_time")
private LocalDateTime createTime;
// 无参构造器(JPA必需)
public User() {}
// 带参构造器(可选)
public User(String name, Integer age, String email) {
this.name = name;
this.age = age;
this.email = email;
this.createTime = LocalDateTime.now();
}
}
常用注解说明:
@Entity:声明类为实体类。@Table:指定数据库表名,不指定则默认使用类名小写。@Id:标识主键字段。@GeneratedValue:指定主键生成策略(IDENTITY对应自增主键)。@Column:配置字段属性(名称、长度、是否可为空、唯一约束等)。
2. 创建 Repository 接口
通过继承 JpaRepository 接口,无需实现即可获得基本 CRUD 方法:
java
package com.example.repository;
import com.example.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
import java.util.Optional;
// 泛型参数:<实体类, 主键类型>
public interface UserRepository extends JpaRepository<User, Long> {
// 1. 方法名查询(无需写SQL,根据方法名自动生成)
// 根据姓名查询(模糊匹配)
List<User> findByNameLike(String name);
// 根据年龄范围查询
List<User> findByAgeBetween(Integer minAge, Integer maxAge);
// 根据邮箱查询(返回Optional,避免空指针)
Optional<User> findByEmail(String email);
// 2. JPQL查询(类似SQL,操作实体类而非表)
@Query("SELECT u FROM User u WHERE u.age > :age ORDER BY u.name ASC")
List<User> findUsersOlderThan(@Param("age") Integer age);
// 3. 原生SQL查询(直接操作数据库表)
@Query(value = "SELECT * FROM t_user WHERE email LIKE %:domain%", nativeQuery = true)
List<User> findByEmailDomain(@Param("domain") String domain);
}
Repository 接口特性:
- 继承
JpaRepository后,自动拥有save()、findById()、findAll()、deleteById()等基础方法。 - 方法名查询 :遵循特定命名规则(如
findByXxx、findByXxxLike),JPA 自动生成 SQL。 - 自定义查询 :通过
@Query注解编写 JPQL 或原生 SQL,@Param绑定参数。
3. Service 层调用 Repository
java
package com.example.service;
import com.example.entity.User;
import com.example.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
// 查询所有用户
public List<User> getAllUsers() {
return userRepository.findAll();
}
// 根据ID查询(返回Optional)
public Optional<User> getUserById(Long id) {
return userRepository.findById(id);
}
// 新增/更新用户(save()方法兼具两种功能)
public User saveUser(User user) {
return userRepository.save(user);
}
// 删除用户
@Transactional // 事务支持
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
// 自定义查询:查询年龄大于指定值的用户
public List<User> getUsersOlderThan(Integer age) {
return userRepository.findUsersOlderThan(age);
}
}
4. Controller 层提供 API
java
package com.example.controller;
import com.example.entity.User;
import com.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping
public List<User> getAllUsers() {
return userService.getAllUsers();
}
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
Optional<User> user = userService.getUserById(id);
return user.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build());
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public User createUser(@RequestBody User user) {
return userService.saveUser(user);
}
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(
@PathVariable Long id,
@RequestBody User user) {
return userService.getUserById(id)
.map(existingUser -> {
user.setId(id); // 确保ID一致
return ResponseEntity.ok(userService.saveUser(user));
})
.orElseGet(() -> ResponseEntity.notFound().build());
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
if (userService.getUserById(id).isPresent()) {
userService.deleteUser(id);
return ResponseEntity.noContent().build();
}
return ResponseEntity.notFound().build();
}
}
五、高级特性
1. 分页与排序
通过 Pageable 实现分页和排序,无需手动编写分页 SQL:
java
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
// 在UserService中添加分页方法
public Page<User> getUsersByPage(int pageNum, int pageSize) {
// 排序规则:按年龄降序,姓名升序
Sort sort = Sort.by(
Sort.Order.desc("age"),
Sort.Order.asc("name")
);
// 分页参数(页码从0开始)
Pageable pageable = PageRequest.of(pageNum - 1, pageSize, sort);
return userRepository.findAll(pageable);
}
Controller 调用:
java
@GetMapping("/page")
public Page<User> getUsersByPage(
@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize) {
return userService.getUsersByPage(pageNum, pageSize);
}
2. 事务管理
通过 @Transactional 注解实现事务控制,与 Spring 无缝集成:
java
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private OrderItemRepository itemRepository;
// 声明事务:发生异常自动回滚
@Transactional
public void createOrder(Order order, List<OrderItem> items) {
orderRepository.save(order);
items.forEach(item -> {
item.setOrderId(order.getId());
itemRepository.save(item);
});
}
}
3. 复杂查询(Specification)
对于动态条件查询,可使用 Specification 构建查询条件:
java
import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
// 动态条件查询
public List<User> findUsersByConditions(Map<String, Object> params) {
Specification<User> spec = (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
// 按姓名模糊查询
if (params.containsKey("name")) {
String name = (String) params.get("name");
predicates.add(cb.like(root.get("name"), "%" + name + "%"));
}
// 按年龄范围查询
if (params.containsKey("minAge") && params.containsKey("maxAge")) {
Integer minAge = (Integer) params.get("minAge");
Integer maxAge = (Integer) params.get("maxAge");
predicates.add(cb.between(root.get("age"), minAge, maxAge));
}
return cb.and(predicates.toArray(new Predicate[0]));
};
return userRepository.findAll(spec);
}
六、整合优势与适用场景
-
优势:
- 完全面向对象,无需编写 SQL 即可完成大部分数据库操作。
- 自动生成表结构(
ddl-auto配置),简化开发流程。 - 内置分页、排序、事务等功能,无需手动实现。
- 支持复杂查询(JPQL、原生 SQL、Specification)。
-
适用场景:
- 快速开发中小型项目,减少重复 SQL 编写。
- 数据库表结构频繁变动的场景(
ddl-auto: update自动同步)。 - 更关注业务逻辑而非 SQL 优化的场景。
Spring Boot 整合 JPA 是一种高效的 ORM 解决方案,通过注解映射实体与表、Repository 接口提供 CRUD 方法、内置分页排序等特性,极大简化了数据访问层开发。对于需要快速开发且 SQL 逻辑不复杂的项目,JPA 是理想选择;若需极致的 SQL 优化或复杂动态 SQL,可考虑 MyBatis。
7.1、Spring Boot的数据缓存Cache
Spring Boot 提供了强大的数据缓存支持,通过抽象层整合多种统一缓存操作,可轻松集成多种缓存实现(如 Caffeine、EhCache、Redis 等)。以下是详细的缓存使用流程和核心特性:
一、缓存核心概念
- 缓存抽象 :Spring 定义了
Cache和CacheManager接口,屏蔽不同缓存产品的实现差异。 - 注解驱动 :通过
@Cacheable、@CachePut、@CacheEvict等注解实现缓存操作,无需手动编写缓存逻辑。 - 自动配置 :Spring Boot 根据类路径下的缓存依赖自动配置缓存管理器(如引入 Redis 则默认配置
RedisCacheManager)。
二、快速集成:使用默认缓存(Caffeine)
1. 引入依赖
Spring Boot 2.x 后默认使用 Caffeine 作为缓存实现(轻量级、高性能):
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- Caffeine 缓存依赖(spring-boot-starter-cache 已包含) -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
2. 启用缓存
在启动类添加 @EnableCaching 注解开启缓存功能:
java
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching // 启用缓存功能
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
3. 配置缓存参数
在 application.yml 中配置 Caffeine 缓存策略:
yaml
spring:
cache:
type: caffeine # 明确指定缓存类型(可选,自动检测)
caffeine:
spec: maximumSize=1000,expireAfterWrite=60s # 最大缓存1000条,写入后60秒过期
cache-names: users,products # 预定义缓存名称(可选)
maximumSize=1000:缓存最大条目数。expireAfterWrite=60s:写入后 60 秒自动过期。- 其他策略:
expireAfterAccess=5m(访问后 5 分钟过期)、initialCapacity=100(初始容量)等。
三、核心缓存注解使用
1. @Cacheable:查询缓存
方法执行前先检查缓存,若存在则返回缓存数据,否则执行方法并将结果存入缓存。
java
package com.example.service;
import com.example.entity.User;
import com.example.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
// 缓存key为"user::id",缓存名称为"users"
@Cacheable(value = "users", key = "#id", unless = "#result == null")
public User getUserById(Long id) {
// 首次调用会执行查询,后续从缓存获取
return userRepository.findById(id).orElse(null);
}
// 复杂key:组合参数作为key
@Cacheable(value = "users", key = "#name + '-' + #age")
public User getUserByNameAndAge(String name, Integer age) {
return userRepository.findByNameAndAge(name, age);
}
}
注解参数说明:
value/cacheNames:缓存名称(必填,可理解为缓存分区)。key:缓存键(支持 SpEL 表达式,默认是所有参数的组合)。unless:条件表达式,为true时不缓存(如unless = "#result == null"表示不缓存 null 结果)。condition:条件表达式,为false时不执行缓存逻辑。
2. @CachePut:更新缓存
执行方法后将结果存入缓存,常用于更新操作(确保缓存与数据库同步)。
java
import org.springframework.cache.annotation.CachePut;
// 在UserService中添加
@CachePut(value = "users", key = "#user.id") // 缓存key与查询时保持一致
public User updateUser(User user) {
User updated = userRepository.save(user);
return updated; // 方法返回值会被存入缓存
}
3. @CacheEvict:删除缓存
移除缓存中的数据,常用于删除操作或数据更新后清理旧缓存。
java
import org.springframework.cache.annotation.CacheEvict;
// 在UserService中添加
// 1. 删除指定key的缓存
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
// 2. 清空整个"users"缓存(如批量删除场景)
@CacheEvict(value = "users", allEntries = true)
public void clearAllUsers() {
userRepository.deleteAll();
}
注解参数说明:
allEntries = true:清空该缓存名称下的所有条目(默认false)。beforeInvocation = true:在方法执行前删除缓存(默认false,方法执行后删除)。
4. @Caching:组合缓存操作
当一个方法需要同时执行多种缓存操作(如既更新又删除)时使用。
java
import org.springframework.cache.annotation.Caching;
// 在UserService中添加
@Caching(
put = {@CachePut(value = "users", key = "#user.id")}, // 更新缓存
evict = {
@CacheEvict(value = "userCounts", key = "#user.departmentId"), // 清除关联缓存
@CacheEvict(value = "allUsers", allEntries = true) // 清除列表缓存
}
)
public User saveOrUpdate(User user) {
return userRepository.save(user);
}
四、集成 Redis 缓存(分布式场景)
对于分布式系统,需使用 Redis 等集中式缓存。Spring Boot 对 Redis 缓存提供自动配置支持。
1. 引入 Redis 依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 缓存抽象依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
2. 配置 Redis 缓存
yaml
spring:
redis:
host: localhost
port: 6379
password: 123456 # 若有密码
timeout: 2000ms
cache:
type: redis # 指定使用Redis缓存
redis:
time-to-live: 600000ms # 全局过期时间(10分钟)
cache-null-values: false # 是否缓存null值(默认false)
key-prefix: "app:" # 缓存key前缀(避免key冲突)
use-key-prefix: true # 启用key前缀
3. 自定义 Redis 缓存配置(可选)
默认情况下,Redis 缓存会将对象序列化为 JSON。若需自定义序列化方式(如使用 FastJSON),可配置 RedisCacheConfiguration:
Redis缓存自定义配置:
java
package com.example.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
@Configuration
@EnableCaching
public class RedisCacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
// 配置序列化(解决乱码问题)
Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(om.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSerializer.setObjectMapper(om);
// 配置缓存过期时间
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10)) // 全局过期时间
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer())) // key序列化
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(jacksonSerializer)) // value序列化
.disableCachingNullValues(); // 不缓存null值
// 对不同缓存名称设置不同过期时间
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.withCacheConfiguration("users",
config.entryTtl(Duration.ofMinutes(5))) // users缓存5分钟过期
.withCacheConfiguration("products",
config.entryTtl(Duration.ofHours(1))) // products缓存1小时过期
.build();
}
}
五、缓存使用最佳实践
-
缓存 key 设计:
- 使用有意义的前缀(如
user::123、product::456),避免 key 冲突。 - 复杂参数组合作为 key 时,确保唯一性(可通过 SpEL 表达式
#p0 + '-' + #p1组合参数)。
- 使用有意义的前缀(如
-
缓存粒度控制:
- 避免缓存过大的集合(如全表数据),可分页缓存或按需缓存单条数据。
- 高频访问且变化少的数据(如字典表)优先缓存。
-
缓存一致性:
- 数据更新时必须同步更新或删除缓存(使用
@CachePut或@CacheEvict)。 - 并发场景下可使用「更新数据库 + 删除缓存」策略,避免脏读。
- 数据更新时必须同步更新或删除缓存(使用
-
缓存穿透防护:
- 对查询结果为 null 的请求,可短暂缓存(
cache-null-values: true),避免缓存穿透。 - 结合布隆过滤器过滤不存在的 key。
- 对查询结果为 null 的请求,可短暂缓存(
-
缓存雪崩防护:
- 不同缓存设置随机过期时间,避免同时失效。
- 核心缓存添加过期时间兜底策略。
六、缓存注解工作原理
- Spring 通过 AOP 对标注缓存注解的方法进行拦截。
- 调用方法前,先检查缓存中是否存在符合条件的 key。
- 若存在则直接返回缓存值,跳过方法执行;若不存在则执行方法,并将结果存入缓存。
- 缓存操作由
CacheManager管理,不同缓存实现(Caffeine/Redis)通过CacheManager接口适配。
Spring Boot 缓存机制通过注解驱动简化了缓存开发,核心优势:
- 零侵入:无需修改业务逻辑,通过注解即可实现缓存。
- 灵活性:支持多种缓存实现,可根据环境(单机/分布式)切换。
- 高性能:减少数据库访问,提升系统响应速度。
实际开发中,需根据业务场景选择合适的缓存实现(单机用 Caffeine,分布式用 Redis),并注意缓存一致性和异常防护,避免缓存引发的数据问题。
8.1、Spring Security
Spring Security 是 Spring 生态中用于身份认证和授权的安全框架,提供了全面的安全解决方案,包括用户认证、授权管理、 CSRF 防护、会话管理等功能。以下是 Spring Security 的核心概念和使用详解:
一、核心概念
- 认证(Authentication):验证用户身份(如用户名密码登录、OAuth2 登录等)。
- 授权(Authorization):验证用户是否有权限访问某个资源(如角色、权限控制)。
- 安全过滤器链(Security Filter Chain):Spring Security 的核心,通过一系列过滤器拦截请求,实现安全控制。
- 用户详情服务(UserDetailsService):提供用户信息(用户名、密码、权限等),用于认证。
- 密码编码器(PasswordEncoder):对密码进行加密存储(如 BCrypt、SHA 等)。
二、快速集成 Spring Security
1. 引入依赖
在 pom.xml 中添加 Spring Security Starter:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
引入后,Spring Security 会自动生效,默认拦截所有请求,生成随机密码(控制台输出),默认用户名为 user。
2. 基本配置类
通过配置类自定义安全规则:
java
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity // 启用 Web 安全支持
public class SecurityConfig {
// 配置安全过滤器链(核心)
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// 关闭 CSRF(前后端分离场景常用)
.csrf(csrf -> csrf.disable())
// 配置请求授权规则
.authorizeHttpRequests(auth -> auth
.antMatchers("/public/**").permitAll() // 公开接口,无需认证
.antMatchers("/admin/**").hasRole("ADMIN") // 需要 ADMIN 角色
.antMatchers("/user/**").hasAnyRole("USER", "ADMIN") // 需要 USER 或 ADMIN 角色
.anyRequest().authenticated() // 其他请求需要认证
)
// 配置表单登录(默认提供登录页)
.formLogin(form -> form
.loginPage("/login") // 自定义登录页路径
.defaultSuccessUrl("/home", true) // 登录成功跳转页
.permitAll() // 登录页允许匿名访问
)
// 配置退出登录
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
.permitAll()
);
return http.build();
}
// 配置用户信息(内存模式,仅用于演示)
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.builder()
.username("user")
.password(passwordEncoder().encode("123456")) // 密码需加密
.roles("USER") // 角色(前缀 ROLE_ 会自动添加)
.build();
UserDetails admin = User.builder()
.username("admin")
.password(passwordEncoder().encode("admin123"))
.roles("ADMIN", "USER") // 拥有多个角色
.build();
return new InMemoryUserDetailsManager(user, admin);
}
// 配置密码编码器(必须配置,否则报错)
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); // BCrypt 加密算法
}
}
三、核心功能详解
1. 认证机制
Spring Security 支持多种认证方式,常用的有:
(1)基于表单的认证(默认)
通过用户名/密码登录,如上述配置中的 formLogin()。
(2)基于数据库的认证
实际项目中,用户信息通常存储在数据库,需自定义 UserDetailsService:
数据库用户详情服务:
java
package com.example.service;
import com.example.entity.User;
import com.example.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 从数据库查询用户
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("用户不存在: " + username));
// 将自定义 User 转换为 Spring Security 的 UserDetails
return org.springframework.security.core.userdetails.User.builder()
.username(user.getUsername())
.password(user.getPassword()) // 数据库中存储的是加密后的密码
.roles(user.getRoles().toArray(new String[0])) // 角色列表
.build();
}
}
(3)JWT 认证(前后端分离常用)
- 引入 JWT 依赖:
xml
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
- JWT 工具类:
java
package com.example.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtUtil {
// 密钥(实际项目中应从配置文件读取,且足够长)
private final Key secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
// 令牌过期时间(24小时)
private static final long EXPIRATION_TIME = 86400000;
// 生成令牌
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(secretKey)
.compact();
}
// 从令牌中获取用户名
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
// 验证令牌是否有效
public boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
// 提取令牌中的声明
private <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private Claims extractAllClaims(String token) {
return Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token)
.getBody();
}
// 检查令牌是否过期
private boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
private Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
}
- JWT 认证过滤器:
java
package com.example.security;
import com.example.service.CustomUserDetailsService;
import com.example.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtUtil jwtUtil;
@Autowired
private CustomUserDetailsService userDetailsService;
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// 从请求头获取令牌
final String authHeader = request.getHeader("Authorization");
final String jwt;
final String username;
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
// 提取 JWT 令牌(去除 "Bearer " 前缀)
jwt = authHeader.substring(7);
try {
username = jwtUtil.extractUsername(jwt);
// 如果用户名不为空且未认证
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
// 验证令牌
if (jwtUtil.validateToken(jwt, userDetails)) {
// 设置认证信息到上下文
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
} catch (Exception e) {
logger.error("无法验证令牌: {}", e.getMessage());
}
filterChain.doFilter(request, response);
}
}
- 更新 Security 配置:
java
package com.example.config;
import com.example.security.JwtAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
public class JwtSecurityConfig {
@Autowired
private JwtAuthenticationFilter jwtAuthFilter;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.antMatchers("/api/auth/**").permitAll() // 登录接口公开
.antMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
// 无状态会话(JWT 不需要会话)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
// 添加 JWT 过滤器(在用户名密码过滤器之前)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
2. 授权机制
Spring Security 支持基于角色(Role)和权限(Permission)的授权:
(1)基于注解的授权
在控制器或方法上使用注解控制访问权限:
java
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DemoController {
// 允许所有认证用户访问
@GetMapping("/home")
public String home() {
return "首页";
}
// 仅允许 ADMIN 角色访问
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/admin/dashboard")
public String adminDashboard() {
return "管理员面板";
}
// 允许 USER 或 ADMIN 角色访问
@PreAuthorize("hasAnyRole('USER', 'ADMIN')")
@GetMapping("/user/profile")
public String userProfile() {
return "用户资料";
}
// 基于权限的控制(需在用户详情中配置权限)
@PreAuthorize("hasPermission('', 'product:delete')")
@GetMapping("/products/delete")
public String deleteProduct() {
return "删除产品";
}
}
需在启动类添加 @EnableGlobalMethodSecurity(prePostEnabled = true) 开启注解支持(Spring Security 5.6+ 可用 @EnableMethodSecurity)。
(2)基于 URL 的授权
在 SecurityFilterChain 中配置 URL 与角色/权限的映射:
java
http.authorizeHttpRequests(auth -> auth
.antMatchers("/api/products/**").hasAnyRole("USER", "ADMIN")
.antMatchers("/api/orders/**").hasPermission("", "order:view")
.anyRequest().authenticated()
);
四、用户信息与权限获取
在控制器中获取当前登录用户信息:
java
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@GetMapping("/current-user")
public String getCurrentUser() {
// 获取认证信息
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// 用户名
String username = authentication.getName();
// 权限列表
String authorities = authentication.getAuthorities().toString();
return "当前用户: " + username + ", 权限: " + authorities;
}
}
五、常用安全配置
- CSRF 防护 :默认开启,前后端分离项目通常关闭(
csrf.disable())。 - 会话管理 :
sessionCreationPolicy(SessionCreationPolicy.STATELESS):无状态会话(JWT 常用)。maximumSessions(1):限制单用户同时登录次数。
- 密码策略 :
- 使用
BCryptPasswordEncoder加密密码(不可逆)。 - 自定义密码强度(如长度、包含特殊字符等)。
- 使用
- CORS 配置:允许跨域请求:
java
http.cors(cors -> cors.configurationSource(request -> {
org.springframework.web.cors.CorsConfiguration config = new org.springframework.web.cors.CorsConfiguration();
config.setAllowedOrigins(Arrays.asList("http://localhost:3000"));
config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
config.setAllowedHeaders(Arrays.asList("*"));
return config;
}));
Spring Security 是一个功能强大的安全框架,核心优势:
- 全面的安全功能:覆盖认证、授权、防护等各环节。
- 灵活的扩展机制:支持自定义认证方式、权限控制等。
- 与 Spring 生态无缝集成:易于在 Spring Boot 项目中使用。
实际开发中,需根据项目类型(传统 Web/前后端分离)选择合适的认证方式(表单登录/JWT/OAuth2),并遵循最小权限原则设计授权规则,确保系统安全。
9.1、Spring Boot整合RabbitMQ
Spring Boot 整合 RabbitMQ 可以轻松实现消息队列的功能,用于异步通信、服务解耦、流量削峰等场景。以下是详细的整合流程和使用方式:
一、核心概念
- 消息代理(Broker):RabbitMQ 服务器,负责接收和转发消息。
- 交换机(Exchange):接收生产者发送的消息,根据路由规则转发到队列。
- 队列(Queue):存储消息,等待消费者消费。
- 绑定(Binding):将交换机和队列按路由规则关联起来。
- 路由键(Routing Key):消息发送时指定的键,交换机根据该键匹配路由规则。
二、准备工作
1. 安装 RabbitMQ
- 本地安装:参考 RabbitMQ 官方文档 安装并启动服务(默认端口 5672,管理界面端口 15672)。
- 访问管理界面:
http://localhost:15672,默认账号密码guest/guest。
2. 引入依赖
在 pom.xml 中添加 RabbitMQ Starter:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
3. 配置 RabbitMQ 连接
在 application.yml 中配置连接信息:
yaml
spring:
rabbitmq:
host: localhost # 服务器地址
port: 5672 # 端口
username: guest # 用户名
password: guest # 密码
virtual-host: / # 虚拟主机(默认 /)
# 生产者确认配置(可选)
publisher-confirm-type: correlated # 开启发布确认
publisher-returns: true # 开启发布返回
三、核心组件与实现
1. 声明交换机、队列和绑定关系
通过配置类声明消息队列相关组件:
RabbitMQ配置类:
java
package com.example.config;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitMQConfig {
// 1. 声明队列
@Bean
public Queue directQueue() {
// 参数:队列名、是否持久化、是否排他、是否自动删除、额外参数
return QueueBuilder.durable("direct_queue")
.autoDelete(false)
.build();
}
// 2. 声明交换机(Direct类型,精确匹配路由键)
@Bean
public DirectExchange directExchange() {
// 参数:交换机名、是否持久化、是否自动删除、额外参数
return ExchangeBuilder.directExchange("direct_exchange")
.durable(true)
.build();
}
// 3. 绑定队列和交换机(指定路由键)
@Bean
public Binding directBinding(Queue directQueue, DirectExchange directExchange) {
// 将队列通过路由键 "direct_routing" 绑定到交换机
return BindingBuilder.bind(directQueue)
.to(directExchange)
.with("direct_routing");
}
// -------------- 以下是其他类型的交换机示例 --------------
// 扇形交换机(Fanout):广播消息到所有绑定的队列,忽略路由键
@Bean
public FanoutExchange fanoutExchange() {
return ExchangeBuilder.fanoutExchange("fanout_exchange").build();
}
// 主题交换机(Topic):通过通配符匹配路由键(* 匹配一个单词,# 匹配多个单词)
@Bean
public TopicExchange topicExchange() {
return ExchangeBuilder.topicExchange("topic_exchange").build();
}
}
2. 消息生产者(发送消息)
使用 RabbitTemplate 发送消息到交换机:消息生产者
java
package com.example.service;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MessageProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
// 发送消息到Direct交换机
public void sendDirectMessage(String message) {
String exchange = "direct_exchange";
String routingKey = "direct_routing";
// 参数:交换机、路由键、消息内容
rabbitTemplate.convertAndSend(exchange, routingKey, message);
System.out.println("发送Direct消息:" + message);
}
// 发送对象消息(自动序列化)
public void sendObjectMessage(User user) {
String exchange = "direct_exchange";
String routingKey = "direct_routing";
rabbitTemplate.convertAndSend(exchange, routingKey, user);
System.out.println("发送对象消息:" + user);
}
// 发送消息到Topic交换机(示例:路由键匹配 "user.#")
public void sendTopicMessage(String message) {
String exchange = "topic_exchange";
String routingKey = "user.update"; // 匹配 "user.#"
rabbitTemplate.convertAndSend(exchange, routingKey, message);
System.out.println("发送Topic消息:" + message);
}
}
// 消息对象示例
class User {
private Long id;
private String name;
// getter/setter、toString
}
3. 消息消费者(接收消息)
使用 @RabbitListener 注解监听队列:消息消费者
java
package com.example.service;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
@Service
public class MessageConsumer {
// 监听Direct队列
@RabbitListener(queues = "direct_queue") // 监听指定队列
public void receiveDirectMessage(String message) {
System.out.println("收到Direct消息:" + message);
// 处理消息逻辑...
}
// 监听对象消息(自动反序列化)
@RabbitListener(queues = "direct_queue")
public void receiveObjectMessage(User user) {
System.out.println("收到对象消息:" + user);
}
// 监听Topic队列(动态声明队列和绑定)
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "topic_queue", durable = "true"), // 声明队列
exchange = @Exchange(value = "topic_exchange", type = "topic"), // 声明交换机
key = "user.#" // 路由键规则(匹配 user.xxx)
))
public void receiveTopicMessage(String message) {
System.out.println("收到Topic消息:" + message);
}
}
四、消息确认与可靠性
1. 生产者确认机制
确保消息成功发送到交换机:生产者确认配置
java
package com.example.config;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
@Configuration
public class RabbitMQConfirmConfig {
@Resource
private RabbitTemplate rabbitTemplate;
@PostConstruct
public void init() {
// 1. 消息发送到交换机确认回调
rabbitTemplate.setConfirmCallback((CorrelationData correlationData, boolean ack, String cause) -> {
if (ack) {
System.out.println("消息成功发送到交换机:" + correlationData.getId());
} else {
System.err.println("消息发送到交换机失败:" + cause);
// 失败处理:重试、记录日志等
}
});
// 2. 消息无法路由到队列的返回回调
rabbitTemplate.setReturnsCallback(returned -> {
System.err.println("消息无法路由到队列:" +
"交换机=" + returned.getExchange() +
", 路由键=" + returned.getRoutingKey() +
", 消息=" + new String(returned.getMessage().getBody()));
});
}
}
发送消息时指定 CorrelationData(消息唯一标识):
java
// 发送消息时添加唯一标识
public void sendWithConfirm(String message) {
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
rabbitTemplate.convertAndSend("direct_exchange", "direct_routing", message, correlationData);
}
2. 消费者确认机制
确保消息被成功消费(默认自动确认,可配置手动确认):
yaml
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: manual # 手动确认模式
prefetch: 1 # 每次从队列获取1条消息,处理完再获取下一条
手动确认消息:
java
@RabbitListener(queues = "direct_queue")
public void receiveWithAck(String message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
try {
System.out.println("收到消息:" + message);
// 处理消息...
channel.basicAck(tag, false); // 手动确认消息已消费
} catch (Exception e) {
// 消息处理失败,拒绝并重回队列(或丢弃)
channel.basicNack(tag, false, true); // requeue=true 重回队列
}
}
五、消息持久化
确保 RabbitMQ 重启后消息不丢失:
- 队列持久化 :声明队列时设置
durable = true(如QueueBuilder.durable("queue_name"))。 - 交换机持久化 :声明交换机时设置
durable = true。 - 消息持久化 :发送消息时设置
deliveryMode = 2:
java
// 发送持久化消息
rabbitTemplate.convertAndSend(exchange, routingKey, message, msg -> {
msg.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
return msg;
});
六、高级特性
1. 死信队列
处理无法消费的消息(如过期、被拒绝、队列满):死信队列配置
java
package com.example.config;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DeadLetterConfig {
// 普通队列(设置死信参数)
@Bean
public Queue normalQueue() {
return QueueBuilder.durable("normal_queue")
.deadLetterExchange("dead_letter_exchange") // 死信交换机
.deadLetterRoutingKey("dead_routing") // 死信路由键
.ttl(10000) // 消息过期时间(10秒)
.maxLength(10) // 队列最大长度
.build();
}
// 死信交换机
@Bean
public DirectExchange deadLetterExchange() {
return ExchangeBuilder.directExchange("dead_letter_exchange").durable(true).build();
}
// 死信队列
@Bean
public Queue deadLetterQueue() {
return QueueBuilder.durable("dead_letter_queue").build();
}
// 绑定死信队列和交换机
@Bean
public Binding deadLetterBinding() {
return BindingBuilder.bind(deadLetterQueue())
.to(deadLetterExchange())
.with("dead_routing");
}
}
2. 延迟队列
实现消息延迟投递(基于死信队列的 TTL 特性):
java
// 发送延迟消息(10秒后处理)
public void sendDelayMessage(String message) {
rabbitTemplate.convertAndSend("normal_exchange", "normal_routing", message, msg -> {
msg.getMessageProperties().setExpiration("10000"); // 延迟10秒
return msg;
});
}
// 死信队列消费者(处理延迟消息)
@RabbitListener(queues = "dead_letter_queue")
public void handleDelayMessage(String message) {
System.out.println("处理延迟消息:" + message);
}
七、测试与监控
- 测试接口:创建控制器调用生产者发送消息:
java
@RestController
@RequestMapping("/mq")
public class MQController {
@Autowired
private MessageProducer producer;
@GetMapping("/send")
public String sendMessage(String msg) {
producer.sendDirectMessage(msg);
return "消息发送成功";
}
}
- 监控工具 :通过 RabbitMQ 管理界面(
http://localhost:15672)查看队列状态、消息数量等。
Spring Boot 整合 RabbitMQ 的核心优势:
- 自动配置 :引入 starter 后自动配置
ConnectionFactory、RabbitTemplate等组件。 - 注解驱动 :通过
@RabbitListener快速实现消息消费。 - 可靠性支持:提供生产者确认、消费者确认、消息持久化等机制。
- 灵活扩展:支持多种交换机类型、死信队列、延迟队列等高级特性。
实际开发中,需根据业务场景选择合适的交换机类型,配置消息可靠性策略,并合理设计队列结构,以实现服务解耦和系统稳定性提升。