学一学SpringBoot集成Redis【全流程详解】

245 阅读6分钟

前言

通过在SpringBoot中集成Redis,详细梳理集成过程。包括SpringBoot启动过程中,容器的刷新、自动配置的流程、各类注解的处理。

类比在纯Spring中集成Redis,体验SpringBoot自动配置给开发带来了哪些便利。

一、测试样例

1.1配置文件

application.yml

 spring:
   redis:
     host: *.*.*.*
     port: 6379
     password: 123456
     database: 0
     timeout: 1000
     lettuce:
       pool:
         max-active: 8
         max-idle: 8
         min-idle: 0

1.2依赖项

pom.xml

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

SpringBoot中默认自带了Redis客户端lettuce

image-20241114103352729

1.3单元测试类

 package com.lazy.snail;
 ​
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.core.StringRedisTemplate;
 ​
 @SpringBootTest
 class ApplicationTests {
 ​
     @Autowired
     private StringRedisTemplate stringRedisTemplate;
 ​
     @Test
     void contextLoads() {
         stringRedisTemplate.opsForValue().set("name", "lazysnail");
         stringRedisTemplate.opsForValue().set("email", "lazy_snail@aliyun.com");
     }
 ​
 }
  • 测试结果:

image-20241114105038125

二、自动配置机制版本差异

Spring Boot自2.7版本开始逐步弃用spring.factories文件,并在3.0版本中将其彻底移除。

2.1SpringBoot2.7版本前

image-20241114110648852

在/META-INF/目录下,包含了一系列的自动配置类,这些类可以在满足特定条件时被Spring Boot自动加载和使用。

2.2SpringBoot2.7.x版本

image-20241114110819604

/META-INF/spring目录下新增了org.springframework.boot.autoconfigure.AutoConfiguration.imports,将原本spring.factories中的自动配置类移过来了。

2.3SpringBoot2.6.x及2.7.x获取候选自动配置类差异

2.7.x版本新增了@AutoConfiguration标记类的处理

image-20241114110001934

2.4SpringBoot3.x获取候选自动配置类

3.x后移除了spring.factories的处理

image-20241114111638663

2.5redis自动配置差异

2.7.x版本后引入了@AutoConfiguration注解。

@AutoConfiguration注解表明该类提供的配置可以被Spring Boot自动应用。

image-20241114145909183

Spring Boot 3.x移除spring.factories中自动配置类的处理机制主要是为了:

增强模块化和清晰性,减少隐式依赖。

适应Spring 6和Java 17的现代化要求。

提供更加显式和可控的配置机制,使得开发者能够更好地理解和调试自动配置的过程。

更好地支持条件化配置和@ConfigurationProperties等现代配置方式。

Spring 6和Java 17还没仔细的研究过,后续再分享这块内容

三、redis集成流程

3.1SpringBoot启动过程刷新容器

3.1.1调用BeanFactoryPostProcessor

  • 实例化ConfigurationClassPostProcessor

image-20241114151752941

  • 调用BeanDefinitionRegistryPostProcessor(ConfigurationClassPostProcessor实现了BeanDefinitionRegistryPostProcessor)

image-20241114151859664

  • 处理配置类bean定义信息

    1. 找到Application(启动SpringBoot的主类)(@SpringBootApplication注解中有@Configuration注解)
    2. 解析处理Application类上的注解
    3. 处理@Import注解(@SpringBootApplication注解中的@EnableAutoConfiguration注解含有@Import(AutoConfigurationImportSelector.class))

image-20241114152440691

  • 获取自动配置类入口

image-20241114153020458

  • 获取候选自动配置类

image-20241114153136769

  • 2.7.x版本从spring.factories和org.springframework.boot.autoconfigure.AutoConfiguration.imports中获取配置类

image-20241114153250081

  • redis自动配置类的全限定名被获取到

image-20241114153448705

  • 经过去重、排除、过滤器,筛选出可用配置类

image-20241114153735039

  • 过滤器主要通过配置类上@ConditionalOnClass等条件进行匹配,不符合条件的配置类将被筛除

