原理解释

友情链接 手写Redis @Cacheable注解参数java对象作为键值

@Cacheable注解作用,将带有该注解方法的返回值存放到redis的的中;

使用方法在方法上使用@Cacheable(键=“测试+#P0 + P1#...”)

表示键值为测试+方法第一个参数+方法第二个参数,值值为该方法的返回值。

以下源代码表示获取人员列表,Redis的中存放的关键值为'领袖'+ leaderGroupId + UUID + yearDetailId

    @Override
	@Cacheable(key="'leader'+#p0+#p1+#p2",value="leader")
	public List<Leader> listLeaders(String leaderGroupId, String uuid, String yearDetailId) {
		return sysIndexMapper.listLeaders(leaderGroupId, uuid, yearDetailId);
	}

等同于

    @Override
	public List<Leader> listLeaders(String leaderGroupId, String uuid, String yearDetailId) {
		String key = "leader" + leaderGroupId + uuid + yearDetailId;
		// 判断缓存是否存在redis中
		boolean hasKey = redisUtil.hasKey(key);
		if (hasKey) {
            //如果存在 返还redis中的值
			Object leadersList = redisUtil.get(key);
			return (List<Leader>) leadersList;
		} else {
			List<Leader> leadersQuotaDetailList = sysIndexMapper.listLeaders(leaderGroupId, uuid, yearDetailId);
            //将查询结果存放在redis中
			redisUtil.set(key, leadersQuotaDetailList);
			return leadersQuotaDetailList;
		}
	}
    ```
说白了就是在原方法的前面判断的关键值是否存在的Redis的中,如果存在就取内存中的值,如果不存在就查询数据库,将查询结果存放在Redis的的中。

#### 实现方法
使用代理模式,在方法执行前和执行后可以添加其他处理程序,本文采用springAOP +注解方式。
集成Redis,封装Redis工具类
原版本不支持关键过期时间设置,本文将实现
源代码
缓存配置类RedisConfig
```java
    package com.huajie.config;

    import org.springframework.beans.factory.annotation.Value;

    import org.springframework.cache.CacheManager;

    import org.springframework.cache.annotation.CachingConfigurerSupport;

    import org.springframework.cache.annotation.EnableCaching;

    import org.springframework.cache.interceptor.KeyGenerator;

    import org.springframework.context.annotation.Bean;

    import org.springframework.context.annotation.Configuration;

    import org.springframework.data.redis.cache.RedisCacheManager;

    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.StringRedisSerializer;

    import com.fasterxml.jackson.annotation.JsonAutoDetect;

    import com.fasterxml.jackson.annotation.PropertyAccessor;

    import com.fasterxml.jackson.databind.ObjectMapper;

    

    /**

    * Redis缓存配置类

    */

    @Configuration

    @EnableCaching

    public class RedisConfig extends CachingConfigurerSupport {

 

	@Value("${spring.redis.host}")

	private String host;

	@Value("${spring.redis.port}")

	private int port;

	@Value("${spring.redis.timeout}")

	private int timeout;

 

	// 自定义缓存key生成策略

	@Bean

	public KeyGenerator keyGenerator() {

		return new KeyGenerator() {

			@Override

			public Object generate(Object target, java.lang.reflect.Method method, Object... params) {

				StringBuffer sb = new StringBuffer();

				sb.append(target.getClass().getName());

				sb.append(method.getName());

				for (Object obj : params) {

					sb.append(obj.toString());

				}

				return sb.toString();

			}

		};

	}

 

	// 缓存管理器

	@Bean

	public CacheManager cacheManager(@SuppressWarnings("rawtypes") RedisTemplate redisTemplate) {

		RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);

		// 设置缓存过期时间

		cacheManager.setDefaultExpiration(10000);

		return cacheManager;

	}

 

	@Bean

	public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {

		RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();

		template.setConnectionFactory(factory);

		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);

		StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

		// key采用String的序列化方式

		template.setKeySerializer(stringRedisSerializer);

		// hash的key也采用String的序列化方式

		template.setHashKeySerializer(stringRedisSerializer);

		// value序列化方式采用jackson

		template.setValueSerializer(jackson2JsonRedisSerializer);

		// hash的value序列化方式采用jackson

		template.setHashValueSerializer(jackson2JsonRedisSerializer);

		template.afterPropertiesSet();

		return template;

	}

 
	private void setSerializer(StringRedisTemplate template) {

		@SuppressWarnings({ "rawtypes", "unchecked" })

		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);

		template.setValueSerializer(jackson2JsonRedisSerializer);

	}
}

Redis的依赖引入,配置文件,工具类RedisUtil,网上几个版本都类似,本文参考以下版本传送门

https://www.cnblogs.com/zeng1994/p/03303c805731afc9aa9c60dbbd32a323.html

准备工作做好之后开始正式编写注解@Cacheable nextkey()用做二级缓存本文中不会用到

nextKey用法详情> 设计模式(实战) - 责任链模式 <

