通过切面结合Redis自定义缓存注解

问题

你每次用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 中的用法

你可能感兴趣的:(AOP,Redis,redis,缓存,java)