image-20241114153927132

  • RedisAutoConfiguration作为普通配置类进行解析

image-20241114154451820

  • 处理RedisAutoConfiguration的@Import

image-20241114155139212

  • 引入LettuceConnectionConfiguration配置

由于没有引入Jedis客户端依赖,JedisConnectionConfiguration配置类会被跳过

image-20241114155646289

所有的配置类都解析完成后 (俄罗斯套娃结束),把配置类上所有的bean配置(@Bean、@Import(xxx.Registrar.class)等)加载为bean定义信息。

3.1.2finishBeanFactoryInitialization实例化所有剩余的单例

实例化后,容器中已经存在stringRedisTemplate和redisTemplate实例,可以直接注入使用

image-20241114161534979

四、Redis自动配置类详解

4.1SpringBoot2.7.x版本RedisAutoConfiguration源码

 package org.springframework.boot.autoconfigure.data.redis;
 ​
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Import;
 import org.springframework.data.redis.connection.RedisConnectionFactory;
 import org.springframework.data.redis.core.RedisOperations;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.core.StringRedisTemplate;
 ​
 @AutoConfiguration
 @ConditionalOnClass(RedisOperations.class)
 @EnableConfigurationProperties(RedisProperties.class)
 @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
 public class RedisAutoConfiguration {
 ​
     @Bean
     @ConditionalOnMissingBean(name = "redisTemplate")
     @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
     public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
         RedisTemplate<Object, Object> template = new RedisTemplate<>();
         template.setConnectionFactory(redisConnectionFactory);
         return template;
     }
 ​
     @Bean
     @ConditionalOnMissingBean
     @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
     public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
         return new StringRedisTemplate(redisConnectionFactory);
     }
 ​
 }

4.2注解说明

1. @AutoConfiguration

  • 作用:@AutoConfiguration是Spring Boot 2.7.0引入的注解,用于标记一个类为自动配置类。自动配置类是Spring Boot启动时根据条件自动注入到 Spring 容器中的配置类。
  • 工作原理:这个注解通过条件注解(如@ConditionalOnClass、@ConditionalOnMissingBean)确定是否应该应用某些配置。它的功能类似于@EnableAutoConfiguration和@Configuration的组合。

2. @ConditionalOnClass(RedisOperations.class)

  • 作用:这是一个条件注解,当类路径中存在RedisOperations时,才会加载这个配置类。
  • 具体含义:@ConditionalOnClass(RedisOperations.class) 表明只有在类路径下找到RedisOperations类时,RedisAutoConfiguration 才会被加载。RedisOperations是Spring Data Redis中的接口,代表Redis操作的基本API,因此,只有在Redis相关依赖存在时Redis自动配置才会生效。
  • 本案例在pom中引入了spring-boot-starter-data-redis依赖项,所以类路径下存在RedisOperations。

3. @EnableConfigurationProperties(RedisProperties.class)

  • 作用:这个注解可以让RedisProperties类的属性能够被注入到Spring容器中。RedisProperties类用来读取application.properties或 application.yml中关于Redis相关的配置。
  • 具体含义:RedisProperties类包含Redis配置的相关属性(如 Redis 服务器地址、端口、密码等),@EnableConfigurationProperties注解确保这些配置能够被自动加载并绑定到相应的属性类中。

4. @Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})

  • 作用:@Import注解用于引入其他配置类。这里会把LettuceConnectionConfiguration和JedisConnectionConfiguration两个配置类导入进来。
  • 具体含义:Spring Data Redis提供了两个主要的客户端(spring-boot-starter-data-redis自带了Lettuce的实现,如果需要使用Jedis还要引入redis.clients):Lettuce和Jedis,这两个类负责Redis连接的配置。通过@Import,Redis自动配置类可以根据需要选择使用其中之一来配置Redis连接。

5. @Bean

  • 作用:@Bean注解用于将一个方法标记为一个 Bean,方法返回值将作为Spring容器中的一个Bean。Spring会管理这个Bean的生命周期,并根据需要注入到其他地方。
  • 具体含义:@Bean注解的作用是将redisTemplate和stringRedisTemplate方法返回的对象注册为Spring容器中的Bean。这样,Spring 容器就会自动注入这两个模板类的实例到需要使用Redis的地方。
  • 本案例直接在单元测试类中注入stringRedisTemplate,既可以对redis进行一些简单的操作。

