配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
spring:
data:
redis:
host: "localhost"
port: 6379
database: 0
# 用户名,如果有
# username:
# 密码,如果有
# password:
connect-timeout: 5s
# 读超时
timeout: 5s
lettuce:
pool:
# 最小空闲连接
min-idle: 0
# 最大空闲连接
max-idle: 8
# 最大活跃连接
max-active: 8
# 从连接池获取连接 最大超时时间,小于等于0则表示不会超时
max-wait: -1ms
redis主要使用是RedisTemplate和RedisCacheManager,RedisTemplate实现了和Redis相关的直接操作,比如增删改查,RedisCacheManager在RedisTemplate基础上对spring cache进行进一步封装,提供缓存管理能力
RedisTemplate
RedisConfig.java
@Configuration
public class RedisConfig {
/**
* redis template.
*
* @param factory factory
* @return RedisTemplate
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
template.afterPropertiesSet();
return template;
}
}
RedisController.java
@RestController
@RequestMapping("redis")
@AllArgsConstructor
public class RedisController {
private final RedisTemplate<String, String> redisTemplate;
@GetMapping("save")
public void save(String key, String value){
redisTemplate.opsForValue().set(key, value);
}
@GetMapping("get")
public String get(String key){
return redisTemplate.opsForValue().get(key);
}
}
RedisCacheManager
Spring Boot 2.x
@Configuration
@EnableCaching
public class RedisCacheConfig extends CachingConfigurerSupport {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(60))
.disableCachingNullValues();
return RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(redisCacheConfiguration).build();
}
}
Spring Boot 3.x
@Configuration
public class RedisConfig {
private RedisCacheConfiguration determineConfiguration() {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(30)) // 设置超时时长为30秒
.disableCachingNullValues() // 防止写入空值
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
}
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.builder(connectionFactory);
builder.cacheDefaults(determineConfiguration());
return builder.build();
}
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
template.afterPropertiesSet();
return template;
}
}
RedisController.java
@RestController
@RequestMapping("redis")
@AllArgsConstructor
@Slf4j
public class RedisController {
private final RedisTemplate<String, String> redisTemplate;
@GetMapping("save")
public void save(String key, String value){
redisTemplate.opsForValue().set(key, value);
}
@GetMapping("get")
public String get(String key){
return redisTemplate.opsForValue().get(key);
}
@GetMapping("getId")
@Cacheable(cacheNames = "test", key = "#key")
public String getId(String key){
log.info("重新获取");
return "key";
}
}
自定义过期时间
参考网上的文章,一般有三种:
- 自定义cacheNames方式,简单,不够语义化
- 自定义派生@Cacheable注解,这种太复杂了
- 注解,无侵入(推荐)
下面的代码是使用注解的方式:
config/redis/CacheTTL.java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public @interface CacheTTL {
@AliasFor("ttl")
long value() default 60;
@AliasFor("value")
long ttl() default 60;
long preExpireRefresh() default 10;
}
config/redis/CustomCacheAspect.java
@Order(value = 1)
@Aspect
@Component
@Slf4j
@AllArgsConstructor
public class CustomCacheAspect {
private final RedisTemplate<String, Object> redisTemplate;
private final SimpleKeyGenerator keyGenerator = new SimpleKeyGenerator();
private final AtomicInteger asyncLock = new AtomicInteger(0);
// 修改为自己的包名
@After("@annotation(com.xxx.xxx.xxx.config.redis.CacheTTL)")
public void after(JoinPoint point) {
Object target = point.getTarget();
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
try {
if (method.isAnnotationPresent(CacheTTL.class) && method.isAnnotationPresent(Cacheable.class)) {
CacheTTL ttlData = AnnotationUtils.getAnnotation(method, CacheTTL.class);
Cacheable cacheAbleData = AnnotationUtils.getAnnotation(method, Cacheable.class);
long ttl = ttlData.ttl();
long preExpireRefresh = ttlData.preExpireRefresh();
String[] cacheNames = cacheAbleData.cacheNames();
// 默认的keyGenerator生成,如果自定义了自己改一下
Object key = keyGenerator.generate(target, method, point.getArgs());
updateExpire(cacheNames, key, preExpireRefresh, ttl);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void updateExpire(String[] cacheNames, Object key, long preExpireRefresh, long ttl) {
if (asyncLock.compareAndSet(0, 1)) {
Arrays.stream(cacheNames).parallel().forEach(cacheName -> {
cacheName = cacheName + "::" + key;
long expire = redisTemplate.getExpire(cacheName, TimeUnit.SECONDS);
log.info("cache ttl: {} | {} ", cacheName, expire);
if (expire > 0 && expire <= preExpireRefresh || expire > ttl || expire == -1) {
redisTemplate.expire(cacheName, ttl, TimeUnit.SECONDS);
}
});
asyncLock.set(0);
}
}
}
测试接口
@GetMapping("getIdTtl")
@CacheTTL(ttl = 60, preExpireRefresh = 10)
@Cacheable(cacheNames = "test", key = "#key")
public String getIdTtl(String key){
log.info("重新获取");
return "666";
}