已经描述了Spring Cache的基本使用。
本文优化方向
SpringCache主动尝试连接Redis,如果无法连接,缓存降级为默认的JVM缓存。
追加TTL
如果缓存异常,打印错误日志
上代码
注释关SpringChche读取配置文件的缓存。SpringCache自动装配机制,可能导致我们配置类不生效。造成Redis连接异常、无法启动等问题
spring:
cache:
# 指定缓存名称,
cache-names: myCache
# 指定缓存种类,可选redis,IDEA会提示你的
# type: redis
添加配置类
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.SimpleCacheErrorHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.StringUtils;
import java.time.Duration;
/**
* 缓存配置类
* 1. 自动检测Redis连接,如果Redis不可用则降级为本地Map缓存
* 2. 支持通过 cacheName#ttl 格式设置过期时间(单位:秒)
* 3. 配置 JSON 序列化,方便调试
* 4. 配置 CacheErrorHandler,运行时缓存异常不影响业务
*
* @author zanglikun
* @since 2026/3/5 16:30
*/
@Slf4j
@Configuration
@EnableCaching
public class CacheConfig implements CachingConfigurer {
// 用于设置Redis缓存Key失效时间的连接符。
// 主要用改装于:@Cacheable(cacheNames = "myCache#300") 这里的300就是你的时间数字。单位是秒
private static final String TTL_SEPARATOR = "#";
@Bean
@Primary
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
try {
// 尝试获取连接以检查Redis是否可用
redisConnectionFactory.getConnection().close();
log.info("Redis连接成功,启用 CustomRedisCacheManager (支持 #ttl 后缀)");
// 配置Redis缓存: 默认过期1小时 + JSON序列化
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
// 使用自定义的 RedisCacheManager
return new CustomRedisCacheManager(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory), defaultCacheConfig);
} catch (Exception e) {
log.error("Redis连接失败 ({} ),SpringCache自动降级为 ConcurrentMapCacheManager (本地内存缓存)", e.getMessage());
// 降级为本地内存缓存
return new ConcurrentMapCacheManager();
}
}
/**
* 运行时缓存异常处理策略
* 当Redis操作(Get/Put/Evict)失败时,仅打印日志,不中断业务逻辑
*/
@Override
public CacheErrorHandler errorHandler() {
return new SimpleCacheErrorHandler() {
@Override
public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
log.error("Cache Get Error: cacheName={}, key={}, msg={}", cache.getName(), key, exception.getMessage());
// 视为缓存未命中,继续查库
}
@Override
public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
log.error("Cache Put Error: cacheName={}, key={}, msg={}", cache.getName(), key, exception.getMessage());
// 写入缓存失败,但业务数据已更新,不影响主流程
}
@Override
public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
log.error("Cache Evict Error: cacheName={}, key={}, msg={}", cache.getName(), key, exception.getMessage());
// 缓存清理失败,可能会导致短暂的数据不一致,但保证业务可用性
}
@Override
public void handleCacheClearError(RuntimeException exception, Cache cache) {
log.error("Cache Clear Error: cacheName={}, msg={}", cache.getName(), exception.getMessage());
}
};
}
/**
* 自定义 RedisCacheManager,支持解析 cacheName#ttl
*/
public static class CustomRedisCacheManager extends RedisCacheManager {
public CustomRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
super(cacheWriter, defaultCacheConfiguration);
}
@Override
protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
String cacheName = name;
// 解析 TTL。因为你会在@Cacheable(cacheNames = "myCache#300") 这里逻辑解析你的 300就是你的时间数字。单位是秒
if (StringUtils.hasText(name) && name.contains(TTL_SEPARATOR)) {
String[] parts = name.split(TTL_SEPARATOR);
cacheName = parts[0];
if (parts.length > 1) {
try {
long ttl = Long.parseLong(parts[1]);
// 设置自定义 TTL
cacheConfig = cacheConfig.entryTtl(Duration.ofSeconds(ttl));
} catch (NumberFormatException e) {
log.warn("解析缓存TTL失败,name: {}, error: {}", name, e.getMessage());
}
}
}
return super.createRedisCache(cacheName, cacheConfig);
}
}
}
使用
@Cacheable(cacheNames = "calculateData")
// 默认缓存1小时
public String calculateData(String type) {
}
@Cacheable(cacheNames = "calculateData#86400")
// 指定缓存1天
public String calculateData(String type) {
}
特殊说明:
上述文章均是作者实际操作后产出。烦请各位,请勿直接盗用!转载记得标注原文链接:www.zanglikun.com
第三方平台不会及时更新本文最新内容。如果发现本文资料不全,可访问本人的Java博客搜索:标题关键字。以获取最新全部资料 ❤
免责声明: 本站文章旨在总结学习互联网技术过程中的经验与见解。任何人不得将其用于违法或违规活动!所有违规内容均由个人自行承担,与作者无关。
第三方平台不会及时更新本文最新内容。如果发现本文资料不全,可访问本人的Java博客搜索:标题关键字。以获取最新全部资料 ❤
免责声明: 本站文章旨在总结学习互联网技术过程中的经验与见解。任何人不得将其用于违法或违规活动!所有违规内容均由个人自行承担,与作者无关。
