Spring Boot 自定义 Redis Starter 开发指南(附动态 TTL 实现)

一、功能概述

本 Starter 基于 Spring Boot 2.7+ 实现以下核心能力:

  1. Redis 增强:标准化 RedisTemplate 配置(JSON 序列化 + LocalDateTime 支持)
  2. 缓存扩展:支持 @Cacheable(value = "key#60s") 语法动态设置 TTL
  3. 配置集中化:通过 dyh.cache 前缀提供扩展参数配置

二、项目结构与实现步骤

1. 项目结构(Maven 标准)

Spring Boot 2.7 开始支持新的 AutoConfiguration.imports 文件,同时兼容旧的 spring.factories ;而 Spring Boot 3.0 完全废弃了 spring.factories 中自动配置类的注册,强制使用 AutoConfiguration.imports (其他非自动配置的条目仍可保留在 spring.factories 中)。

text 复制代码
dyh-spring-boot-starter-redis/
├── src/main/java/
│   └── cn/iocoder/dyh/framework/redis/
│       ├── config/                  # 自动配置类
│       │   ├── DyhRedisAutoConfiguration.java
│       │   └── DyhCacheAutoConfiguration.java
│       ├── core/                    # 核心实现
│       │   └── TimeoutRedisCacheManager.java
│       └── properties/             # 配置属性
│           └── DyhCacheProperties.java
├── src/main/resources/
│   └── META-INF/
│ 		├── spring.factories                    # 旧版配置方式
│       └── spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports # 新版配置方式
└── pom.xml

2. 核心实现步骤

步骤 1:配置pom关键依赖

xml 复制代码
    <dependencies>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
        </dependency>
        <!-- DB 相关 -->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-data-27</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId> <!-- 实现对 Caches 的自动化配置 -->
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
        </dependency>
    </dependencies>

步骤 2:配置自动装配

text 复制代码
# 新版使用文件:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
内容示例:
cn.iocoder.dyh.framework.redis.config.DyhRedisAutoConfiguration
cn.iocoder.dyh.framework.redis.config.DyhCacheAutoConfiguration

# 旧版使用:
META-INF/spring.factories
内容示例:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  cn.iocoder.dyh.framework.redis.config.DyhRedisAutoConfiguration,\
  cn.iocoder.dyh.framework.redis.config.DyhCacheAutoConfiguration

步骤 3:定义配置属性类

java 复制代码
@ConfigurationProperties("dyh.cache")
@Data
@Validated
public class DyhCacheProperties {

    /**
     * {@link #redisScanBatchSize} 默认值
     */
    private static final Integer REDIS_SCAN_BATCH_SIZE_DEFAULT = 30;

    /**
     * redis scan 一次返回数量
     */
    private Integer redisScanBatchSize = REDIS_SCAN_BATCH_SIZE_DEFAULT;

}

步骤 4:定义Redis 自动配置类

核心作用:配置 Redis 基础组件

java 复制代码
@AutoConfiguration(before = RedissonAutoConfiguration.class) // 目的:使用自己定义的 RedisTemplate Bean
public class DyhRedisAutoConfiguration {

