手寫[email protected]註解 支援過期時間設定
阿新 • • 發佈:2018-12-15
-
原理解釋
友情連結 手寫redis @ Cacheable註解引數java物件作為鍵值
@Cacheable註解作用,將帶有該註解方法的返回值存放到redis的的中;
使用方法在方法上使用@Cacheable(鍵=“測試+#P0 + P1#...”)
表示鍵值為測試+方法第一個引數+方法第二個引數,值值為該方法的返回值。
以下源程式碼表示獲取人員列表,Redis的中存放的關鍵值為'領袖'+ leaderGroupId + UUID + yearDetailId
@Override @Cacheable(key="'leader'+#p0+#p1+#p2",value="leader") public List<Leader> listLeaders(String leaderGroupId, String uuid, String yearDetailId) { return sysIndexMapper.listLeaders(leaderGroupId, uuid, yearDetailId); }
等同於
@Override public List<Leader> listLeaders(String leaderGroupId, String uuid, String yearDetailId) { String key = "leader" + leaderGroupId + uuid + yearDetailId; // 判斷快取是否存在redis中 boolean hasKey = redisUtil.hasKey(key); if (hasKey) { //如果存在 返還redis中的值 Object leadersList = redisUtil.get(key); return (List<Leader>) leadersList; } else { List<Leader> leadersQuotaDetailList = sysIndexMapper.listLeaders(leaderGroupId, uuid, yearDetailId); //將查詢結果存放在redis中 redisUtil.set(key, leadersQuotaDetailList); return leadersQuotaDetailList; } }
說白了就是在原方法的前面判斷的關鍵值是否存在的Redis的中,如果存在就取記憶體中的值,如果不存在就查詢資料庫,將查詢結果存放在Redis的的中。
-
實現方法
- 使用代理模式,在方法執行前和執行後可以新增其他處理程式,本文采用springAOP +註解方式。
- 整合的řEDIS,Redis的的封裝工具類
- 原版本不支援關鍵過期時間設定,本文將實現
-
原始碼
快取配置類RedisConfig
package com.huajie.config; import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; /** * Redis快取配置類 */ @Configuration @EnableCaching public class RedisConfig extends CachingConfigurerSupport { @Value("${spring.redis.host}") private String host; @Value("${spring.redis.port}") private int port; @Value("${spring.redis.timeout}") private int timeout; // 自定義快取key生成策略 @Bean public KeyGenerator keyGenerator() { return new KeyGenerator() { @Override public Object generate(Object target, java.lang.reflect.Method method, Object... params) { StringBuffer sb = new StringBuffer(); sb.append(target.getClass().getName()); sb.append(method.getName()); for (Object obj : params) { sb.append(obj.toString()); } return sb.toString(); } }; } // 快取管理器 @Bean public CacheManager cacheManager(@SuppressWarnings("rawtypes") RedisTemplate redisTemplate) { RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate); // 設定快取過期時間 cacheManager.setDefaultExpiration(10000); return cacheManager; } @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<String, Object>(); template.setConnectionFactory(factory); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); // key採用String的序列化方式 template.setKeySerializer(stringRedisSerializer); // hash的key也採用String的序列化方式 template.setHashKeySerializer(stringRedisSerializer); // value序列化方式採用jackson template.setValueSerializer(jackson2JsonRedisSerializer); // hash的value序列化方式採用jackson template.setHashValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; } private void setSerializer(StringRedisTemplate template) { @SuppressWarnings({ "rawtypes", "unchecked" }) Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); template.setValueSerializer(jackson2JsonRedisSerializer); } }
Redis的依賴引入,配置檔案,工具類RedisUtil,網上幾個版本都類似,本文參考以下版本傳送門
https://www.cnblogs.com/zeng1994/p/03303c805731afc9aa9c60dbbd32a323.html
準備工作做好之後開始正式編寫註解@Cacheable nextkey()用做二級快取本文中不會用到
NEXTKEY用法詳情> 設計模式(實戰) - 責任鏈模式 <
建立的Java的註解@ExtCacheable
package com.huajie.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtCacheable {
String key() default "";
String nextKey() default "";
int expireTime() default 1800;//30分鐘
}
SpringAop切面CacheableAspect
package com.huajie.aspect;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.huajie.annotation.ExtCacheable;
import com.huajie.utils.RedisUtil;
/**
* redis快取處理
* 不適用與內部方法呼叫(this.)或者private
*/
@Component
@Aspect
public class CacheableAspect {
@Autowired
private RedisUtil redisUtil;
@Pointcut("@annotation(com.huajie.annotation.ExtCacheable)")
public void annotationPointcut() {
}
@Around("annotationPointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
// 獲得當前訪問的class
Class<?> className = joinPoint.getTarget().getClass();
// 獲得訪問的方法名
String methodName = joinPoint.getSignature().getName();
// 得到方法的引數的型別
Class<?>[] argClass = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
Object[] args = joinPoint.getArgs();
String key = "";
int expireTime = 1800;
try {
// 得到訪問的方法物件
Method method = className.getMethod(methodName, argClass);
method.setAccessible(true);
// 判斷是否存在@ExtCacheable註解
if (method.isAnnotationPresent(ExtCacheable.class)) {
ExtCacheable annotation = method.getAnnotation(ExtCacheable.class);
key = getRedisKey(args,annotation);
expireTime = getExpireTime(annotation);
}
} catch (Exception e) {
throw new RuntimeException("redis快取註解引數異常", e);
}
// 獲取快取是否存在
boolean hasKey = redisUtil.hasKey(key);
if (hasKey) {
return redisUtil.get(key);
} else {
//執行原方法(java反射執行method獲取結果)
Object res = joinPoint.proceed();
//設定快取
redisUtil.set(key, res);
//設定過期時間
redisUtil.expire(key, expireTime);
return res;
}
}
private int getExpireTime(ExtCacheable annotation) {
return annotation.expireTime();
}
private String getRedisKey(Object[] args,ExtCacheable annotation) {
String primalKey = annotation.key();
//獲取#p0...集合
List<String> keyList = getKeyParsList(primalKey);
for (String keyName : keyList) {
int keyIndex = Integer.parseInt(keyName.toLowerCase().replace("#p", ""));
Object parValue = args[keyIndex];
primalKey = primalKey.replace(keyName, String.valueOf(parValue));
}
return primalKey.replace("+","").replace("'","");
}
// 獲取key中#p0中的引數名稱
private static List<String> getKeyParsList(String key) {
List<String> ListPar = new ArrayList<String>();
if (key.indexOf("#") >= 0) {
int plusIndex = key.substring(key.indexOf("#")).indexOf("+");
int indexNext = 0;
String parName = "";
int indexPre = key.indexOf("#");
if(plusIndex>0){
indexNext = key.indexOf("#") + key.substring(key.indexOf("#")).indexOf("+");
parName = key.substring(indexPre, indexNext);
}else{
parName = key.substring(indexPre);
}
ListPar.add(parName.trim());
key = key.substring(indexNext + 1);
if (key.indexOf("#") >= 0) {
ListPar.addAll(getKeyParsList(key));
}
}
return ListPar;
}
}
業務模組使用方法
@Override
@ExtCacheable(key = "Leaders+#p0+#p1+#p2")
// 手機端獲取領導人員列表
public List<Leader> listLeaders(String leaderGroupId, String uuid, String yearDetailId) {
List<Leader> leadersQuotaDetailList = sysIndexMapper.listLeaders(leaderGroupId, uuid, yearDetailId);
return leadersQuotaDetailList;
}
業務模組過期時間使用方法,5分鐘過期
@Override
@ExtCacheable(key = "mobileCacheFlag", expireTime = 60 * 5)
public int cacheFlag() {
int mobileCacheFlag = 1;
mobileCacheFlag = sysIndexMapper.cacheFlag();
return mobileCacheFlag;
}
Redis的的截圖