Springboot4基础教程

Springboot4基础教程

_

必读部分:

分享内容: SpringBoot框架和相关生态技术

适应人群: 有java基础, 了解Spring,知道容器概念, 了解Web开发,Ajax最好。

课程难度: 基础部分:简单,其余部分:中级

Spring Boot 4 文档: https://docs.spring.io/spring-boot/4.0/index.html

🔰 第一部分:从0开始基础篇

1. 为什么需要SpringBoot

Spring Boot 诞生的核心目的是解决传统 Spring 开发中的痛点,让开发者能更专注于业务逻辑而非繁琐的配置和环境搭建。其必要性可以从以下几个核心痛点和解决方案来理解:

1.1. 传统 Spring 开发的痛点:配置繁琐到 “劝退”

传统 Spring 开发中,一个简单的 Web 应用可能需要:

  • 手动编写大量 XML 配置(如 applicationContext.xmlspring-mvc.xml),定义 Bean、配置扫描路径、数据源、事务管理器等;

  • 手动管理依赖版本(如 Spring 核心包、Spring MVC、数据库驱动、日志框架等),稍不注意就会出现版本冲突;

  • 手动部署到外部容器(如 Tomcat),需要打包成 War 包,再配置容器参数。

1.2. Spring Boot 的核心解决方案:“约定优于配置”

Spring Boot 通过 “自动配置”“ starters 依赖”“嵌入式容器” 三大核心特性,直接解决上述问题

  • 自动配置:基于 “约定优于配置” 原则,Spring Boot 会根据项目中引入的依赖,自动生成常用 Bean,无需手动编写 XML 或 Java 配置。

  • starters 依赖:starter 是一组预定义的依赖集合,自动管理版本兼容。 简化依赖管理。

  • 嵌入式容器:Spring Boot 内置了 Tomcat(默认)、Jetty容器,项目可直接打包成 Jar 包,通过 java -jar 命令启动,无需部署到外部容器,简化了开发、测试、部署流程。

1.3. 提升开发效率:从 “配置” 到 “业务” 的聚焦

  • 快速启动:一个基础 Web 应用,通过 Spring Initializr 生成骨架,只需写一个@RestControllor 就能跑通 HTTP 接口,全程无需手动配置;

  • 简化测试:内置@SpringBootTest 等注解,可一键启动上下文进行集成测试,无需额外配置测试环境;

  • 生态无缝衔接:与 Spring 生态(Spring Security、Spring Data、Spring Cloud 等)完美兼容,且对主流技术(Redis、RabbitMQ、MyBatis 等)提供了开箱即用的集成方案。

1.4. 微服务时代的刚需

在微服务架构中,需要快速开发、部署大量独立服务。Spring Boot 的 “轻量、快速、可独立运行” 特性,成为微服务的最佳载体:

Spring Boot 不是对 Spring 的替代,而是对 Spring 的 “增强” 和 “简化”—— 它通过自动化和约定,消除了传统 Spring 中重复、冗余的配置工作,让开发者能 “开箱即用” 地专注于业务逻辑,同时为微服务架构提供了高效的实现方式。

image-jGJs.png

2. Spring,SpringMVC,SpringBoot关系

三者有紧密的关系,Spring 生态体系中从基础到具体场景、再到简化开发的递进式技术,核心目标都是为了简化 Java 开发,但聚焦点和功能范围不同。三者的关系可以用 “基础框架→Web 层解决方案→开发效率增强工具” 来概括关系图

2.1. Spring:整个生态的 “地基”

Spring 是一个全方位的企业级应用开发框架,诞生于 2002 年,核心解决的是传统 Java 开发中 “对象管理复杂”“耦合度高” 的问题,其核心思想是 “IoC(控制反转)” 和 “AOP(面向切面编程)。Spring 是整个生态的基础,所有后续技术(Spring MVC、Spring Boot、Spring Cloud 等)都基于 Spring 的核心机制(IoC/AOP)构建。

2.2. Spring MVC:Spring 生态中的 “Web 层解决方案”

Spring MVC 是 Spring 框架的一个子模块,专注于解决 Web 开发(前后端交互)的问题,相当于 Spring 在 Web 领域的具体实现。

基于 “MVC(Model-View-Controller)” 设计模式,提供了一套完整的 Web 开发流程:

  • Controller:接收前端请求(如 HTTP 请求),处理业务逻辑(调用 Service);

  • Model:封装数据(如从数据库查询的结果),传递给前端;

  • View:展示数据(如 JSP、Thymeleaf 等模板);

  • 此外还包含请求映射、参数绑定、JSON 转换、拦截器等 Web 开发必需的功能。

2.3. Spring Boot:“简化 Spring 开发的加速器”

Spring Boot 是 2014 年推出的开发工具,核心目标是 “简化 Spring 应用的搭建和开发过程”,它并不替代 Spring 或 Spring MVC,而是基于两者之上,通过 “约定优于配置” 的思想减少冗余工作。

3. 搭建开发环境

开发环境: jdk25(open-jdk), Maven3.9以上 , IDEA2024以上

💠 JDK下载安装:

  1. 打开浏览器:https://adoptium.net/zh-CN/temurin/releases , 选择Windows(JDK选中),点 击“Temurin 25.0.1+8-LTS”下方的".ZIP"图标。

  2. 解压下载文件到非中文,无空格目录

  3. 设置环境变量JAVA_HOME 为 jdk解压目录\bin

  4. path变量添加 %JAVA_HOME%\bin

  5. 验证安装, cmd执行 java -version, java后面是空格

💠 Maven下载安装

  1. 打开浏览器:https://maven.apache.org/

  2. 解压下载文件到非中文,无空格目录

  3. 设置环境变量 MAVEN_HOME 为 maven解压后目录

  4. path变量添加 %MAVEN_HOME%\bin

  5. 验证安装 cmd 执行mvn -v , mvn后面是空格

💠 IDEA下载安装

打开浏览器:https://www.jetbrains.com/zh-cn/idea/download/?section=windows 或者https://www.jetbrains.com.cn/en-us/idea/download/?section=windows,下载windows版本的idea。 使用最新的就可以。 exe版本需要安装,zip版本解压后可以使用。

认识Open JDK 和 Oracle JDKOpenJDK是Java的官方开源实现,Oracle JDK是基于OpenJDK的商业发布版本。它们核心代码几乎一致,但在许可协议、发布周期、附加工具支持方面存在差异。

我的Oracle JDK 位于F:\develop\jdk-21.0.8\bin

我的Open-JDK输出:

这是两者最本质的区别:

OpenJDK:采用 GPLv2 + Classpath Exception 许可

  • 完全免费,可用于生产环境

  • 允许自由修改和分发

  • 无法律风险

Oracle JDK:采用 OTN(Oracle Technology Network)许可

  • 开发/测试环境免费

  • 生产环境需商业授权(按处理器或员工数收费)

  • 违反协议可能面临法律风险

4. 用好IDEA开发利器

4.1. 配置IDEA

包含jdk,maven,字符编码,风格,字体字号 , Spring Boot4 使用Junit6版本, idea2025才支持junit6.

4.2. 安装插件:

Show Comment: 增强文件树

Draw Graph: 方法调用图 和 Maven 依赖图

Spring Debugger: spring 调试器

https://plugins.jetbrains.com/plugin/25302-spring-debugger

5. 开发SpringBoot项目

5.1. 推荐方式 Spring Initializr

Spring Initializr 是 Spring 官方提供的项目初始化工具(脚手架),能快速生成可直接运行的 Spring Boot 项目骨架。可视化选择项目配置,包括编程语言、Spring Boot 版本、依赖组件等。自动生成项目结构、配置文件(如 pom.xml 或 build.gradle)

在线地址: https://start.spring.io/ (官方的),国内的https://start.springboot.io/(包含国内的开源项目)

打开浏览器:访问上述地址,设置项目基本信息,添加依赖。最后创建项目

5.2. IDEA创建项目

IDEA中创建Project左侧选中Spring Boot , 右侧就是Spring Initializr 界面, 设置项目基本信息,生成项目基本骨架。

5.3. 继承父项目(重点)

继承SpringBoot父项目方式,创建SpringBoot项目

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>4.0.0-RC2</version>
</parent>

优点:

自动管理依赖版本,减少版本冲突:spring-boot-starter-parent 内置了一套经过严格测试的依赖版本管理体系,定义了常用库的版本号。如需自行指定版本并覆盖Spring Boot的推荐设置,仍然可以这样做。

统一项目基础配置,简化开发

  • 父项目预设了一系列通用的项目配置,开发者无需重复定义,直接继承即可使用

  • 默认指定 Java 编译版本

  • 默认开启src/main/resourcessrc/test/resources目录的资源过滤

  • 内置spring-boot-maven-plugin等核心插件的版本和配置,开发者只需在plugins中声明插件,无需指定版本,确保插件与 Spring Boot 版本兼容

5.4. 无Spring Boot父项目方式

项目需要自定义父级配置,或者更倾向于显式声明所有Maven配置。 此时不适合SpringBoot父项目(maven单继承)

<dependencyManagement>
	<dependencies>
		<dependency>
			<!-- Import dependency management from Spring Boot -->
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-dependencies</artifactId>
			<version>4.0.0-RC2</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
	</dependencies>
</dependencyManagement>

这种方式使用了SpringBoot提供依赖版本管理体系。 需要额外知道Java 编译版本,无spring-boot-maven-plugin等核心插件的版本和配置。

5.5. 1分钟创建Web项目

IDEA创建SpringBoot Web项目,基于SpringMVC模块的Web应用。

<dependency>
     <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>

@RestController
public class HelloController {

    @GetMapping("/index")
    public String hello(){
        return "Hello Spring Boot 4.0版本";
    }
}

运行项目,打开浏览器,输入http://localhost:8080/index 访问此Web项目。

5.6. 推荐的项目结构

Spring Boot不需要任何特定的代码结构即可工作。 建议将主应用程序类(启动类)放置在根包中,高于其他类。@SpringBootApplication注解通常放置在主类上。 其他类放在子包中。

5.7. starter是什么(重点)

starter 的核心价值是 “简化依赖管理”“默认配置赋能”。

在 Spring Boot 中,starter( starters )是一种预定义的依赖描述符,它通过整合某类功能所需的所有相关依赖(包括 Spring 框架组件、第三方库等),并提供默认配置,实现了 “开箱即用” 的效果,让开发者无需手动梳理和引入零散依赖,就能快速集成特定功能。

依赖整合:每个 starter 本质上是一个 Maven/Gradle 模块,在其依赖描述文件(pom.xmlbuild.gradle)中,预先声明了实现某功能所需的所有依赖。比如spring-boot-starter-webmvc 整合了 Spring MVC、Tomcat 容器、Jackson(JSON 处理)等 Web 开发必备依赖。

打开maven仓库中 spring-boot-starter-webmvc-4.0.0-RC2.pom文件

<dependencies>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter</artifactId>

<version>4.0.0-RC2</version>

<scope>compile</scope>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-jackson</artifactId>

<version>4.0.0-RC2</version>

<scope>compile</scope>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-tomcat</artifactId>

<version>4.0.0-RC2</version>

<scope>compile</scope>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-http-converter</artifactId>

<version>4.0.0-RC2</version>

<scope>compile</scope>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-webmvc</artifactId>

<version>4.0.0-RC2</version>

<scope>compile</scope>

</dependency>

</dependencies>

默认自动配置:starter 通常会包含一个或多个 “自动配置类”类似:XXXAutoConfiguration.java。 例如 webmvc的自动配置类

public final class WebMvcAutoConfiguration {
    public static final String DEFAULT_PREFIX = "";
    public static final String DEFAULT_SUFFIX = "";
    private static final String SERVLET_LOCATION = "/";

    public WebMvcAutoConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
    @ConditionalOnBooleanProperty({"spring.mvc.hiddenmethod.filter.enabled"})
    OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
        return new OrderedHiddenHttpMethodFilter();
    }
    //.........................
}

starter命名:

1.由 Spring Boot 官方提供,命名格式为 spring-boot-starter-xxx

2.第三方 starters:由社区或企业开发,用于集成非官方组件,命名格式通常为 xxx-spring-boot-starter

理解starter:

Spring Boot Starter(译作"启动器")是 Spring Boot 最核心的设计之一,本质是一组预定义的 Maven/Gradle 依赖描述符 + 自动配置逻辑的集合,目的是解决“依赖管理混乱”、“版本控制”和“配置繁琐”的问题 —— 开发者只需引入一个 Starter 依赖,就能一键启用某类功能(如数据库、Web、缓存),无需手动导入零散依赖、编写大量配置。

简单来说:Starter 是"功能套餐",比如引入 spring-boot-starter-web,就自动包含了 Web 开发所需的 Tomcat、Spring MVC、JSON 解析等依赖,且自动配置好 DispatcherServlet、字符编码、视图解析器等核心组件。

6. 注册Bean的多种方式(重点)

Bean 是 Spring IoC 容器管理的对象,是应用功能的载体。Spring 通过控制反转接管 Bean 的创建和依赖管理,通过依赖注入实现 Bean 之间的协作,极大简化了对象管理的复杂度,使开发者能更聚焦于业务逻辑。理解 Bean 的概念和生命周期,是掌握 Spring 框架的基础。(Spring Boot底层就是Spring框架)

传统 Java 开发中,对象的创建(如new UserService())由开发者手动控制;而 Spring 中,Bean 的创建、初始化、依赖注入、生命周期管理等全由 容器(Spring框架)负责,开发者只需定义 Bean 的规则,无需关心具体创建过程(即 “控制反转”)。

Spring 容器会自动将 Bean 之间的依赖关系(如Service依赖Dao)通过构造函数、Setter 方法或字段注入的方式组装好,开发者无需手动编写依赖组装代码。

将代码需要的多种不同类型的java对象,注入到Spring容器, 才能组成完整,可用的Bean, 实现业务逻辑功能。

Spring Boot环境中,灵活注册Bean是构建松耦合应用的关键。这节梳理多种Bean注册方式及其典型场景:

  • 通过@Component注解实现零配置组件扫描;

  • 利用@Bean显式控制第三方库集成;

  • 借助@Import与ImportSelector实现模块化动态配置;

  • 运用自动配置机制简化Starter开发;

  • 采用FactoryBean定制复杂对象创建流程;

  • SpringBoot 4的BeanRegistrar接口动态注册Bean,

  • 以及通过编程式ApplicationContext注册满足运行时动态需求。

这些方式覆盖了从简单业务组件到高度定制化场景的全谱系需求,帮助开发者根据实际场景选择最优方案。

6.1. 使用@Component

在类定义上面声明@Component注解,创建Bean对象,还可以使用策略注解(@Service,@Controller,@Repository)。

@Componen通用注解,没有特定的业务含义,可标注在任何需要被 Spring 管理的类上(如工具类、配置辅助类等)

@Service,@Controller,@Repository是@Component“特殊化”。从@Component衍生而来。 他们具有“语义性”

它是所有 Bean 注解的 “父类”,@Repository@Controller也都基于它实现。

  • @Service:是专用注解,明确标识该类是业务逻辑层(Service 层)的组件(如处理核心业务逻辑的服务类),

  • @Repository:是专用注解,数据访问层。

  • @Controller:是专用注解,控制层。

应用场景:适用于常规业务组件,如Service层、Controller层、Repository层等。Spring Boot启动时,@ComponentScan会自动扫描这些注解,并将标记的类注册为Bean。

@Component
public class RedisManager {
    
    public void set(String key, String value) {
        // redis 操作
    }
}

@RestController
public class HelloController {

    @GetMapping("/index")
    public String hello(){
        return "Hello Spring Boot 4.0版本";
    }
}

@Service
public class UserServiceImpl {
    
    public void addUser(User user) {
        // 添加用户业务逻辑
    }
}

6.2. 使用@Bean

在配置类(@Configuration标记的类)中,通过@Bean标注方法,返回对象实例。方法名默认为Bean的唯一标识(name属性指定bean名称)。

应用场景:适用于引入第三方库的类(如数据库连接池、工具类)或需要自定义初始化逻辑的Bean。

@Configuration
public class DataSourceConfig {

    @Bean
    public Connection dataSource(){
        DataSource ds = new PooledDataSource();
        try {
            Connection connection = ds.getConnection("zhangsan", "suier23");
            return connection;
        } catch (SQLException e) {
           e.printStackTrace();
        }
        return null;

    }
}

@Configuration(proxyBeanMethods = false) : 当设置为 false 时,Spring 会 不生成配置类的 CGLIB 代理,配置类会以「普通 Java 类」的形式存在。

优点:

  • 无需生成 CGLIB 代理对象,减少内存占用;

  • 容器启动时无需处理代理逻辑,加快启动速度(尤其适合配置类多、@Bean 方法多的场景)。

推荐使用 proxyBeanMethods = false 的场景:

  • 配置类中的 @Bean 方法 互不依赖(即没有一个 @Bean 方法调用另一个 @Bean 方法);

  • 追求容器 快速启动 和 低内存占用(如微服务、轻量级应用);

6.3. 使用@Import注解

在配置类中使用@Import导入其他配置类或普通类,使其成为Bean。

应用场景:

模块化配置:将多个配置类分散管理,根据需要组合它们

快速注册单个Bean:等同于在类上标注@Component

@Configuration
@Import({Student.class} )
public class ApplicationConfig {
    // 其他声明bean的代码
}

6.4. 通过BeanRegistrar接口

从SpringBoot4 开始,Spring提供了编程式注册Bean,通过BeanRegistrar接口可以以灵活高效的方式编程式注册Bean。这一新特性为开发者提供了更强大的工具来动态管理应用上下文中的Bean定义。

