开发背景:
针对Cacheable的痛点,缓存的数据在客户端中查看,如果遇到错误很难排查错误。
Cacheable不方便使用
指向针对Map类型做处理,并且Redis中也想存储成Map集合。
针对Redis的Set集合也可以开发类似的组件
1、定义注解
1)、获取map中某一项值的注解
package com.biubiu.annotation; import java.lang.annotation.*; @Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface CacheItemGet { String key(); String hKey();}
2)、获取map集合的注解
package com.biubiu.annotation; import java.lang.annotation.*; @Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface CacheMapGet { String key(); long expire() default 12 * 60 * 60L; boolean parse() default false;}
3)、更新map的注解
package com.biubiu.annotation; import java.lang.annotation.*; @Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface CacheMapPut { String key(); long expire() default 12 * 60 * 60L; boolean parse() default false;}
2、redisTemplate
package com.biubiu; @Configurationpublic class Config extends WebMvcConfigurationSupport { @Bean(name = "hashRedisTemplate") public RedisTemplate hashRedisTemplate(LettuceConnectionFactory lettuceConnectionFactory) { RedisTemplate template = new RedisTemplate<>(); template.setConnectionFactory(lettuceConnectionFactory); 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; } }
3、aop切面
package com.biubiu.aop; import com.biubiu.annotation.CacheItemGet;import com.biubiu.util.CommonUtil;import com.biubiu.util.MD5;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.reflect.MethodSignature;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.core.DefaultParameterNameDiscoverer;import org.springframework.data.redis.core.HashOperations;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.expression.EvaluationContext;import org.springframework.expression.Expression;import org.springframework.expression.ExpressionParser;import org.springframework.expression.spel.standard.SpelExpressionParser;import org.springframework.expression.spel.support.StandardEvaluationContext;import org.springframework.stereotype.Component; import javax.annotation.Resource;import java.lang.reflect.Method; @Aspect@Componentpublic class CacheItemGetAspect { @Qualifier("hashRedisTemplate") @Resource private RedisTemplate redisTemplate; @Around("@annotation(com.biubiu.annotation.CacheItemGet)") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = joinPoint.getTarget().getClass().getMethod(signature.getName(), signature.getMethod().getParameterTypes()); CacheItemGet cacheItemGet = method.getAnnotation(CacheItemGet.class); String key = cacheItemGet.key(); String hKeyEl = cacheItemGet.hKey(); //创建解析器 ExpressionParser parser = new SpelExpressionParser(); Expression hKeyExpression = parser.parseExpression(hKeyEl); //设置解析上下文有哪些占位符。 EvaluationContext context = new StandardEvaluationContext(); //获取方法参数 Object[] args = joinPoint.getArgs(); String[] parameterNames = new DefaultParameterNameDiscoverer().getParameterNames(method); for(int i = 0; i < parameterNames.length; i++) { context.setVariable(parameterNames[i], args[i]); } //解析得到 item的 key String hKeyValue = hKeyExpression.getValue(context).toString(); String hKey = MD5.getMD5Str(hKeyValue); HashOperations ops = redisTemplate.opsForHash(); Object value = ops.get(key, hKey); if(value != null) { return value.toString(); } return joinPoint.proceed(); } }
package com.biubiu.aop; import com.biubiu.annotation.CacheMapGet;import com.biubiu.annotation.CacheMapPut;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.reflect.MethodSignature;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.data.redis.core.HashOperations;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.stereotype.Component; import javax.annotation.Resource;import java.lang.reflect.Method;import java.util.Map;import java.util.concurrent.TimeUnit; @Aspect@Componentpublic class CacheMapGetAspect { @Qualifier("hashRedisTemplate") @Resource private RedisTemplate redisTemplate; @Around("@annotation(com.biubiu.annotation.CacheMapGet)") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = joinPoint.getTarget().getClass().getMethod(signature.getName(), signature.getMethod().getParameterTypes()); CacheMapGet cacheMap = method.getAnnotation(CacheMapGet.class); String key = cacheMap.key(); //强制刷缓存 HashOperations ops = redisTemplate.opsForHash(); Object val; Map value = ops.entries(key); if(value.size() != 0) { return value; } //加锁 synchronized (this) { value = ops.entries(key); if(value.size() != 0) { return value; } //执行目标方法 val = joinPoint.proceed(); ops.delete(key); //把值设置回去 ops.putAll(key, (Map) val); redisTemplate.expire(key, cacheMap.expire(), TimeUnit.SECONDS); return val; } } }
package com.biubiu.aop; import com.biubiu.annotation.CacheMapPut;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.reflect.MethodSignature;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.data.redis.core.HashOperations;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.stereotype.Component; import javax.annotation.Resource;import java.lang.reflect.Method;import java.util.Map;import java.util.concurrent.TimeUnit; @Aspect@Componentpublic class CacheMapPutAspect { @Qualifier("hashRedisTemplate") @Resource private RedisTemplate redisTemplate; @Around("@annotation(com.biubiu.annotation.CacheMapPut)") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = joinPoint.getTarget().getClass().getMethod(signature.getName(), signature.getMethod().getParameterTypes()); CacheMapPut cacheMap = method.getAnnotation(CacheMapPut.class); String key = cacheMap.key(); //强制刷缓存 HashOperations ops = redisTemplate.opsForHash(); Object val; //加锁 synchronized (this) { //执行目标方法 val = joinPoint.proceed(); redisTemplate.delete(key); //把值设置回去 ops.putAll(key, (Map) val); redisTemplate.expire(key, cacheMap.expire(), TimeUnit.SECONDS); return val; } } }
4、使用方法
@CacheItemGet(key = "biubiu-field-hash", hKey = "#tableName+#colName") public String getValue(String tableName, String colName) { //省略... }
@CacheMapGet(key = "biubiu-field-hash", expire = 6 * 60 * 60L) public Map getFieldTypeMap() { //省略... }
@CacheMapPut(key = "biubiu-field-hash", expire = 6 * 60 * 60L) public Map putFieldTypeMap() { //省略... }
5、MD5工具类
package com.biubiu.util; import java.nio.charset.Charset;import java.security.MessageDigest;import java.security.NoSuchAlgorithmException; import org.apache.log4j.LogManager;import org.apache.log4j.Logger; /** * @Author yule.zhang * @CreateTime 2019-6-16下午05:28:11 * @Version 1.0 * @Explanation 用MD5对数据进行加密 */public class MD5 { static final Logger log = LogManager.getLogger(MD5.class); MessageDigest md5; static final char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; public MD5() {try {// 获得MD5摘要算法的 MessageDigest 对象md5 = MessageDigest.getInstance("MD5");} catch (NoSuchAlgorithmException e) {log.error("创建MD5对象出错, ", e);throw new IllegalArgumentException("创建md5对象时出错");}} public synchronized String getMD5(String s) {return this.getMD5(s.getBytes()).toLowerCase();} public synchronized String getMD5(byte[] btInput) {try {// 使用指定的字节更新摘要md5.update(btInput);// 获得密文byte[] md = md5.digest();// 把密文转换成十六进制的字符串形式int j = md.length;char str[] = new char[j * 2];int k = 0;for (int i = 0; i < j; i++) {byte byte0 = md[i];str[k++] = hexDigits[byte0 >>> 4 & 0xf];str[k++] = hexDigits[byte0 & 0xf];}return new String(str).toLowerCase();} catch (Exception e) {log.error("生成MD5码时出错,", e);throw new IllegalArgumentException("生成MD5出错");}} /** * 获取32位的MD5加密 * @param sourceStr * @return */public static String getMD5Str(String sourceStr) { String result = ""; try { MessageDigest md = MessageDigest.getInstance("MD5"); md.update(sourceStr.getBytes(Charset.forName("utf-8"))); byte b[] = md.digest(); int i; StringBuffer buf = new StringBuffer(""); for (int offset = 0; offset < b.length; offset++) { i = b[offset]; if (i < 0) i += 256; if (i < 16) buf.append("0"); buf.append(Integer.toHexString(i)); } result = buf.toString(); } catch (NoSuchAlgorithmException e) { System.out.println(e); } return result; } }