作者 朱兆平

redis缓存

package com.tianbo.warehouse.annotation.cache.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author mrz
* 缓存清除
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface RedisCacheEvict {
//缓存名称
String[] cacheNames() default "";
//缓存key
String cacheKey();
//是否清空cacheName的全部数据
boolean allEntries() default false;
}
... ...
package com.tianbo.warehouse.annotation.cache.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author mrz
* 缓存写入/更新
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface RedisCachePut {
//缓存名称,可以多个
String[] cacheNames() default "";
//缓存key
String cacheKey();
//有效期时间(单位:秒),默认8个小时
int expire() default 28800;
}
... ...
package com.tianbo.warehouse.annotation.cache.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author mrz
* 用于缓存读取
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface RedisCacheable {
//缓存名称,可以多个
String[] cacheNames() default "";
//缓存key
String cacheKey();
//有效期时间(单位:秒),默认8个小时
int expire() default 28800;
//缓存主动刷新时间(单位:秒),默认不主动刷新
int reflash() default -1;
}
... ...
package com.tianbo.warehouse.annotation.cache.imp;
import com.tianbo.warehouse.annotation.cache.annotation.RedisCacheEvict;
import com.tianbo.warehouse.annotation.cache.annotation.RedisCachePut;
import com.tianbo.warehouse.annotation.cache.annotation.RedisCacheable;
import com.tianbo.warehouse.util.IO.JDKSerializeUtil;
import com.tianbo.warehouse.util.redis.DefaultKeyGenerator;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@Aspect
@Component
@Slf4j
public class RedisCacheableAspect {
@Resource
private RedisTemplate<String , Object> redisTemplate;
@Autowired
private DefaultKeyGenerator defaultKeyGenerator;
/**
* @Description: 读取缓存数据
* @param:
* @return:
* @throws:
* @author: pengl
* @Date: 2017/11/13 16:46
*/
@Around(value = "@annotation(cache)")
public Object cached(final ProceedingJoinPoint pjp , RedisCacheable cache) throws Throwable {
try{
//生成缓存KEY
String[] keys = defaultKeyGenerator.generateKey(pjp, cache.cacheNames(), cache.cacheKey());
Object valueData = null;
for(String key : keys){
//获取缓存中的值
ValueOperations<String, Object> valueOper = redisTemplate.opsForValue();
byte[] value = (byte[]) valueOper.get(key);
if(value != null){
//如果缓存有值,需要判断刷新缓存设置和当前缓存的失效时间
int reflash = cache.reflash();
if(reflash > 0){
//查询当前缓存失效时间是否在主动刷新规则范围内
long exp = redisTemplate.getExpire(key, TimeUnit.SECONDS);
if(exp <= reflash){
//主动刷新缓存,为不影响本次获取效率,采用异步线程刷新缓存
}
}
return JDKSerializeUtil.unserialize(value);
}
//缓存中没有值,执行实际数据查询方法
if(valueData == null) {
valueData = pjp.proceed();
}
//写入缓存
if(cache.expire() > 0) {
valueOper.set(key, JDKSerializeUtil.serialize(valueData),cache.expire(),TimeUnit.SECONDS); //否则设置缓存时间 ,序列化存储
} else {
valueOper.set(key, JDKSerializeUtil.serialize(valueData));
}
}
return valueData;
}catch(Exception e){
log.error("读取Redis缓存失败,异常信息:" + e.getMessage());
return pjp.proceed();
}
}
/**
* @Description: 新增缓存
* @param:
* @return:
* @throws:
* @author:pengl
* @Date:2017/11/13 17:09
*/
@Around(value = "@annotation(cacheput)")
public Object cachePut (final ProceedingJoinPoint pjp , RedisCachePut cacheput) throws Throwable{
try{
//生成缓存KEY
String[] keys = defaultKeyGenerator.generateKey(pjp, cacheput.cacheNames(), cacheput.cacheKey());
Object valueData = pjp.proceed();
//写入缓存
for(String key : keys){
ValueOperations<String, Object> valueOper = redisTemplate.opsForValue();
if(cacheput.expire() > 0) {
valueOper.set(key, JDKSerializeUtil.serialize(pjp.getArgs()[0]),cacheput.expire(),TimeUnit.SECONDS);
} else {
valueOper.set(key, JDKSerializeUtil.serialize(pjp.getArgs()[0]));
}
}
return valueData;
}catch (Exception e){
log.error("写入Redis缓存失败,异常信息:" + e.getMessage());
return pjp.proceed();
}
}
/**
* @Description: 删除缓存
* @param:
* @return:
* @throws:
* @author: pengl
* @Date:2017/11/13 17:09
*/
@Around(value = "@annotation(cachevict)")
public Object cacheEvict (final ProceedingJoinPoint pjp , RedisCacheEvict cachevict) throws Throwable{
try{
String[] cacheNames = cachevict.cacheNames();
boolean allEntries = cachevict.allEntries();
if(allEntries){
if(cacheNames == null || cacheNames.length == 0){
Signature signature = pjp.getSignature();
cacheNames = new String[]{signature.getDeclaringTypeName() + "." + signature.getName()};
}
for(String cacheName : cacheNames){
redisTemplate.delete(redisTemplate.keys("*" + "RedisKey_CacheName_" + cacheName + "*"));
}
}else{
String[] keys = defaultKeyGenerator.generateKey(pjp, cachevict.cacheNames(), cachevict.cacheKey());
for(String key : keys) {
redisTemplate.delete(key);
}
}
}catch (Exception e){
log.error("删除Redis缓存失败,异常信息:" + e.getMessage());
}
return pjp.proceed();
}
}
... ...
package com.tianbo.warehouse.bean;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
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.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
/**
* 缓存管理器
* @param redisConnectionFactory
* @return
*/
@Bean
public CacheManager cacheManager(
@SuppressWarnings("rawtypes") RedisConnectionFactory redisConnectionFactory) {
return new RedisCacheManager(
RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
this.getRedisCacheConfigurationWithTtl(246060), // 默认缓存时间策略
this.getRedisCacheConfigurationMap()); // 指定缓存时间策略
}
/**
* redis模板操作类
* @param factory
* @return
*/
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
final StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new JdkSerializationRedisSerializer());
return template;
}
/**
* @Description: 设置缓存时间, 设置key与value的序列化.
* @param seconds 缓存时间, 单位: 秒.
* @return
*/
private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
return RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new Jackson2JsonRedisSerializer<>(String.class)))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new Jackson2JsonRedisSerializer<>(Object.class)))
.entryTtl(Duration.ofSeconds(seconds));
}
private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
redisCacheConfigurationMap.put("short_cache", this.getRedisCacheConfigurationWithTtl(30));
redisCacheConfigurationMap.put("long_cache", this.getRedisCacheConfigurationWithTtl(60));
return redisCacheConfigurationMap;
}
}
... ...
package com.tianbo.warehouse.util.IO;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
/**
*
* @author baoy
*
*/
@Slf4j
public class JDKSerializeUtil {
/**
* 序列化
* @param object
* @return
*/
public static byte[] serialize(Object object) {
ObjectOutputStream oos = null;
ByteArrayOutputStream baos = null;
try {
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(object);
byte[] bytes = baos.toByteArray();
return bytes;
} catch (Exception e) {
}
return null;
}
/**
* 反序列化
* @param bytes
* @return
*/
public static Object unserialize(byte[] bytes) {
ByteArrayInputStream bais = null;
try {
bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
return ois.readObject();
} catch (Exception e) {
}
return null;
}
/**
* 序列化存储list
* @param list
* @return
* @throws IOException
*/
public static byte[] serializeList(List<Object> list) throws IOException {
if (list == null) {
throw new NullPointerException("Can't serialize null");
}
byte[] rv = null;
ByteArrayOutputStream bos = null;
ObjectOutputStream os = null;
try {
bos = new ByteArrayOutputStream();
os = new ObjectOutputStream(bos);
for (Object o : list) {
os.writeObject(o);
}
os.writeObject(null);
rv = bos.toByteArray();
} catch (IOException e) {
throw new IllegalArgumentException("Non-serializable object", e);
} finally {
if (os != null) {
os.close();
}
if (bos != null) {
bos.close();
}
}
return rv;
}
/**
* 反序列化
* @param in
* @return
* @throws IOException
*/
public static List<Object> deserialize(byte[] in) throws IOException {
List<Object> list = new ArrayList<Object>();
ByteArrayInputStream bis = null;
ObjectInputStream is = null;
try {
if (in != null) {
bis = new ByteArrayInputStream(in);
is = new ObjectInputStream(bis);
while (true) {
Object object = is.readObject();
if (object == null) {
break;
} else {
list.add(object);
}
}
}
} catch (IOException e) {
log.error("Caught IOException decoding %d bytes of data", in.length, e);
} catch (ClassNotFoundException e) {
log.error("Caught CNFE decoding %d bytes of data", in.length, e);
} finally {
if (is != null) {
is.close();
}
if (bis != null) {
bis.close();
}
}
return list;
}
}
... ...
package com.tianbo.warehouse.util.redis;
import com.alibaba.druid.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
@Slf4j
public class DefaultKeyGenerator {
/**
* @Description: redis key生成
* @param: cacheKey:key值必传 ,cacheNames:缓存名称,不传取方法路径
* @return:
* @throws:
* @author:pengl
* @Date:2017/11/13 10:58
*/
public String[] generateKey(ProceedingJoinPoint pjp, String[] cacheNames, String cacheKey) throws NoSuchMethodException {
if (StringUtils.isEmpty(cacheKey)) {
throw new NullPointerException("CacheKey can not be null...");
}
Signature signature = pjp.getSignature();
if(cacheNames == null || cacheNames.length == 0){
cacheNames = new String[]{signature.getDeclaringTypeName() + "." + signature.getName()};
}
String[] results = new String[cacheNames.length];
//解析cacheKey
EvaluationContext evaluationContext = new StandardEvaluationContext();
if (!(signature instanceof MethodSignature)) {
throw new IllegalArgumentException("This annotation can only be used for methods...");
}
MethodSignature methodSignature = (MethodSignature) signature;
//method参数列表
String[] parameterNames = methodSignature.getParameterNames();
Object[] args = pjp.getArgs();
for(int i = 0; i < parameterNames.length; i++){
String parameterName = parameterNames[i];
evaluationContext.setVariable(parameterName, args[i]);
}
for(int j = 0; j < cacheNames.length; j++){
results[j] = "RedisKey_CacheName_" + cacheNames[j] + "_CacheKey_" +
new SpelExpressionParser().parseExpression(cacheKey).getValue(evaluationContext, String.class);//暂时只使用String类型
}
log.info("=============>>>generateKeys : " + Arrays.toString(results));
return results;
}
}
... ...