一.定义注解
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomRedisCache {
String name() ;
long expireTime() default 60;
TimeUnit timeUnit () default TimeUnit.SECONDS;
/**
* support SPEL expresion 锁的key = name + keys
*
* @return KEY
*/
String[] keys() default "";
}
二.自定义过期时间接收类(可选)
由于动态的过期时间不好取,需要定一个类来接收
import lombok.Data;
@Data
public class CustomCache {
/**
* 过期时间
*/
private Long expireTime;
}
三.Aop实现
如果结果是空值,就不缓存
import com.alibaba.fastjson.JSONObject;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
@Aspect
@Component
public class CacheAop {
private static final Logger log = LoggerFactory.getLogger(CacheAop.class);
@Autowired
private StringRedisTemplateClient redisTemplateClient;
@Autowired
private RedisKeyBuilder redisKeyBuilder;
@Pointcut("@annotation(customRedisCache)")
public void cachePointcut(CustomRedisCache customRedisCache) {
}
@Around("cachePointcut(customRedisCache)")
public Object cacheable(ProceedingJoinPoint joinPoint, CustomRedisCache customRedisCache) throws Throwable {
try {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
String prefix = StringUtils.hasText(customRedisCache.name()) ? customRedisCache.name() :
method.getDeclaringClass().getName() + method.getName();
Object[] args = joinPoint.getArgs();
String key = prefix + "#" + redisKeyBuilder.buildKey(signature, customRedisCache.keys(), args);
int length = joinPoint.getArgs().length;
TimeUnit timeUnit = customRedisCache.timeUnit();
Type returnType = method.getGenericReturnType();
CustomCache customCache = null;
if (length > 0) {
for (Object obj : args) {
if (obj instanceof CustomCache) {
customCache = (CustomCache) obj;
break;
}
}
}
long expireTime = customRedisCache.expireTime();
if (customCache != null) {
if (customCache.getExpireTime() != null) {
expireTime = customCache.getExpireTime();
}
}
String returnVal = redisTemplateClient.get(key);
if (!StringUtils.isEmpty(returnVal)) {
Object obj = JSONObject.parseObject(returnVal, returnType);
return obj;
} else {
Object proceed = joinPoint.proceed();
if(proceed==null){
return null;
}
if(proceed instanceof Collection){
if(((Collection<?>) proceed).size()==0){
return proceed;
}
}
if(proceed instanceof Map){
if(((Map) proceed).size()==0){
return proceed;
}
}
returnVal = JSONObject.toJSONString(proceed);
redisTemplateClient.put(key, returnVal, expireTime, timeUnit);
return proceed;
}
} catch (Exception e) {
log.info("aop 异常===============》", e);
return joinPoint.proceed();
}
}
}
四.key的构建接口
参考 https://gitee.com/baomidou/lock4j
import org.aspectj.lang.reflect.MethodSignature;
public interface RedisKeyBuilder {
/**
* 构建key
*
* @param definitionKeys 定义
* @return key
*/
String buildKey(MethodSignature signature, String[] definitionKeys,Object[] args);
}
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.BeanResolver;
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 org.springframework.util.StringUtils;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* Key生成器
*
*/
@Component
public class DefaultLockKeyBuilder implements RedisKeyBuilder {
private static final ParameterNameDiscoverer NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
private static final ExpressionParser PARSER = new SpelExpressionParser();
private BeanResolver beanResolver;
public DefaultLockKeyBuilder(BeanFactory beanFactory) {
this.beanResolver = new BeanFactoryResolver(beanFactory);
}
@Override
public String buildKey(MethodSignature signature, String[] definitionKeys,Object[] args) {
Method method = signature.getMethod();
if (definitionKeys.length > 1 || !"".equals(definitionKeys[0])) {
return getSpelDefinitionKey(definitionKeys, method, args);
}
return "";
}
protected String getSpelDefinitionKey(String[] definitionKeys, Method method, Object[] parameterValues) {
StandardEvaluationContext context = new MethodBasedEvaluationContext(null, method, parameterValues, NAME_DISCOVERER);
context.setBeanResolver(beanResolver);
List<String> definitionKeyList = new ArrayList<>(definitionKeys.length);
for (String definitionKey : definitionKeys) {
if (definitionKey != null && !definitionKey.isEmpty()) {
String key = PARSER.parseExpression(definitionKey).getValue(context, String.class);
definitionKeyList.add(key);
}
}
return StringUtils.collectionToDelimitedString(definitionKeyList, ".", "", "");
}
}
五.效果
如果需要动态自定义过期时间的,需要继承CustomCache 这个类
@Data
public class User extends CustomCache {
private String name;
private Integer id;
}
@Override
@CustomRedisCache(name ="redisCacheTest",expireTime = 10,keys = {"#user.id", "#user.name"})
public String redisCacheTest(User user) {
return "success";
}
User user=new User();
user.setId(123);
user.setName("hello");
user.setExpireTime(20L);
service.redisCacheTest(user);
生成的key为:redisCacheTest#123.hello