应用场景: 需要编程,动态根据多种条件创建Bean。

两步实现:

  1. 编程注册Bean

  2. 配置类@Import

代码部分:

需求:注册线上,线下的商品折扣类

public class OffLineDiscountService {

    //八折
    private int discount = 8;

    public int compute(Integer price){
        return discount * price / 10;
    }
}

//线上商品折扣
public class OnLineDiscountService {

    //6折
    private int discount = 6;

    public int compute(Integer price){
        return discount * price / 10;
    }
}

public class ManagerBeanRegistrar implements BeanRegistrar {
    @Override
    public void register(BeanRegistry registry, Environment env) {
        System.out.println("==============Registered Bean===============");
        registry.registerBean("offLineDiscountService", OffLineDiscountService.class);

        if( env.matchesProfiles("online")){
            System.out.println("=============online=Registered Bean===============");
            registry.registerBean("onLineDiscountService", OnLineDiscountService.class , spe->{
                spe.description("电商渠道使用的折扣对象")
                        .lazyInit()
                        .order(-1);
            });
        }
    }
}

@Configuration
@Import(ManagerBeanRegistrar.class)
public class MyConfiguration {
}

spring:
  profiles:
    active: online

@SpringBootTest
class Boot06RegistBeanApplicationTests {


    @Resource
    private ApplicationContext ac;

    @Test
    void testRegistBean() {
        System.out.println("myUserService = " + myUserService);

        OnLineDiscountService online = ac.getBean(OnLineDiscountService.class);
        System.out.println("online = " + online);

        OffLineDiscountService offLine = ac.getBean(OffLineDiscountService.class);
        System.out.println("offLine = " + offLine);

    }
}

7. 外部文件

一个应用程序包含 代码 和 数据, 配置外部化是数据的一部分,影响代码的运行方式,运行结果。Spring Boot 允许你将配置外部化,配置文件用于配置Spring Boot程序。包括但不限于数据库连接、服务器端口、应用上下文路径、日志配置等。文件名称为application。有两种格式的配置文件:.properties文件 和 .yml(yaml)文件。 其他数据源还包含:环境变量和命令行参数。

配置外部化作用:

  • 环境隔离:可以为不同环境(开发、测试、生产)提供不同的配置文件,如 application-dev.properties、application-prod.properties

  • 代码和数据分离:将配置从代码中分离出来,便于管理和修改,无需重新编译代码

application.properties(yml)配置文件:

  • Spring Boot 原有配置项

  • 自定义配置项

7.1. property属性文件

指application.properties文件, 文件内容格式 key=value 一行一个独立配置。

spring.application.name=MyFirstWebApp
server.port=8888
server.servlet.context-path=/myweb
#我是注释

这种文件配置格式清晰,容易理解。 缺点:内容有重复,占用空间大。

7.2. yaml文件

yaml(yml)文件: 是一种简洁易读的数据序列化格式,在Spring Boot中广泛用于配置文件。

主要采用“退一格、换行、冒号、空格”等格式进行配置。

application.yml

spring:
  application:
     name: MyFirstWebApp
server:
  port: 8888
  servlet:
    context-path: /myweb
#我是注释    

yaml 基本语法规则

  • 缩进表示层级关系: 使用两个空格表示一级

  • 键值对表示法: key:空格value

  • 数据结构表示: 字面量, 数组,map结构

7.3. 读取配置文件数据

7.3.1. @Value:读取单个值

Environment : 环境对象读取

@ConfigurationProperties: 读取结构化数据

编码部分:

创建Spring Boot应用, 加入Web依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>

修改application.yml

spring:
  application:
     name: MyFirstWebApp
server:
  port: 8888
  servlet:
    context-path: /myweb
#我是注释
app.info: 掌握外部配置

创建控制器读取数:

@RestController
public class HelloController {

    @Value("${server.servlet.context-path}")
    private String path;

    @Value("${server.port:9090}")
    private Integer port;

    @Value("${app.info}")
    private String appInfo;

    @GetMapping("/index")
    public String hello(@Value("${spring.application.name}") String appName){
        return "Hello Spring Boot 4.0版本: 应用名称:" + appName + ", info: " + appInfo;
    }
}

7.3.2. Environment

Environment 是一个核心接口,它提供了对应用程序运行环境的抽象,主要用于管理配置属性和 profiles(环境标识)。它整合了多种配置来源(如 properties 文件、环境变量、命令行参数等),并提供统一的接口供开发者访问这些配置信息,是 Spring 处理配置的核心组件。

即所有的代码之外的配置数据都集中到Environment接口。 由Environment接口提供对多种配置来源的访问途径。

核心方法

  • getProperty(String key) - 获取指定键的属性值

  • containsProperty(String key) - 检查属性是否存在

  • getActiveProfiles() - 获取当前激活的profiles

    @Resource
    private Environment env;
    @GetMapping("/env")
    public String env(){
        String appName = env.getProperty("spring.application.name");
        String appPort = env.getProperty("server.port");
        String javaPath = env.getProperty("JAVA_HOME");
        return "Hello Spring Boot 4.0版本: 应用名称:"
                + appName + ", 运行端口号: " + appPort
                + ", JAVA_HOME: " + javaPath;
    }

7.3.3. 读取结构化数据

@ConfigurationProperties 是 Spring Boot 提供的一个核心注解,用于将外部配置属性(如 application.propertiesapplication.yml、环境变量等)绑定到 Java 实体类中,实现配置的结构化管理。它简化了配置参数的读取和使用,尤其适合处理大量相关配置项的场景。

@ConfigurationProperties按照属性名匹配规则自动映射到 Java 类的字段上,无需手动调用 @Value 逐个注入。 是Spring Boot 中管理外部配置的最佳实践之一,尤其适合处理复杂配置,提升代码的可读性和可维护性

application.yml 定义一个Java对象的属性状态

zhang:
  name: 张三
  age: 18

Java对象定义

@ConfigurationProperties(prefix = "zhang")
public class Student {
    private String name;
    private int age;
    // set | get | toString

这个类应该有无参数构造方法, set 和 get方法。

@ConfigurationProperties(prefix = "zhang") : prefix 和 application文件 zhang 开头的key匹配。

配置类

@EnableConfigurationProperties({Student.class})
@SpringBootApplication
public class Course01QuickApplication {

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

}

@EnableConfigurationProperties 启用结构化配置能力。

测试对象状态

@Resource
private Student student;

@GetMapping("/student")
public String configStudent(){
    return student.toString();
}

8. 多环境配置

Spring Boot 中,多环境配置是应对应用在不同阶段(如开发、测试、生产)运行需求的核心机制。其核心作用是隔离不同环境的配置差异,确保应用在不同场景下能使用对应的参数正常运行,同时简化环境切换和配置管理。多环境允许应用程序在不同环境中使用不同的配置

8.1. 应用多环境Profile

环境:影响应用运行的参数, 比如数据库的ip, 数据库名称,端口号等等。 开发时使用自己的数据库,redis。 测试项目使用公司测试数据库,redis等。 自己数据库ip,用户名和公司数据库的ip,用户名等不同。 同一个项目需要修改数据库和redis的ip,端口能才能适应代码。 Spring Boot支持多环境配置。

Profiles 用于区分不同的环境(如开发、测试、生产),Environment 查询当前激活的 profiles 的能力:

通过 spring.profiles.active 属性激活特定的环境配置。

首先定义环境需要的配置文件,格式 application-环境名称.yml(properties) , 一种环境对应一个配置文件。application.yml成为默认文件

示例:

  • application-dev.properties - 开发环境

  • application-prod.properties - 生产环境

  • application-test.properties - 测试环境

环境是自定义的, 环境名称dev, prod, test都是。 多个环境文件配置包含的key都是一样,区别是值不同。 这个值和环境对应。

激活环境

# 在 application.yml 中指定激活的 profile
spring.profiles.active=dev

# 或通过命令行参数
--spring.profiles.active=prod

# 或通过环境变量
SPRING_PROFILES_ACTIVE=test

编码部分

创建两个环境文件, application-dev.yml , application-test.yml ,

zhang:
  name: 张三-测试
  age: 28

zhang:
  name: 张三-开发
  age: 30

spring:
  application:
     name: MyFirstWebApp
server:
  port: 8808
  servlet:
    context-path: /myweb
#我是注释
app.info: 掌握外部配置

文件结构如图:

编码

@Resource
private Student student;

@GetMapping("/student")
public String configStudent(){
    return student.toString();
}

开始测试:

激活环境Profiles

spring:
  application:
     name: MyFirstWebApp
  profiles:
    active: dev

运行代码, 在激活spring.profiles.active 为 test

8.2. @Pofile

@Profile 配合@Component , @Bean 根据环境注入不同的Bean到容器。 @Profile("环境名称")可声明在类定义或方法上面。

编码:创建两个类

@Component
@Profile("dev")
public class DevService {
}


@Component
@Profile("!prod")
public class ProdService {
    // 除了 prod 环境都生效
}

使用者类

@Autowired(required = false)
private DevService devService;

@Autowired
private ProdService prodService;

@GetMapping("/profile")
public String profile(){
    return devService + " | " + prodService;
}

激活环境: 先激活dev , 在测试test。

9. 自定义初始化逻辑

如果 SpringApplication 启动后需要运行特定代码,可以实现 ApplicationRunner 或 CommandLineRunner 接口。 这两个接口的工作原理相同,提供一个方法,调用SpringApplication.run(…)完成前执行。非常适合那些应该在应用启动后、开始接受流量之前运行的任务。是 Spring Boot 生命周期中的重要扩展点,适合执行启动后的必要初始化工作

实际应用场景

  • 数据库初始化

  • 缓存预热

  • 启动后检查任务

  • 定时任务注册

  • 日志记录初始化

实现步骤:

  1. 定义类实现 ApplicationRunner 或 CommandLineRunner 接口, 这个类需要注入到容器

  2. 实现run() 方法

@Component
public class InitCache implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        String dbName = args[0];
        System.out.println("从数据库["+dbName+"]读取 字典数据 放入内存");
    }
}

IDEA传递命令行参数 Edit Configurations --->Modify Options --->Program arguments 在文本框输入 db1

也可以将项目打包 java -jar xxx.jar db1

ApplicationRunner 使用方式同CommandLineRunner 接口, 区别是run()参数。

执行顺序

如果定义了多个必须按特定顺序调用的 CommandLineRunner 或 ApplicationRunner 豆子,你还可以额外实现 Ordered 接口或使用 Order 注释

在原有代码,在实现ApplicationRunner, 并加入@Order编排顺序

@Component
@Order(2)
public class InitRedis implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("初始化redis数据" + args.getNonOptionArgs().get(0));
    }
}

InitCache.java 加入@Order(1)

🔰 第二部分:Web模块

SpringBoot开发Web应用涉及Spring MVC模块, 包含两个分类 Servlet Web Applications 和 Reactive Web Applications 。

Servlet Web Applications: 基于Servlet规范的Web应用,Servlet作为此种应用的基础。 Servlet接收http请求,在通过Spring MVC的其他组件进行后续的处理。 本节内容关于Servlet Web Applications。

Spring MVC 的设计初衷和主要应用场景是 HTTP 协议的 Web 开发,其核心机制深度依赖 HTTP 相关的 Servlet API。 SpringMVC接收http协议请求。如果需要处理非 HTTP 协议,应选择更适合的框架(如 WebSocket 用 Spring WebSocket,TCP 用 Netty + Spring 整合等)

Spring MVC模块优点:

  1. 基于MVC架构:基于MVC架构,功能分工明确。解耦合,

  2. 容易理解,上手快;使用简单。

  3. 作为Spring框架一部分,能够使用Spring的IoC和Aop。方便整合MyBatis,Hiberate,JPA等其他框架。

  4. SpringMVC强化注解的使用,在控制器,Service,Dao都可以使用注解。方便灵活。

使用@Controller创建处理器对象,@Service创建业务对象,@Autowired或者@Resource在控制器类中注入Service, Service类中注入Dao。

1. 快速上手SpringMVC

创建Web应用, IDEA Spring Initializr 创建项目, starter选择Spring Web

创建好后的项目代码结构

重要的starter

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>

创建一个控制器Controller对象

@RestController
public class WebController {

    @RequestMapping("/index")
    public String index(){
        return "SpringMVC模块创建http协议的Web应用";
    }
}

@RequestMapping: 请求映射 ,某个用户请求(uri) 映射到 控制器中的某个方法。

"/index" : 请求的uri路径, 比如浏览器 http://localhost:8080/index 这个请求有 index()方法 响应处理。

@RestControler: 标记为Spring MVC的控制器组件,使其能够处理Web请求。

URI (Uniform Resource Identifier):

是统一资源标识符,用来标识某一互联网资源

是一个更广泛的概念,包括URL。代码中,@RequestMapping("/index") 中的 /index 就是一个URI,它标识了该资源的位置

URL (Uniform Resource Locator):

是统一资源定位符,是URI的一个子集。包含协议、主机、端口、路径等完整信息

例如:http://localhost:8080/index 就是一个完整的URL。

URI是资源的标识符,而URL是资源的具体定位地址

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#jGwqr

2. SpringMVC请求执行流程

DispatcherServlet : SpringMVC重要的组件,他是一个Servlet(对应上图红色的部分)。

WebController对象: springmvc管理的控制器对象。 自定义处理请求逻辑。

DispatcherServlet接收到请求后,根据URL找到对应的控制器对象,调用控制器中匹配的方法处理请求,返回处理结果给客户端。

3. SpringBoot自动配置初探

自动配置DispatcherServlet, 他是Servlet对象

protected static class DispatcherServletConfiguration {
    protected DispatcherServletConfiguration() {
    }

    @Bean(
        name = {"dispatcherServlet"}
    )
    DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
        DispatcherServlet dispatcherServlet = new DispatcherServlet();
        dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
        dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
        dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
        dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
        return dispatcherServlet;
    }
    //...
}

Spring Boot 默认禁用后缀模式匹配,这意味着类似 "GET /projects/spring-boot.json" 的请求不会匹配到 @GetMapping("/projects/spring-boot") 的映射。这被视为 Spring MVC 应用的最佳实践。此功能在过去主要用于那些未发送正确 "Accept" 请求头的 HTTP 客户端;我们需要确保向客户端发送正确的内容类型。如今,内容协商机制已更加可靠。

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#EQAhx

4. 控制器对象

4.1. 什么是控制器(Controller)

在Spring MVC中,控制器对象是处理器(Handler),控制器也叫做处理器Handler。

控制器对象的定义

  1. @Controller 或 @RestController 注解标记的类实例

  2. 负责处理用户发送的HTTP请求

  3. 作为Spring容器管理的Bean存在

控制器是普通的Java类, 职责处理http请求。 作为Bean存在Spring容器中。 SpringMVC根据uri查找容器中的控制器对象。

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#esGEf

4.2. 声明控制器的方式

  1. 类定义上面@Controller

  2. 类定义上面声明@RestController

控制器对象是单列的, 整个项目生命周期中只有一个对象。singleton作用域。

SpringMVC处理请求是多线程的, 单个Controller 实例同时处理多个并发请求,每个请求在独立的线程中执行。

@Controller:

主要功能

声明控制器handler对象, 类中方法返回值表示视图页面(一个html页面,一个thymeleaf页面)

@RestController:

主要功能

  • 组合注解: @RestController 是一个组合注解,相当于 @Controller 和 @ResponseBody 的组合

  • 简化开发: 用于创建RESTful风格的Web服务,避免在每个方法上都添加 @ResponseBody 注解

具体作用

  • 标记控制器: 将 WebController 类标记为Spring MVC的控制器组件,使其能够处理Web请求

  • 自动序列化: 使该类中所有方法的返回值都会被自动序列化为JSON或XML格式,并直接写入HTTP响应体中

  • REST API开发: 特别适用于构建REST API,返回数据而不是视图页面

@Controller例子

首先: /resources/static/ 创建 users.html

<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>显示用户列表</title>
  </head>
  <body>
    <h1>显示用户列表的页面</h1>
    <div>
      第一个用户... <br/>
      第二个用户... <br/>
    </div>
  </body>
</html>

@Controller
public class UserController {
    @RequestMapping("/list")
    public String userList(){
        return "/users.html";
    }
}

SpringMVC控制器建议:

在 Spring MVC 中创建控制器(Controller)需遵循职责单一、约定优先、松耦合性三大核心原则

  1. 职责单一原则:

  • 控制器的唯一职责是接收请求、参数绑定、调用业务逻辑、返回响应,不包含复杂业务逻辑(如事务处理、数据持久化)

  • 一个控制器聚焦一类业务场景(如 UserController 仅处理用户相关请求,OrderController 仅处理订单相关)

  • 避免使用 @Scope("prototype") 破坏单例,若需存储线程相关数据,使用 ThreadLocal 或请求域(RequestAttributes)

  1. 约定优于配置

  • 控制器类名统一以 XxxController 命名(如 UserController),便于识别和扫描。

  • 响应格式统一(如 REST 接口统一返回 Result<T> 包装类,包含 code、msg、data 字段)

  1. 松耦合原则

  • 依赖注入(DI):通过 @Resources 或构造器注入 Service 接口(而非实现类),便于后续替换实现或单元测试

  • 不依赖 Servlet 原生 API(如 HttpServletRequest、HttpServletResponse),优先使用 Spring 提供的参数绑定

(如 @RequestParam、@PathVariable)

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#fpKBA

5. Controller类中方法签名

