注解与AOP切面编程实现redis缓存与数据库查询的解耦

  一般缓存与数据库的配合使用是这样的。

1.查询缓存中是否有数据。

2.缓存中无数据,查询数据库。

3.把数据库数据插入到缓存中。

  其实我们发现 1,3 都是固定的套路,只有2 是真正的业务代码。我们可以把1,3 抽取出来,封装到一个自定义注解@myCache 上,通过给2方法加一个注解,实现代码的解耦。

package com.itbac.common.cache;

import org.springframework.stereotype.Service;

@Service
public class SkuQueryService {
   //注解的使用
    @myCache(key = "'SkuQueryService_findById' + #id")
    public Object findById(String id){

        System.out.println("findById方法查询数据库");

        return  "数据库返回值:"+id;
    }
}

自定义注解

package com.itbac.common.cache;


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 注解的生命周期:运行时
@Retention(RetentionPolicy.RUNTIME)
// 注解的应用范围:修饰方法
@Target(ElementType.METHOD)
public @interface myCache {
    /**
     * key 的生成规则,通过springEL表达式
     * @return
     */
    String key();
}

AOP切面类,切面编程,动态解析注解。

package com.itbac.common.cache;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * Aop切面编程
 */
@Component
@Aspect
public class cacheAOP {

    @Autowired
    private RedisTemplate redisTemplate;

    @Around("@annotation(com.itbac.common.cache.myCache)")
    public Object doAnyThing(ProceedingJoinPoint joinPoint) throws Throwable {
        String key =null;
        //1.反射技术:从注解里里面,读取key的生成规则。
        //1.1 从切入点,获取方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //1.2 从切点,获取目标对象的字节码,的方法。参数:(方法名,方法的所有参数)。
        Method method = joinPoint.getTarget().getClass().getMethod(signature.getName(), signature.getMethod().getParameterTypes());
        //1.3 从方法获取注解。
        myCache annotation = method.getAnnotation(myCache.class);
        //1.4 从注解,获取注解信息。'SkuQueryService_findById' + #id
        String keyEL = annotation.key();


        //2. 创建 springEL表达式 解析器
        SpelExpressionParser parser = new SpelExpressionParser();
        // 解析器 获取指定表达式  'SkuQueryService_findById' + #id     的表达式对象
        Expression expression = parser.parseExpression(keyEL);
        // 设置解析上下文
        StandardEvaluationContext context = new StandardEvaluationContext();
        //2.1 创建默认参数名 发现者
        DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
        //2.2 获取方法中的所有参数名。
        String[] parameterNames = discoverer.getParameterNames(method);
        //2.3 获取切点方法中的所有参数值。
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < parameterNames.length; i++) {
            // 把参数名,参数值,设置到解析器上下文
            context.setVariable(parameterNames[i],args[i].toString());
        }
        //表达式 匹配 解析上下文 中的内容 ,拿到key
         key = expression.getValue(context).toString();

        Object o = redisTemplate.opsForValue().get(key);
        if (o !=null){
            // 缓存中有数据,直接返回。
            //查询缓存
            System.out.println("查询缓存返回。");
            //延迟缓存失效时间,1天。
            redisTemplate.expire(key,1,TimeUnit.DAYS);
            return o;
        }
        // 缓存穿透标记
        Object penetrateFlag =  redisTemplate.opsForValue().get(key + "penetrateFlag");
        if (null == penetrateFlag){
            // 没有防止 缓存穿透标记,查数据库。

            // 执行切点 中的代码,查询数据库。
            Object proceed = joinPoint.proceed();

            if (null == proceed){
                // 数据库数据为空,设置缓存穿透标记。15分钟
                redisTemplate.opsForValue().set(key + "penetrateFlag",true,15,TimeUnit.MINUTES);
            }else {
                // 数据库数据不为空,把数据存到缓存中。缓存1天。
                redisTemplate.opsForValue().set(key,proceed,1, TimeUnit.DAYS);
            }
         return  proceed ;
        }
        // 延迟缓存失效时间  15分钟 缓存穿透标记
        redisTemplate.expire(key+"penetrateFlag",15,TimeUnit.MINUTES);
        // 返回
        return null;
    }
}

这样就可以通过一个自定义注解@myCache ,实现了缓存与业务代码的解耦。其中还包含了防止缓存穿透的使用技巧。不要告诉别人哦。

猜你喜欢

转载自www.cnblogs.com/itbac/p/10629987.html