spring cache是一种可以通过注解或XML配置方式来实现缓存的组件。 在开发过程中很方便,如@Cacheable、@CacheEvict等。一般都是事先在方法上去加入注解,并且写死了cacheName,比如如下例子:
@Cacheable(value = "oxx.applications", key = "#packageName")
public Application fetchByPackageName(String packageName) {
Application application = new Application();
application.setPackageName(packageName);
Application application1 = applicationMapper.selectOne(application);
return application1;
}
这样只要调个方法,都会去namespace为oxx.applications下去找key为oxx.applications:+packageName的redis键值(PS: 这里使用的是RedisCacheManager)redis存储结构如下:
但是有一天生产环境出问题了,怀疑是缓存问题,于是想看下redis某个key的值,这时候只能通过找DBA了,一两次没所谓,但经常去找就得买可乐了。 于是想搞个后门(做一个接口,前端输入key去拿缓存)。
说干就干,以为很简单,不就是写个Controller吗,简单。。。 可是那么多的key,每一种key的namespace(cacheName)都不同,难道要写N个接口?
@Cacheable(value = "oxx.applications1", key = "#packageName")
public Application fetchByPackageName(String packageName) {
return null;
}
@Cacheable(value = "oxx.applications2", key = "#key2")
public Application fetchByKey2(String key2) {
return null;
}
@Cacheable(value = "oxx.applications3", key = "#key3")
public Application fetchByKey3(String key3) {
return null;
}
..........
..........
..........
..........
..........
..........
例如这样? 这肯定不行。
点开@Cacheable注解,里面有一个cacheResolver的东东,看字面意义是缓存解析的意思,想到能不能通过这个来做下文章,于是查看了Api文档发现可行。
NamedCacheResolver类实现了CacheResolver接口,里面包含了cacheNames集合,并且关联了CacheManager对象,所以是支持通过调用方法方式修改cahceName的。
思路:可以在调用获取缓存接口的时候传入cahceName和key,每次都去动态修改CacheManager的cacheNames属性,最后再去调用加了@Cacheable注解的方法。
代码如下:
package com.xxxxx.xxxx.xxx.xx.xx.cache;
import com.boot.service.cache.manager.CustomizerRedisCacheManager;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.interceptor.NamedCacheResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
/**
* @author
*/
@ConditionalOnProperty(name = "spring.cache.type", havingValue = "redis")
@Service
public class DynamicRedisCache {
@Cacheable(cacheResolver = "namedCacheResolver", key = "#key")
public Object get(String key){
// 只查不覆盖,所以return null,redis默认不会存为null的key
return null;
}
@CacheEvict(cacheResolver = "namedCacheResolver", key = "#key")
public void del(String key) {
}
@Bean
public CustomizerRedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {
CustomizerRedisCacheManager redisCacheManager = new CustomizerRedisCacheManager(redisTemplate);
redisCacheManager.setUsePrefix(true);
return redisCacheManager;
}
@Bean
public NamedCacheResolver namedCacheResolver(RedisCacheManager redisCacheManager){
NamedCacheResolver namedCacheResolver = new NamedCacheResolver(redisCacheManager);
namedCacheResolver.setCacheNames(redisCacheManager.getCacheNames());
return namedCacheResolver;
}
}
RedisCacheManager
import org.springframework.cache.Cache;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.util.CollectionUtils;
import java.util.*;
/**
* @Description RedisCacheManager 覆盖父类的loadCaches方法,父类每次会保留上一次的cacheName
* @Author
* @Date 2019-07-04 09:34
**/
public class CustomizerRedisCacheManager extends RedisCacheManager {
private Set configuredCacheNames;
public CustomizerRedisCacheManager(RedisOperations redisOperations) {
super(redisOperations);
}
@Override
public void setCacheNames(Collection cacheNames) {
Set newCacheNames = CollectionUtils.isEmpty(cacheNames) ? Collections.emptySet() : new HashSet(cacheNames);
this.configuredCacheNames = newCacheNames;
}
@Override
protected Collection extends Cache> loadCaches() {
Set caches = new LinkedHashSet(new ArrayList());
Set cachesToLoad = new LinkedHashSet(this.configuredCacheNames);
if (!CollectionUtils.isEmpty(cachesToLoad)) {
Iterator var3 = cachesToLoad.iterator();
while(var3.hasNext()) {
String cacheName = (String)var3.next();
caches.add(this.createCache(cacheName));
}
}
return caches;
}
}
RedisTemplate
@Bean
public RedisTemplate
注意:@ConditionalOnProperty增加此注解是因为在单元测试中使用的spring.cache.type = simple,如果不加此注解单元测试会跑不过-_- 如果没有单元测试或者也是使用的redis则去掉即可。
redisCacheManager.setUsePrefix(true); – 意思是key包含前缀,一般都是包含前缀的,所以必须要。
CustomizerRedisCacheManager 是我自定义的类,继承了RedisCacheManager,因为每次请求都缓存了上一次的cacheName所以重写了父类的loadCaches,保证每次都是当前的cacheName。
有了NamedCacheResolver 和 CustomizerRedisCacheManager 两个实例,那么我们主要在调用缓存接口的时候动态去修改cacheName就能实现动态获取缓存了
import com.google.common.collect.Lists;
import com.service.cache.manager.CustomizerRedisCacheManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.interceptor.NamedCacheResolver;
import org.springframework.stereotype.Service;
/**
* @author
*/
@Service
public class CacheService {
@Autowired(required = false)
private DynamicRedisCache dynamicRedisCache;
@Autowired(required = false)
private CustomizerRedisCacheManager redisCacheManager;
@Autowired(required = false)
private NamedCacheResolver namedCacheResolver;
/**
* 动态获取spring cache cacheName 对应key的redis值
* @param cacheName
* @param key
* @return
*/
public Object getFromRedis(String cacheName, String key) {
setCacheResolver(cacheName);
return dynamicRedisCache.get(key);
}
/**
* 删除redis指定namespace的键值
* @param cacheName
* @param key
*/
public boolean delRedisCache(String cacheName, String key){
setCacheResolver(cacheName);
dynamicRedisCache.del(key);
return true;
}
private void setCacheResolver(String cacheName){
redisCacheManager.setCacheNames(Lists.newArrayList(cacheName));
redisCacheManager.afterPropertiesSet();
namedCacheResolver.setCacheNames(redisCacheManager.getCacheNames());
namedCacheResolver.setCacheManager(redisCacheManager);
}
}
Controller
@Autowired
private CacheService cacheService;
@GetMapping("/getFromRedis")
public CommonResponse getFromRedis(@Param("cacheName") String cacheName, @Param("cacheKey") String cacheKey) {
return Response.ok("获取成功", cacheService.getFromRedis(cacheName, cacheKey));
}
@GetMapping("/del")
public CommonResponse delRedisCache(@Param("cacheName") String cacheName, @Param("cacheKey") String cacheKey) {
return Response.ok("删除指定缓存成功", cacheService.delRedisCache(cacheName, cacheKey));
}