    /**
     * 创建 RedisTemplate Bean,使用 JSON 序列化方式
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        // 创建 RedisTemplate 对象
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 设置 RedisConnection 工厂。😈 它就是实现多种 Java Redis 客户端接入的秘密工厂。感兴趣的胖友,可以自己去撸下。
        template.setConnectionFactory(factory);
        // 使用 String 序列化方式,序列化 KEY 。
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string());
        // 使用 JSON 序列化方式(库是 Jackson ),序列化 VALUE 。
        template.setValueSerializer(buildRedisSerializer());
        template.setHashValueSerializer(buildRedisSerializer());
        return template;
    }

    public static RedisSerializer<?> buildRedisSerializer() {
        RedisSerializer<Object> json = RedisSerializer.json();
        // 解决 LocalDateTime 的序列化
        ObjectMapper objectMapper = (ObjectMapper) ReflectUtil.getFieldValue(json, "mapper");
        objectMapper.registerModules(new JavaTimeModule());
        return json;
    }

}

步骤 5:定义RedisCache 自动配置类

核心作用:整合缓存组件

java 复制代码
@AutoConfiguration
@EnableConfigurationProperties({CacheProperties.class, DyhCacheProperties.class})
@EnableCaching
public class DyhCacheAutoConfiguration {

    @Bean
    @Primary
    public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        config = config.computePrefixWith(cacheName -> {
            String keyPrefix = cacheProperties.getRedis().getKeyPrefix();
            if (StringUtils.hasText(keyPrefix)) {
                keyPrefix = keyPrefix.lastIndexOf(StrUtil.COLON) == -1 ? keyPrefix + StrUtil.COLON : keyPrefix;
                return keyPrefix + cacheName + StrUtil.COLON;
            }
            return cacheName + StrUtil.COLON;
        });
        // 设置使用 JSON 序列化方式
        config = config.serializeValuesWith(
                RedisSerializationContext.SerializationPair.fromSerializer(buildRedisSerializer()));

        // 设置 CacheProperties.Redis 的属性
        CacheProperties.Redis redisProperties = cacheProperties.getRedis();
        if (redisProperties.getTimeToLive() != null) {
            config = config.entryTtl(redisProperties.getTimeToLive());
        }
        if (!redisProperties.isCacheNullValues()) {
            config = config.disableCachingNullValues();
        }
        if (!redisProperties.isUseKeyPrefix()) {
            config = config.disableKeyPrefix();
        }
        return config;
    }

    @Bean
    public RedisCacheManager redisCacheManager(RedisTemplate<String, Object> redisTemplate,
                                               RedisCacheConfiguration redisCacheConfiguration,
                                               DyhCacheProperties dyhCacheProperties) {
        // 创建 RedisCacheWriter 对象
        RedisConnectionFactory connectionFactory = Objects.requireNonNull(redisTemplate.getConnectionFactory());
        RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory,
                BatchStrategies.scan(dyhCacheProperties.getRedisScanBatchSize()));
        // 创建 TenantRedisCacheManager 对象
        return new TimeoutRedisCacheManager(cacheWriter, redisCacheConfiguration);
    }

}

步骤 6:定义RedisCache 扩展类(动态 TTL 实现)

核心功能:扩展 Spring 默认缓存管理器,实现动态 TTL

java 复制代码
public class TimeoutRedisCacheManager extends RedisCacheManager {

    private static final String SPLIT = "#";

    public TimeoutRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
        super(cacheWriter, defaultCacheConfiguration);
    }

    @Override
    protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
        // 空名称处理(保持父类逻辑)
        if (StrUtil.isEmpty(name)) {
            return super.createRedisCache(name, cacheConfig);
        }
        // 如果使用 # 分隔,大小不为 2,则说明不使用自定义过期时间
        // 拆分缓存名称(示例:"user#60s" → ["user","60s"])
        String[] names = StrUtil.splitToArray(name, SPLIT);
        if (names.length != 2) {
            return super.createRedisCache(name, cacheConfig);
        }

        // 核心:通过修改 cacheConfig 的过期时间,实现自定义过期时间
        if (cacheConfig != null) {
            // 处理带冒号的复杂情况(示例:"user#60s:dev" → ttlStr="60s", names[1]=":dev")
            // 移除 # 后面的 : 以及后面的内容,避免影响解析
            String ttlStr = StrUtil.subBefore(names[1], StrUtil.COLON, false); // 获得 ttlStr 时间部分
            names[1] = StrUtil.subAfter(names[1], ttlStr, false); // 移除掉 ttlStr 时间部分
            // 解析时间
            // 解析并应用 TTL
            Duration duration = parseDuration(ttlStr);
            
            cacheConfig = cacheConfig.entryTtl(duration);
        }
        // 重构缓存名称(示例:"user" + ":dev" → "user:dev")
        // 创建 RedisCache 对象,需要忽略掉 ttlStr
        return super.createRedisCache(names[0] + names[1], cacheConfig);
    }

    /**
     * 解析过期时间 Duration
     *
     * @param ttlStr 过期时间字符串
     * @return 过期时间 Duration
     */
    private Duration parseDuration(String ttlStr) {
        String timeUnit = StrUtil.subSuf(ttlStr, -1);
        switch (timeUnit) {
            case "d":
                return Duration.ofDays(removeDurationSuffix(ttlStr));
            case "h":
                return Duration.ofHours(removeDurationSuffix(ttlStr));
            case "m":
                return Duration.ofMinutes(removeDurationSuffix(ttlStr));
            case "s":
                return Duration.ofSeconds(removeDurationSuffix(ttlStr));
            default:
                return Duration.ofSeconds(Long.parseLong(ttlStr));
        }
    }