Controller类中方法作用是处理http请求。 除了请求处理方法,当然能够定义其他方法。

请求处理方法的定义:

  1. 方法权限修饰符为 public

  2. @RequestMapping声明请求映射(派生注解@GetMapping/@PostMapping/@PutMapping/@DeleteMapping)

  3. 方法形参表示来着请求方的参数(提交的请求数据)

  4. 方法的返回值为响应数据(String,ModelAndView,自定义Result或其他类)

5.1. @RequestMapping和派生注解

@RequestMapping 是 Spring MVC 中核心的请求映射注解,用于将 HTTP 请求与控制器方法绑定。它既可以作用于类级别(指定基础路径),也可以作用于方法级别(指定具体请求路径),支持灵活配置请求方式、参数、请求头、媒体类型等条件。

5.1.1. 属性value:

请求的uri, 和请求的url地址匹配。

例如:代码@RequestMapping("/list") , 访问url http://localhost:8080/list

类级别指定基础路径,方法级别指定子路径,最终请求路径为「类路径 + 方法路径」。优点:统一管理接口前缀,便于维护(如权限控制、版本控制)

@Controller
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/list")
    public String userList(){
        return "/users.html";
    }
}

浏览器访问地址: http://localhost:8080/user/list

uri: 通配符

https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-requestmapping.html#mvc-ann-requestmapping-uri-templates

5.1.2. 属性method

method: 表示客户端请求方式, 值为RequestMethod类的enum。 前端使用GET请求方式, method = RequestMethod.GET

@Controller
@RequestMapping("/user")
public class UserController {
    @RequestMapping(value = "/list",method = RequestMethod.GET)
    public String userList(){
        return "/users.html";
    }
}

派生注解:

GetMapping : GET请求方式

PostMapping : POST请求方式

PutMapping : PUT请求方式

DeleteMapping : DELETE请求方式

5.1.3. 其他属性

params, headers, consumes , produces 这些属性用的较少。 在其他章节讲。

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#drKzJ

5.2. Handler Method形参

控制器方法的形参表示与请求request相关的, 请求中的参数, header等都可以通过控制器方法的形参获取。 不需要原生的HttpServletRequest。

参数分成两类:

  • 框架预定义的参数(26种)

  • 其他的任意参数(主要指简单类型)

框架预定义的参数常用的

参数

说明

作用

jakarta.servlet.ServletRequest, jakarta.servlet.ServletResponse

请求和响应,可以是ServletRequest, HttpServletRequest, MultipartRequest、MultipartHttpServletRequest

原生的请求对象和SpringMVC中处理文件上传的请求对象

jakarta.servlet.http.HttpSession

会话对象

使用会话技术的有关对象

java.io.InputStream, java.io.Reader

用于访问 Servlet API 所提供的原始请求体。

通过IO API读取request body内容。

java.io.OutputStream, java.io.Writer

用于访问 Servlet API 所提供的原始应答体。

通过IO API访问response body内容。

@PathVariable

访问路径变量

提供url中的参数

@RequestParam

用于访问 Servlet 请求参数,包括Multipart文件参数。参数值将转换为声明的方法参数类型,简单类型参数可选

接收request中的单一参数。 一次接收一个参数

@RequestHeader

接收请求header中的值,转为形参

接收请求header中的数据

@RequestBody

接收请求体(request body)

主要是接收json/xml数据使用。

@RequestPart

访问上传文件

接收上传文件

....

5.2.1. 简单参数

简单参数诸如int ,long, float ,double ,String等等。

作用:接收客户端提交过来的多个参数, 控制器方法形参和请求中参数名称相同。

适用:参数较少,5个以下

优点:简单,直观。

编码:

前端html页面

<form action="/user/add" method="post">
  用户姓名:<input type="text" name="name">
  用户年龄:<input type="text" name="age">
  <input type="submit" value="提交">
</form>

控制器:

@RestController
@RequestMapping("/user")
public class SysUserController {
    @RequestMapping(value = "/add",method = RequestMethod.POST)
    public String userList(String name,Integer age){
        String msg="注册的用户是:" + name + ",年龄是:" + age;
        return msg;
    }
}

深入理解:

接收参数和请求方式无关, 客户端使用get,post 都能使用简单参数。 使用http协议沟通两个应用。 http协议中规定了 get,post等请求方式传递数据的格式。 上面的例子 <form>默认存在请求参数编码方式的语句。 enctype="application/x-www-form-urlencoded"

完整如下: <form action="/user/add" method="post" enctype="application/x-www-form-urlencoded">

enctype 属性用于指定表单数据提交时的编码类型。enctype="application/x-www-form-urlencoded" 是默认的表单编码类型。

所有字符在发送前都会被编码(空格转换为 "+" 符号,特殊字符转换为 ASCII HEX 值)适用于普通的文本数据提交。 提交参数格式 name=value&name=value

再次测试: 将用户名称填入 li空格空格空格si , 查看提交的参数格式: name=li+++si&age=20; 客户端提交的参数格式是 name=value&name=value 都能使用简单类型接收

5.2.2. @RequestParam

@RequestParam : 用于指示方法参数应绑定到Web请求参数。 SpringMVC中请求参数包含 查询参数(get请求?后面的值),form表单参数, 以及上传的文件。 @RequestParam将这些类型的参数绑定到控制器方法的形参(对应简单类型可以不使用此注解)

作用:接收客户端提交过来的多个参数

适用:参数名与形参名不同, 以及上传文件时使用

优点:简单,可读性强

编码:

<form action="/user/edit" method="post" enctype="application/x-www-form-urlencoded">
  用户姓名:<input type="text" name="username">
  用户年龄:<input type="text" name="userage">
  <input type="submit" value="提交">
</form>

@PostMapping(value = "/edit")
public String userEdit(@RequestParam("username") String name,
                       @RequestParam("userage") Integer age){
    String msg="编辑的用户是:" + name + ",年龄是:" + age;
    return msg;
}

@RequestParam 属性:

required : 默认true, 请求中必须包含此参数

defaultValue: 请求中此参数默认值

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#yVrwc

5.2.3. Object参数

形参使用自定义对象是常用方式,一个java对象接收多个请求参数。 客户端发送参数格式 name=value&name=value , 形参是java对象即可。

Object要求:

  1. java对象存在无参数构造方法

  2. 属性名称与参数名称相同

  3. 有set/get方法

场景:

请求参数多余5个的, 使用对象方便。

编码:

<form action="/user/query" method="get" enctype="application/x-www-form-urlencoded">
  用户姓名:<input type="text" name="userName">
  用户年龄:<input type="text" name="userAge">
  <input type="submit" value="提交">
</form>

@GetMapping("/query")
public String userQuery(UserQuery userQuery){
    String msg="查询条件是:" + userQuery.getUserName()
             + ",年龄是:" + userQuery.getUserAge();
    return msg;
}

5.2.4. 消费json

前面的例子参数 格式为 name=value&name=value , 除此之外常用json作为应用之间的数据交换格式。

SpringMVC对json格式参数提供了@RequestBody注解。

要求:

  1. 客户端需要指定 'Content-Type': 'application/json'

  2. 服务器控制器方法使用@RequestBody 声明形参,形参常用对象类型。

  • 对象有无参数构造方法 ,

  • 属性名称与json key相同,

  • 提供属性 set / get方法

  1. 需要有json解析库依赖(比如Jackson)

  2. GET方式不能使用, POST,PUT 有请求体的方式可以使用

html form 不能提交json, 需要ajax方式提交,这里我们使用fetch库

<button onclick="fetchJson()">点击获取用户列表</button>

<script>

  function fetchJson() {
    fetch('http://localhost:8080/user/json', {
      body: JSON.stringify({
        userName: '张三',
        userAge: 18
      }),
      headers: {
        //告诉服务器本次提交json格式数据
        'Content-Type': 'application/json'
      },
      method: 'post',
    }).then(function (response) {
      //return response.json();
      console.log(response.body)
    }).then(function (json) {
      console.log(json);
    })
  }

</script>

@PostMapping("/json")
public String receiveJson(@RequestBody UserQuery userQuery){
    return "接收到的JSON数据是:" + userQuery.getUserName()
    + ",年龄是:" + userQuery.getUserAge();
}

原理:

@RequestBody 是 Spring MVC(含 Spring Boot)中用于 接收 HTTP 请求体数据并绑定到方法参数 的核心注解,其核心原理是:拦截请求体字节流,根据请求头 Content-Type 选择对应的「消息转换器」,将字节流解析为目标 Java 对象(如 POJO、Map、String 等)

简单说:@RequestBody 的作用是「自动完成请求体 → Java 对象的解析」。

涉及客户端的Content-Type + Spring 内置的消息转换器。

  1. SpringMVC 扫描到接口方法参数上有 @RequestBody 注解时,会标记需要从请求体中读取数据并解析

  2. SpringMVC 的 DispatcherServlet(前端控制器)接收到请求后,会通过 HttpServletRequest 对象读取 请求体的原始字节流

  3. 根据 Content-Type 匹配消息转换器,Spring 内置了多个 HttpMessageConverter(消息转换器),每个转换器都对应特定的 Content-Type 和目标数据类型(如 JSON、XML、表单键值对)

消息转换器

支持的 Content-Type

能解析的目标类型

典型场景

MappingJackson2HttpMessageConverter

application/json

POJO 类、Map、List 等

前后端 JSON 交互(最常用)

FormHttpMessageConverter

application/x-www-form-urlencoded

MultiValueMap<String, String>

表单键值对格式的请求体(非默认表单提交)

ByteArrayHttpMessageConverter

任意 Content-Type

byte [](字节数组)

接收二进制数据(如文件流)

StringHttpMessageConverter

text/plain

String(字符串)

接收纯文本请求体

  1. 对于 JSON 场景(MappingJackson2HttpMessageConverter):底层使用 Jackson 库(ObjectMapper),将请求体的 JSON 字符串字节流,反序列化为目标 POJO 对象。

说明:

  1. 请求参数和Object属性名称不同, 需要给java Object属性增加 @JsonProperty("user_name") // 映射 JSON 字段名

  2. @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") // 日期格式

  3. 默认情况下,@RequestBody 要求请求体不能为空(否则抛出异常),若需允许空请求体,可添加 required = false:

@RequestBody(required = false)

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#kvWuu

5.3. Handler Method返回值

控制器方法的返回值表示对请求的应答结果。 可以是视图名称, 文本数据, json字符串等等。

其返回值常用的有四种类型:

  1. 第一种:ModelAndView

  2. 第二种:String

  3. 第三种:无返回值void

  4. 第四种:返回对象类型(包含自定义的)

根据不同的情况,使用不同的返回值。

Handler Method返回值的使用需要控制器注解的配合

使用@Controller声明控制器, 返回值String类型和ModelAndView表示视图(一个html, 一个模版引擎文件)

使用@RestController 返回值String, Object类型的都是数据。此时和视图无关。

主要讲@RestController的情况。

5.3.1. String表示文本应答结果

上一个例子的接收json

@PostMapping("/json")
public String receiveJson(@RequestBody UserQuery userQuery){
    return "接收到的JSON数据是:" + userQuery.getUserName()
    + ",年龄是:" + userQuery.getUserAge();
}

5.3.2. 返回值是void

控制器方法返回是void, 通过返回值不能表示数据,视图。 使用场景ajax请求,HttpServletResponse输出响应数据

@PostMapping("/json")
public void receiveJson(@RequestBody UserQuery userQuery, HttpServletResponse response) throws IOException {
    String msg = "接收到的JSON数据是:" + userQuery.getUserName()
    + ",年龄是:" + userQuery.getUserAge();

    response.setContentType("text/html;charset=utf-8");
    PrintWriter writer = response.getWriter();
    writer.println(msg);
    writer.flush();
    writer.close();

}

5.3.3. 返回值类型Object

控制器方法也可以返回Object对象。这个Object可以是Integer,String,自定义对象,Map,List等。但返回的对象不是作为逻辑视图出现的,而是作为直接作为数据出现的。

返回对象,需要使用@ResponseBody注解,将转换后的JSON数据放入到响应体中。另一个使用@RestController注解。 它是有@Controller与@ResponseBody的组合。 @RestController用来声明控制器对象, 同时表示当前控制器类中的所有方法默认都加入@ResponseBody注解。 每个方法的返回值都表示数据,非视图页面。

默认是返回值对象转为json格式(如果有处理json的库,比如Jackson)

编码

public class UserVo {
    private String name;
    private String sex;
    private String birthday;
    // set | get方法
}

@GetMapping("/detail")
public UserVo queryUser(String id){
    UserVo userVo = new UserVo();
    userVo.setName("张三");
    userVo.setSex("男");
    userVo.setBirthday("1990-01-01");
    return userVo;
}

{"birthday":"1990-01-01","name":"张三","sex":"男"}

5.3.4. 统一应答结果

标准化是项目开发中重要内容,控制器返回给客户端的响应数据,格式必须统一。自定义结果类(如 Result),实现接口响应的标准化(状态码 + 提示信息 + 业务数据)。

统一应答结果通常包含三个内容:

  • 自定义状态码

  • 提示信息

  • 业务数据

自定义类,包含上述三个属性,作为控制器方法返回值, 这样所有的api返回结果都是统一格式。 客户端可以标准化处理。

public class Result<T> {
    //自定义响应code
    private String code;
    //自定义响应msg
    private String msg;
    //自定义响应业务数据
    private T data;
    public Result() {
    }
    public Result(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }
    public Result(String code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }
    public static Result<String> success() {
        return new Result<>("200", "操作成功");
    }

    public static <T> Result<T> success(T data) {
        return new Result<>("200", "操作成功",data);
    }

    public static <T> Result<T> fail(String code, String msg) {
        return new Result<>(code, msg );
    }

    public String getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }

    public T getData() {
        return data;
    }
}

@GetMapping("/getUser")
public Result<UserVo> getUser(String id){
    UserVo userVo = new UserVo();
    userVo.setName("张三");
    userVo.setSex("男");
    userVo.setBirthday("1990-01-01");
    return Result.success(userVo);
}

请求成功,http状态码是200

{"code":"200","data":{"birthday":"1990-01-01","name":"张三","sex":"男"},"msg":"操作成功"}

http状态成功是200, 如果服务器抛出异常。

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#OYerv

5.3.5. ResponseEntity

ResponseEntity 是 Spring MVC中用于 灵活控制 HTTP 响应 的核心类,作为控制器方法返回值使用 —— 它能统一封装响应状态码响应头响应体,替代传统的 @ResponseBody 或 ModelAndView,尤其适合需要自定义响应结果的场景(如接口返回 JSON 时指定状态码、添加响应头)。

ResponseEntity 是泛型类(ResponseEntity<T>),T 为响应体数据类型,使用时通过 静态构造方法 快速创建,核心语法:

方法

作用

示例

ok(T body)

返回 200 OK + 响应体

ResponseEntity.ok(user)

created(URI location)

返回 201 Created(资源创建成功)+ 位置

ResponseEntity.created(uri).build()

badRequest()

返回 400 Bad Request(参数错误)

ResponseEntity.badRequest().body("参数错误")

notFound()

返回 404 Not Found(资源不存在)

ResponseEntity.notFound().build()

status(HttpStatus status)

自定义状态码(灵活配置)

ResponseEntity.status(403).body("无权限")

headers(HttpHeaders headers)

添加响应头

见下文示例

5.3.5.1. 示例1: 基础使用,200和json应答结果
@GetMapping("/resp1")
public ResponseEntity<UserVo> actionOne(){
    UserVo userVo = new UserVo();
    userVo.setName("张三");
    userVo.setSex("男");
    userVo.setBirthday("1990-01-01");

    return ResponseEntity.ok(userVo);
}

5.3.5.2. 示例2:指定HTTP状态码

根据业务逻辑返回非 200 状态码(如参数错误返回 400、无权限返回 403)

@GetMapping("/resp1")
public ResponseEntity<String> actionTwo(String id){
    if( id == null ){
        // 400 ,请求参数错误
        return ResponseEntity.badRequest().build();
    }
    
    if ( !id.equals("1")){
        // 403 权限不足
        return ResponseEntity.status(HttpStatus.FORBIDDEN).body("权限不足");
    }
    
    return ResponseEntity.ok("查询成功");
}

5.3.5.3. 示例3: 增加header

public ResponseEntity(@Nullable T body, @Nullable HttpHeaders headers, int rawStatus) {
    this(body, headers, HttpStatusCode.valueOf(rawStatus));
}

@GetMapping("/resp3")
public ResponseEntity<UserVo> actionThree(String id){
    UserVo userVo = new UserVo();
    userVo.setName("张三");
    userVo.setSex("男");
    userVo.setBirthday("1990-01-01");
    
    //增加应答header
    HttpHeaders headers = new HttpHeaders();
    headers.add("token", "123456");
    //响应头名称:只能包含 A-Z/a-z/0-9/-/_ 等可打印 ASCII 字符,不能包含中文、空格、特殊符号(如:、;、= 等);
    //headers.add("name", "张三"); 无效,会被tomcat自动删除
    headers.add("name", "zhangsan");
    //构造方法,参数1:响应体,参数2:响应头,参数3:状态码
    return new ResponseEntity(userVo, headers, HttpStatus.OK );
}

5.3.5.4. 示例4:结合统一应答对象

结合统一应答对象,一起使用。

