仿照Spring Cache手动实现Redis Hash缓存注解功能
原理
基于注解切面,将方法返回值放入redis hash集合之中 注解名称和功能,基本和spring cache注解一致
实现
配置类(RedisCacheProperties)
值序列化器默认使用json序列化器
@SpringBootConfiguration
@ConfigurationProperties(prefix = "pangju.cache.redis")
public class RedisCacheProperties {
// 是否允许缓存null值
private boolean cacheNullValues = true;
// 缓存前缀
private String cachePrefix;
private boolean useCachePrefix = true;
public boolean isCacheNullValues() {
return cacheNullValues;
}
public void setCacheNullValues(boolean cacheNullValues) {
this.cacheNullValues = cacheNullValues;
}
public String getCachePrefix() {
return cachePrefix;
}
public void setCachePrefix(String cachePrefix) {
this.cachePrefix = cachePrefix;
}
public boolean isUseCachePrefix() {
return useCachePrefix;
}
public void setUseCachePrefix(boolean useCachePrefix) {
this.useCachePrefix = useCachePrefix;
}
}
RedisTemplate配置
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
@SpringBootConfiguration
public class BeanConfig {
@Bean
public RedisTemplate<String, Object> cacheRedisTemplate(RedisConnectionFactory redisConnectionFactory, RedisCacheProperties redisCacheProperties) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
template.setKeySerializer(RedisSerializer.string());
template.setValueSerializer(RedisSerializer.json());
template.setHashKeySerializer(RedisSerializer.string());
template.setHashValueSerializer(RedisSerializer.json());
return template;
}
}
工具类
Spring EL 表达式工具类(SpELUtils)
代码
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.lang.reflect.Method;
import java.util.Objects;
public class SpELUtils {
public static final SpelExpressionParser DEFAULT_EXPRESSION_PARSER = new SpelExpressionParser();
public static final StandardEvaluationContext DEFAULT_EVALUATION_CONTEXT = new StandardEvaluationContext();
protected SpELUtils() {
}
public static EvaluationContext initEvaluationContext(final Method method, final Object[] args,
final ParameterNameDiscoverer discoverer) {
EvaluationContext context = new MethodBasedEvaluationContext(method, method, args, discoverer);
String[] parametersName = discoverer.getParameterNames(method);
if (ArrayUtils.isNotEmpty(args)) {
for (int i = 0; i < args.length; i++) {
context.setVariable(Objects.requireNonNull(parametersName)[i], args[i]);
}
}
return context;
}
public static Object parseExpression(final String expressionString) {
Expression expression = DEFAULT_EXPRESSION_PARSER.parseExpression(expressionString);
return expression.getValue(DEFAULT_EVALUATION_CONTEXT);
}
public static <T> T parseExpression(final String expressionString, final Class<T> desiredResultType) {
Expression expression = DEFAULT_EXPRESSION_PARSER.parseExpression(expressionString);
return expression.getValue(DEFAULT_EVALUATION_CONTEXT, desiredResultType);
}
}
反射工具类(ReflectionUtils)
依赖
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.10.2</version>
</dependency>
代码
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import java.lang.reflect.*;
import java.util.Objects;
public class ReflectionUtils extends org.reflections.ReflectionUtils {
protected ReflectionUtils() {
}
@SuppressWarnings("unchecked")
public static <E> E getFieldValue(final Object obj, final String fieldName) {
Field field = getField(obj, fieldName);
if (Objects.isNull(field)) {
return null;
}
return getFieldValue(obj, field);
}
@SuppressWarnings("unchecked")
public static <E> E getFieldValue(final Object obj, final Field field) {
boolean accessible = isAccessible(field, obj);
if (!accessible && !makeAccessible(field)) {
return null;
}
try {
E value = (E) field.get(obj);
if (!accessible) {
field.setAccessible(false);
}
return value;
} catch (IllegalAccessException e) {
org.apache.commons.lang3.exception.ExceptionUtils.rethrow(e);
return null;
}
}
public static <E> void setFieldValue(final Object obj, final String fieldName, final E value) {
Field field = getField(obj, fieldName);
if (Objects.isNull(field)) {
return;
}
setFieldValue(obj, field, value);
}
public static <E> void setFieldValue(final Object obj, final Field field, final E value) {
boolean accessible = isAccessible(field, obj);
if (!accessible && !makeAccessible(field)) {
return;
}
try {
field.set(obj, value);
if (!accessible) {
field.setAccessible(false);
}
} catch (IllegalAccessException e) {
ExceptionUtils.rethrow(e);
}
}
public static Field getField(final Object obj, final String fieldName) {
if (Objects.isNull(obj)) {
return null;
}
for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) {
try {
return superClass.getDeclaredField(fieldName);
} catch (NoSuchFieldException ignored) {
}
}
return null;
}
public static <T> String getClassName(final T t) {
return getClassName(t.getClass());
}
public static String getClassName(final Class<?> clz) {
return StringUtils.substringAfterLast(clz.getName(), ".");
}
public static <T> Class<T> getClassGenericType(final Class<?> clazz) {
return getClassGenericType(clazz, 0);
}
@SuppressWarnings("unchecked")
public static <T> Class<T> getClassGenericType(final Class<?> clazz, final int index) {
Type genType = clazz.getGenericSuperclass();
if (!(genType instanceof ParameterizedType)) {
return null;
}
Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
if (index >= params.length || index < 0) {
return null;
}
if (!(params[index] instanceof Class)) {
return null;
}
return (Class<T>) params[index];
}
public static Class<?> getUserClass(final Object instance) {
Class<?> clazz = instance.getClass();
if (clazz.getName().contains("$$")) {
Class<?> superClass = clazz.getSuperclass();
if (Objects.nonNull(superClass) && !Object.class.equals(superClass)) {
return superClass;
}
}
return clazz;
}
public static boolean isEqualsMethod(Method method) {
return (method != null && method.getParameterCount() == 1 && method.getName().equals("equals") &&
method.getParameterTypes()[0] == Object.class);
}
public static boolean isHashCodeMethod(Method method) {
return (method != null && method.getParameterCount() == 0 && method.getName().equals("hashCode"));
}
public static boolean isToStringMethod(Method method) {
return (method != null && method.getParameterCount() == 0 && method.getName().equals("toString"));
}
public static boolean isObjectMethod(Method method) {
return (method != null && (method.getDeclaringClass() == Object.class ||
isEqualsMethod(method) || isHashCodeMethod(method) || isToStringMethod(method)));
}
public static boolean isCglibRenamedMethod(Method renamedMethod) {
String name = renamedMethod.getName();
if (name.startsWith("CGLIB$")) {
int i = name.length() - 1;
while (i >= 0 && Character.isDigit(name.charAt(i))) {
i--;
}
return (i > "CGLIB$".length() && (i < name.length() - 1) && name.charAt(i) == '$');
}
return false;
}
public static boolean isAccessible(Field field, Object instance) {
return field.canAccess(instance);
}
public static boolean isPublicStaticFinal(Field field) {
int modifiers = field.getModifiers();
return (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers));
}
@SuppressWarnings("deprecation")
public static boolean makeAccessible(Field field) {
if ((!Modifier.isPublic(field.getModifiers()) ||
!Modifier.isPublic(field.getDeclaringClass().getModifiers()) ||
Modifier.isFinal(field.getModifiers())) && !field.isAccessible()) {
field.setAccessible(true);
return true;
}
return false;
}
@SuppressWarnings("deprecation")
public static boolean makeAccessible(Method method) {
if ((!Modifier.isPublic(method.getModifiers()) ||
!Modifier.isPublic(method.getDeclaringClass().getModifiers())) && !method.isAccessible()) {
method.setAccessible(true);
return true;
}
return false;
}
}
缓存管理器(RedisCacheManager)
封装RedisTemplate,实现对缓存的操作
代码
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Component
public class RedisCacheManager {
private final RedisTemplate<String, Object> redisTemplate;
private final String cacheNamePrefix;
private final boolean cacheNullValues;
public RedisCacheManager(RedisTemplate<String, Object> redisTemplate, RedisCacheProperties properties) {
this.redisTemplate = redisTemplate;
this.cacheNullValues = properties.isCacheNullValues();
this.cacheNamePrefix = properties.isUseCachePrefix() ? properties.getCachePrefix() : StringUtils.EMPTY;
}
public boolean existCache(String cacheName) {
return Boolean.TRUE.equals(redisTemplate.hasKey(getCacheName(cacheName)));
}
public boolean exist(String cacheName, String key) {
return redisTemplate.opsForHash().hasKey(getCacheName(cacheName), key);
}
public Object get(String cacheName, String key) {
return redisTemplate.opsForHash().get(getCacheName(cacheName), key);
}
public List<Object> multiGet(String cacheName, Collection<String> keys) {
Set<Object> hashKeys = CollectionUtils.emptyIfNull(keys)
.stream()
.filter(StringUtils::isNotBlank)
.collect(Collectors.toSet());
if (CollectionUtils.isEmpty(hashKeys)) {
return Collections.emptyList();
}
return redisTemplate.opsForHash().multiGet(getCacheName(cacheName), hashKeys);
}
public List<Object> getAll(String cacheName) {
return redisTemplate.opsForHash().values(getCacheName(cacheName));
}
public void put(String cacheName, String key, Object value) {
if (Objects.nonNull(value) || cacheNullValues) {
redisTemplate.opsForHash().put(getCacheName(cacheName), key, value);
}
}
public void putAll(String cacheName, @Nullable String keyFieldName, Collection<?> values) {
Map<String, Object> map;
if (StringUtils.isBlank(keyFieldName)) {
map = CollectionUtils.emptyIfNull(values)
.stream()
.filter(value -> ObjectUtils.isNotEmpty(value) || cacheNullValues)
.collect(Collectors.toMap(Object::toString, item -> item));
} else {
map = CollectionUtils.emptyIfNull(values)
.stream()
.filter(Objects::nonNull)
.map(item -> Pair.of(ReflectionUtils.getFieldValue(item, keyFieldName), item))
.filter(pair -> ObjectUtils.isNotEmpty(pair.getKey()) && (Objects.nonNull(pair.getValue()) || cacheNullValues))
.collect(Collectors.toMap(pair -> pair.getKey().toString(), Pair::getValue));
}
if (MapUtils.isNotEmpty(map)) {
redisTemplate.opsForHash().putAll(getCacheName(cacheName), map);
}
}
public void evict(String cacheName, String key) {
redisTemplate.opsForHash().delete(getCacheName(cacheName), key);
}
public void evictAll(String cacheName, @Nullable String keyFieldName, Collection<?> keys) {
Set<Object> hashKeys;
if (StringUtils.isBlank(keyFieldName)) {
hashKeys = keys.stream()
.filter(ObjectUtils::isNotEmpty)
.map(Object::toString)
.collect(Collectors.toSet());
} else {
hashKeys = keys.stream()
.filter(ObjectUtils::isNotEmpty)
.map(item -> ReflectionUtils.getFieldValue(item, keyFieldName).toString())
.collect(Collectors.toSet());
}
if (CollectionUtils.isNotEmpty(hashKeys)) {
redisTemplate.opsForHash().delete(getCacheName(cacheName), hashKeys);
}
}
public void clear(String cacheName) {
redisTemplate.delete(getCacheName(cacheName));
}
public void clearAll(String... cacheNames) {
Set<String> keys = Arrays.stream(cacheNames)
.filter(StringUtils::isNotBlank)
.map(this::getCacheName)
.collect(Collectors.toSet());
if (CollectionUtils.isNotEmpty(keys)) {
redisTemplate.delete(keys);
}
}
public void clearAll(Collection<String> cacheNames) {
Set<String> keys = CollectionUtils.emptyIfNull(cacheNames)
.stream()
.filter(StringUtils::isNotBlank)
.map(this::getCacheName)
.collect(Collectors.toSet());
if (CollectionUtils.isNotEmpty(keys)) {
redisTemplate.delete(keys);
}
}
private String getCacheName(String cacheName) {
if (StringUtils.isNotBlank(cacheNamePrefix)) {
return RedisUtils.generateKey(cacheNamePrefix, cacheName);
}
return cacheName;
}
}
缓存注解(RedisCacheable)
调用方法时,根据key判断redis是否存在值,存在则返回,否则调用方法并将返回值写入redis
注解定义
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisCacheable {
// 缓存名称(redis key名称)
String cache();
// redis hashKey名称(基于spel计算)
String key() default "";
// redis hashKey字段名称(key为集合时生效)
String keyField() default "";
// 排序字段名称
String sortField() default "";
// 是否逆序排序(基于spel计算)
String reverseOrder() default "";
// 是否获取全部值(为true时,key可以为空)
boolean allEntries() default false;
// 是否读取缓存(基于spel计算)
String condition() default "";
// 为true时,则不将返回值写入缓存(基于spel计算,可以引用 result)
String unless() default "";
}
切面实现
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Component
@Aspect
public class RedisCacheableAspect {
private final SpelExpressionParser parser;
private final ParameterNameDiscoverer discoverer;
private final RedisCacheManager cacheManager;
public RedisCacheAspect(RedisCacheManager cacheManager) {
this.parser = new SpelExpressionParser();
this.discoverer = new DefaultParameterNameDiscoverer();
this.cacheManager = cacheManager;
}
// 环绕切面
@Around("@annotation(RedisCacheable)")
public Object handleRedisCacheable(ProceedingJoinPoint point) throws Throwable {
MethodSignature methodSignature = (MethodSignature) point.getSignature();
Method method = methodSignature.getMethod();
EvaluationContext context = SpELUtils.initEvaluationContext(method, point.getArgs(), discoverer);
RedisCacheable annotation = method.getAnnotation(RedisCacheable.class);
Boolean condition = true;
if (StringUtils.isNotBlank(annotation.condition())) {
// 计算缓存读取条件
Expression conditionExpression = parser.parseExpression(annotation.condition());
condition = conditionExpression.getValue(context, Boolean.class);
}
// 判断缓存读取条件
if (Boolean.TRUE.equals(condition)) {
String cacheName = annotation.cache();
// 如果缓存名称为空,则直接调用方法,不操作缓存
if (StringUtils.isBlank(cacheName)) {
return point.proceed();
}
// 判断是否读取全部缓存
if (annotation.allEntries()) {
if (!cacheManager.existCache(cacheName)) {
Object returnValue = point.proceed();
putResultToCache(returnValue, context, cacheName, annotation);
return returnValue;
}
List<Object> result = cacheManager.getAll(cacheName);
if (CollectionUtils.isNotEmpty(result)) {
result.sort(getResultComparator(context, annotation.keyField(), annotation.sortField(), annotation.reverseOrder()));
}
return result;
}
Expression keyExpression = parser.parseExpression(annotation.key());
Object key = keyExpression.getValue(context, Object.class);
if (ObjectUtils.isEmpty(key)) {
return point.proceed();
}
if (key instanceof Collection<?> collection) {
if (CollectionUtils.isEmpty(collection)) {
return Collections.emptyList();
}
if (!cacheManager.existCache(cacheName)) {
Object returnValue = point.proceed();
putResultToCache(returnValue, context, cacheName, annotation);
return returnValue;
}
// 计算hashKey集合
Set<String> hashKeys;
Class<?> keyClass = collection.iterator()
.next()
.getClass();
if (ClassUtils.isPrimitiveOrWrapper(keyClass)) {
hashKeys = collection
.stream()
.map(Object::toString)
.collect(Collectors.toSet());
} else {
hashKeys = collection
.stream()
.map(object -> getFieldValue(object, annotation.keyField()))
.filter(Objects::nonNull)
.map(Object::toString)
.collect(Collectors.toSet());
}
// 过滤返回值
List<Object> result = ListUtils.emptyIfNull(cacheManager.multiGet(cacheName, hashKeys))
.stream()
.filter(Objects::nonNull)
.sorted(getResultComparator(context, annotation.keyField(), annotation.sortField(), annotation.reverseOrder()))
.toList();
// 判断传入的key集合大小和返回值集合大小是否一致,不一致则调用方法
if (result.size() != hashKeys.size()) {
Object returnValue = point.proceed();
putResultToCache(returnValue, context, cacheName, annotation);
return returnValue;
}
return result;
} else {
String hashKey = key.toString();
if (StringUtils.isBlank(hashKey)) {
return point.proceed();
}
if (!ClassUtils.isPrimitiveOrWrapper(key.getClass())) {
hashKey = getFieldValue(key, annotation.keyField()).toString();
}
if (cacheManager.exist(cacheName, hashKey)) {
return cacheManager.get(cacheName, hashKey);
}
Object returnValue = point.proceed();
context.setVariable("result", returnValue);
Boolean unless = false;
if (StringUtils.isNotBlank(annotation.unless())) {
Expression unlessExpression = parser.parseExpression(annotation.unless());
unless = unlessExpression.getValue(context, Boolean.class);
}
if (!Boolean.TRUE.equals(unless)) {
cacheManager.put(cacheName, hashKey, returnValue);
}
return returnValue;
}
}
return point.proceed();
}
// 计算返回结果排序方式
private Comparator<? super Object> getResultComparator(EvaluationContext context,
String hashKeyField, String sortField, String reverseOrder) {
Boolean isReverseOrder = false;
if (StringUtils.isNotBlank(reverseOrder)) {
Expression reverseOrderExpression = parser.parseExpression(reverseOrder);
isReverseOrder = reverseOrderExpression.getValue(context, Boolean.class);
}
Comparator<? super Object> comparator = (a, b) -> {
if (StringUtils.isBlank(hashKeyField)) {
Object left = a;
Object right = b;
if (StringUtils.isNotBlank(sortField)) {
left = ReflectionUtils.getFieldValue(a, sortField);
right = ReflectionUtils.getFieldValue(b, sortField);
}
if (left instanceof Long longA && right instanceof Long longB) {
return longA.compareTo(longB);
}
if (left instanceof Integer integerA && right instanceof Integer integerB) {
return integerA.compareTo(integerB);
}
if (left instanceof Short shortA && right instanceof Short shortB) {
return shortA.compareTo(shortB);
}
if (left instanceof Double doubleA && right instanceof Double doubleB) {
return doubleA.compareTo(doubleB);
}
if (left instanceof Float floatA && right instanceof Float floatB) {
return floatA.compareTo(floatB);
}
if (left instanceof Date dateA && right instanceof Date dateB) {
return dateA.compareTo(dateB);
}
if (left instanceof LocalDate localDateA && right instanceof LocalDate localDateB) {
return localDateA.compareTo(localDateB);
}
if (left instanceof LocalDateTime localDateTimeA && right instanceof LocalDateTime localDateTimeB) {
return localDateTimeA.compareTo(localDateTimeB);
}
if (left instanceof BigInteger bigIntegerA && right instanceof BigInteger bigIntegerB) {
return bigIntegerA.compareTo(bigIntegerB);
}
if (left instanceof BigDecimal bigDecimalA && right instanceof BigDecimal bigDecimalB) {
return bigDecimalA.compareTo(bigDecimalB);
}
if (left instanceof String stringA && right instanceof String stringB) {
return stringA.compareTo(stringB);
}
return Integer.compare(left.hashCode(), right.hashCode());
}
if (StringUtils.isNotBlank(sortField)) {
return Comparator.naturalOrder().compare(
ReflectionUtils.getFieldValue(a, sortField),
ReflectionUtils.getFieldValue(b, sortField)
);
} else {
return Comparator.naturalOrder().compare(
ReflectionUtils.getFieldValue(a, hashKeyField),
ReflectionUtils.getFieldValue(b, hashKeyField)
);
}
};
return Boolean.TRUE.equals(isReverseOrder) ? comparator.reversed() : comparator;
}
// 将结果写入缓存
private void putResultToCache(Object result, EvaluationContext context,
String cacheName, RedisCacheable annotation) {
if (result instanceof Collection<?> resultCollection) {
context.setVariable("result", resultCollection);
Boolean unless = false;
if (StringUtils.isNotBlank(annotation.unless())) {
Expression unlessExpression = parser.parseExpression(annotation.unless());
unless = unlessExpression.getValue(context, Boolean.class);
}
if (!Boolean.TRUE.equals(unless)) {
cacheManager.putAll(cacheName, StringUtils.defaultIfBlank(annotation.keyField(), null), resultCollection);
}
}
}
private Object getFieldValue(Object object, String field) {
return StringUtils.isNotBlank(field) ? ReflectionUtils.getFieldValue(object, field) : object;
}
}
代码案例
- key值可以为集合、对象、基本/包装类型
- 如果key为对象时,会根据每项元素计算key值,再根据多个key值读取缓存
- 如果key为对象时,需要指定keyField,否则key值则调用对象的**toString()**方法计算得出
- 如果key为集合时,会自动过滤掉其中为null的元素
- 如果key为集合时,如果元素不是基本/包装类型时,需要指定keyField,否则key值则调用元素的 toString() 方法计算得出
// 缓存名称:app_secret key:id值 缓存写入条件:返回值不为null
@RedisCacheable(cache="app_secret", key="#p0", unless="#result == null")
public AppSecretDO getById(Long id);
// 缓存名称:app_secret key:id值 缓存读取条件:id不为null 缓存写入条件:返回值不为null
@RedisCacheable(cache="app_secret", key="#p0", condition="#p0 != null", unless="#result == null")
public AppSecretDO getById(Long id);
// 缓存名称:app_secret key:id值 缓存读取条件:id不为null 缓存写入条件:返回值不为null
@RedisCacheable(cache="app_secret", key="#p0", condition="#p0 != null", unless="#result == null")
public AppSecretDO getById(Long id);
// 缓存名称:app_secret key:id值 缓存读取条件:id不为null 缓存写入条件:返回值不为null 排序规则:id, 排序方向:逆序
@RedisCacheable(cache="app_secret", key="#p0", condition="#p0 != null", unless="#result == null", sortField="id", reverseOrder="'true'")
public AppSecretDO getById(Long id);
// 缓存名称:app_secret key:id值 缓存写入条件:返回值不为null
@RedisCacheable(cache="app_secret", key="#p0.id", unless="#result == null")
public AppSecretDO getById(AppSecretDO id);
// 缓存名称:app_secret key:id值 缓存写入条件:返回值不为null
@RedisCacheable(cache="app_secret", key="#p0", keyField="id", unless="#result == null")
public AppSecretDO getById(AppSecretDO id);
// 缓存名称:app_secret key:所有id值 缓存写入条件:返回值不为null
@RedisCacheable(cache="app_secret", key="#p0", unless="!#result.isEmpty()")
public List<AppSecretDO> listByIds(List<Long> ids);
// 缓存名称:app_secret key:所有id值 缓存写入条件:返回值不为null
@RedisCacheable(cache="app_secret", key="#p0", keyField="id", unless="!#result.isEmpty()")
public List<AppSecretDO> listByIds(List<AppSecretDO> ids);
// 缓存名称:app_secret key:全部 缓存写入条件:返回值不为null
@RedisCacheable(cache="app_secret", allEntries=true, keyField="id", unless="!#result.isEmpty()")
public List<AppSecretDO> listByIds(List<AppSecretDO> ids);
缓存写入注解(RedisCachePut)
注解定义
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisCachePut {
// 缓存名称
String cache();
// redis hashKey名称(value为集合时为字段名称也可以为空,不为集合时基于spel计算)
String key() default "";
// redis hashKey值(基于spel计算)
String value();
// 是否读取缓存(基于spel计算)
String condition() default "";
}
切面实现
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Component
@Aspect
public class RedisCachePutAspect {
private final SpelExpressionParser parser;
private final ParameterNameDiscoverer discoverer;
private final RedisCacheManager cacheManager;
public RedisCacheAspect(RedisCacheManager cacheManager) {
this.parser = new SpelExpressionParser();
this.discoverer = new DefaultParameterNameDiscoverer();
this.cacheManager = cacheManager;
}
@AfterReturning(pointcut = "@annotation(RedisCachePut)", returning = "returnValue")
public void handleRedisCachePut(JoinPoint point, Object returnValue) {
MethodSignature methodSignature = (MethodSignature) point.getSignature();
Method method = methodSignature.getMethod();
EvaluationContext context = SpELUtils.initEvaluationContext(method, point.getArgs(), discoverer);
context.setVariable("result", returnValue);
RedisCachePut annotation = method.getAnnotation(RedisCachePut.class);
put(context, annotation);
}
private void put(EvaluationContext context, RedisCachePut annotation) {
Boolean condition = true;
if (StringUtils.isNotBlank(annotation.condition())) {
Expression conditionExpression = parser.parseExpression(annotation.condition());
condition = conditionExpression.getValue(context, Boolean.class);
}
if (Boolean.TRUE.equals(condition)) {
String cacheName = annotation.cache();
if (StringUtils.isBlank(cacheName)) {
return;
}
Expression valueExpression = parser.parseExpression(annotation.value());
Object value = valueExpression.getValue(context, Object.class);
if (Objects.nonNull(value)) {
if (value instanceof Collection<?> collection) {
cacheManager.putAll(cacheName, StringUtils.defaultIfBlank(annotation.key(), null), CollectionUtils.emptyIfNull(collection));
} else {
Expression keyExpression = parser.parseExpression(annotation.key());
Object key = keyExpression.getValue(context, Object.class);
if (Objects.nonNull(key)) {
String keyStr = StringUtils.defaultIfBlank(key.toString(), value.toString());
cacheManager.put(cacheName, keyStr, value);
}
}
}
}
}
}
代码案例
value为集合时,key则被判断为字段名称,否则使用spel进行计算
@RedisCachePut(cache = "app_secret", key = "#p0.id", value="#p0", condition = "#result")
public boolean save(AppSecretDO entity);
@RedisCachePut(cache = "app_text", key="#p0", value="#p0", condition = "#result")
public boolean save(String value);
@RedisCachePut(cache = "app_text", value="#p0", condition = "#result")
public boolean save(String value);
@RedisCachePut(cache = "app_secret", key = "#p0", value="#p1", condition = "#result")
public boolean save(Long id, AppSecretDO entity);
@RedisCachePut(cache = "app_secret", key = "id", value="#p0", condition = "#result")
public boolean save(List<AppSecretDO> entities);
@RedisCachePut(cache="app_text", value="#p0", condition="#result")
public boolean save(List<String> values);
缓存删除注解(RedisCacheEvict)
注解定义
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisCacheEvict {
// 缓存集合名称(可以删除多个缓存中的key)
String[] caches() default {};
// redis hashKey名称(基于spel计算)
String key() default "";
// redis hashKey字段名称(key为集合时生效)
String keyField() default "";
// 是否读取缓存(基于spel计算)
String condition() default "";
// 是否删除全部值
boolean allEntries() default false;
}
切面实现
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Component
@Aspect
public class RedisCacheEvictAspect {
private final SpelExpressionParser parser;
private final ParameterNameDiscoverer discoverer;
private final RedisCacheManager cacheManager;
public RedisCacheAspect(RedisCacheManager cacheManager) {
this.parser = new SpelExpressionParser();
this.discoverer = new DefaultParameterNameDiscoverer();
this.cacheManager = cacheManager;
}
@AfterReturning(pointcut = "@annotation(RedisCacheEvict)", returning = "returnValue")
public void handleRedisCacheEvict(JoinPoint point, Object returnValue) {
MethodSignature methodSignature = (MethodSignature) point.getSignature();
Method method = methodSignature.getMethod();
EvaluationContext context = SpELUtils.initEvaluationContext(method, point.getArgs(), discoverer);
context.setVariable("result", returnValue);
RedisCacheEvict annotation = method.getAnnotation(RedisCacheEvict.class);
evict(context, annotation);
}
private void evict(EvaluationContext context, RedisCacheEvict annotation) {
Boolean condition = true;
if (StringUtils.isNotBlank(annotation.condition())) {
Expression conditionExpression = parser.parseExpression(annotation.condition());
condition = conditionExpression.getValue(context, Boolean.class);
}
if (Boolean.TRUE.equals(condition)) {
String[] cacheNames = annotation.caches();
if (cacheNames.length == 0) {
return;
}
if (annotation.allEntries()) {
cacheManager.clearAll(cacheNames);
} else {
Expression keyExpression = parser.parseExpression(annotation.key());
Object key = keyExpression.getValue(context, Object.class);
if (Objects.nonNull(key)) {
if (key instanceof Collection<?> collection) {
for (String cacheName : cacheNames) {
cacheManager.evictAll(cacheName, StringUtils.defaultIfBlank(annotation.keyField(), null), collection);
}
} else {
String keyStr = key.toString();
if (StringUtils.isNotBlank(keyStr)) {
for (String cacheName : cacheNames) {
cacheManager.evict(cacheName, keyStr);
}
}
}
}
}
}
}
}
代码案例
@RedisCacheEvict(caches = "app_secret", key = "#p0.id", condition = "#result")
public boolean removeById(AppSecretDO entity);
@RedisCacheEvict(caches = "app_secret", allEntries=true, condition = "#result")
public boolean removeAll();
@RedisCacheEvict(caches = "app_secret", key = "#p0", condition = "#result")
public boolean removeById(Long id);
@RedisCacheEvict(caches = "app_secret", key = "#p0", keyField="id", condition = "#result")
public boolean removeByIds(List<AppSecretDO> entities);
@RedisCacheEvict(caches="app_secret", key="#p0", condition="#result")
public boolean removeByIds(List<Long> values);
批量缓存注解
注解定义
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisCaching {
RedisCachePut[] put() default {};
RedisCacheEvict[] evict() default {};
}
切面实现
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Component
@Aspect
public class RedisCachingAspect {
private final SpelExpressionParser parser;
private final ParameterNameDiscoverer discoverer;
private final RedisCacheManager cacheManager;
public RedisCacheAspect(RedisCacheManager cacheManager) {
this.parser = new SpelExpressionParser();
this.discoverer = new DefaultParameterNameDiscoverer();
this.cacheManager = cacheManager;
}
@AfterReturning(pointcut = "@annotation(RedisCaching)", returning = "returnValue")
public void handleRedisCaching(JoinPoint point, Object returnValue) {
MethodSignature methodSignature = (MethodSignature) point.getSignature();
Method method = methodSignature.getMethod();
EvaluationContext context = SpELUtils.initEvaluationContext(method, point.getArgs(), discoverer);
context.setVariable("result", returnValue);
RedisCaching annotation = method.getAnnotation(RedisCaching.class);
for (RedisCacheEvict evictAnnotation : annotation.evict()) {
evict(context, evictAnnotation);
}
for (RedisCachePut putAnnotation : annotation.put()) {
put(context, putAnnotation);
}
}
private void put(EvaluationContext context, RedisCachePut annotation) {
Boolean condition = true;
if (StringUtils.isNotBlank(annotation.condition())) {
Expression conditionExpression = parser.parseExpression(annotation.condition());
condition = conditionExpression.getValue(context, Boolean.class);
}
if (Boolean.TRUE.equals(condition)) {
String cacheName = annotation.cache();
if (StringUtils.isBlank(cacheName)) {
return;
}
Expression valueExpression = parser.parseExpression(annotation.value());
Object value = valueExpression.getValue(context, Object.class);
if (Objects.nonNull(value)) {
if (value instanceof Collection<?> collection) {
cacheManager.putAll(cacheName, StringUtils.defaultIfBlank(annotation.key(), null), CollectionUtils.emptyIfNull(collection));
} else {
Expression keyExpression = parser.parseExpression(annotation.key());
Object key = keyExpression.getValue(context, Object.class);
if (Objects.nonNull(key)) {
String keyStr = StringUtils.defaultIfBlank(key.toString(), value.toString());
cacheManager.put(cacheName, keyStr, value);
}
}
}
}
}
private void evict(EvaluationContext context, RedisCacheEvict annotation) {
Boolean condition = true;
if (StringUtils.isNotBlank(annotation.condition())) {
Expression conditionExpression = parser.parseExpression(annotation.condition());
condition = conditionExpression.getValue(context, Boolean.class);
}
if (Boolean.TRUE.equals(condition)) {
String[] cacheNames = annotation.caches();
if (cacheNames.length == 0) {
return;
}
if (annotation.allEntries()) {
cacheManager.clearAll(cacheNames);
} else {
Expression keyExpression = parser.parseExpression(annotation.key());
Object key = keyExpression.getValue(context, Object.class);
if (Objects.nonNull(key)) {
if (key instanceof Collection<?> collection) {
for (String cacheName : cacheNames) {
cacheManager.evictAll(cacheName, StringUtils.defaultIfBlank(annotation.keyField(), null), collection);
}
} else {
String keyStr = key.toString();
if (StringUtils.isNotBlank(keyStr)) {
for (String cacheName : cacheNames) {
cacheManager.evict(cacheName, keyStr);
}
}
}
}
}
}
}
}
代码案例
先执行删除后执行写入
@RedisCaching(
puts = {
@RedisCachePut(cache = "app_secret1", key = "#p0.id", value="#p0", condition = "#result"),
@RedisCachePut(cache = "app_secret2", key = "#p0.id", value="#p0", condition = "#result")
},
evicts = {
@RedisCacheEvict(caches = "app_secret1", key = "#p0.id", condition = "#result"),
@RedisCacheEvict(caches = "app_secret2", key = "#p0.id", condition = "#result")
}
)
public boolean removeById(AppSecretDO entity);
缓存初始化和销毁
接口
public interface RedisCacheProcessor {
void init(RedisCacheManager cacheManager);
void destroy(RedisCacheManager cacheManager);
}
Spring容器监听(用于Spring容器启动和销毁时,初始化和销毁缓存)
容器启动监听
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import java.util.Map;
public class RedisCacheInitApplicationListener implements ApplicationListener<ApplicationReadyEvent> {
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
RedisCacheManager redisCacheManager = event.getApplicationContext().getBean(RedisCacheManager.class);
Map<String, RedisCacheProcessor> beanMap = event.getApplicationContext().getBeansOfType(RedisCacheProcessor.class);
for (RedisCacheProcessor processor : beanMap.values()) {
processor.init(redisCacheManager);
}
}
}
容器退出监听
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import java.util.Map;
public class RedisCacheClearApplicationListener implements ApplicationListener<ContextClosedEvent> {
@Override
public void onApplicationEvent(ContextClosedEvent event) {
RedisCacheManager redisCacheManager = event.getApplicationContext().getBean(RedisCacheManager.class);
Map<String, RedisCacheProcessor> beanMap = event.getApplicationContext().getBeansOfType(RedisCacheProcessor.class);
for (RedisCacheProcessor processor : beanMap.values()) {
processor.destroy(redisCacheManager);
}
}
}
代码示例
import org.springframework.stereotype.Repository;
@Repository
public class AppSecretRepository implements RedisCacheProcessor {
@Override
public void init(RedisCacheManager cacheManager) {
cacheManager.putAll("app_secret", "id", list());
}
@Override
public void destroy(RedisCacheManager cacheManager) {
cacheManager.clear("app_secret");
}
}
代码仓库
说明
我没怎么写注释,有看不懂的地方可以给我发私信或者评论区留言
转载自:https://juejin.cn/post/7381997397822341174