问题
你每次用Redis做缓存的时候是不是都要写这么一大堆重复的代码?
String result = jedis.get(key); if(StringUtils.isEmpty(result)){ // 查询数据库,并将结果存入Redis }else { return result; }
接下来看我怎么解决这个问题,去除重复的代码,以后每次用缓存的时候只需要添加一个注解就ok了
解决方案
- redis配置(集群)
redis: # host: 127.0.0.1 # port: 6379 # timeout: 60000 jedis: pool.max-active: 8 cluster: nodes: 192.168.174.128:7000,192.168.174.128:7001,192.168.174.128:7002,192.168.174.128:7003,192.168.174.128:7004,192.168.174.128:7005
- 配置类
@Configuration public class RedisConfig { @Value("${spring.redis.cluster.nodes}") private String nodes; /*单机模式*/ @Bean public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) { // key序列化 RedisSerializer<?> stringSerializer = new StringRedisSerializer(); // value序列化 Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); // 配置redisTemplate RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>(); redisTemplate.setConnectionFactory(lettuceConnectionFactory); redisTemplate.setKeySerializer(stringSerializer);// key序列化 redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);// value序列化 redisTemplate.setHashKeySerializer(stringSerializer);// Hash key序列化 redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);// Hash value序列化 redisTemplate.afterPropertiesSet(); return redisTemplate; } /*集群模式*/ @Bean public JedisCluster jedisCluster() { Set<HostAndPort> setNodes = new HashSet<>(); String[] arrayNode = nodes.split(","); //node{IP:PORT} for (String node : arrayNode) { String host = node.split(":")[0]; int port = Integer.parseInt(node.split(":")[1]); setNodes.add(new HostAndPort(host, port)); } return new JedisCluster(setNodes); } }
- 自定义注解
/** * 该注解主要实现查询操作. * 有缓存查询缓存,没缓存查询数据库 * @author zhushanglin * * 操作规范: * key: * 1.用户没有赋值 * 如果key为"",表示用户使用自动生成的key * key:包名.类名.方法名.拼接第一个参数 * 2.如果用户赋值 * key:使用用户的数据 * seconds: * 如果时间不为0,表示用户需要设定超时时间 */ @Target({ElementType.METHOD}) //对方法生效 @Retention(RetentionPolicy.RUNTIME) public @interface Cache_Find { String key() default ""; int seconds() default 0; }
- 注解的切面(对使用该注解的处理)
这里使用的是Redis集群,Redis集群搭建可以参考Redis集群搭建
@Aspect //标识切面 @Component //交给spring容器管理 public class CacheAspect { //required = false 当用户使用时才注入 @Autowired(required = false) private JedisCluster jedis; //注入redis集群对象 /** * 利用AOP规则:动态获取注解对象 * 步骤: * 1.根据key查询redis. * 2.没有数据,需要让目标方法执行.查询的结果保存redis * 3.将json串转化为返回值对象 返回. * @param joinPoint * @param cacheFind * @return */ @SuppressWarnings("unchecked") @Around("@annotation(cacheFind)") public Object around(ProceedingJoinPoint joinPoint, Cache_Find cacheFind) { Object data = null; String key = getKey(joinPoint,cacheFind); //1.从redis中获取数据 String result = jedis.get(key); //2.判断缓存中是否有数据 try { if(StringUtils.isEmpty(result)) { //2.1缓存中没有数据 data = joinPoint.proceed();//目标方法执行 //2.2将返回值结果,转化为JSON String json = ObjectMapperUtil.toJSON(data); if(StringUtils.isEmpty(json)){ // 把空的数据也缓存起来,并设置过期时间,预防缓存穿透! jedis.setex(key,60, json); }else { //2.3判断用户是否编辑时间 //如果有时间,必须设定超时时间. if(cacheFind.seconds()>0) { int seconds = cacheFind.seconds(); jedis.setex(key,seconds, json); }else { jedis.set(key,json); } } System.out.println("AOP查询数据库!!!!!"); }else { //表示缓存数据不为空,将缓存数据转化为对象 Class returnClass = getReturnClass(joinPoint); data = ObjectMapperUtil.toObject(result,returnClass); System.out.println("AOP查询缓存!!!!"); } } catch (Throwable e) { e.printStackTrace(); throw new RuntimeException(e); } return data; } /** * 获取目标方法的返回值类型 * @param joinPoint * @return */ private Class getReturnClass(ProceedingJoinPoint joinPoint) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); return signature.getReturnType(); } /** * 动态获取key * @param joinPoint * @param cacheFind * @return */ private String getKey(ProceedingJoinPoint joinPoint, Cache_Find cacheFind) { String key = cacheFind.key(); if(StringUtils.isEmpty(key)) { //key自动生成 String className = joinPoint.getSignature().getDeclaringTypeName(); String methodName = joinPoint.getSignature().getName(); if(joinPoint.getArgs().length>0) //拼接第一个参数 key = className+"."+methodName+"::" + joinPoint.getArgs()[0]; else key = className+"."+methodName; } return key; } }
上面使用到的ObjectMapperUtil工具类是:
import java.io.IOException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; /** * 作用: 实现对象与JSON串之间的转化 * @author zhushanglin * */ public class ObjectMapperUtil { //常量对象 可以调用对象的方法 线程安全 不安全??? private static final ObjectMapper MAPPER = new ObjectMapper(); /** * 将检查异常转化为运行时异常. * @param data * @return */ public static String toJSON(Object data) { String json = null; try { json = MAPPER.writeValueAsString(data); } catch (JsonProcessingException e) { e.printStackTrace(); throw new RuntimeException(e); } return json; } /** * 根据JSON转化为对象 * 参数:json数据,Class * 返回值:由用户决定. */ @SuppressWarnings("unchecked") public static <T> T toObject(String json,Class<T> target) { T obj = null; try { obj = MAPPER.readValue(json, target); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException(e); } return obj; } }
注解的使用
在需要使用Redis缓存的方法上 使用@Cache_Find注解即可,这样再也不用每次都写文章最开始的那个if else 了,是不是很实用,有用的话帮忙点个赞哦~
@Override @Cache_Find//实现缓存处理 public ItemDesc findItemDescById(Long id) { String url = "http://manage.jt.com/web/item/findItemDescById/"+id; String itemDescJSON = httpClient.doGet(url); return ObjectMapperUtil.toObject(itemDescJSON, ItemDesc.class); }
分布式存取
@Autowired private JedisCluster jedisCluster; jedisCluster.setex("cookie", 20*60, cookie); String cookie = jedisCluster.get("cookie"); // 其他对象类型,参考CacheAspect 中的用法