@GetMapping("/resp4")
public ResponseEntity<Result<UserVo>> actionFour(String id){
UserVo userVo = new UserVo();
userVo.setName("张三");
userVo.setSex("男");
userVo.setBirthday("1990-01-01");

//增加应答header
HttpHeaders headers = new HttpHeaders();
headers.add("token", "123456");
headers.add("name", "zhangsan");

//构造方法,参数1:响应体,参数2:响应头,参数3:状态码
return new ResponseEntity(Result.success(userVo), headers, HttpStatus.OK );
}

5.3.6. Handler Method总结

  1. 控制器方法用来处理 客户端的请求, @RequestMappping的value=“uri" , uri就是API接口。

  2. 控制器方法名称,要求见名知其意,使用有意义的名称和动词。

  3. 方法参数表示来自客户端的数据, 参数少于5个的, 用简单类型和@RequestParam, 更多的参数使用java object

  4. 目前应用使用json很多,所以掌握 @RequestBody 。

  5. 方法返回值表示结果,掌握自定义对象,比如Result; 还有ResponseEntity 即可。

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#Er80t

6. Web应用异常处理

6.1. Spring Boot 的默认异常处理机制

Spring Boot 的默认异常处理机制是一套兜底方案,核心目标是无需开发者手动配置,就能对未捕获的异常返回标准化响应,同时提供扩展入口让开发者自定义逻辑。

org.springframework.boot.webmvc.autoconfigure.error.ErrorMvcAutoConfiguration 错误自动配置类,整合了「错误页面 / JSON 响应」,异常处理Controller。

BasicErrorController:未捕获的异常返回标准化响应, 处理页面错误和响应json错误。 根据「请求头 Accept」判断返回 JSON 还是错误页。Spring Boot 会拦截所有未处理的异常(包括 404、500、参数错误等), 将请求转发到 /error 端点(由 BasicErrorController 处理)

方法

触发条件

响应形式

errorHtml(HttpServletRequest)

客户端接受 HTML(如浏览器直接访问)

错误页面(Whitelabel Error Page)

error(HttpServletRequest)

客户端接受 JSON(如 Ajax/Postman 请求)

JSON 格式错误响应

两个方法异常处理效果:

  1. 浏览器发起请求,引起的异常

  1. ajax请求,异常

{
  "timestamp": "2025-11-26T11:10:55.702Z",
  "status": 500,
  "error": "Internal Server Error",
  "path": "/user/getUser"
}

访问一个错误的html页面, 一个ajax请求,访问接口,接口内抛出异常。能够产生上面的错误。

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#ISx1g

6.2. 全局异常处理器

通过 @RestControllerAdvice + @ExceptionHandler 实现「全局统一异常捕获」,这是 Spring Boot 中最主流的方案,无需在每个接口写 try-catch,代码简洁且统一

import java.net.URI;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler( { ArithmeticException.class, NullPointerException.class})
    public Result<String> handleException(Exception e){
        e.printStackTrace();
        return Result.fail("500","服务器异常");
    }

    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity<Result<String>> handleRuntimeException(RuntimeException e){
        e.printStackTrace();
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                         .body(Result.fail("500","服务器异常"));
    }

    @ExceptionHandler(ArrayIndexOutOfBoundsException.class)
    public ResponseEntity<ProblemDetail> handleMethodArgumentNotValid(ArrayIndexOutOfBoundsException e, HttpServletRequest request) {

        ProblemDetail problemDetail = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST);
        problemDetail.setTitle("数组越界了异常");
        problemDetail.setDetail("访问数组元素不在范围内容");
        problemDetail.setInstance( URI.create(request.getRequestURI()));
        problemDetail.setProperty("errorCode", "VALIDATE_001");
        return ResponseEntity.badRequest().body(problemDetail);
    }
}

注意:全局异常处理器捕获的异常,不会进入 /error 端点;只有未被捕获的异常,才会触发默认机制。

6.3. ProblemDetail

它能帮助客户端理解和处理错误,提升API的可用性和可调试性,从而带来更好的开发体验和更健壮的应用程序。

采用它还能提供更丰富的错误信息,这对服务维护和故障排查至关重要。

在ProblemDetail出现前,我们通常通过自定义异常处理器和响应实体来处理Spring Boot中的错误。ProblemDetail规范基于RFC 7807标准。它定义了包含type, title, status, detail和instance等字段的统一错误响应结构。 这种标准化为API开发者和消费者提供了通用的错误信息格式。实现ProblemDetail能确保错误响应可预测且易于理解,从而改善API与客户端间的通信质量

{
  "detail": "访问数组元素不在范围内容",
  "instance": "/user/getUser",
  "status": 400,
  "title": "数组越界了异常",
  "errorCode": "VALIDATE_001"
}

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#dROsG

7. Version版本号

Spring Boot 4 新特性太香了,让 API 变更更加丝滑

API接口版本控制是现代 Web 开发中的关键实践,允许开发者在不破坏现有客户端的情况下管理 API 的变更。Spring Boot 4 引入了对 API 版本控制的原生支持,为服务器端和客户端应用程序提供了强大的工具,以高效处理版本特定的路由和请求。

API版本控制作用:

API 版本控制允许开发者在 API 迭代过程中引入新功能、修复错误,性能优化,并行开发。同时确保向后兼容性,服务连续性。

常见的版本控制策略包括:

  • URI 版本控制:在 URL 路径中包含版本号,例如 /api/v1/orders, /api/v2/orders

  • 请求头版本控制:通过请求头(如自定义 version 头)指定版本,例如 version=1.1

  • 查询参数版本控制:通过查询参数传递版本号,例如 /api/orders?version=1。

  • 媒体类型:application/vnd.company.app-v1+json, application/vnd.company.app-v2+json

Spring Boot4 为Web 应用程序引入了 API 版本控制支持,允许通过 @RequestMapping 注解中的version属性将请求映射到不同的控制器方法。这种方法简化了多版本 API 的管理,使开发者能够在一个应用程序中处理多个 API 版本。

以下注解,同样增加了version属性

  • @GetMapping,

  • @PostMapping,

  • @PutMapping,

  • @DeleteMapping,

  • @PatchMapping

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#owlcM

7.1. 请求头版本控制

需求:

自定义请求header表示版本信息。 例如 请求header名称: X-API-VERSION , 版本信息如 X-API-VERSION=1.0 , X-API-VERSION=2.0

使用@RequestMapping (version=”1.0“) , @RequestMapping (version="2.0") 定义两个方法,处理两个版本的请求

编码:

订单数据类:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
    private String id;
    private String name;
}

控制器类ScmController

@RestController
public class ScmController {

    @RequestMapping(value = "/orders",version = "1.0")
    public ResponseEntity<Order> listOrdersByV1(){
        Order order = new Order("A001","杭州3日自由行");
        return ResponseEntity.ok(order);
    }

    @RequestMapping(value = "/orders",version = "2.0")
    public ResponseEntity<Order> listOrdersByV2(){
        Order order = new Order("A002","15日游轮欧洲游AA");
        return ResponseEntity.ok(order);
    }
}

SpringMVC配置类,设置版本控制参数

@Configuration
public class WebMvcConfig  implements WebMvcConfigurer {
    @Override
    public void configureApiVersioning(ApiVersionConfigurer configurer) {
       configurer.useRequestHeader("X-API-VERSION");
    }
}

configurer.useRequestHeader( 请求header名称) , 以此名称作为版本号的标志。

测试:Postman 发送测试请求。

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#KDYL1

7.2. 请求路径参数版本控制

请求url路径包含version, 例如/api/v1/orders , /api/v2/orders

主要设置ApiVersionConfigurer对象usePathSegment(index)方法

configurer.usePathSegment(index);

index——要检查的路径段的索引;例如,对于像“/{version}/…”这样的URL,使用索引0,对于“/api/{version]/…”,使用索引1。

修改Controller

@RestController
public class ScmController {

    @RequestMapping(value = "/api/{version}/orders",version = "v1")
    public ResponseEntity<Order> listOrdersByV1(){
        Order order = new Order("A001","杭州3日自由行");
        return ResponseEntity.ok(order);
    }

    @RequestMapping(value = "/api/{version}/orders",version = "v2")
    public ResponseEntity<Order> listOrdersByV2(){
        Order order = new Order("A002","15日游轮欧洲游AA");
        return ResponseEntity.ok(order);
    }
}

${version} : 自定义路径变量 , 名称自定义

访问接口的URL 类似 http://ip:port/api/v1/orders

SpringMVC配置类,设置版本控制参数-路径片段

@Configuration
public class WebMvcConfig  implements WebMvcConfigurer {
    @Override
    public void configureApiVersioning(ApiVersionConfigurer configurer) {
        //路径包含版本参数
        configurer.usePathSegment(1);
    }
}

Postman测试

7.3. 请求参数版本控制

请求包含版本控制参数 ,例如ver=v1 , ver=v2

控制器定义

@RestController
public class ScmController {

    @RequestMapping(value = "/api/orders",version = "v1")
    public ResponseEntity<Order> listOrdersByV1(){
        Order order = new Order("A001","杭州3日自由行");
        return ResponseEntity.ok(order);
    }

    @RequestMapping(value = "/api/orders",version = "v2")
    public ResponseEntity<Order> listOrdersByV2(){
        Order order = new Order("A002","15日游轮欧洲游AA");
        return ResponseEntity.ok(order);
    }
}

SpringMVC配置类,设置版本控制参数-路径片段

@Configuration
public class WebMvcConfig  implements WebMvcConfigurer {
    @Override
    public void configureApiVersioning(ApiVersionConfigurer configurer) {
        //请求参数ver表示版本参数
        configurer.useQueryParam("ver");
    }
}

测试接口,Postman

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#AWm1q

8. 拦截器组件

8.1. 什么是拦截器

SpringMVC中的Interceptor拦截器是非常重要和相当有用的,基于AOP(面向切面编程) 思想工作,作用于请求处理的整个生命周期,可在不侵入业务代码的前提下,对控制器(Controller)的请求进行前置处理、后置处理、完成处理,实现通用功能的统一管控。

SpringMVC拦截器是普通Java类,需要实现HandlerInterceptor接口。

自定义拦截器,需要实现HandlerInterceptor接口。而该接口中含有三个方法:

  • preHandle(request,response, Object handler):

该方法在处理器方法执行之前执行。其返回值为boolean,若为true,则紧接着会执行处理器方法,且会将afterCompletion()方法放入到一个专门的方法栈中等待执行。

  • postHandle(request,response, Object handler,modelAndView):

该方法在处理器方法执行之后执行。处理器方法若最终未被执行,则该方法不会执行。由于该方法是在处理器方法执行完后执行,且该方法参数中包含ModelAndView,所以该方法可以修改处理器方法的处理结果数据,且可以修改跳转方向。

  • afterCompletion(request,response, Object handler, Exception ex):

当preHandle()方法返回true时,会将该方法放到专门的方法栈中,等到对请求进行响应的所有工作完成之后才执行该方法。即该方法是在中央调度器渲染(数据填充)了响应页面之后执行的,此时对ModelAndView再操作也对响应无济于事。

方法执行时间表:

方法

执行时机

核心作用

preHandle(...)

控制器(Controller)方法执行之前

权限校验、参数预处理、日志初始化

postHandle(...)

控制器方法执行之后、视图渲染(ModelAndView)之前

响应数据包装、修改视图数据

afterCompletion(...)

整个请求完成后(视图渲染完成 / 异常抛出后)

资源释放、日志最终提交、耗时统计

拦截器的核心价值是抽离通用逻辑、统一管控请求流程,常见应用场景包括:

1. 权限校验(最核心场景)

统一验证用户身份、权限,避免在每个接口重复编写校验逻辑:

  • 校验用户是否登录(如判断 Token/SESSION 是否有效);

  • 校验用户是否有接口访问权限(如管理员 / 普通用户权限区分);

  • 校验接口访问的 IP 白名单、接口限流后的权限等。

2. 日志记录 / 监控

统一收集请求的关键信息,用于排查问题、性能监控:

  • 记录请求的 URL、请求参数、客户端 IP、请求时间;

  • 记录响应状态、响应耗时、异常信息;

  • 统计接口访问量、成功率、平均响应时间等监控指标。

3. 请求参数预处理 / 响应数据统一包装

  • 请求预处理:统一转换参数格式(如日期字符串转 Date 对象)、参数脱敏(如手机号 / 身份证号隐藏)、参数校验(补充 JSR 303 之外的自定义校验);

  • 响应统一包装:将所有接口的返回值封装为统一格式(如 {code:200, data:{}, msg:"success"}),避免每个接口重复编写包装逻辑。

实现拦截器步骤:

  1. 创建拦截器类,实现所需方法

  2. 注册拦截器对象,设置拦截的uri

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#DhBGH

8.2. 权限验证拦截器

@RestController
public class AdminController {
    @GetMapping("/admin/index")
    public String listAdmin(){
        return "管理员页面";
    }
}

拦截器对象

@Component
public class AuthInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String role = request.getParameter("role");
        String uri = request.getRequestURI();
        if ("/admin/index".equals(uri) && "admin".equals(role)) {
            return true;
        }
        return false;
    }
}

