一. 概述
在分布式系统中, 有些请求比较频繁的接口, 接口的返回结果又不经常变化或实时性要求不高, 为了避免频繁查询数据库, 就可以给接口加缓存. 本文将介绍利用代理的方式用redis实现加缓存, 使用时只需在方法上面加一个注解即可
例如:
@CacheData(keyName = "WPR_WPR",cacheTime = 3600)
public Map<String,Object> getRedisMap(String param) throws IOException {
HashMap<String, Object> map = new HashMap<>();
map.put("hello","helloWorld");
return map;
}
只需在方法上加上注解@CacheData
, 即可实现加入缓存
二. 实现原理
2.1 加入缓存的注解
@Target({ElementType.METHOD}) //作用的位置
@Retention(RetentionPolicy.RUNTIME) //作用域
@Documented
public @interface CacheData {
String keyName() default ""; // 缓存唯一KEY,默认类路径名+方法名
long cacheTime() default 3600; // 缓存时间,默认1个小时
String interfaceName() default ""; // 接口名称
/**
* 自定义参数序列化, 默认全部参数序列化, 数组填写从0开始
* 例如: 方法有3个参数, Function(String param1,String param2,String param3)
* 现在只想把第一和第三个参数作为KEY, 那么 paramIndex = {0,2}
* @return
*/
int[] paramIndex() default {};
}
2.2 清除缓存注解
@Target({ElementType.METHOD}) //作用的位置
@Retention(RetentionPolicy.RUNTIME) //作用域
@Documented
public @interface CacheClear {
String[] keyName(); // 缓存唯一KEY
}
2.3 缓存代理实现
@Aspect
@Slf4j
public class CacheAop {
@Around(value = "@annotation(com.wpr.common.annotation.CacheClear)")
public Object cacheClear(ProceedingJoinPoint pjp) throws Throwable {
log.info("----------------缓存清除AOP开始--------------------");
// 执行业务
Object obj = pjp.proceed();
MethodSignature signature = (MethodSignature) pjp.getSignature();
// 获取注解
Method method = signature.getMethod(); //获取Method对象
CacheClear cacheClear = method.getAnnotation(CacheClear.class);
// 如果没有注解则直接返回
if(null == cacheClear) return obj;
// 缓存key
String[] keyNames = cacheClear.keyName();
if(ObjectUtils.isEmpty(keyNames)){
keyNames = new String[] {pjp.getSignature().getDeclaringType().getName() + pjp.getSignature().getName()};
}
if(ObjectUtils.isEmpty(keyNames)) return obj;
try {
// 获取redis工具类
RedisUtil redisUtil = (RedisUtil) SpringContextHolder.getBean(RedisUtil.class.getName());
Arrays.stream(keyNames).forEach(redisKey -> {
log.info("清除缓存的KEY:"+redisKey);
// 删除缓存
redisUtil.deleteByPattern(redisKey+"*");
});
} catch (Exception e) {
log.error("清除缓存报错:"+e.getMessage());
log.error("清除缓存报错:"+e);
}
log.info("----------------缓存清除AOP结束--------------------");
return obj;
}
@Around(value = "@annotation(com.wpr.common.annotation.CacheData)")
public Object aroundCacheData(ProceedingJoinPoint pjp) throws Throwable {
log.info("----------------缓存AOP执行开始--------------------");
MethodSignature signature = (MethodSignature) pjp.getSignature();
// 获取注解
Method method = signature.getMethod(); //获取Method对象
CacheData cacheData = method.getAnnotation(CacheData.class);
// 如果没有注解则直接返回
if(null == cacheData) return pjp.proceed();
// 缓存key
String keyName = cacheData.keyName();
if(StringUtils.isEmpty(keyName)){
log.info("目标方法所属类的名:" + pjp.getSignature().getDeclaringType().getName());
log.info("目标方法名:" + pjp.getSignature().getName());
keyName = pjp.getSignature().getDeclaringType().getName() + pjp.getSignature().getName();
}
log.info("传参缓存KEY:"+keyName);
// 缓存时间
long cacheTime = cacheData.cacheTime();
log.info("传参缓存时间:"+cacheTime+"秒");
// 参数
Object[] args = pjp.getArgs();
// 获取参数下标
int[] paramIndex = cacheData.paramIndex();
// 拼装缓存KEY
AtomicReference<String> redisKey = new AtomicReference<>(keyName);
if(!getRedisKey(args, keyName, paramIndex, redisKey)){
return pjp.proceed();
}
Object obj = null;
try {
// 获取redis工具类
RedisUtil redisUtil = (RedisUtil) SpringContextHolder.getBean(RedisUtil.class.getName());
// 从缓存中获取数据
obj = redisUtil.get(redisKey.get());
if (null == obj) {
obj = pjp.proceed();
redisUtil.set(redisKey.get(), obj, cacheTime);
}
} catch (SerializationException e){
log.error("缓存执行报错: 方法返回对象需实现接口Serialization!");
obj = pjp.proceed();
} catch (Throwable throwable) {
log.error("缓存执行报错:"+throwable.getMessage());
log.error("缓存执行报错:"+throwable);
obj = pjp.proceed();
}
log.info("----------------缓存AOP执行结束--------------------");
return obj;
}
}
/**
* 拼接redisKey
* @param args
* @param keyName
* @param paramIndex
* @param redisKey
* @return
*/
public boolean getRedisKey(Object[] args, String keyName, int[] paramIndex, AtomicReference<String> redisKey) {
boolean flag = true;
if (!ObjectUtils.isEmpty(args)) {
if(!ObjectUtils.isEmpty(paramIndex)){
/**
* paramIndex的元素不能大于args的size
*/
Object[] argsNew = new Object[paramIndex.length];
try {
for(int i = 0;i < paramIndex.length;i++){
int index = paramIndex[0];
Assert.isTrue(index < args.length,"paramIndex存在参数下标大于方法参数个数");
argsNew[i] = args[i];
}
flag = setRedisKey(argsNew, redisKey);
} catch (Exception e) {
log.error("拼接自定义参数序列化报错:"+e.getMessage());
log.error("拼接自定义参数序列化报错:"+e);
flag = false;
}
}else {
flag = setRedisKey(args, redisKey);
}
}
return flag;
}
public boolean setRedisKey(Object[] args, AtomicReference<String> redisKey) {
boolean flag = true;
try {
String paramJson = JSON.toJSONString(args);
log.info("方法参数:"+paramJson);
StringBuffer key = new StringBuffer().append(redisKey.get()).append(":").append(paramJson);
redisKey.set(key.toString());
} catch (Throwable e) {
log.error("方法参数传JSON,报错信息:"+e.getMessage());
log.error("方法参数传JSON,报错堆栈信息:"+e);
flag = false;
}
return flag;
}
2.4 redis全局配置
@Configuration
public class RedisConfig {
/**
* 设置redisTemplate的序列化方式
* @param factory
* @return
*/
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
// 注册到系统中去
redisTemplate.setConnectionFactory(factory);
redisTemplate.setValueSerializer(RedisSerializer.json());
redisTemplate.setHashValueSerializer(RedisSerializer.json());
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setHashKeySerializer(RedisSerializer.string());
return redisTemplate;
}
}
2.4 redis工具类
@Component
public class RedisUtil {
@Resource
private RedisTemplate<String, Object> redisTemplate;
/**
* 设置指定 key 的值
*
* @param key 键
* @param value 值
* @param seconds 时间(秒) seconds要大于0 如果seconds小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long seconds) {
try {
if (seconds == 0) {
redisTemplate.opsForValue().set(key, value);
} else {
redisTemplate.opsForValue().set(key, value, seconds, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 设置指定 key 的值
*
* @param key 键
* @return
*/
public Object get(String key) {
return redisTemplate.opsForValue().get(key);
}
/**
* 模糊匹配批量删除
*
* @param pattern 匹配的前缀
*/
public void deleteByPattern(String pattern) {
Set<String> keys = redisTemplate.keys(pattern);
if (!CollectionUtils.isEmpty(keys)) {
redisTemplate.delete(keys);
}
}
}
2.5 使用示例
- 添加缓存
@CacheData(keyName = "WPR",cacheTime = 3600)
public Map<String,Object> getRedisMap(String param) throws IOException {
HashMap<String, Object> map = new HashMap<>();
map.put("hello","helloWorld");
return map;
}
- 清除缓存
@CacheClear(keyName = "WPR")
public void cacheClear(){
}