1. 程式人生 > 實用技巧 >spring aop + 自定義註解實現本地快取

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