注册拦截器

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    private static final String[] EXCLUDE_PATH = {"/", "/index", "/login",
                                                  "/css/**", "/js/**", "/fonts/**", "/img/**", "/error"};
    private static final String[] INCLUDE_PATH = { "/**"};

    @Resource
    private AuthInterceptor authInterceptor;

    /**
     * 添加拦截器
     * @param registry 协助添加拦截器的对象
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptor)
        .addPathPatterns(INCLUDE_PATH)  //拦截uri,对这些uri请求应用拦截的preHandle()
        .excludePathPatterns(EXCLUDE_PATH);  //这些地址不拦截

    }
}

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#xihpV

9. Servlet容器

Spring Boot 内置了 Tomcat 作为默认嵌入式 Web 容器,可通过配置文件灵活调整 Tomcat 的核心参数(如端口、线程池、连接超时、编码等)。以下是完整的配置方案,覆盖基础配置、进阶优化和特殊场景。

9.1. tomcat服务器

最常用的配置方式,通过 Spring Boot 内置的配置项直接调整 Tomcat 核心参数,无需编写代码,适用于绝大多数场景。

application.yml

server:
  # 基础Web配置
  port: 8080 # 服务端口(默认8080,修改为0则随机端口)
  servlet:
    context-path: /demo # 应用上下文路径(默认/,访问路径变为 http://localhost:8080/demo/xxx)
  # Tomcat专属配置
  tomcat:
    # 字符编码(与server.servlet.encoding配合,双重保障)
    uri-encoding: UTF-8
    # 线程池配置(核心!影响并发能力)
    threads:
      max: 200 # 最大工作线程数(默认200,根据服务器CPU核数调整:核数*2+1)
      min-spare: 10 # 核心线程数(默认10,保持一定空闲线程,减少创建开销)
      max-queue-capacity: 100 # 请求队列最大容量(默认无界,建议设置,避免OOM)
    # 连接配置
    connection-timeout: 20000ms # 连接超时时间(默认20秒,单位ms/s/m/h)
    keep-alive-timeout: 5000ms # 长连接超时时间(默认5秒,0表示禁用)
    max-connections: 10000 # 最大连接数(默认8192,包括同步+异步连接)
    # 访问日志配置
    accesslog:
      enabled: true # 开启访问日志
      directory: d:/logs/tomcat # 日志存储目录(默认tomcat/logs)
      prefix: access_log # 日志文件名前缀
      suffix: .log # 日志文件名后缀
      # 日志格式(记录IP、请求、状态、耗时等)
      pattern: "%h %l %u %t \"%r\" %s %b \"%{Referer}i\" \"%{User-Agent}i\" %D"
      rotate: true # 按天轮转日志(默认true)
    # 其他优化
    max-swallow-size: 1MB # 最大请求体大小(默认2MB,超出则断开连接)
    accept-count: 100 # 连接接受队列大小(默认100,超出则拒绝连接)

对于Servlet应用,Spring Boot4 内置了对嵌入式Tomcat和Jetty服务器的支持。大多数开发者会通过合适的启动器来获取一个完全配置好的实例。默认情况下,嵌入式服务器会在8080端口监听HTTP请求。

9.2. 使用Jetty

默认是Tomcat内嵌服务器, 依赖包含tomcat

改换:Jetty

<!-- 引入 Jetty 嵌入式容器依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

Jetty 是 “效率型选手”,适合对资源、速度、灵活性要求高的现代架构;Tomcat 是 “稳妥型选手”,适合对稳定性、功能完备性、规范兼容性要求高的传统 / 企业级场景。选择的核心是匹配业务需求 —— 轻量高并发选 Jetty,需企业级功能选 Tomcat。

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#gTbeI

9.3. Spring Boot应用部署运行

Spring Boot 应用的部署运行方式丰富多样,核心可分为嵌入式容器部署(最主流)、外置容器部署容器化 / 云原生部署三大类

部署方式

核心特点

适用场景

优势

嵌入式容器(JAR 包)

打包为可执行 JAR,内置 Tomcat/Jetty/Undertow,直接 java -jar

运行

微服务、云原生、单机 / 小规模部署

零依赖、部署简单、轻量、易水平扩展

外置容器(WAR 包)

打包为 WAR,部署到外部 Tomcat/Jetty 等 Web 容器

传统企业应用、需共享容器 / 集群管理

兼容老旧容器、可统一管理多个应用

Docker 容器化

打包为 Docker 镜像,通过容器运行

容器化部署、K8s 集群、多环境一致性

环境隔离、易迁移、扩缩容灵活

Kubernetes (K8s) 部署

基于 Docker 镜像,通过 K8s 编排管理(Deployment/Service)

大规模集群、高可用、自动扩缩容

自动化运维、故障自愈、资源调度高效

主流方式嵌入式容器部署, 将Spring Boot应用打包为jar文件, web应用也打包为jar。 通过java -jar xxx.jar 启动运行

Spring Boot 默认打包为可执行 JAR,内置 Web 容器,是最简洁的部署方式。

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#Uzr90

10. 常见问题

10.1. localhost 和 127.0.0.1 到底有啥区别

localhost 是一个域名,它被广泛用于表示当前这台主机(也就是你自己的电脑)。当你在浏览器地址栏输入 localhost 时,操作系统会查找 hosts 文件(在 Windows 中通常位于 C:\Windows\System32\drivers\etc\hosts,在 MacOS 或者 Linux 系统中,一般位于 /etc/hosts),查找 localhost 对应的 IP 地址。如果没有找到,它将默认解析为 127.0.0.1

特点

  • 是一个域名,默认指向当前设备。

  • 不需要联网也能工作。

  • 用于测试本地服务,例如开发中的 Web 应用或 API。


127.0.0.1 是一个特殊的 IP 地址,它被称为 回环地址(loopback address)。这个地址专门用于通信时指向本机,相当于告诉电脑“别出门,就在家里转一圈”

特点

  • 127.0.0.1 不需要 DNS 解析,因为它是一个硬编码的地址,直接指向本地计算机。

  • 是 IPv4 地址范围中的一个保留地址。

  • 只用于本机网络通信,不能通过这个地址访问外部设备或网络。

  • 是开发测试中最常用的 IP 地址之一。

两者的不同点

区别

localhost

127.0.0.1

类型

域名

IP 地址

解析过程

需要通过 DNS 或 hosts 文件解析为 IP 地址

不需要解析,直接使用

协议版本支持

同时支持 IPv4 和 IPv6

仅支持 IPv4

访问速度

解析时可能稍慢(需要 DNS 解析ip)

通常更快,因为不需要额外的解析步骤

注意:

localhost 默认可以解析为 IPv4(127.0.0.1)或 IPv6(::1)地址,具体取决于系统配置。如果你的程序只支持 IPv4,而 localhost 被解析为 IPv6 地址,可能会导致连接失败

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#etwW4

10.2. URL地址末尾加不加”/“有什么区别

URL 末尾加不加 /(斜杠)看似细微,实则会影响路径解析、重定向行为、资源加载等核心环节,不同场景下的差异可能导致访问异常或性能损耗。

  • 末尾加 /:表示当前路径是目录(文件夹),服务器会解析为 “访问该目录下的默认资源(如 index.html、index.jsp)”;

  • 末尾不加 /:表示当前路径是文件(或待匹配的路由),服务器会先判断该路径是否为文件,若不存在则尝试补 / 重定向,或直接返回 404。

维度

末尾加 /

(如 http://example.com/api/

末尾不加 /

(如 http://example.com/api

路径语义

明确表示 api 是 “目录”,服务器优先查找该目录下的默认资源(如 index)。

表示 api 是 “文件” 或 “路由名称”,服务器先匹配精准路径,无匹配则尝试重定向。

服务器重定向

无额外重定向,直接处理请求。

若服务器判定 api 是目录,会返回 301/302

重定向到 http://example.com/api/

(多一次网络请求)。

Spring Boot 路由

若路由配置为 /api/,则仅匹配加 / 的请求;若配置为 /api,则加 / 不加都匹配(框架自动兼容)。

若路由配置为 /api/,不加 / 会返回 404;

若配置为 /api,框架会自动补 / 且无重定向(内置优化)。

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#bZAHA

11. JSON 视图

JSON 视图(JSON View)是在 JSON 序列化 / 反序列化过程中,为不同场景定制字段展示规则的机制—— 简单来说,就是让同一个 Java 对象,根据不同的业务场景(如 “列表页”“详情页”)输出不同的 JSON 字段集合,避免返回冗余数据或敏感信息。

11.1. 创建实例项目

创建新的Web应用。Spring Boot 4

public class SysUser {
    private String id;
    private String realName;
    private String loginAccount;
    private String loginPassword;
    private LocalDate birthday;
    private LocalDateTime createTime;
    // set |get | toString
}

简单的控制器

@RestController
public class SysUserController {

    @GetMapping("/user/detail")
    public SysUser getUser(){
        SysUser user = new SysUser();
        user.setId("8209");
        user.setRealName("张三");
        user.setLoginAccount("zhangsan");
        user.setLoginPassword("123456");
        user.setBirthday(LocalDate.of(1990, 1, 1));
        user.setCreateTime(LocalDateTime.now());
        return user;

    }
}

访问接口:json数据

{"birthday":"1990-01-01","createTime":"2025-12-11T11:44:09.7067537","id":"8209","loginAccount":"zhangsan","loginPassword":"123456","realName":"张三"}

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#UsjXe

11.2. @JsonIgnore

@JsonIgnore : 声明在字段上,序列化忽略此字段

@JsonIgnoreProperties: 类型上声明, 标注忽略的字段列表, 序列化不包含这些字段

//@JsonIgnoreProperties(ignoreUnknown = true) // 忽略未知字段
@JsonIgnoreProperties(
    ignoreUnknown = true,
    value = {"loginPassword","realName"}
)
public class SysUser {
    private String id;
    private String realName;
    private String loginAccount;

    //@JsonIgnore
    private String loginPassword;
    private LocalDate birthday;
    private LocalDateTime createTime; 
    // set | get | toString()
}

应用API响应结果:

{"birthday":"1990-01-01","createTime":"2025-12-11T11:51:03.312762","id":"8209","loginAccount":"zhangsan"}

11.3. @JsonProperty

定义 JSON 字段名(序列化 / 反序列化时映射),解决“Java 字段名 ≠ JSON 字段名”

public class SysUser {
    private String id;

    @JsonProperty("username")
    private String realName;

    @JsonProperty("useraccount")
    private String loginAccount;

    @JsonIgnore
    private String loginPassword;
    private LocalDate birthday;
    private LocalDateTime createTime;
    // set | get | toString()
}

API响应结果:

{"birthday":"1990-01-01","createTime":"2025-12-11T15:37:02.5675818","id":"8209","useraccount":"zhangsan","username":"张三"}

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#DTogr

11.4. @JsonSerialize

自定义序列化, 对某个字段进行序列化定制

import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.SerializationContext;
import tools.jackson.databind.ValueSerializer;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

public class CustomDateSerializer extends ValueSerializer<LocalDate> {
    @Override
    public void serialize(LocalDate value, JsonGenerator gen, SerializationContext ctxt) throws JacksonException {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy年-MM月-dd日");
        String dateString = dateTimeFormatter.format(value);
        gen.writeString(  dateString +"("+ (value.isLeapYear() ?"闰年":"平年") +")");
    }
}

Jackson3版本, 自定义序列化,继承ValueSerializer类, 在Jackson2是JsonSerializer

public class SysUser {
    private String id;

    @JsonProperty("username")
    private String realName;

    @JsonProperty("useraccount")
    private String loginAccount;

    @JsonIgnore
    private String loginPassword;

    // CustomDateSerializer自定义序列化类
    @JsonSerialize(using = CustomDateSerializer.class)
    private LocalDate birthday;


    //格式化是LocalDateTime类型
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
    // set | get | toString
}

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#J8xiC

11.5. 字段命名策略

PropertyNamingStrategies( Jackson3. 位于tools.jackson.databind包): 字段命名策略类

命名策略类

示例

SNAKE_CASE - 下划线命名

private String userName; // JSON 中变为 "user_name"

UPPER_CAMEL_CASE - 首字母大写驼峰

private String userName; // JSON 中变为 "UserName"

LOWER_CAMEL_CASE - 默认策略

private String firstName; // JSON 中保持 "firstName"

KEBAB_CASE - 短横线命名

private String userName; // JSON 中变为 "user-name"

LOWER_CASE - 全小写

private String userName; // JSON 中变为 "username"

LOWER_DOT_CASE - 点分隔

private String userName; // JSON 中变为 "user.name"

组合策略和优先级,优先级(从高到低):

  • @JsonProperty注解指定名称

  • @JsonNaming类/字段级别策略

  • 全局 ObjectMapper 配置

  • 默认策略(LOWER_CAMEL_CASE)

@JsonPropertyOrder(alphabetic = true)
@JsonNaming(PropertyNamingStrategies.UpperSnakeCaseStrategy.class)
public class SysUser {
    private String id;

    @JsonProperty("username")
    private String realName;

    @JsonProperty("useraccount")
    private String loginAccount;

    @JsonIgnore
    private String loginPassword;

    // CustomDateSerializer自定义序列化类
    @JsonSerialize(using = CustomDateSerializer.class)
    private LocalDate birthday;


    //格式化是LocalDateTime类型
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
   // set | get | toString
}

API接口应答结果

{"BIRTHDAY":"1990年-01月-01日(平年)","CREATE_TIME":"2025-12-11 20:06:08","ID":"8209","useraccount":"zhangsan","username":"张三"}

注意:useraccount, username 标注@JsonProperty 它的优先级高。所以转为大写 、下划线

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#PGHym

11.6. application.yml 全局设置

spring:
  jackson:
    # 全局日期格式化
    date-format: "yyyy-MM-dd HH:mm:ss"
    # 全局时区
    time-zone: "Asia/Shanghai"
    # 全局空值处理
    default-property-inclusion: non_null
    #忽略未知字段 , 字段非null进行序列化
    fail-on-unknown-properties: true
    # 字段命名策略
    property-naming-strategy: SNAKE_CASE

11.7. @JsonView

按“视图”序列化字段(不同场景返回不同字段),首先定义视图的标志,一般使用接口名称(全限定的包名类名一定是唯一值)

public interface SysUserView {

    // 用户简单信息
    interface SysUserSimpleView {
    }

    // 用户详细信息
    interface SysUserDetailView extends SysUserSimpleView {
    }
}

public class SysUser {

    @JsonView(SysUserView.SysUserSimpleView.class)
    private String id;
    @JsonView(SysUserView.SysUserSimpleView.class)
    private String realName;
    @JsonView(SysUserView.SysUserSimpleView.class)
    private String loginAccount;

    @JsonIgnore
    private String loginPassword;

    @JsonView(SysUserView.SysUserDetailView.class)
    @JsonSerialize(using = CustomDateSerializer.class)
    private LocalDate birthday;

    @JsonView(SysUserView.SysUserDetailView.class)
    //格式化是LocalDateTime类型
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
    // set | get | toString()
}

@RestController
public class SysUserController {

    @JsonView(SysUserView.SysUserSimpleView.class)
    @GetMapping("/user/detail")
    public SysUser getUser(){
        SysUser user = new SysUser();
        user.setId("8209");
        user.setRealName("张三");
        user.setLoginAccount("zhangsan");
        user.setLoginPassword("123456");
        user.setBirthday(LocalDate.of(1990, 1, 1));
        user.setCreateTime(LocalDateTime.now());
        return user;

    }

    @JsonView(SysUserView.SysUserDetailView.class)
    @GetMapping("/user/detail/all")
    public SysUser getUserAll(){
        SysUser user = new SysUser();
        user.setId("8209");
        user.setRealName("张三");
        user.setLoginAccount("zhangsan");
        user.setLoginPassword("123456");
        user.setBirthday(LocalDate.of(1990, 1, 1));
        user.setCreateTime(LocalDateTime.now());
        return user;

    }
}

http://localhost:8080/user/detail

{"id":"8209","login_account":"zhangsan","real_name":"张三"}

http://localhost:8080/user/detail/all

{"birthday":"1990年-01月-01日(平年)","create_time":"2025-12-12 13:52:31","id":"8209","login_account":"zhangsan","real_name":"张三"}

总结:

Web开发主要是围绕SpringMVC框架实现的,核心是Controller的定义, Spring MVC框架的配置功能,JSON处理。 理解MVC架构, Web开发目前基于Servlet技术的是主流方式。 Web开发掌握Controller定义, 统一应答结果, 统一异常处理。 JSON序列化/反序列化。

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#IlWA6

🔰 第三部分:数据持久化

数据持久化(Persistence)是指将内存中的临时数据保存到磁盘、数据库、文件等持久化存储介质中,使其在应用重启、进程终止后仍能保留的过程。Spring Boot 作为一站式微服务框架,通过简化配置、整合主流持久化技术,让数据持久化开发更高效。

Spring Boot 不直接实现持久化,而是整合并简化业界主流的持久化技术,解决原生技术(如 JDBC)配置繁琐、代码冗余的问题。

能做什么?

  • 简化数据读写的配置成本(如自动配置、起步依赖);

  • 降低业务代码与持久化层的耦合(面向接口编程);

  • 统一异常处理、事务管理、连接池管理;

  • 支持多种存储介质(关系型数据库、NoSQL、文件等)

支持整合的技术/框架:

  • Spring Data JPA

  • MyBatis/MyBatis-Plus

  • Spring JDBC,

  • MongoDB/Redis/Elasticsearch等NoSQL

1. 整合 MyBatis CRUD

Spring Boot快速集成MyBatis, 创建MyBatis需要使用的DataSource, SqlSessionFactory,SqlSession这些对象。 集成是自动化的,无需手工。初始化由 MybatisAutoConfiguration 自动完成,无需手动创建 SqlSessionFactory

1.1. 整合步骤

创建数据表

CREATE TABLE `t_order` (
  `id` VARCHAR(50) NULL DEFAULT NULL COMMENT '订单id' COLLATE 'utf8mb4_bs_0900_ai_ci',
  `stock` INT NULL DEFAULT NULL COMMENT '商品数量',
  `code` VARCHAR(50) NULL DEFAULT NULL COMMENT '商品code' COLLATE 'utf8mb4_bs_0900_ai_ci',
  `money` DECIMAL(10,2) NULL DEFAULT NULL COMMENT '订单金额'
)

编码部分

创建SpringBoot 4项目。 选择依赖,注意

依赖兼容问题, 当前spring boot 4 与 mybatis最新的版本不匹配 。 解决方式,修改spring boot 或使用其他 SQL 依赖。

<dependency>
  <groupId>org.mybatis.spring.boot</groupId>
  <artifactId>mybatis-spring-boot-starter</artifactId>
  <version>3.0.5</version>
</dependency>

<dependency>
  <groupId>com.mysql</groupId>
  <artifactId>mysql-connector-j</artifactId>
  <scope>runtime</scope>
</dependency>

public class TOrder {
    private String id;
    private String code;
    private Integer stock;
    private BigDecimal money;
    // set | get | toString
}

public interface TOrderMapper {

    int insertOrder(TOrder order);

    TOrder selectOrderById(String id);
}

@MapperScan("com.bjpowernode.mapper")
@SpringBootApplication
public class Course02MybatisApplication {

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

}

resources目录创建子目录mapper, 创建xml文件,写sql

<?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">
<mapper namespace="com.bjpowernode.mapper.TOrderMapper">
  <insert id="insertOrder" parameterType="com.bjpowernode.domain.TOrder">
    insert into t_order(id,code,stock,money) values(#{id},#{code},#{stock},#{money})
  </insert>

  <select id="selectOrderById" parameterType="String"
    resultType="com.bjpowernode.domain.TOrder">
    select * from t_order where id = #{id}
  </select>
</mapper>

连接数据库

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/plus?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
    #数据库连接池类型
    type: com.zaxxer.hikari.HikariDataSource

mybatis:
  # xml文件存储位置
  mapper-locations: classpath:/mapper/**/*.xml

单元测试:

@SpringBootTest
class Course02MybatisApplicationTests {

    @Resource
    private TOrderMapper orderMapper;

    @Test
    void testInsert(){
        TOrder order = new TOrder();
        order.setId(UUID.randomUUID().toString());
        order.setCode("DQ2032");
        order.setMoney(new BigDecimal(100));
        order.setStock(1);

        int insert =  orderMapper.insertOrder(order);
        System.out.println("创建新的订单 = " + insert);
    }

    @Test
    void testSelect(){
        TOrder order = orderMapper.selectOrderById("db8842b9-cc86-4090-8d40-1803867c4670");
        System.out.println("查询结果 = " + order);
    }

}

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#kYmdN

1.2. MyBatis执行流程

概述:MyBatis 执行流程的核心是 “配置解析(初始化) → 会话创建 → SQL 执行 → 结果映射 → 资源释放”,所有操作围绕 Configuration 全局配置和 SqlSession 会话展开;

  1. 初始化阶段

MyBatis 启动时会解析所有配置(核心配置文件、Mapper 接口 / XML),构建全局唯一的 Configuration 对象(MyBatis 所有配置的容器)。加载 mybatis-config.xml、Mapper XML / 注解, 将解析后的配置(数据源、Mapper、插件、类型处理器等)存入 Configuration

  1. 创建 SqlSession(每次请求 / 操作时执行)

SqlSession 是 MyBatis 与数据库交互的核心会话对象,封装了执行 SQL 的所有方法,生命周期为 “一次请求 / 操作”。

  1. SQL 执行:

通过 Mapper 接口代理对象触发 SQL 执行,核心是将 Mapper 方法转换为具体的 SQL 语句,执行并处理参数

SqlSession 会委托给 Executor 来执行 SQL 语句 。Executor 是 MyBatis 执行 SQL 的核心组件,它有多种实现类,如 SimpleExecutor、ReuseExecutor 和 BatchExecutor 。以 SimpleExecutor 为例,它会先创建 StatementHandler,然后通过 StatementHandler 来执行 SQL 语句 。StatementHandler 就像是演员的助手,负责具体的表演细节,如创建 Statement、设置参数和执行 SQL 语句

  1. 结果映射(将 ResultSet 转为 Java 对象)