    /**
     * 移除多余的后缀,返回具体的时间
     *
     * @param ttlStr 过期时间字符串
     * @return 时间
     */
    private Long removeDurationSuffix(String ttlStr) {
        return NumberUtil.parseLong(StrUtil.sub(ttlStr, 0, ttlStr.length() - 1));
    }

}

步骤 7:打包发布

核心功能:使用 mvn clean install 安装到本地仓库

java 复制代码
<!-- 添加 spring-boot-maven-plugin -->
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

三、使用说明

1. 添加依赖

xml 复制代码
<dependency>
    <groupId>cn.iocoder.boot</groupId>
    <artifactId>dyh-spring-boot-starter-redis</artifactId>
    <version>1.0.0</version>
</dependency>

2. 应用配置

yaml 复制代码
spring:
  redis:
    host: localhost
    port: 6379
  cache:
    type: redis
    redis:
      key-prefix: dyh # 键前缀(可选)
      time-to-live: 1h # 设置默认过期时间为 1 小时
dyh:
  cache:
    redis-scan-batch-size: 50 # 批量大小(可选)

3. 动态 TTL 使用示例

java 复制代码
@Service
public class ProductService {
    
    // 缓存 30 分钟:语法 = 缓存名#时间值+单位
    @Cacheable(value = "product_detail#30m", key = "#productId")
    public ProductDetail getDetail(Long productId) {
        return productDao.findDetail(productId);
    }
    
    // 支持的时间单位:d(天)、h(小时)、m(分钟)、s(秒)
    @Cacheable(value = "hot_products#2h") 
    public List<Product> getHotProducts() {
        return productDao.findHotList();
    }
}

四、核心组件原理解析

1. RedisCacheManager 工作流程

text 复制代码
          用户调用
             ↓
@Cacheable 注解拦截
             ↓
CacheManager 获取缓存
             ↓
TimeoutRedisCacheManager(自定义)
             ├── 解析 name#ttl → 生成带 TTL 的 RedisCacheConfiguration
             ↓
RedisCacheWriter 执行底层操作
             ↓
RedisTemplate 序列化数据

2. 序列化关键实现

java 复制代码
// 修复 LocalDateTime 序列化问题
public static RedisSerializer<?> buildRedisSerializer() {
    RedisSerializer<Object> json = RedisSerializer.json();
    // 通过反射获取 ObjectMapper 实例
    ObjectMapper objectMapper = (ObjectMapper) ReflectUtil.getFieldValue(json, "mapper");
    objectMapper.registerModule(new JavaTimeModule()); // 关键代码
    return json;
}

五、结论

通过此 Starter 的实现,开发者可以快速获得企业级 Redis 集成方案,同时掌握 Spring Boot Starter 开发的核心技巧

相关推荐
星辰徐哥6 小时前
Spring Boot 微服务架构设计与实现
spring boot·后端·微服务
星辰徐哥6 小时前
Spring Boot 数据导入导出与报表生成
spring boot·后端·ui
明夜之约6 小时前
Spring Boot 自动装配源码
java·spring boot·后端
Leaton Lee6 小时前
Spring Boot分层架构详解:从Controller到Service再到Mapper的完整流程
java·spring boot·后端·架构
Micro麦可乐6 小时前
Spring Boot 实战:从零设计一个短链系统(含完整代码与数据库设计)
数据库·spring boot·后端·哈希算法·雪花算法·短链系统
Jinkxs6 小时前
Resilience4j- 与 Spring Boot 快速集成:自动配置与基础注解使用
java·spring boot·后端
毕设源码_郑学姐6 小时前
计算机毕业设计springboot网络相册设计与实现 基于Spring Boot框架的在线相册管理系统开发与应用 Spring Boot驱动的网络影集设计与实践
spring boot·后端·课程设计
辣机小司6 小时前
【踩坑记录:Spring Boot 配置文件读取值不一致?警惕 YAML 的“八进制陷阱”与 SnakeYAML 版本之谜】
java·spring boot·后端·yaml·踩坑记录
一条小锦吕*6 小时前
基于Spring Boot + 数据可视化 + 协同过滤算法的推荐系统设计与实现(源码+论文+部署全讲解)
spring boot·算法·信息可视化
Jinkxs6 小时前
Prometheus - 监控微服务:Spring Boot 应用指标暴露与监控
spring boot·微服务·prometheus