创建的Java的注解@ExtCacheable

    package com.huajie.annotation;

    import java.lang.annotation.ElementType;

    import java.lang.annotation.Retention;

    import java.lang.annotation.RetentionPolicy;

    import java.lang.annotation.Target;

    
    @Target({ ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ExtCacheable {

	String key() default "";

	String nextKey() default "";

	int expireTime() default 1800;//30分钟

}

SpringAop切面CacheableAspect

    package com.huajie.aspect;

    import java.lang.reflect.Method;

    import java.util.ArrayList;

    import java.util.List;

    import org.aspectj.lang.ProceedingJoinPoint;

    import org.aspectj.lang.annotation.Around;

    import org.aspectj.lang.annotation.Aspect;

    import org.aspectj.lang.annotation.Pointcut;

    import org.aspectj.lang.reflect.MethodSignature;

    import org.springframework.beans.factory.annotation.Autowired;

    import org.springframework.stereotype.Component;

    import com.huajie.annotation.ExtCacheable;

    import com.huajie.utils.RedisUtil;

    

    /**

     * redis缓存处理

     * 不适用与内部方法调用(this.)或者private

    */

    @Component

    @Aspect

    public class CacheableAspect {

	

	@Autowired

	private RedisUtil redisUtil;

 

	@Pointcut("@annotation(com.huajie.annotation.ExtCacheable)")

	public void annotationPointcut() {

	}

 

	@Around("annotationPointcut()")

	public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {

		// 获得当前访问的class

		Class<?> className = joinPoint.getTarget().getClass();

		// 获得访问的方法名

		String methodName = joinPoint.getSignature().getName();

		// 得到方法的参数的类型

		Class<?>[] argClass = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();

		Object[] args = joinPoint.getArgs();

		String key = "";

		int expireTime = 1800;

		try {

			// 得到访问的方法对象

			Method method = className.getMethod(methodName, argClass);

			method.setAccessible(true);

			// 判断是否存在@ExtCacheable注解

			if (method.isAnnotationPresent(ExtCacheable.class)) {

				ExtCacheable annotation = method.getAnnotation(ExtCacheable.class);

				key = getRedisKey(args,annotation);

				expireTime = getExpireTime(annotation);

			}

		} catch (Exception e) {

			throw new RuntimeException("redis缓存注解参数异常", e);

		}

		// 获取缓存是否存在

		boolean hasKey = redisUtil.hasKey(key);

		if (hasKey) {

			return redisUtil.get(key);

		} else {

                         //执行原方法(java反射执行method获取结果)

			Object res = joinPoint.proceed();

                         //设置缓存

			redisUtil.set(key, res);

                         //设置过期时间

			redisUtil.expire(key, expireTime);

			return res;

		}

	}

	

	private int getExpireTime(ExtCacheable annotation) {

		return annotation.expireTime();

	}

 

	private String getRedisKey(Object[] args,ExtCacheable annotation) {

		String primalKey = annotation.key();

		//获取#p0...集合

		List<String> keyList = getKeyParsList(primalKey);

		for (String keyName : keyList) {

			int keyIndex = Integer.parseInt(keyName.toLowerCase().replace("#p", ""));

			Object parValue = args[keyIndex];

			primalKey = primalKey.replace(keyName, String.valueOf(parValue));

		}

		return primalKey.replace("+","").replace("'","");

	}

 

	// 获取key中#p0中的参数名称

	private static List<String> getKeyParsList(String key) {

		List<String> ListPar = new ArrayList<String>();

		if (key.indexOf("#") >= 0) {

			int plusIndex = key.substring(key.indexOf("#")).indexOf("+");

			int indexNext = 0;

			String parName = "";

			int indexPre = key.indexOf("#");

			if(plusIndex>0){

				indexNext = key.indexOf("#") + key.substring(key.indexOf("#")).indexOf("+");

				parName = key.substring(indexPre, indexNext);

			}else{

				parName = key.substring(indexPre);

			}

			ListPar.add(parName.trim());

			key = key.substring(indexNext + 1);

			if (key.indexOf("#") >= 0) {

				ListPar.addAll(getKeyParsList(key));

			}

		}

		return ListPar;

	}
}

业务模块使用方法

    @Override
	@ExtCacheable(key = "Leaders+#p0+#p1+#p2")
	// 手机端获取领导人员列表
	public List<Leader> listLeaders(String leaderGroupId, String uuid, String yearDetailId) {
		List<Leader> leadersQuotaDetailList = sysIndexMapper.listLeaders(leaderGroupId, uuid, yearDetailId);
		return leadersQuotaDetailList;
}

业务模块过期时间使用方法,5分钟过期

    @Override
	@ExtCacheable(key = "mobileCacheFlag", expireTime = 60 * 5)
	public int cacheFlag() {
		int mobileCacheFlag = 1;
		mobileCacheFlag = sysIndexMapper.cacheFlag();
		return mobileCacheFlag;

}

Redis的的截图