MyBatis 将 JDBC ResultSet 转换为指定的 Java 实体类 / 集合,核心是处理字段与属性的映射(驼峰转换、结果映射)。

  1. 资源释放(收尾阶段)

执行完 SQL 后,释放 JDBC 资源并管理事务,关闭 SqlSession

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#zwJKQ

1.3. 数据库连接池

Spring Boot默认使用HikariCP数据库连接池;

HikariCP is a "zero-overhead" production ready JDBC connection pool.

HikariCP是一个“零开销”的JDBC连接池产品;

作者Brett Wooldridge,他是一个从2008年一直生活在日本东京的的美国人开发开源的;

日语发音是Hi-ka-li(lee) Hikari的意思是光,“光”的意思是“…的速度”;HikariCP的全称Hikari Connection Pool,即Hikari连接池;

优化调整连接池

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/plus?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
    #数据库连接池类型
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      #最大连接数,默认是10,  cpu核心数量*2+1
      maximum-pool-size: 30
      #最小空闲连接,默认是10
      minimum-idle: 30
      #等待连接池分配连接的最大时长,超过该时长还没有可用连接则发生超时异常(单位毫秒)
      connection-timeout: 5000
      #空闲连接的最大时长,空闲多久就备释放回收,设置为0不让连接回收
      idle-timeout: 0
      #一个连接的最大生命时间,超过该时间还没有使用就回收掉(单位毫秒)
      max-lifetime: 18000000

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#OyUSe

1.4. 辅助 IDEA插件

free mybatis tool工具(idea插件)反向工程生成代码;通过数据库的表,创建Java代码。 生成数据库表的 domain类,Mapper接口,Mapper.xml

使用free mybatis tool

1. 在idea中安装该插件;

2. 在idea中配置一个数据源连接(DataSource)

3. 在mysql中准备一个数据库,创建一张表;

4. 在你的数据库表上右键鼠标,选择插件,可以生成代码;

2. 自动配置(Auto-Configuration

Spring Boot 的自动配置会根据你添加的 jar 依赖,自动配置你的 Spring 应用。例如,如果你在你的类路径上有数据库相关的jar(mybatis-spring-boot-starter), 在没有配置开发者的数据源Bean时,Spring Boot 会自动配置数据库的连接,创建数据库操作需要的Bean对象, 比如DataSource。 如果是MyBatis框架, 创建SqlSessionFactory对象。 将这些对象注入到Spring容器。 代替手工创建这些对象。用户可以自定义Bean覆盖默认配置

看一下不使用自动配置, 整合MyBatis框架

<!-- Spring 核心 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>6.1.0</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>6.1.0</version>
</dependency>
<!-- MyBatis 核心 -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.15</version>
</dependency>
<!-- MyBatis 整合 Spring -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>3.0.3</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>8.0.33</version>
</dependency>
<!-- 数据源(Druid 示例) -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.18</version>
</dependency

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 1. 配置数据源 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="jdbc:mysql://localhost:3306/test_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    </bean>

    <!-- 2. 配置 MyBatis SqlSessionFactory -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 注入数据源 -->
        <property name="dataSource" ref="dataSource"/>
        <!-- MyBatis 核心配置文件路径 -->
        <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/>
        <!-- XML 映射文件路径 -->
        <property name="mapperLocations" value="classpath:mybatis/mapper/**/*.xml"/>
        <!-- 实体类别名包 -->
        <property name="typeAliasesPackage" value="com.example.entity"/>
    </bean>

    <!-- 3. 配置 Mapper 扫描器(自动创建 Mapper 代理对象) -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 扫描 Mapper 接口包 -->
        <property name="basePackage" value="com.example.mapper"/>
        <!-- 关联 SqlSessionFactory -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>

    <!-- 4. 扫描 Service 层(自动注入 Mapper) -->
    <context:component-scan base-package="com.example.service"/>

</beans>

Spring Boot自动配置

<dependency>
  <groupId>org.mybatis.spring.boot</groupId>
  <artifactId>mybatis-spring-boot-starter</artifactId>
  <version>3.0.5</version>
</dependency>

<dependency>
  <groupId>com.mysql</groupId>
  <artifactId>mysql-connector-j</artifactId>
  <scope>runtime</scope>
</dependency>

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#YRxCI

2.1. @SpringBootApplication 注解

@SpringBootApplication

┣ @SpringBootConfiguration

┣ @EnableAutoConfiguration // 启用自动配置的关键

┗ @ComponentScan

2.2. @EnableAutoConfiguration 的原理

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration

  1. Spring Boot 启动

  2. AutoConfigurationImportSelector 被执行

  3. 加载 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

  4. 读取所有自动配置类

  5. 按条件筛选适用的配置类

2.3. 条件注解

// 常用条件注解 : 按条件筛选适用的配置类

@ConditionalOnClass // 类路径存在指定类

@ConditionalOnMissingBean // 容器中不存在指定Bean

@ConditionalOnProperty // 指定属性有特定值

@ConditionalOnWebApplication // 是Web应用

@ConditionalOnBean // 容器中存在指定Bean

@ConditionalOnJava // 指定Java版本

@ConditionalOnResource // 类路径存在指定资源

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#WhG6A

3. 整合 MyBatis Plus

MyBatis-Plus 是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。 支持国内,国外多种数据库。

3.1. MyBatis增强框架

网站:https://www.baomidou.com/

简介:MyBatis-Plus(简称MP)是一个 MyBatis的增强工具,在MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

MyBatis-Plus和MyBatis是一对好搭档, MyBatis-Plus对单表的操作几乎不用写一行代码,效率特别高。对于多表,复杂查询可以在XML写SQL。而且MyBatis-Plus提供大量的查询方法和条件方法提供开发效率。

MyBatis-Plus核心依然是MyBatis。在MyBatis中能够使用的技术点和语法在MP中同样使用。

3.2. MyBatis-Plus特点

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑

  • 损耗小:启动即会自动注入基本CURD,性能基本无损耗,直接面向对象操作

  • 强大的CRUD 操作:内置通用Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求

  • 支持Lambda 形式调用:通过Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错

  • 支持主键自动生成:支持多达4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题

  • 支持ActiveRecord 模式:支持ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作

  • 支持自定义全局通用操作:支持全局通用方法注入(Write once, use anywhere )

  • 内置代码生成器:采用代码或者Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用

  • 内置分页插件:基于MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询

  • 分页插件支持多种数据库:支持MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库

  • 内置性能分析插件:可输出SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询

  • 内置全局拦截插件:提供全表delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

支持的数据库:

MySQL,Oracle,DB2,H2,HSQL,SQLite,PostgreSQL,SQLServer,Phoenix,Gauss ,ClickHouse,Sybase,OceanBase,Firebird,Cubrid,Goldilocks,csiidb, 达梦数据库,虚谷数据库,人大金仓数据库,南大通用(华库)数据库,南大通用数据库,神通数据库,瀚高数据库

3.3. 整合步骤

创建表: 使用MyBatis中的t_order表

CREATE TABLE `t_order` (
  `id` VARCHAR(50) NULL DEFAULT NULL COMMENT '订单id' COLLATE 'utf8mb4_bs_0900_ai_ci',
  `stock` INT NULL DEFAULT NULL COMMENT '商品数量',
  `code` VARCHAR(50) NULL DEFAULT NULL COMMENT '商品code' COLLATE 'utf8mb4_bs_0900_ai_ci',
  `money` DECIMAL(10,2) NULL DEFAULT NULL COMMENT '订单金额'
)

创建Spring Boot 4项目

maven依赖如下:

<!--
Spring Boot4 (自3.5.13开始)
-->
<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>mybatis-plus-spring-boot4-starter</artifactId>
  <version>3.5.15</version>
</dependency>

<dependency>
  <groupId>com.mysql</groupId>
  <artifactId>mysql-connector-j</artifactId>
  <scope>runtime</scope>
</dependency>

@TableName("t_order")
public class TOrder {
    @TableId
    private String id;

    @TableField("code")
    private String code;

    @TableField("stock")
    private Integer stock;

    @TableField("money")
    private BigDecimal money; 
    // set | get方法
}

public interface OrderMapper extends BaseMapper<TOrder> {
}

@MapperScan("com.bjpowernode.mapper")
@SpringBootApplication
public class Prj01MybatisplusApplication {

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

}

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/plus?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.zaxxer.hikari.HikariDataSource

#mybatis-plus配置
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true

@SpringBootTest
class Prj01MybatisplusApplicationTests {

    @Resource
    private OrderMapper orderMapper;


    @Test
    void insert() {
        TOrder order = new TOrder();
        order.setId(UUID.randomUUID().toString());
        order.setCode("DQ5601");
        order.setMoney(new BigDecimal(100));
        order.setStock(1);
        int add = orderMapper.insert(order);
        System.out.println("添加记录的行数:" + add);
    }

    @Test
    void select() {
        TOrder order = orderMapper.selectById("DQ5601");
        System.out.println(order);
    }

    @Test
    void selectColumn() {
        QueryWrapper<TOrder> wrapper = new QueryWrapper<>();
        wrapper.eq("code", "DQ5601")
        .orderByAsc("id");
        List<TOrder> orderList = orderMapper.selectList(wrapper);

        System.out.println("orderList = " + orderList);
    }

    @Test
    void selectLambda() {
        LambdaQueryWrapper<TOrder> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(TOrder::getCode, "DQ5601")
        .orderByAsc(TOrder::getId);

        List<TOrder> orderList = orderMapper.selectList(wrapper);
        System.out.println("orderList = " + orderList);
    }

        // 自定义处理结果集
    @Test
    void selectSQL() {
        QueryWrapper<TOrder> wrapper = new QueryWrapper<>();
        wrapper.eq("code", "DQ5601")
                .orderByAsc("id");
        orderMapper.selectList(wrapper, resultContext->{
            int count = resultContext.getResultCount();
            TOrder order = resultContext.getResultObject();
            System.out.println("count = " + count);
            System.out.println("order = " + order);
        });
    }

}

IDEA 中 Database 连接数据库, 执行上述查询。

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#Mwvlu

3.4. XML文件写SQL

自定义SQL, 多表查询需要在XML中编写SQL语句

在resources目录下创建自定义目录mapper, 存放 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">
<mapper namespace="com.bjpowernode.mapper.OrderMapper">

  <select id="selectOrderById" parameterType="String"
    resultType="com.bjpowernode.domain.TOrder">
    select * from t_order where id = #{id}
  </select>
</mapper>

public interface OrderMapper extends BaseMapper<TOrder> {

    TOrder selectOrderById(String id);
}

#mybatis-plus配置
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
  mapper-locations: classpath:/mapper/**/*.xml

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#zuJfq

3.5. MyBatisX插件

MybatisX 是一款基于 IDEA 的快速开发插件,为效率而生。由baomidou维护的。

安装MyBatisX插件

安装方法:打开IDEA,进入 File -> Settings -> Plugins -> Browse Repositories,输入 mybatisx 搜索并安装。

首先配置idea数据源,才能使用代码生成功能。插件推荐配合Lombok一起使用。

参数说明:

module path:模块路径,此处填写项目所在路径,生成的代码会在相应位置。

basepackage:填写项目的包

encoding:编码,使用UTF-8即可

base path:代码所在路径

relative package:决定实体类会被生成在哪里,默认生成在domain包中,可以修改为其他包但不要为空。

extra class suffix:生成的实体类后面会加上里面的内容。举例:图中如果这里写入Test,最后生成实体类会叫FilesTest

ignore xxx:生成实体类相应字段中忽略指定的前缀/后缀,以下为举例:

①:数据库中有字段名称为type,若ignore field prefix参数设置为ty,则生成实体类中

相应的属性名为pe,忽略了ty;

②:数据库名为files,若ignore table prefix设置为为fi,则生成的实体类的名字变为Les,忽略了fi

superClass:生成实体类继承的父类

点击next后,下一步为生成mapper、service等

参数说明:

  1. annotation:生成的代码中是否加上注解(例如,@TableName等注解)。None为没有注解,其他为字面意思。

  2. options各选项经过尝试后,作用大致如下

  • comment:字段生成注释,注释内容是:数据库列的注释。

  • toString/hashCode/equals:是否生成相应的方法;建议勾选。

  • Lombok:勾选后实体类自动添加Lombok的@Data注解;建议勾选(建议安装Lombok)

  • Actual Column:勾选后,生成的实体类属性名和表中字段名大小写会保持一致。例如,表中有字段classID,勾选该选项后生成的属性名也为classID,未勾选则为classid。建议根据实际需要勾选。

  • Actual Column Annotation:是否对所有属性名都加上注解标明对应字段,例如@TableName。建议勾选。

  • JSR310:Data API:是否使用新标准的时间日期API,包括 Instant、Clock、LocalDateTime、DateTimeFormatter、ZonedDateTime 以及替换 Calendar 的 Chronology 等类。建议勾选(新标准的时间日期API比老版本友好多了,强烈建议使用新版时间日期API)。

  • template:生成mapper、service等相关代码所使用的模板。template可以修改,默认位于 草稿和控制台——扩展——MybatisX 。对应文件夹内即为相关模板的具体配置文件。如果需要重置,右键template文件夹点击重置默认扩展即可。

如果没有使用MybatisPlus,可以选择default-empty模板。或自定义其他模板。

最底下的表:显示所要生成文件的类型、模块路径、基本路径、生成后位于哪个包。后面三个列都可以修改值以符合项目需要。这些也可以在对应的template的文件夹内的.meta.xml中进行修改。

点击Finish即完成对应代码的生成。

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#PSwdF

🔰 第四部分:HTTP Service Client

HTTP协议的请求 在Web大流行,微服务调用也采用HTTP请求方式。 支持HTTP请求处理的技术,比如Apache Http Client, OkHttp等等。

Spring 家族提供了RestTemplate, RestClient, WebClient等类支持HTTP请求的快速处理。

RestClient : 同步阻塞式 HTTP 客户端 , Spring Boot 3.2以后版本可用, 仅支持同步。 替代RestTemplate

WebClient: 异步非阻塞式 HTTP 客户端(反应式编程),学习成本高(反应式编程)、调试复杂。

使用场景:

项目HTTP请求数量少, 只做简单的请求发送,应答接收。 同步请求用RestClient, 需要异步的用WebClient.

1. RestClient

@RestController
public class EmpController {

    @GetMapping("/emp/{name}")
    public Emp queryEmp(@PathVariable String name){
        Emp emp = new Emp();
        emp.setName(name);
        emp.setJob("程序员");
        return emp;
    }
}

@Test
void testGet(){
    RestClient restClient = RestClient.create();
    Emp emp = restClient.get().uri("http://localhost:8080/emp/{name}", "lisi")
    .retrieve()  //执行请求,检索应答
    .onStatus(response -> {
        boolean isError = false;
        if (response.getStatusCode().is2xxSuccessful()) {
            System.out.println("请求成功");
        } else {
            isError = true;
            System.out.println("请求失败" + response.getStatusText());
        }
        return isError;
    })
    .body(Emp.class);  // 应答体转为结果类型

    System.out.println("emp = " + emp);
}

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#h79J5

2. Http Service Client

另一个是Http Service Client 是Spring Boot 为HTTP服务提供单独的设计, 让一个Java接口,包含HTTP处理方法。由SpringBoot生成一个实现此接口代理。代理提供了HTTP请求方法的细节实现。这有助于简化HTTP远程访问,并为如同步或响应式提供了灵活性。

通过这种方式,原本复杂的远程 HTTP 服务调用被转化为本地的方法调用,极大地简化了代码结构。开发者无需再手动编写繁琐的 HTTP 请求发送和响应解析代码,只需像调用本地服务一样调用这些代理接口方法,就能轻松实现对远程 HTTP 服务的访问。这不仅减少了代码量,还提高了代码的可读性和维护性,使开发者能够更加专注于业务逻辑的实现 。

@HttpExchange 是 Spring 6.1(Spring Boot 3.2+)推出的声明式 HTTP 客户端注解,对标 Feign,无需引入第三方依赖,在springboot内置,可结合 RestClient/WebClient 实现“接口定义 + 自动代理”的 HTTP 调用,大幅简化 HTTP 接口调用代码。

Http请求调用由声明的java方法表示, 类似定义Controller类中的handle method。

实现步骤:

  • 创建java interface , 声明http请求方法(形参,返回值,请求映射注解)

  • 基于RestClient或WebClient创建接口代理

  • 通过代理对象执行接口方法,实现Http请求调用,无需关心调用内部细节

2.1. 基础实例

首先创建Api项目, 创建SpringBoot Web项目,提供操作Emp的接口

@RestController
public class EmpController {

    @GetMapping("/emp/{name}")
    public Emp queryEmp(@PathVariable String name){
        Emp emp = new Emp();
        emp.setName(name);
        emp.setJob("程序员");
        return emp;
    }


    @PostMapping("/emp/create")
    public String createEmp(@RequestBody Emp emp){
        System.out.println("emp = " + emp);
        return "创建员工成功!";
    }
}

public class Emp implements Serializable {
    private String name;
    private String job;
    // set | get | toString
}

再次创建访问API接口的项目,模拟两个应用的服务调用

创建Spring Boot4项目, 需要 Http Client即可

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-restclient</artifactId>
</dependency>

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

public class Emp implements Serializable {
    private String name;
    private String job;
    // set | get | toString
}

@HttpExchange(url = "http://localhost:8080")
public interface EmpHttpClient {

    // GET 请求 /emp/xxxx
    @GetExchange("/emp/{empName}")
    Emp queryEmpByName(@PathVariable("empName") String name);


    // POST 请求 /emp/create
    @PostExchange("/emp/create")
    String createEmp(@RequestBody Emp emp);
}

接口定义的方法,用来对Emp服务发起HTTP请求。

@Configuration
public class HttpClientConfig {

    @Bean
    public RestClient restClient(){
        //通过配置RestClient增加http请求的功能
        return RestClient.builder()
        .build();
    }


    @Bean
    public EmpHttpClient empHttpClient(RestClient restClient){
        HttpServiceProxyFactory factory =
        HttpServiceProxyFactory.builderFor(RestClientAdapter.create(restClient)).build();

        return factory.createClient(EmpHttpClient.class);
    }
}

使用代理执行Http请求

@SpringBootTest
class Prj03DeptServiceApplicationTests {

    @Resource
    private EmpHttpClient empHttpClient;

    @Test
    void testQueryEmpByName() {
        Emp emp  = empHttpClient.queryEmpByName("lisi");
        System.out.println("emp = " + emp);
    }

    @Test
    void createEmp(){
        Emp emp = new Emp();
        emp.setName("张三");
        emp.setJob("项目经理");
        String res  = empHttpClient.createEmp(emp);
        System.out.println("res = " + res);

    }

}

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#vtTLo

2.2. 批量注册Client

增加一个Client声明

@HttpExchange(url = "http://localhost:8080")
public interface DeptHttpClient {

    // GET 请求 /dept/xxxx
    @GetExchange("/dept/{id}")
    Emp queryEmpByName(@PathVariable("id") Integer deptId);


    // POST 请求 /dept/create
    @PostExchange("/dept/create")
    String createEmp(@RequestBody Dept dept);
}

@Configuration
public class HttpClientConfigBatch {

    @Bean
    public RestClient restClient(){
        //通过配置RestClient增加http请求的功能
        return RestClient.builder()
        .build();
    }


    @Bean
    public HttpServiceProxyFactory httpServiceProxyFactory(RestClient restClient){
        HttpServiceProxyFactory factory =
        HttpServiceProxyFactory.builderFor(RestClientAdapter.create(restClient)).build();

        return factory;
    }

    @Bean
    public EmpHttpClient empHttpClient(HttpServiceProxyFactory factory){
        return factory.createClient(EmpHttpClient.class);
    }

    @Bean
    public DeptHttpClient deptHttpClient(HttpServiceProxyFactory factory){
        return factory.createClient(DeptHttpClient.class);
    }
}

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#i70g8

2.3. @HttpExchange备注

  1. 接口必须是抽象的:@HttpExchange 只能标注在接口上,不能是类;

@GetExchange 快捷方式,等于@HttpExchange(method = "GET"); @PostExchange同样作用。

  1. 返回值类型限制:

  • 同步客户端(RestClient):支持普通类型(User、List<User>)、ResponseEntity;

  • 异步客户端(WebClient):仅支持反应式类型(Mono/Flux);

  1. 参数注解兼容:@PathVariable/@RequestParam 等注解复用 Spring Web 注解,用法一致;

  2. 异常处理:默认抛出 HttpClientErrorException(4xx)/HttpServerErrorException(5xx),可通过 RestClient 的 defaultStatusHandler 自定义;

Http请求方法(HttpExchange 注解声明的方法)参数:

方法返回值:

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#gXoep

2.4. @ImportHttpServices

@ImportHttpServices 是 Spring Boot 中用于批量导入 HTTP 客户端服务接口的注解。核心作用是:扫描指定包 / 类中的 HTTP 客户端接口(标注 @HttpExchange 等注解),并自动注册为 Spring Bean,避免手动逐个声明代理 Bean,简化 HTTP 客户端的配置与使用。替代手动创建 HttpServiceProxyFactory 并生成代理 Bean 的繁琐操作。

注解来自spring-web:7.1.jar, 要使用他需要spring-web依赖。还需要HTTP Client依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-restclient</artifactId>
</dependency>

这个starter已经包含了 web和 RestClient 类。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(ImportHttpServices.Container.class)
@Import(ImportHttpServiceRegistrar.class)
public @interface ImportHttpServices {


    /**
     * 对Http服务代理分组, 每个组单独配置http服务的url,超时等等
     */
    String group() default HttpServiceGroup.DEFAULT_GROUP_NAME;

    /**
     * 要扫描的类/接口所在的包(通过类指定包路径)
     */
    Class<?>[] basePackageClasses() default {};

    /**
     * 要扫描的包(多个),与 basePackageClasses 二选一/同时使用
     */
    String[] basePackages() default {};
}

示例代码:

创建两个HTTP客户端接口

package com.bjpowernode.dept.service;


@HttpExchange(url = "http://localhost:8080")
public interface DeptHttpClient {

    // GET 请求 /dept/xxxx
    @GetExchange("/dept/{id}")
    Dept getById(@PathVariable("id") Integer deptId);

}

// 两个接口位于不同的包


package com.bjpowernode.emp.service;

@HttpExchange(url = "http://localhost:8080")
public interface EmpHttpClient {

    // GET 请求 /emp/xxxx
    @GetExchange("/emp/{empName}")
    Emp queryEmpByName(@PathVariable("empName") String name);


    // POST 请求 /emp/create
    @PostExchange("/emp/create")
    String createEmp(@RequestBody Emp emp);
}

配置类

@Configuration
@ImportHttpServices(
    basePackages = {
        "com.bjpowernode.dept.service",
        "com.bjpowernode.emp.service"
    }
)

/*
 或者指定类所在包
 @ImportHttpServices(
        basePackageClasses = {
                EmpHttpClient.class,
                DeptHttpClient.class
        }
)*/
public class HttpClientConfig {


    @Bean
    public HttpServiceProxyFactory httpServiceProxyFactory(){

        RestClient restClient = RestClient.builder()
        .baseUrl("http://localhost:8080")
        .defaultHeader("Accept", "application/json")
        .build();
        return HttpServiceProxyFactory.builderFor(RestClientAdapter.create(restClient)).build();
    }
}

单元测试:

@SpringBootTest
class Prj03DeptServiceApplicationTests {

    @Resource
    private EmpHttpClient empHttpClient;

    @Test
    void testQueryEmpByName() {
        Emp emp  = empHttpClient.queryEmpByName("lisi");
        System.out.println("emp = " + emp);
    }


    @Resource
    private DeptHttpClient deptHttpClient;
    @Test
    void testQueryDept(){
        Dept dept = deptHttpClient.getById(1);
        System.out.println("dept = " + dept);
    }


}

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#RSzOl

源码分析:

@Import(ImportHttpServiceRegistrar.class)

class ImportHttpServiceRegistrar extends AbstractHttpServiceRegistrar {} , 实现了ImportBeanDefinitionRegistrar接口,因此 Spring 会回调其 registerBeanDefinitions(...) 方法

ImportBeanDefinitionRegistrar 是 Spring 框架中核心的扩展接口,核心作用是在 Spring 容器解析 @Import 注解时,允许开发者手动向容器注册自定义的 BeanDefinition(Bean 定义),以编程方式灵活注册 Bean。

@Override
public final void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry beanRegistry) {

    registerHttpServices(new DefaultGroupRegistry(), metadata);

    if (this.groupsMetadata.isEmpty()) {
        return;
    }

    RootBeanDefinition proxyRegistryBeanDef = createOrGetRegistry(beanRegistry);
    mergeGroups(proxyRegistryBeanDef);

    this.groupsMetadata.forEachRegistration((groupName, types) -> types.forEach(type -> {
        RootBeanDefinition proxyBeanDef = new RootBeanDefinition();
        proxyBeanDef.setBeanClassName(type);
        proxyBeanDef.setAttribute(HTTP_SERVICE_GROUP_NAME_ATTRIBUTE, groupName);
        proxyBeanDef.setInstanceSupplier(() -> getProxyInstance(groupName, type));
        String beanName = (groupName + "#" + type);
        if (!beanRegistry.containsBeanDefinition(beanName)) {
             //注册 BeanDefinition 到 Spring 容器
            beanRegistry.registerBeanDefinition(beanName, proxyBeanDef);
        }
    }));
}

