spring aop + 自定义注解实现本地缓存
1.首先加入本地缓存依赖这里用到的是caffine
<!--本地缓存 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.7.0</version>
</dependency>
2. 进行缓存配置管,(这里每一个接口都是缓存2秒,不用考虑缓存更新,同时也可以防止缓存过度使用导致内存泄漏,最主要的是防止恶意攻击接口和瞬时并发,直接拉崩数据库,如果需要单独对每一个接口的缓存时间进行单独配置需要配置CacheManager)
@Configuration
public class CacheConfig {
@Bean
public Cache<String, Object> caffeineCache() {
return Caffeine.newBuilder()
// 设置最后一次写入或访问后经过固定时间过期
.expireAfterWrite(2, TimeUnit.SECONDS)
// 初始的缓存空间大小
.initialCapacity(100)
// 缓存的最大条数
.maximumSize(1000)
.build();
}
3.编写一个缓存加入缓存,查询缓存的工具类
@Component
@Slf4j
public class CacheUtils {
@Resource
Cache<String, Object> caffeineCache;
public static final String CACHE_PREFIX = "staff_center";
/**
* 查询缓存是否存在
*
* @param key
* @return
*/
public boolean checkCacheByKey(Object key) {
String realKey = CACHE_PREFIX + "_" + key;
log.info("检查缓存是否存在key为======={}", realKey);
if (Objects.nonNull(caffeineCache.asMap().get(realKey))) {
log.info("缓存存在,执行缓存key为======={}", realKey);
return true;
} else {
log.info("缓存不存在,执行持久层,传入的key为======={}", realKey);
return false;
}
}
/**
* 加入缓存
*
* @param key
* @return
*/
public void addCache(Object key, CrispsResponse value) {
String realKey = CACHE_PREFIX + "_" + key;
log.info("添加缓存,缓存key可以为======={},value为========={}", realKey, value.getData());
caffeineCache.put(realKey, value);
}
/**
* 查询缓存
*
* @param key
* @return
*/
public Object getCache(Object key) {
String realKey = CACHE_PREFIX + "_" + key;
log.info("执行缓存,缓存key为======={}", realKey);
return caffeineCache.asMap().get(realKey);
}
}
4.自定义注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface LocalCache {
@AliasFor("cacheNames")
String[] value() default {};
@AliasFor("value")
String[] cacheNames() default {};
String key() default "";
@Deprecated
String keyGenerator() default "";
}
5.编写aop
@Aspect
public class CacheAspect {
private final Logger logger = LoggerFactory.getLogger(getClass());
private static final String CACHE_KEY_ERROR_MESSAGE = "缓存Key %s 不能为NULL";
private static final String CACHE_NAME_ERROR_MESSAGE = "缓存名称不能为NULL";
private final CacheOperationExpressionEvaluator evaluator = new CacheOperationExpressionEvaluator();
@Autowired(required = false)
private KeyGenerator keyGenerator = new SimpleKeyGenerator();
// 注入缓存工具类
@Resource
CacheUtils cacheUtils;
@Pointcut("@annotation(net.crisps.cloud.common.annotation.LocalCache)")
public void pointcut() {
}
@Around(" pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
CacheOperationInvoker aopAllianceInvoker = getCacheOperationInvoker(joinPoint);
// 获取method
Method method = this.getSpecificMethod(joinPoint);
// 获取注解
LocalCache cacheAble = AnnotationUtils.findAnnotation(method, LocalCache.class);
try {
// 执行查询缓存方法
return executeCacheAble(aopAllianceInvoker, cacheAble, method, joinPoint.getArgs(), joinPoint.getTarget());
} catch (Exception e) {
logger.error("异常信息为=={}", e.getMessage());
return aopAllianceInvoker.invoke();
}
}
// 返回 CacheOperationInvoker
private CacheOperationInvoker getCacheOperationInvoker(ProceedingJoinPoint joinPoint) {
return () -> {
try {
return joinPoint.proceed();
} catch (Throwable ex) {
throw new CacheOperationInvoker.ThrowableWrapper(ex);
}
};
}
/**
* 获取Method
*/
private Method getSpecificMethod(ProceedingJoinPoint pjp) {
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
Method method = methodSignature.getMethod();
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(pjp.getTarget());
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
return specificMethod;
}
/**
* 执行Cacheable切面
*/
private Object executeCacheAble(CacheOperationInvoker invoker, LocalCache cacheAble,
Method method, Object[] args, Object target) {
// 解析SpEL表达式获取cacheName和key
Assert.notEmpty(cacheAble.cacheNames(), CACHE_NAME_ERROR_MESSAGE);
Object key = generateKey(cacheAble.key(), method, args, target);
Assert.notNull(key, String.format(CACHE_KEY_ERROR_MESSAGE, cacheAble.key()));
// 判断是否有缓存,没有则执行方法,加入缓存
if (cacheUtils.checkCacheByKey(key)) {
return cacheUtils.getCache(key);
} else {
// 调用本身方法,获取返回值
CrispsResponse crispsResponse = (CrispsResponse) invoker.invoke();
if (ResponseCode.SUCCESS.getCode() == crispsResponse.getCode() && Objects.nonNull(crispsResponse.getData())) {
cacheUtils.addCache(key, crispsResponse);
}
return crispsResponse;
}
}
/**
* 解析SpEL表达式,获取注解上的key属性值
*/
private Object generateKey(String keySpEl, Method method, Object[] args, Object target) {
// 获取注解上的key属性值
Class<?> targetClass = getTargetClass(target);
if (StringUtils.hasText(keySpEl)) {
EvaluationContext evaluationContext = evaluator.createEvaluationContext(method, args, target, targetClass, CacheOperationExpressionEvaluator.NO_RESULT);
AnnotatedElementKey methodCacheKey = new AnnotatedElementKey(method, targetClass);
// 兼容传null值得情况
Object keyValue = evaluator.key(keySpEl, methodCacheKey, evaluationContext);
return Objects.isNull(keyValue) ? "null" : keyValue;
}
return this.keyGenerator.generate(target, method, args);
}
}
6.配置aop
@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
@Bean
public CacheAspect getCacheAspect() {
return new CacheAspect();
}
}
7.解析SpEL表达式 需要的类
package net.crisps.cloud.common.cache.exception;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.ParameterNameDiscoverer;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
class CacheEvaluationContext extends MethodBasedEvaluationContext {
private final Set<String> unavailableVariables = new HashSet<String>(1);
CacheEvaluationContext(Object rootObject, Method method, Object[] arguments, ParameterNameDiscoverer parameterNameDiscoverer) {
super(rootObject, method, arguments, parameterNameDiscoverer);
}
public void addUnavailableVariable(String name) {
this.unavailableVariables.add(name);
}
@Override
public Object lookupVariable(String name) {
if (this.unavailableVariables.contains(name)) {
throw new VariableNotAvailableException(name);
}
return super.lookupVariable(name);
}
}
package net.crisps.cloud.common.cache.exception;
import org.springframework.util.Assert;
import java.lang.reflect.Method;
class CacheExpressionRootObject {
private final Method method;
private final Object[] args;
private final Object target;
private final Class<?> targetClass;
public CacheExpressionRootObject(Method method, Object[] args, Object target, Class<?> targetClass) {
Assert.notNull(method, "Method必传");
Assert.notNull(targetClass, "targetClass必传");
this.method = method;
this.target = target;
this.targetClass = targetClass;
this.args = args;
}
public Method getMethod() {
return this.method;
}
public String getMethodName() {
return this.method.getName();
}
public Object[] getArgs() {
return this.args;
}
public Object getTarget() {
return this.target;
}
public Class<?> getTargetClass() {
return this.targetClass;
}
}
package net.crisps.cloud.common.cache.exception;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.context.expression.CachedExpressionEvaluator;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class CacheOperationExpressionEvaluator extends CachedExpressionEvaluator {
public static final Object NO_RESULT = new Object();
public static final Object RESULT_UNAVAILABLE = new Object();
public static final String RESULT_VARIABLE = "result";
private final Map<ExpressionKey, Expression> keyCache = new ConcurrentHashMap<ExpressionKey, Expression>(64);
private final Map<ExpressionKey, Expression> cacheNameCache = new ConcurrentHashMap<ExpressionKey, Expression>(64);
private final Map<ExpressionKey, Expression> conditionCache = new ConcurrentHashMap<ExpressionKey, Expression>(64);
private final Map<ExpressionKey, Expression> unlessCache = new ConcurrentHashMap<ExpressionKey, Expression>(64);
private final Map<AnnotatedElementKey, Method> targetMethodCache =
new ConcurrentHashMap<AnnotatedElementKey, Method>(64);
public EvaluationContext createEvaluationContext(Method method, Object[] args, Object target, Class<?> targetClass) {
return createEvaluationContext(method, args, target, targetClass, NO_RESULT);
}
public EvaluationContext createEvaluationContext(Method method, Object[] args,
Object target, Class<?> targetClass, Object result) {
CacheExpressionRootObject rootObject = new CacheExpressionRootObject(
method, args, target, targetClass);
Method targetMethod = getTargetMethod(targetClass, method);
CacheEvaluationContext evaluationContext = new CacheEvaluationContext(
rootObject, targetMethod, args, getParameterNameDiscoverer());
if (result == RESULT_UNAVAILABLE) {
evaluationContext.addUnavailableVariable(RESULT_VARIABLE);
} else if (result != NO_RESULT) {
evaluationContext.setVariable(RESULT_VARIABLE, result);
}
return evaluationContext;
}
public Object key(String expression, AnnotatedElementKey methodKey, EvaluationContext evalContext) {
return getExpression(this.keyCache, methodKey, expression).getValue(evalContext);
}
public Object cacheName(String expression, AnnotatedElementKey methodKey, EvaluationContext evalContext) {
return getExpression(this.cacheNameCache, methodKey, expression).getValue(evalContext);
}
public boolean condition(String conditionExpression, AnnotatedElementKey methodKey, EvaluationContext evalContext) {
return getExpression(this.conditionCache, methodKey, conditionExpression).getValue(evalContext, boolean.class);
}
public boolean unless(String unlessExpression, AnnotatedElementKey methodKey, EvaluationContext evalContext) {
return getExpression(this.unlessCache, methodKey, unlessExpression).getValue(evalContext, boolean.class);
}
void clear() {
this.keyCache.clear();
this.conditionCache.clear();
this.unlessCache.clear();
this.targetMethodCache.clear();
}
private Method getTargetMethod(Class<?> targetClass, Method method) {
AnnotatedElementKey methodKey = new AnnotatedElementKey(method, targetClass);
Method targetMethod = this.targetMethodCache.get(methodKey);
if (targetMethod == null) {
targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
if (targetMethod == null) {
targetMethod = method;
}
this.targetMethodCache.put(methodKey, targetMethod);
}
return targetMethod;
}
}
package net.crisps.cloud.common.cache.exception;
import org.springframework.expression.EvaluationException;
@SuppressWarnings("serial")
class VariableNotAvailableException extends EvaluationException {
private final String name;
public VariableNotAvailableException(String name) {
super("Variable '" + name + "' is not available");
this.name = name;
}
public String getName() {
return this.name;
}
}
8. 加上自定义注解测试缓存
8.执行看控制台输出