推薦一個公衆號
號主爲一線大廠架構師,CSDN博客專家,博客訪問量突破一千萬。主要分享Java、golang架構,源碼,分佈式,高併發等技術,用大廠程序員的視角來探討技術進階、面試指南、職業規劃等。15W技術人的選擇!
開發背景:
針對Cacheable的痛點,緩存的數據在客戶端中查看,如果遇到錯誤很難排查錯誤。
Cacheable不方便使用
指向針對Map類型做處理,並且Redis中也想存儲成Map集合。
針對Redis的Set集合也可以開發類似的組件
1、定義註解
1)、獲取map中某一項值的註解
package com.biubiu.annotation;
import java.lang.annotation.*;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheItemGet {
String key();
String hKey();
}
2)、獲取map集合的註解
package com.biubiu.annotation;
import java.lang.annotation.*;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @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)
@Documented
public @interface CacheMapPut {
String key();
long expire() default 12 * 60 * 60L;
boolean parse() default false;
}
2、redisTemplate
package com.biubiu;
@Configuration
public class Config extends WebMvcConfigurationSupport {
@Bean(name = "hashRedisTemplate")
public RedisTemplate<String, Object> hashRedisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
RedisTemplate<String, Object> 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
@Component
public class CacheItemGetAspect {
@Qualifier("hashRedisTemplate")
@Resource
private RedisTemplate<String, Object> 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<String, String, Object> 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
@Component
public class CacheMapGetAspect {
@Qualifier("hashRedisTemplate")
@Resource
private RedisTemplate<String, Object> 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<String, String, Object> ops = redisTemplate.opsForHash();
Object val;
Map<String, Object> 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<String, Object>) 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
@Component
public class CacheMapPutAspect {
@Qualifier("hashRedisTemplate")
@Resource
private RedisTemplate<String, Object> 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<String, String, Object> ops = redisTemplate.opsForHash();
Object val;
//加鎖
synchronized (this) {
//執行目標方法
val = joinPoint.proceed();
redisTemplate.delete(key);
//把值設置回去
ops.putAll(key, (Map<String, Object>) 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<String, String> getFieldTypeMap() {
//省略...
}
@CacheMapPut(key = "biubiu-field-hash", expire = 6 * 60 * 60L)
public Map<String, String> 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;
}
}