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.執行看控制台輸出