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 开发的核心技巧

相关推荐
可乐仙人1 小时前
常见MQ及类MQ对比:Redis Stream、Redis Pub/Sub、RocketMQ、Kafka 和 RabbitMQ
redis·kafka·rocketmq
计算机学长felix2 小时前
基于springboot的“嗨玩旅游网站”的设计与实现(源码+数据库+文档+PPT)
spring boot·毕业设计
追逐时光者2 小时前
C#/.NET/.NET Core拾遗补漏合集(25年4月更新)
后端·.net
FG.3 小时前
GO语言入门
开发语言·后端·golang
转转技术团队3 小时前
加Log就卡?不加Log就瞎?”——这个插件治好了我的精神
java·后端
小杜-coding4 小时前
黑马头条day02
java·spring boot·spring·spring cloud·java-ee·maven·mybatis
谦行4 小时前
前端视角 Java Web 入门手册 5.5:真实世界 Web 开发——控制反转与 @Autowired
java·后端
今天多喝热水4 小时前
Redis适用场景
数据库·redis
uhakadotcom4 小时前
PyTorch 2.0:最全入门指南,轻松理解新特性和实用案例
后端·面试·github
bnnnnnnnn4 小时前
前端实现多服务器文件 自动同步宝塔定时任务 + 同步工具 + 企业微信告警(实战详解)
前端·javascript·后端