HttpServiceProxyFactory

工厂用于根据带有@HttpExchange方法的HTTP服务接口创建客户端代理。

首先通过:builder创建HttpServiceProxyFactory实例

public HttpServiceProxyFactory build() {
    Assert.notNull(this.exchangeAdapter, "HttpClientAdapter is required");
    HttpExchangeAdapter adapterToUse = this.exchangeAdapterDecorator.apply(this.exchangeAdapter);

    return new HttpServiceProxyFactory(
        adapterToUse, initArgumentResolvers(), this.requestValuesProcessors,
        this.embeddedValueResolver);
}

public <S> S createClient(Class<S> serviceType) {

    List<HttpServiceMethod> httpServiceMethods =
    MethodIntrospector.selectMethods(serviceType, this::isExchangeMethod).stream()
    .map(method -> createHttpServiceMethod(serviceType, method))
    .toList();

    return getProxy(serviceType, httpServiceMethods);
}

jdk动态代理

总结

@HttpExchange 是 Spring 原生的声明式 HTTP 客户端方案,核心优势:

  • 极简使用:声明式接口 + 自动代理,无需手动编写 HTTP 调用代码;

  • 类型安全:编译期校验参数 / 返回值,避免手动序列化错误;

  • 轻量无依赖:原生内置,无需引入 Feign 等第三方库;

  • 多客户端适配:支持同步(RestClient)和异步(WebClient)

适合场景:

  • 非微服务场景的简单 HTTP 调用;

  • 不想引入额外依赖(如 Feign)的项目;

  • Spring Boot 3.2+ 新项目(替代 RestTemplate)。

  • 若需微服务特性(负载均衡、服务发现),仍推荐使用 OpenFeign;普通 HTTP 调用场景,@HttpExchange 是更轻量的选择。

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#O3AFo

🔰 第五部分:NoSQL-Redis

Java项目使用Redis作为数据存储, 常用的驱动是Jedis , Lettuce, Spring Data Redis

💠Jedis 是纯 Java 实现的 “阻塞式(同步)” 客户端

  • 纯 Java”:用 Java 语言开发,能直接在 Java 项目中集成;

  • 阻塞式(同步)”:发送 Redis 命令后,会等待 Redis 返回结果,再执行后续代码(不是异步非阻塞模式)

  • 使用简单,直接操作 Redis 命令:API 设计和 Redis 原生命令几乎一致(比如 Redis 命令是 SET key value,Jedis 里就是 jedi s.set(key, value)),学习成本低。

  • 线程非安全,需配合连接池(如 JedisPool):单个 Jedis 对象不能被多线程共享(会出现并发问题),所以要通过连接池管理 Jedis 实例(复用连接、控制资源)。

适用场景

  • 简单业务场景或中小型应用;

  • 对性能要求不高的场景(因为阻塞 IO + 线程绑定的特性,高并发下性能不如非阻塞客户端)。

💠 Lettuce 是基于 Netty 的非阻塞式(异步 / 反应式)客户端:

  • “基于 Netty”:底层用 Netty 框架实现网络通信(Netty 是高性能异步 IO 框架);

  • “非阻塞式(异步 / 反应式)”:发送 Redis 命令后,不会阻塞等待结果,而是通过回调 / 流的方式处理响应,更适合高并发场景。

特点

  • 反应式,支持 Reactive Streams 规范,能和 Spring WebFlux 等反应式框架无缝集成;

  • 事件驱动,线程高效:

  • 基于 Netty 的事件驱动模型,少量线程就能处理大量并发连接(不用 “一个连接一个线程”),资源开销低,适合高吞吐量应用。

  • 同样能连接 Redis 单机、哨兵、集群模式,适配各种生产环境。

  • 线程安全,单个 Lettuce 客户端实例可以被多线程共享,不用额外加锁或依赖连接池

适用场景

  • 高并发、高吞吐量的分布式系统(比如大型电商、秒杀场景);

  • 与 Spring WebFlux 等反应式框架集成的项目(契合反应式编程模型)。

简单总结:Lettuce 是高性能、线程安全、支持异步 / 反应式的 Redis 客户端,是 Spring Boot 默认客户端,尤其适合高并发场景。

💠Spring Data Redis,它是 Spring 生态中对 Redis 操作的抽象层框架,它本身不直接实现 Redis 通信,而是基于底层的 Redis 客户端(比如 Jedis、Lettuce)做了封装,提供更统一、更贴合 Spring 风格的操作方式。

特点:

  • 提供统一的操作接口:核心是 RedisTemplate(同步操作)和 ReactiveRedisTemplate(反应式操作),通过这两个模板可以便捷操作 Redis 所有数据类型(String、Hash、List 等)。

  • 可切换底层驱动:可以配置 Jedis 或 Lettuce 作为底层通信客户端(Spring Boot 2.x+ 默认用 Lettuce),不用修改业务代码就能切换驱动。

  • 支持高级功能:内置序列化(解决对象存储问题)、事务、批量操作等能力,不用自己手动封装 Redis 原生命令。

  • 与 Spring 生态深度集成:比如 Spring Boot 会自动配置 RedisTemplate,不用手动编写大量配置代码,开箱即用。

使用场景:

  • 需要与 Spring 框架(如 Spring Boot)无缝集成的项目;

  • 需要便捷操作 Redis 各种数据类型、依赖高级功能(序列化、事务)的场景。

简单总结:Spring Data Redis 是 Spring 项目操作 Redis 的 “标配工具”,它屏蔽了底层客户端的差异,提供统一、易用的操作模板,大幅降低了 Spring 项目中 Redis 的使用成本。

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#Wbh21

1. Redis快速入门

最简单的入门方式是使用Redis独立运行,使用单一的Redis服务器。 通过RedisTemplate连接并访问redis, 读写Redis数据。

首先启动Redis服务(windows,linux的都可以)

然后创建SpringBoot项目, 选择NoSQL-Spring Data Redis(Access + Driver) 依赖。 把Web依赖同时添加到项目

<!--Redis依赖-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!--Web依赖-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>

RedisTemplate模板类

与其他技术使用的模板类相似, jdbc 有JdbcTemplate, redis 有RedisTemplate. 模板类提供了读写Redis数据的方法

这里用的是Junit单元测试。

@SpringBootTest
class Prj02NosqlRedisApplicationTests {


    @Resource
    private RedisTemplate redisTemplate;

    @Test
    void testAddString(){
        redisTemplate.opsForValue().set("name","张三");

        Object name = redisTemplate.opsForValue().get("name");
        System.out.println("name = " + name);

    }

}

连接Redis服务器

spring:
  data:
    redis:
      host: localhost
      port: 6379

查看Redis存储的数据

工具: IDEA的 Database工具, Another-Redis-Desktop-Manager.1.5.9等Redis 客户端GUI工具

存储的Redis key 和 value 是jdk序列化后的结果, 看着像乱码。 可读性差。

RedisTemplate提供的方法

方法

返回对象

作用

opsForValue()

ValueOperations<K, V>

操作string字符串类型

opsForSet()

SetOperations<K, V>

操作set类型

opsForList()

ListOperations<K, V>

操作list类型

opsForHash()

HashOperations<K, HK, HV>

操作hash类型

opsForZSet()

ZSetOperations<K, V>

操作sorted set 类型

其他有 opsForStream() , opsForHyperLogLog() , opsForGeo() 等等。

其他系列方法 boundXXXXOps(), 例如 boundValueOps()。 对一个key进行多种操作,适合的方法。

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#yK6bz

2. Redis序列化

Redis 的核心存储格式仅支持字符串与二进制,而实际开发中常需存储对象、数组等复杂数据,序列化就是解决这类数据在 Redis 中存储与交互问题的关键手段。

合理的序列化方式能压缩数据体积,节省 Redis 的内存空间,同时提升数据传输速度。例如相较于 JSON 序列化,MessagePack 序列化后的数据体积可减小约 30%;适合存储大量热点数据。若不进行序列化,复杂对象不仅操作繁琐,还会占用更多存储空间与网络带宽。

定义:序列化是指把对象转换为字节序列的过程;反序列化是指把字节序列恢复为对象的过程;

一般都会知道有两个目的:对象持久化和网络传输。

  • 对象持久化。实现了数据的持久化,通过序列化可以把数据永久的保存在硬盘上;

  • 网络传输。我们知道网络传输的是字节序列,那么利用序列化实现远程通信,即在网络上传递对象的字节序列。 (序列化与反序列化则实现了 进程通信间的对象传送,发送方需要把这个 Java 对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为 Java 对象)

总结:序列化的目的是将对象变成字节序列,这样一来方便持久化存储到磁盘,避免程序运行结束后对象就从内存里消失,另外字节序列也更便于网络运输和传播

2.1. 序列化基础

RedisTemplate默认 JDK(JdkSerializationRedisSerializer) 序列化会导致 Redis 中存储的是二进制乱码,且对象必须实现 Serializable 接口。

设置序列化:

key和value都可以单独设置序列化。 org.springframework.data.redis.serializer.RedisSerializer是序列化的核心接口,

  • RedisTemplate:通用操作(支持所有数据类型)。

  • StringRedisTemplate:专用于 String 类型(默认 String 序列化,无需自定义)。

在使用RedisTemplate操作数据之前,设置序列化。

@Test
void testAddString(){

    // 设置key序列化
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    // 设置 hash之外类型的序列化
    redisTemplate.setValueSerializer(new StringRedisSerializer());
    // hash类型 field 的序列化
    redisTemplate.setHashKeySerializer(new StringRedisSerializer());
    // hash类型 value 的序列化
    redisTemplate.setHashValueSerializer(new StringRedisSerializer());

    redisTemplate.opsForValue().set("myname","张三");
    Object name = redisTemplate.opsForValue().get("name");
    System.out.println("name = " + name);
}

