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

     

  • 相关阅读:
    「自己开发直播」实现nginx-rtmp-module多频道输入输出与权限控制
    抢购代码留存
    抢红包代码留存
    Table '' is marked as crashed and should be repaired 解决方法
    extundelete实现Linux下文件/文件夹数据恢复!
    RedHat设置Yum源
    MFC 自定义消息
    单例模式
    工厂模式(转)
    hash_map
  • 原文地址:https://www.cnblogs.com/bt2882/p/14089219.html
Copyright © 2011-2022 走看看