作者 朱兆平

redis缓存

  1 +package com.tianbo.warehouse.annotation.cache.annotation;
  2 +
  3 +import java.lang.annotation.ElementType;
  4 +import java.lang.annotation.Retention;
  5 +import java.lang.annotation.RetentionPolicy;
  6 +import java.lang.annotation.Target;
  7 +
  8 +/**
  9 + * @author mrz
  10 + * 缓存清除
  11 + */
  12 +@Retention(RetentionPolicy.RUNTIME)
  13 +@Target({ElementType.METHOD})
  14 +public @interface RedisCacheEvict {
  15 +
  16 + //缓存名称
  17 + String[] cacheNames() default "";
  18 +
  19 + //缓存key
  20 + String cacheKey();
  21 +
  22 + //是否清空cacheName的全部数据
  23 + boolean allEntries() default false;
  24 +}
  1 +package com.tianbo.warehouse.annotation.cache.annotation;
  2 +
  3 +import java.lang.annotation.ElementType;
  4 +import java.lang.annotation.Retention;
  5 +import java.lang.annotation.RetentionPolicy;
  6 +import java.lang.annotation.Target;
  7 +
  8 +/**
  9 + * @author mrz
  10 + * 缓存写入/更新
  11 + */
  12 +@Retention(RetentionPolicy.RUNTIME)
  13 +@Target({ElementType.METHOD})
  14 +public @interface RedisCachePut {
  15 +
  16 + //缓存名称,可以多个
  17 + String[] cacheNames() default "";
  18 +
  19 + //缓存key
  20 + String cacheKey();
  21 +
  22 + //有效期时间(单位:秒),默认8个小时
  23 + int expire() default 28800;
  24 +}
  1 +package com.tianbo.warehouse.annotation.cache.annotation;
  2 +
  3 +import java.lang.annotation.ElementType;
  4 +import java.lang.annotation.Retention;
  5 +import java.lang.annotation.RetentionPolicy;
  6 +import java.lang.annotation.Target;
  7 +
  8 +/**
  9 + * @author mrz
  10 + * 用于缓存读取
  11 + */
  12 +@Retention(RetentionPolicy.RUNTIME)
  13 +@Target({ElementType.METHOD})
  14 +public @interface RedisCacheable {
  15 +
  16 + //缓存名称,可以多个
  17 + String[] cacheNames() default "";
  18 +
  19 + //缓存key
  20 + String cacheKey();
  21 +
  22 + //有效期时间(单位:秒),默认8个小时
  23 + int expire() default 28800;
  24 +
  25 + //缓存主动刷新时间(单位:秒),默认不主动刷新
  26 + int reflash() default -1;
  27 +
  28 +}
  1 +package com.tianbo.warehouse.annotation.cache.imp;
  2 +
  3 +import com.tianbo.warehouse.annotation.cache.annotation.RedisCacheEvict;
  4 +import com.tianbo.warehouse.annotation.cache.annotation.RedisCachePut;
  5 +import com.tianbo.warehouse.annotation.cache.annotation.RedisCacheable;
  6 +import com.tianbo.warehouse.util.IO.JDKSerializeUtil;
  7 +import com.tianbo.warehouse.util.redis.DefaultKeyGenerator;
  8 +import lombok.extern.slf4j.Slf4j;
  9 +import org.aspectj.lang.ProceedingJoinPoint;
  10 +import org.aspectj.lang.Signature;
  11 +import org.aspectj.lang.annotation.Around;
  12 +import org.aspectj.lang.annotation.Aspect;
  13 +import org.springframework.beans.factory.annotation.Autowired;
  14 +import org.springframework.data.redis.core.RedisTemplate;
  15 +import org.springframework.data.redis.core.ValueOperations;
  16 +import org.springframework.stereotype.Component;
  17 +
  18 +import javax.annotation.Resource;
  19 +import java.util.concurrent.TimeUnit;
  20 +
  21 +@Aspect
  22 +@Component
  23 +@Slf4j
  24 +public class RedisCacheableAspect {
  25 +
  26 + @Resource
  27 + private RedisTemplate<String , Object> redisTemplate;
  28 +
  29 + @Autowired
  30 + private DefaultKeyGenerator defaultKeyGenerator;
  31 +
  32 + /**
  33 + * @Description: 读取缓存数据
  34 + * @param:
  35 + * @return:
  36 + * @throws:
  37 + * @author: pengl
  38 + * @Date: 2017/11/13 16:46
  39 + */
  40 + @Around(value = "@annotation(cache)")
  41 + public Object cached(final ProceedingJoinPoint pjp , RedisCacheable cache) throws Throwable {
  42 + try{
  43 + //生成缓存KEY
  44 + String[] keys = defaultKeyGenerator.generateKey(pjp, cache.cacheNames(), cache.cacheKey());
  45 + Object valueData = null;
  46 + for(String key : keys){
  47 + //获取缓存中的值
  48 + ValueOperations<String, Object> valueOper = redisTemplate.opsForValue();
  49 + byte[] value = (byte[]) valueOper.get(key);
  50 + if(value != null){
  51 + //如果缓存有值,需要判断刷新缓存设置和当前缓存的失效时间
  52 + int reflash = cache.reflash();
  53 + if(reflash > 0){
  54 + //查询当前缓存失效时间是否在主动刷新规则范围内
  55 + long exp = redisTemplate.getExpire(key, TimeUnit.SECONDS);
  56 + if(exp <= reflash){
  57 + //主动刷新缓存,为不影响本次获取效率,采用异步线程刷新缓存
  58 + }
  59 + }
  60 + return JDKSerializeUtil.unserialize(value);
  61 + }
  62 + //缓存中没有值,执行实际数据查询方法
  63 + if(valueData == null) {
  64 + valueData = pjp.proceed();
  65 + }
  66 + //写入缓存
  67 + if(cache.expire() > 0) {
  68 + valueOper.set(key, JDKSerializeUtil.serialize(valueData),cache.expire(),TimeUnit.SECONDS); //否则设置缓存时间 ,序列化存储
  69 + } else {
  70 + valueOper.set(key, JDKSerializeUtil.serialize(valueData));
  71 + }
  72 + }
  73 + return valueData;
  74 + }catch(Exception e){
  75 + log.error("读取Redis缓存失败,异常信息:" + e.getMessage());
  76 + return pjp.proceed();
  77 + }
  78 + }
  79 + /**
  80 + * @Description: 新增缓存
  81 + * @param:
  82 + * @return:
  83 + * @throws:
  84 + * @author:pengl
  85 + * @Date:2017/11/13 17:09
  86 + */
  87 + @Around(value = "@annotation(cacheput)")
  88 + public Object cachePut (final ProceedingJoinPoint pjp , RedisCachePut cacheput) throws Throwable{
  89 + try{
  90 + //生成缓存KEY
  91 + String[] keys = defaultKeyGenerator.generateKey(pjp, cacheput.cacheNames(), cacheput.cacheKey());
  92 + Object valueData = pjp.proceed();
  93 + //写入缓存
  94 + for(String key : keys){
  95 + ValueOperations<String, Object> valueOper = redisTemplate.opsForValue();
  96 + if(cacheput.expire() > 0) {
  97 + valueOper.set(key, JDKSerializeUtil.serialize(pjp.getArgs()[0]),cacheput.expire(),TimeUnit.SECONDS);
  98 + } else {
  99 + valueOper.set(key, JDKSerializeUtil.serialize(pjp.getArgs()[0]));
  100 + }
  101 + }
  102 + return valueData;
  103 + }catch (Exception e){
  104 + log.error("写入Redis缓存失败,异常信息:" + e.getMessage());
  105 + return pjp.proceed();
  106 + }
  107 + }
  108 + /**
  109 + * @Description: 删除缓存
  110 + * @param:
  111 + * @return:
  112 + * @throws:
  113 + * @author: pengl
  114 + * @Date:2017/11/13 17:09
  115 + */
  116 + @Around(value = "@annotation(cachevict)")
  117 + public Object cacheEvict (final ProceedingJoinPoint pjp , RedisCacheEvict cachevict) throws Throwable{
  118 + try{
  119 + String[] cacheNames = cachevict.cacheNames();
  120 + boolean allEntries = cachevict.allEntries();
  121 + if(allEntries){
  122 + if(cacheNames == null || cacheNames.length == 0){
  123 + Signature signature = pjp.getSignature();
  124 + cacheNames = new String[]{signature.getDeclaringTypeName() + "." + signature.getName()};
  125 + }
  126 + for(String cacheName : cacheNames){
  127 + redisTemplate.delete(redisTemplate.keys("*" + "RedisKey_CacheName_" + cacheName + "*"));
  128 + }
  129 + }else{
  130 + String[] keys = defaultKeyGenerator.generateKey(pjp, cachevict.cacheNames(), cachevict.cacheKey());
  131 + for(String key : keys) {
  132 + redisTemplate.delete(key);
  133 + }
  134 + }
  135 + }catch (Exception e){
  136 + log.error("删除Redis缓存失败,异常信息:" + e.getMessage());
  137 + }
  138 + return pjp.proceed();
  139 + }
  140 +}
  1 +package com.tianbo.warehouse.bean;
  2 +
  3 +import org.springframework.cache.CacheManager;
  4 +import org.springframework.cache.annotation.CachingConfigurerSupport;
  5 +import org.springframework.cache.annotation.EnableCaching;
  6 +import org.springframework.context.annotation.Bean;
  7 +import org.springframework.context.annotation.Configuration;
  8 +import org.springframework.data.redis.cache.RedisCacheConfiguration;
  9 +import org.springframework.data.redis.cache.RedisCacheManager;
  10 +import org.springframework.data.redis.cache.RedisCacheWriter;
  11 +import org.springframework.data.redis.connection.RedisConnectionFactory;
  12 +import org.springframework.data.redis.core.RedisTemplate;
  13 +import org.springframework.data.redis.core.StringRedisTemplate;
  14 +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
  15 +import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
  16 +import org.springframework.data.redis.serializer.RedisSerializationContext;
  17 +import org.springframework.data.redis.serializer.StringRedisSerializer;
  18 +
  19 +import java.time.Duration;
  20 +import java.util.HashMap;
  21 +import java.util.Map;
  22 +
  23 +@Configuration
  24 +@EnableCaching
  25 +public class RedisConfig extends CachingConfigurerSupport {
  26 +
  27 + /**
  28 + * 缓存管理器
  29 + * @param redisConnectionFactory
  30 + * @return
  31 + */
  32 + @Bean
  33 + public CacheManager cacheManager(
  34 + @SuppressWarnings("rawtypes") RedisConnectionFactory redisConnectionFactory) {
  35 + return new RedisCacheManager(
  36 +
  37 + RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
  38 +
  39 + this.getRedisCacheConfigurationWithTtl(246060), // 默认缓存时间策略
  40 +
  41 + this.getRedisCacheConfigurationMap()); // 指定缓存时间策略
  42 +
  43 + }
  44 + /**
  45 + * redis模板操作类
  46 + * @param factory
  47 + * @return
  48 + */
  49 + @Bean
  50 + public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
  51 + final StringRedisTemplate template = new StringRedisTemplate();
  52 + template.setConnectionFactory(factory);
  53 + template.setKeySerializer(new StringRedisSerializer());
  54 + template.setValueSerializer(new JdkSerializationRedisSerializer());
  55 + return template;
  56 + }
  57 +
  58 + /**
  59 + * @Description: 设置缓存时间, 设置key与value的序列化.
  60 + * @param seconds 缓存时间, 单位: 秒.
  61 + * @return
  62 + */
  63 + private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
  64 +
  65 + return RedisCacheConfiguration.defaultCacheConfig()
  66 +
  67 + .serializeKeysWith(RedisSerializationContext.SerializationPair
  68 +
  69 + .fromSerializer(new Jackson2JsonRedisSerializer<>(String.class)))
  70 +
  71 + .serializeValuesWith(RedisSerializationContext.SerializationPair
  72 +
  73 + .fromSerializer(new Jackson2JsonRedisSerializer<>(Object.class)))
  74 +
  75 + .entryTtl(Duration.ofSeconds(seconds));
  76 +
  77 + }
  78 +
  79 +
  80 + private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
  81 +
  82 + Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
  83 +
  84 + redisCacheConfigurationMap.put("short_cache", this.getRedisCacheConfigurationWithTtl(30));
  85 +
  86 + redisCacheConfigurationMap.put("long_cache", this.getRedisCacheConfigurationWithTtl(60));
  87 +
  88 + return redisCacheConfigurationMap;
  89 +
  90 + }
  91 +}
  1 +package com.tianbo.warehouse.util.IO;
  2 +
  3 +import java.io.ByteArrayInputStream;
  4 +import java.io.ByteArrayOutputStream;
  5 +import java.io.IOException;
  6 +import java.io.ObjectInputStream;
  7 +import java.io.ObjectOutputStream;
  8 +import java.util.ArrayList;
  9 +import java.util.List;
  10 +import lombok.extern.slf4j.Slf4j;
  11 +
  12 +/**
  13 + *
  14 + * @author baoy
  15 + *
  16 + */
  17 +@Slf4j
  18 +public class JDKSerializeUtil {
  19 +
  20 + /**
  21 + * 序列化
  22 + * @param object
  23 + * @return
  24 + */
  25 + public static byte[] serialize(Object object) {
  26 + ObjectOutputStream oos = null;
  27 + ByteArrayOutputStream baos = null;
  28 + try {
  29 + baos = new ByteArrayOutputStream();
  30 + oos = new ObjectOutputStream(baos);
  31 + oos.writeObject(object);
  32 + byte[] bytes = baos.toByteArray();
  33 + return bytes;
  34 + } catch (Exception e) {
  35 +
  36 + }
  37 + return null;
  38 + }
  39 +
  40 + /**
  41 + * 反序列化
  42 + * @param bytes
  43 + * @return
  44 + */
  45 + public static Object unserialize(byte[] bytes) {
  46 + ByteArrayInputStream bais = null;
  47 + try {
  48 + bais = new ByteArrayInputStream(bytes);
  49 + ObjectInputStream ois = new ObjectInputStream(bais);
  50 + return ois.readObject();
  51 + } catch (Exception e) {
  52 +
  53 + }
  54 + return null;
  55 + }
  56 +
  57 + /**
  58 + * 序列化存储list
  59 + * @param list
  60 + * @return
  61 + * @throws IOException
  62 + */
  63 + public static byte[] serializeList(List<Object> list) throws IOException {
  64 + if (list == null) {
  65 + throw new NullPointerException("Can't serialize null");
  66 + }
  67 + byte[] rv = null;
  68 + ByteArrayOutputStream bos = null;
  69 + ObjectOutputStream os = null;
  70 + try {
  71 + bos = new ByteArrayOutputStream();
  72 + os = new ObjectOutputStream(bos);
  73 + for (Object o : list) {
  74 + os.writeObject(o);
  75 + }
  76 + os.writeObject(null);
  77 + rv = bos.toByteArray();
  78 + } catch (IOException e) {
  79 + throw new IllegalArgumentException("Non-serializable object", e);
  80 + } finally {
  81 + if (os != null) {
  82 + os.close();
  83 + }
  84 + if (bos != null) {
  85 + bos.close();
  86 + }
  87 + }
  88 + return rv;
  89 + }
  90 +
  91 + /**
  92 + * 反序列化
  93 + * @param in
  94 + * @return
  95 + * @throws IOException
  96 + */
  97 + public static List<Object> deserialize(byte[] in) throws IOException {
  98 + List<Object> list = new ArrayList<Object>();
  99 + ByteArrayInputStream bis = null;
  100 + ObjectInputStream is = null;
  101 + try {
  102 + if (in != null) {
  103 + bis = new ByteArrayInputStream(in);
  104 + is = new ObjectInputStream(bis);
  105 + while (true) {
  106 + Object object = is.readObject();
  107 + if (object == null) {
  108 + break;
  109 + } else {
  110 + list.add(object);
  111 + }
  112 + }
  113 + }
  114 + } catch (IOException e) {
  115 + log.error("Caught IOException decoding %d bytes of data", in.length, e);
  116 + } catch (ClassNotFoundException e) {
  117 + log.error("Caught CNFE decoding %d bytes of data", in.length, e);
  118 + } finally {
  119 + if (is != null) {
  120 + is.close();
  121 + }
  122 + if (bis != null) {
  123 + bis.close();
  124 + }
  125 + }
  126 + return list;
  127 + }
  128 +}
  129 +
  1 +package com.tianbo.warehouse.util.redis;
  2 +
  3 +import com.alibaba.druid.util.StringUtils;
  4 +import lombok.extern.slf4j.Slf4j;
  5 +
  6 +import org.aspectj.lang.ProceedingJoinPoint;
  7 +import org.aspectj.lang.Signature;
  8 +import org.aspectj.lang.reflect.MethodSignature;
  9 +import org.springframework.expression.EvaluationContext;
  10 +import org.springframework.expression.spel.standard.SpelExpressionParser;
  11 +import org.springframework.expression.spel.support.StandardEvaluationContext;
  12 +import org.springframework.stereotype.Component;
  13 +
  14 +import java.util.Arrays;
  15 +
  16 +@Component
  17 +@Slf4j
  18 +public class DefaultKeyGenerator {
  19 + /**
  20 + * @Description: redis key生成
  21 + * @param: cacheKey:key值必传 ,cacheNames:缓存名称,不传取方法路径
  22 + * @return:
  23 + * @throws:
  24 + * @author:pengl
  25 + * @Date:2017/11/13 10:58
  26 + */
  27 + public String[] generateKey(ProceedingJoinPoint pjp, String[] cacheNames, String cacheKey) throws NoSuchMethodException {
  28 +
  29 + if (StringUtils.isEmpty(cacheKey)) {
  30 + throw new NullPointerException("CacheKey can not be null...");
  31 + }
  32 +
  33 + Signature signature = pjp.getSignature();
  34 + if(cacheNames == null || cacheNames.length == 0){
  35 + cacheNames = new String[]{signature.getDeclaringTypeName() + "." + signature.getName()};
  36 + }
  37 + String[] results = new String[cacheNames.length];
  38 + //解析cacheKey
  39 + EvaluationContext evaluationContext = new StandardEvaluationContext();
  40 + if (!(signature instanceof MethodSignature)) {
  41 + throw new IllegalArgumentException("This annotation can only be used for methods...");
  42 + }
  43 + MethodSignature methodSignature = (MethodSignature) signature;
  44 + //method参数列表
  45 + String[] parameterNames = methodSignature.getParameterNames();
  46 + Object[] args = pjp.getArgs();
  47 + for(int i = 0; i < parameterNames.length; i++){
  48 + String parameterName = parameterNames[i];
  49 + evaluationContext.setVariable(parameterName, args[i]);
  50 + }
  51 + for(int j = 0; j < cacheNames.length; j++){
  52 + results[j] = "RedisKey_CacheName_" + cacheNames[j] + "_CacheKey_" +
  53 + new SpelExpressionParser().parseExpression(cacheKey).getValue(evaluationContext, String.class);//暂时只使用String类型
  54 + }
  55 + log.info("=============>>>generateKeys : " + Arrays.toString(results));
  56 + return results;
  57 + }
  58 +}