测试结果

StringRedisTemplate已经配置好 key和value都是String序列化,如果需要使用String序列化,使用这个对象即可。

@Resource
private StringRedisTemplate stringRedisTemplate;
@Test
void testAddString2(){
    stringRedisTemplate.opsForValue().set("myaddress","北京市朝阳区");
    Object name = stringRedisTemplate.opsForValue().get("myaddress");
    System.out.println("name = " + name);
}

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#ZrEeB

2.2. JSON序列化

大多数项目JSON序列化较多,将java对象转为json格式字符串存储到redis。 json的优势可读性高, 几乎所有的编程语言都支持, 应用可以跨语言,多平台支持。

常用配置 key 为string序列化, value为json序列化

Jackson2JsonRedisSerializer<T> SpringBoot4 开始不建议使用了。

GenericJackson2JsonRedisSerializer:SpringBoot4 开始不建议使用了。 SpringBoot 2,3 都是使用的这个类。转换的json包含了类型信息。

JacksonJsonRedisSerializer<T>: 内部使用Jackson3 实现 json序列化有关处理(Spring Boot4全面支持Jackson3版本)。

public class Dept implements Serializable {
    private String name;
    private Integer deptId;
    private Integer count;
    private List<Emp> empList;
    // set | get | toString
}
     

public class Emp implements Serializable {
    private String name;
    private String job;
    // set | get | toString
}

@Test
void testJsonSerializable(){

    // 设置key序列化
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    // 设置 hash之外类型的序列化
    redisTemplate.setValueSerializer(RedisSerializer.json());

    // hash类型 field 的序列化
    redisTemplate.setHashKeySerializer(new StringRedisSerializer());
    // hash类型 value 的序列化
    redisTemplate.setHashValueSerializer(RedisSerializer.json());

    Emp emp1 = new Emp();
    emp1.setName("张三");
    emp1.setJob("程序员");
    redisTemplate.opsForValue().set("emp_dev",emp1);


    Emp emp2 = new Emp();
    emp2.setName("李四");
    emp2.setJob("配置管理");

    Dept dept = new Dept();
    dept.setName("开发部");
    dept.setDeptId(1);
    dept.setCount(2);
    dept.setEmpList(Arrays.asList(emp1,emp2));
    redisTemplate.opsForValue().set("dept_dev",dept);


    HashOperations hashOperations = redisTemplate.opsForHash();
    hashOperations.put("dept_members","zhangsan",emp1);
    hashOperations.put("dept_members","lisi",emp2);

    System.out.println("====操作完成===");

}

Redis存储结果

{
    "@class": "com.bjpowernode.Dept",
    "count": 2,
    "deptId": 1,
    "empList": [
        "java.util.Arrays$ArrayList",
        [
            {
                "@class": "com.bjpowernode.Emp",
                "job": "程序员",
                "name": "张三"
            },
            {
                "@class": "com.bjpowernode.Emp",
                "job": "配置管理",
                "name": "李四"
            }
        ]
    ],
    "name": "开发部"
}

@Test
void testJsonToObject(){
    // 设置key序列化
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    // 设置 hash之外类型的序列化
    redisTemplate.setValueSerializer(RedisSerializer.json());

    // hash类型 field 的序列化
    redisTemplate.setHashKeySerializer(new StringRedisSerializer());
    // hash类型 value 的序列化
    redisTemplate.setHashValueSerializer(RedisSerializer.json());

    Emp emp = (Emp) redisTemplate.opsForValue().get("emp_dev");
    Dept dept = (Dept) redisTemplate.opsForValue().get("dept_dev");


    System.out.println("emp = " + emp);
    System.out.println("dept = " + dept);

    System.out.println("操作完成");

}

总结:

json 格式也是常见的一种,但是在 json 在解析的时候非常耗时,json 结构非常占内存。JSON 不适合存数字,特别是 float, double。

优势

  1. 简单易用开发成本低

  2. 跨语言

  3. 轻量级数据交换

  4. 非冗长性(对比 xml 标签简单括号闭环)

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#AL0yb

2.3. MessagePack序列化

MessagePack(简称 MsgPack)是一种高效的二进制序列化格式,定位是 “像 JSON 一样简单,但更快、更小”,专为数据交换和存储设计,核心目标是在保持易用性的同时,大幅提升序列化 / 反序列化性能和数据压缩率。

  • 体积更小:同等数据下,MsgPack 序列化后的数据体积通常比 JSON 小 30%~50%,比 XML 小更多;

  • 速度更快:二进制解析无需处理字符编码、转义等,序列化 / 反序列化速度是 JSON 的数倍(尤其适合大数据量场景)

MsgPack 支持 JSON 的核心数据类型(字符串、数字、布尔、数组、对象 / 字典、null),且可以和 JSON 无缝转换 —— 任何 MsgPack 数据都能解码为 JSON,反之亦然,保留了 JSON“易理解、跨语言” 的优势。

网站: https://msgpack.org/

github: https://github.com/msgpack/msgpack-java

这里使用springboot 3版本, 4版本和MessagePack目前不成熟。

SpringBoot 3.5.8

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>3.5.8</version>
  <relativePath/> <!-- lookup parent from repository -->
</parent>

主要依赖

<!-- MessagePack -->
<dependency>
  <groupId>org.msgpack</groupId>
  <artifactId>msgpack-core</artifactId>
  <version>0.9.0</version>
</dependency>
<dependency>
  <groupId>org.msgpack</groupId>
  <artifactId>jackson-dataformat-msgpack</artifactId>
  <version>0.9.0</version>
</dependency>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>

@Configuration
public class RedisConfig {

    /**
     * 创建 MessagePack 序列化器
     */
    @Bean
    @ConditionalOnMissingBean
    public RedisSerializer<Object> messagePackRedisSerializer() {
        // 创建 MessagePack 的 ObjectMapper
        ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory());

        // 激活默认类型信息,用于反序列化时识别具体类型
        objectMapper.activateDefaultTyping(
            LaissezFaireSubTypeValidator.instance,
            ObjectMapper.DefaultTyping.NON_FINAL,
            com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY
        );

        return new Jackson2JsonRedisSerializer<>(objectMapper, Object.class);
    }

    /**
     * 配置 RedisTemplate,使用 MessagePack 序列化
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(
        RedisConnectionFactory connectionFactory,
        RedisSerializer<Object> messagePackRedisSerializer) {

        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        // 设置 Key 的序列化器
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        template.setKeySerializer(stringRedisSerializer);
        template.setHashKeySerializer(stringRedisSerializer);

        // 设置 Value 的序列化器(使用 MessagePack)
        template.setValueSerializer(messagePackRedisSerializer);
        template.setHashValueSerializer(messagePackRedisSerializer);

        // 设置默认序列化器
        template.setDefaultSerializer(messagePackRedisSerializer);

        // 启用事务支持
        template.setEnableTransactionSupport(true);

        template.afterPropertiesSet();
        return template;
    }

    /**
     * 可选的:创建专用的 Emp RedisTemplate
     */
    @Bean
    public RedisTemplate<String, Emp> userRedisTemplate(
        RedisConnectionFactory connectionFactory,
        RedisSerializer<Object> messagePackRedisSerializer) {

        RedisTemplate<String, Emp> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        template.setKeySerializer(stringRedisSerializer);
        template.setHashKeySerializer(stringRedisSerializer);

        // 专用类型的序列化器
        ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory());
        Jackson2JsonRedisSerializer<Emp> userSerializer =
        new Jackson2JsonRedisSerializer<>(objectMapper, Emp.class);

        template.setValueSerializer(userSerializer);
        template.setHashValueSerializer(userSerializer);

        template.afterPropertiesSet();
        return template;
    }
}

spring:
  data:
    redis:
      host: localhost
      port: 6379

@Resource
private RedisTemplate<String,Object> redisTemplate;

@Test
void testJsonSerializable(){


    Emp emp1 = new Emp();
    emp1.setName("张三");
    emp1.setJob("程序员");
    redisTemplate.opsForValue().set("emp_dev",emp1);


    Emp emp2 = new Emp();
    emp2.setName("李四");
    emp2.setJob("配置管理");

    Dept dept = new Dept();
    dept.setName("开发部");
    dept.setDeptId(1);
    dept.setCount(2);
    dept.setEmpList(Arrays.asList(emp1,emp2));
    redisTemplate.opsForValue().set("dept_dev",dept);


    HashOperations hashOperations = redisTemplate.opsForHash();
    hashOperations.put("dept_members","zhangsan",emp1);
    hashOperations.put("dept_members","lisi",emp2);

    System.out.println("====操作完成===");

}


@Test
void testJsonToObject(){


    Emp emp = (Emp) redisTemplate.opsForValue().get("emp_dev");
    Dept dept = (Dept) redisTemplate.opsForValue().get("dept_dev");


    System.out.println("emp = " + emp);
    System.out.println("dept = " + dept);

    System.out.println("操作完成");

}

比较两个例子json 和 msgapack 存储的数据大小

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#JSwzb

2.4. 自定义RedisTemplate

自定义 RedisTemplate 是 Spring Boot 整合 Redis 时的核心操作,其核心目标是优化序列化规则(避免默认 JDK 序列化的二进制乱码)、适配业务数据类型(如自定义对象、集合、时间类型)、提升性能和安全性。以下是一套通用、可扩展的 RedisTemplate 自定义方案,涵盖序列化配置、多场景适配、核心优化点,适配 Spring Boot 2.x/3.x

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
package com.bjpowernode.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CustomRedisTemplateConfig {

    
    /**
     * 自定义 RedisTemplate<String, Object>
     * 核心:Key/HashKey 用 String 序列化,Value/HashValue 用 Jackson JSON 序列化
     */
    @Bean
    public RedisTemplate<String, Object> customRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        // 1. 创建 RedisTemplate 实例
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        // 绑定连接工厂
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        // 2. 配置 Jackson 序列化器(处理 Value/HashValue)
        Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();

        // 2.1 配置类型校验器(安全控制,避免恶意类型注入)
        PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder()
                .allowIfSubType("com.bjpowernode") // 仅允许业务包下的类序列化类型信息
                .allowIfSubType("java.util")       // 允许集合类(List/Map 等)
                .build();

        // 2.2 保留类型信息(反序列化时不丢失对象类型)
        objectMapper.activateDefaultTyping(
                ptv,
                ObjectMapper.DefaultTyping.NON_FINAL // 非 final 类保留类型信息
        );

        // 2.3 基础配置:允许访问所有字段、支持时间类型
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.registerModule(new JavaTimeModule()); // 支持 LocalDateTime/LocalDate
        objectMapper.configure(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // 忽略未知字段

        jacksonSerializer.setObjectMapper(objectMapper);

        // 3. 配置 String 序列化器(处理 Key/HashKey,保证可读性)
        StringRedisSerializer stringSerializer = new StringRedisSerializer();

        // 4. 绑定序列化器到 RedisTemplate
        redisTemplate.setKeySerializer(stringSerializer);          // Key 序列化
        redisTemplate.setValueSerializer(jacksonSerializer);       // Value 序列化
        redisTemplate.setHashKeySerializer(stringSerializer);       // Hash Key 序列化
        redisTemplate.setHashValueSerializer(jacksonSerializer);   // Hash Value 序列化

        // 5. 初始化 RedisTemplate(必须调用,否则序列化配置不生效)
        redisTemplate.afterPropertiesSet();

        // 可选:开启事务支持(根据业务需求) @Transaction
        redisTemplate.setEnableTransactionSupport(true);

        return redisTemplate;
    }
}

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#n5HQh

2.5. Redis自动配置

RedisAutoConfiguration 是Redis的自动配置类。 通过@Bean方式,创建了RedisTemplate 和 StringRedisTemplate

以及配置信息类RedisProperties

@ConfigurationProperties("spring.data.redis")
public class RedisProperties {
    .............
}

3. 连接池

Redis 连接池用于复用 Redis 连接、控制资源开销、提升性能(避免频繁创建 / 销毁连接)。当前 Spring Boot 默认使用 Lettuce 客户端,搭配 commons-pool2 实现连接池;若使用 Jedis 客户端,则搭配 JedisPool

无论使用 Lettuce 还是 Jedis,都需要引入连接池依赖

<!-- 连接池核心依赖(commons-pool2) -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

spring:
  data:
    redis:
      host: localhost
      port: 6379
      password: ""  # 无密码留空
      database: 0
      timeout: 10000ms  # 连接超时

      # Lettuce 连接池配置
      lettuce:
        pool:
          max-active: 16    # 最大活跃连接数(根据并发量调整,建议不超过 Redis 最大连接数)
          max-idle: 8       # 最大空闲连接数(空闲时保留的连接数)
          min-idle: 2       # 最小空闲连接数(避免频繁创建连接)
          max-wait: 5000ms  # 获取连接的最大等待时间(超时则抛出异常)
        shutdown-timeout: 100ms  # 客户端关闭时的超时时间(确保连接优雅关闭)

连接池参数

参数名

作用

建议值

max-active

连接池允许的最大活跃连接数(超过则排队)

中小型应用:8~16;高并发:32~64

max-idle

连接池允许的最大空闲连接数(空闲连接过多会被回收)

一般为 max-active 的 50%

min-idle

连接池保持的最小空闲连接数(避免频繁创建连接)

2~4

max-wait

获取连接的最大等待时间(-1 表示无限制,建议设置超时避免线程阻塞)

5000ms(5 秒)

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#qLTCe

4. 客户端工具

4.1. IDEA Database

配置 Redis驱动:

连接Redis DB

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#Xv6tr

4.2. GUI图形客户端

Redis的客户端工具有很多

Another-Redis-Desktop-Manager: 更快、更好、更稳定的Redis桌面(GUI)管理客户端,兼容Windows、Mac、Linux,性能出众,轻松加载海量键值。 支持哨兵, 集群, ssh通道, ssl认证, stream, subscribe订阅, 树状视图, 命令行, 以及暗黑模式; 多种格式化方式, 甚至能够自定义格式化脚本, 满足你的一切需求.

网站: https://goanother.com/cn/

其他:

RedisInsight : Redis团队维护的

Tiny RDM: 国内开发者推出的

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#P57O0

5. RedisOperations&事务

RedisOperations 是 Spring Data Redis 中操作 Redis 的核心接口,它定义了 Redis 所有数据类型(String、Hash、List 等)的通用操作方法,RedisTemplate 是它的主要实现类。

  • RedisOperations是 Spring Data Redis 对 Redis 操作的抽象接口,屏蔽了底层客户端(Lettuce/Jedis)的差异;

  • 提供了统一的 API 操作 Redis 所有数据结构(String、Hash、List、Set、ZSet);

  • 支持事务、序列化、批量操作等高级功能。

@Resource(name="redisTemplate")
private RedisOperations<String,Object> redisOperations;

@Test
void test01(){
    redisOperations.opsForValue().set("welcome", "hello world");
    Object welcome = redisOperations.opsForValue().get("welcome");
    System.out.println("welcome = " + welcome);
}

Redis通过multi、exec和discard命令提供事务支持。这些操作在RedisTemplate上可用。但是,不能保证RedisTemplate使用同一连接运行事务中的所有操作。Spring Data Redis提供了SessionCallback接口,用于在需要使用同一连接执行多个操作时使用,例如在使用Redis事务时

@Test
void testTrans(){
    Emp user = new Emp();
    user.setName("lisi");
    user.setJob("程序员");

    List<Object> txResults = redisOperations.execute(new SessionCallback<List<Object>>() {
        public List<Object> execute(RedisOperations operations) throws DataAccessException {
            try{
                //事务开始
                operations.multi();
                operations.opsForValue().set("user:" + user.getName(), user);
                operations.opsForSet().add("users:job", user.getJob());
                operations.opsForValue().increment("user:count", 1);

                //执行事务
                return operations.exec();
            }catch (RuntimeException e){
                /**
                 * 如果在multi()和exec()之间发生异常(例如,如果Redis在超时时间内未响应,则会发生超时异常),
                 * 那么连接可能会陷入事务状态。为了防止这种情况,需要放弃事务状态以清除连接:
                 */
                operations.discard(); //取消事务
                e.printStackTrace();
            }
            return List.of();

        }
    });

    //打印事务执行结果
    txResults.forEach( obj-> System.out.println("obj = " + obj));
}

总结:

Spring Boot 整合 Redis 是后端开发的核心场景之一,其核心目标是简化 Redis 配置、统一操作方式、适配业务场景(缓存 / 分布式锁 / 队列等), Spring Boot 通过 Spring Data Redis项目,提供了从Spring应用程序轻松配置和访问Redis的功能。它提供了低级和高级抽象,使用户无需担心基础设施问题。

Spring Boot 支持Lettuce 和 Jedis库, 默认Lettuce 高性能,异步的库。 RedisTemplate提供了模板方法操作多种Redis类型。 支持单个Redis,集群Cluster, 哨兵Sentinel访问模式。

commons-pool2 支持Redis连接池, 合理设置连接池大小对系统非常重要。 RedisTemplate提供了多种序列化方案,默认jdk序列化很少使用。 json序列化上手快,学习成本低。 msgpack占用空间小,性能稍胜一筹。

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/liuxiaoshizhiwai/fya052/fcz722cx0goa4g0w#YCJTl

Springboot 4开发环境 2026-03-02
Ubuntu系统安装 2026-03-05