6. @ConditionalOnMissingBean(name = "redisTemplate")

  • 作用:这个注解表示当Spring容器中没有名为"redisTemplate"的Bean时,才会执行该方法并创建RedisTemplate的Bean。
  • 具体含义:如果容器中已经存在一个名为 "redisTemplate" 的 Bean,redisTemplate方法不会被调用,也不会再次创建一个新的 RedisTemplate实例。防止覆盖已经存在的Redis配置。

7. @ConditionalOnSingleCandidate(RedisConnectionFactory.class)

  • 作用:这个注解表示只有当Spring容器中存在且仅有一个RedisConnectionFactory类型的Bean时,才会执行该方法。
  • 具体含义:RedisConnectionFactory是Redis连接的核心接口,@ConditionalOnSingleCandidate确保在容器中只有一个符合条件的连接工厂时,才会进行Redis模板的创建。避免多个Redis连接工厂的冲突。

8. @ConditionalOnMissingBean

  • 作用:这个注解表示当Spring容器中没有指定类型的Bean时,才会执行该方法并创建对应的Bean。
  • 具体含义:如果容器中没有定义过StringRedisTemplate类型的Bean,就会调用 stringRedisTemplate方法创建并注册这个Bean。如果容器中已有这个类型的Bean,这个方法不会被执行。

五、集成完善(初级)

5.1封装一个Redis工具类的redis服务

 package com.lazy.snail;
 ​
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.core.StringRedisTemplate;
 import org.springframework.stereotype.Service;
 import org.springframework.util.CollectionUtils;
 ​
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 ​
 /**
  * @ClassName RedisService
  * @Description TODO
  * @Author lazysnail
  * @Date 2024/11/14 18:05
  * @Version 1.0
  */
 @Service
 public class RedisService {
 ​
     private final RedisTemplate<String, Object> redisTemplate;
     private final StringRedisTemplate stringRedisTemplate;
 ​
     @Autowired
     public RedisService(RedisTemplate<String, Object> redisTemplate, StringRedisTemplate stringRedisTemplate) {
         this.redisTemplate = redisTemplate;
         this.stringRedisTemplate = stringRedisTemplate;
     }
 ​
     // 保存对象
     public void set(String key, Object value) {
         redisTemplate.opsForValue().set(key, value);
     }
 ​
     // 保存对象并设置过期时间
     public void set(String key, Object value, long timeout, TimeUnit timeUnit) {
         redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
     }
 ​
     // 获取对象
     public Object get(String key) {
         return redisTemplate.opsForValue().get(key);
     }
 ​
     // 删除对象
     public void delete(String key) {
         redisTemplate.delete(key);
     }
 ​
     // 设置 String 类型的值
     public void setString(String key, String value) {
         stringRedisTemplate.opsForValue().set(key, value);
     }
 ​
     // 获取 String 类型的值
     public String getString(String key) {
         return stringRedisTemplate.opsForValue().get(key);
     }
 ​
     // 批量删除
     public void deleteBatch(List<String> keys) {
         if (!CollectionUtils.isEmpty(keys)) {
             redisTemplate.delete(keys);
         }
     }
 ​
     // 设置 key 的过期时间
     public Boolean expire(String key, long timeout, TimeUnit timeUnit) {
         return redisTemplate.expire(key, timeout, timeUnit);
     }
 ​
     // 检查 key 是否存在
     public Boolean hasKey(String key) {
         return redisTemplate.hasKey(key);
     }
 }

5.2单元测试类

 package com.lazy.snail;
 ​
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 ​
 @SpringBootTest
 class ApplicationTests {
 ​
     @Autowired
     private RedisService redisService;
 ​
     @Test
     void contextLoads() {
         redisService.setString("name", "lazysnail");
         redisService.setString("email", "lazy_snail@aliyun.com");
     }
 }