zoukankan      html  css  js  c++  java
  • 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.执行看控制台输出

     

  • 相关阅读:
    Largest Rectangle in Histogram
    Valid Sudoku
    Set Matrix Zeroes
    Unique Paths
    Binary Tree Level Order Traversal II
    Binary Tree Level Order Traversal
    Path Sum II
    Path Sum
    Validate Binary Search Tree
    新手程序员 e
  • 原文地址:https://www.cnblogs.com/bt2882/p/14089219.html
Copyright © 2011-2022 走看看