SPRING CACHE REDIS 註解式實現快取策略
阿新 • • 發佈:2018-12-19
為了解決資料庫查詢效率瓶頸,提升併發系統能力,快取的應用已經非常普遍和必要了。剛接觸REDIS時,如何使SPRING框架與REDIS更高效地整合,困擾了我很長時間。 先說下不使用SPRING CACHE時的兩種快取應用模式: 1.使用redis作為持久層的二級快取 特點: 快取策略全部由框架實現,配置少,使用方便,快取key為執行過的SQL語句,value為查詢結果。 缺點: 過於不靈活,作用範圍一般為持久層二級快取的作用範圍,無法精確管理快取粒度。 2.直接操作redis 特點: 控制靈活,想怎麼快取,快取什麼資料完全由開發人員決定。 缺點: 開發量大,如果不實現快取策略,service層侵入性高,並且快取內容雜亂無章難以管理。
下面是SPRING CACHE與REDIS整合的程式碼片段: 新增依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
在一個service的方法中開啟快取:
@Cacheable(keyGenerator ="cacheKeyGenerator" )
@CacheExpire(expire = 60)
public List<UserVo> listUser(String departmentID)
CacheExpire: 自定義快取超時的註解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CacheExpire {
/**
* expire time, default 60s
*/
@AliasFor ("expire")
long value() default 60L;
/**
* expire time, default 60s
*/
@AliasFor("value")
long expire() default 60L;
}
CacheKeyGenerator: 快取key生成器 將目標方法類名,方法名,引數拼接的字串生成MD5,不直接用字串做key主要是為了提升redis檢索快取項的速度,用過長的字串作key並不是一個理想的做法。
public class CacheKeyGenerator implements KeyGenerator {
public static final int NO_PARAM_KEY = 0;
public static final int DEFAULT_KEY = 53;
@Override
public Object generate(Object o, Method method, Object... objects) {
StringBuilder key = new StringBuilder();
key.append(o.getClass().getSimpleName()).append(".").append(method.getName()).append(":");
if (objects.length == 0) {
return key.append(NO_PARAM_KEY).toString();
}else{
try{
return MD5Util.getObjectsMD5(objects,key.toString());
}catch(Exception e){
return key.append(DEFAULT_KEY).toString();
}
}
}
}
RedisConfiguration: 主要用來定義redis序列化器,完成一些配置工作 使用jackson作為redis的資料的預設序列化工具。
@Configuration
@EnableCaching
public class RedisConfiguration extends CachingConfigurerSupport {
private final RedisConnectionFactory redisConnectionFactory;
RedisConfiguration(RedisConnectionFactory redisConnectionFactory) {
this.redisConnectionFactory = redisConnectionFactory;
}
@Bean
public KeyGenerator cacheKeyGenerator() {
CacheKeyGenerator cacheKeyGenerator = new CacheKeyGenerator();
return cacheKeyGenerator;
}
@Bean
public RedisTemplate<Object, Object> redisTemplate() {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
//key的string序列化器
StringRedisSerializer serializer = new StringRedisSerializer();
//value序列化器jackson
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);
//設定key序列化器
redisTemplate.setKeySerializer(serializer);
redisTemplate.setHashKeySerializer(serializer);
//設定value序列化器
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
//預設序列化器
redisTemplate.setDefaultSerializer(jackson2JsonRedisSerializer);
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
/**
* 配置 RedisCacheManager,使用 cache 註解管理 redis 快取
*/
@Bean
@Override
public CacheManager cacheManager() {
// 初始化一個RedisCacheWriter
RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
// 設定預設過期時間
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofDays(7))
// 使用註解時的序列化、反序列化
.serializeKeysWith(TedisCacheManager.STRING_PAIR)
.serializeValuesWith(TedisCacheManager.JACKSON__PAIR);
return new TedisCacheManager(cacheWriter, defaultCacheConfig);
}
}
TedisCacheManager: 處理快取超時,解決異常
public class TedisCacheManager extends RedisCacheManager implements ApplicationContextAware, InitializingBean {
private ApplicationContext applicationContext;
private static Logger log = LogManager.getLogger("TedisCacheManager");
private Map<String, RedisCacheConfiguration> initialCacheConfiguration = new LinkedHashMap<>();
/**
* key serializer
*/
public static final StringRedisSerializer STRING_SERIALIZER = new StringRedisSerializer();
/**
* value serializer
*/
public static final GenericJackson2JsonRedisSerializer JACKSON_SERIALIZER = new GenericJackson2JsonRedisSerializer();
/**
* key serializer pair
*/
public static final RedisSerializationContext.SerializationPair<String> STRING_PAIR = RedisSerializationContext
.SerializationPair.fromSerializer(STRING_SERIALIZER);
/**
* value serializer pair
*/
public static final RedisSerializationContext.SerializationPair<Object> JACKSON__PAIR = RedisSerializationContext
.SerializationPair.fromSerializer(JACKSON_SERIALIZER);
public TedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
super(cacheWriter, defaultCacheConfiguration);
}
@Override
public Cache getCache(String name) {
Cache cache = super.getCache(name);
return new RedisCacheWrapper(cache);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void afterPropertiesSet() {
String[] beanNames = applicationContext.getBeanNamesForType(Object.class);
for (String beanName : beanNames) {
final Class clazz = applicationContext.getType(beanName);
add(clazz);
}
super.afterPropertiesSet();
}
@Override
protected Collection<RedisCache> loadCaches() {
List<RedisCache> caches = new LinkedList<>();
for (Map.Entry<String, RedisCacheConfiguration> entry : initialCacheConfiguration.entrySet()) {
caches.add(super.createRedisCache(entry.getKey(), entry.getValue()));
}
return caches;
}
private void add(final Class clazz) {
ReflectionUtils.doWithMethods(clazz, method -> {
ReflectionUtils.makeAccessible(method);
CacheExpire cacheExpire = AnnotationUtils.findAnnotation(method, CacheExpire.class);
if (cacheExpire == null) {
return;
}
Cacheable cacheable = AnnotationUtils.findAnnotation(method, Cacheable.class);
if (cacheable != null) {
add(cacheable.cacheNames(), cacheExpire);
return;
}
Caching caching = AnnotationUtils.findAnnotation(method, Caching.class);
if (caching != null) {
Cacheable[] cs = caching.cacheable();
if (cs.length > 0) {
for (Cacheable c : cs) {
if (cacheExpire != null && c != null) {
add(c.cacheNames(), cacheExpire);
}
}
}
} else {
CacheConfig cacheConfig = AnnotationUtils.findAnnotation(clazz, CacheConfig.class);
if (cacheConfig != null) {
add(cacheConfig.cacheNames(), cacheExpire);
}
}
}, method -> null != AnnotationUtils.findAnnotation(method, CacheExpire.class));
}
private void add(String[] cacheNames, CacheExpire cacheExpire) {
for (String cacheName : cacheNames) {
if (cacheName == null || "".equals(cacheName.trim())) {
continue;
}
long expire = cacheExpire.expire();
log.info("cacheName: {}, expire: {}", cacheName, expire);
if (expire >= 0) {
// 快取配置
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(expire))
.disableCachingNullValues()
// .prefixKeysWith(cacheName)
.serializeKeysWith(STRING_PAIR)
.serializeValuesWith(JACKSON__PAIR);
initialCacheConfiguration.put(cacheName, config);
} else {
log.warn("{} use default expiration.", cacheName);
}
}
}
protected static class RedisCacheWrapper implements Cache {
private final Cache cache;
RedisCacheWrapper(Cache cache) {
this.cache = cache;
}
@Override
public String getName() {
// log.info("name: {}", cache.getName());
try {
return cache.getName();
} catch (Exception e) {
log.error("getName ---> errmsg: {}", e.getMessage(), e);
return null;
}
}
@Override
public Object getNativeCache() {
// log.info("nativeCache: {}", cache.getNativeCache());
try {
return cache.getNativeCache();
} catch (Exception e) {
log.error("getNativeCache ---> errmsg: {}", e.getMessage(), e);
return null;
}
}
@Override
public ValueWrapper get(Object o) {
// log.info("get ---> o: {}", o);
try {
return cache.get(o);
} catch (Exception e) {
log.error("get ---> o: {}, errmsg: {}", o, e.getMessage(), e);
return null;
}
}
@Override
public <T> T get(Object o, Class<T> aClass) {
// log.info("get ---> o: {}, clazz: {}", o, aClass);
try {
return cache.get(o, aClass);
} catch (Exception e) {
log.error("get ---> o: {}, clazz: {}, errmsg: {}", o, aClass, e.getMessage(), e);
return null;
}
}
@Override
public <T> T get(Object o, Callable<T> callable) {
// log.info("get ---> o: {}", o);
try {
return cache.get(o, callable);
} catch (Exception e) {
log.error("get ---> o: {}, errmsg: {}", o, e.getMessage(), e);
return null;
}
}
@Override
public void put(Object o, Object o1) {
// log.info("put ---> o: {}, o1: {}", o, o1);
try {
cache.put(o, o1);
} catch (Exception e) {
log.error("put ---> o: {}, o1: {}, errmsg: {}", o, o1, e.getMessage(), e);
}
}
@Override
public ValueWrapper putIfAbsent(Object o, Object o1) {
// log.info("putIfAbsent ---> o: {}, o1: {}", o, o1);
try {
return cache.putIfAbsent(o, o1);
} catch (Exception e) {
log.error("putIfAbsent ---> o: {}, o1: {}, errmsg: {}", o, o1, e.getMessage(), e);
return null;
}
}
@Override
public void evict(Object o) {
// log.info("evict ---> o: {}", o);
try {
cache.evict(o);
} catch (Exception e) {
log.error("evict ---> o: {}, errmsg: {}", o, e.getMessage(), e);
}
}
@Override
public void clear() {
// log.info("clear");
try {
cache.clear();
} catch (Exception e) {
log.error("clear ---> errmsg: {}", e.getMessage(), e);
}
}
}
}
最後分享一個連結,是redis淘汰策略的配置方式。