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.执行看控制台输出

 

 

 

posted @ 2020-12-05 13:26  历尽千帆归来任是少年  阅读(1915)  评论(0编辑  收藏  举报