Redis(四):自定义注解实现 redis缓存操作

 

一、注解的基础
1.注解的定义:Java文件叫做Annotation,用@interface表示。

2.元注解:@interface上面按需要注解上一些东西,包括@Retention、@Target、@Document、@Inherited四种。

3.注解的保留策略:

  @Retention(RetentionPolicy.SOURCE)   // 注解仅存在于源码中,在class字节码文件中不包含

  @Retention(RetentionPolicy.CLASS)     // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得

  @Retention(RetentionPolicy.RUNTIME)  // 注解会在class字节码文件中存在,在运行时可以通过反射获取到

4.注解的作用目标:

  @Target(ElementType.TYPE)                      // 接口、类、枚举、注解

  @Target(ElementType.FIELD)                     // 字段、枚举的常量

  @Target(ElementType.METHOD)                 // 方法

  @Target(ElementType.PARAMETER)            // 方法参数

  @Target(ElementType.CONSTRUCTOR)       // 构造函数

  @Target(ElementType.LOCAL_VARIABLE)   // 局部变量

  @Target(ElementType.ANNOTATION_TYPE) // 注解

  @Target(ElementType.PACKAGE)               // 包

5.注解包含在javadoc中:

  @Documented

6.注解可以被继承:

  @Inherited

7.注解解析器:用来解析自定义注解。

2、实现缓存系统

/**
 * @author 高锋
 * @className: RedisCacheRemove
 * @description: 自定义注解,结合AOP实现Redis自动缓存
 * @date 2019/8/1614:40
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
@Documented
public @interface RedisCacheRemove {

    /**key的前缀*/
    String nameSpace() default "";

    String key() default "";


}
/**
 * @author 高锋
 * @className: RedisCacheSave
 * @description: 自定义注解,结合AOP实现Redis自动缓存
 * 此注解还可以使用布隆过滤器,对数据库和缓存中都不存在的查询放进过滤器,防止缓存击穿攻击;
 * @date 2019/8/1614:39
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
@Documented
public @interface RedisCacheSave{

    /**key的前缀*/
    String nameSpace() default "";

    /**key*/
    String key();

    /**过期时间*/
    long expire() default -1;

    /**过期时间单位*/
    TimeUnit unit() default TimeUnit.SECONDS;

    /**
     * 是否为查询操作
     * 如果为写入数据库的操作,该值需置为 false
     */
    boolean read() default true;
}
/**
 * @author 高锋
 * @className: RedisCacheAspect
 * @description: 切面核心=切入点+通知/增强
 * @date 2019/8/1614:48
 */
@Aspect
@Component
@Slf4j
public class RedisCacheAspect {

    @Resource
    private RedisHandler handler;

    @Pointcut(value = "@annotation(com.guahao.wedoctor.venus.annotation.RedisCacheSave)")
    public void saveCache() {
    }

    @Pointcut(value = "@annotation(com.guahao.wedoctor.venus.annotation.RedisCacheRemove)")
    public void removeCache() {
    }

    // 在使用RedisCacheSave注解的地方织入此切点
    @Around(value = "saveCache()")
    private Object saveCache(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

        log.info("<======拦截到saveCache方法:{}.{}======>" ,
                proceedingJoinPoint.getTarget().getClass().getName(), proceedingJoinPoint.getSignature().getName());
        // 获取切入的方法对象
        // 这个m是代理对象的,没有包含注解
        Method m = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
        // this()返回代理对象,target()返回目标对象,目标对象反射获取的method对象才包含注解
        Method methodWithAnnotations = proceedingJoinPoint.getTarget().getClass().getDeclaredMethod(
                proceedingJoinPoint.getSignature().getName(), m.getParameterTypes());

        Object result;
        // 根据目标方法对象获取注解对象
        RedisCacheSave annotation = methodWithAnnotations.getDeclaredAnnotation(RedisCacheSave.class);
        // 解析key
        String key = parseKey(methodWithAnnotations, proceedingJoinPoint.getArgs(), annotation.key(), annotation.nameSpace());
        // 注解的属性本质是注解里的定义的方法
        //Method methodOfAnnotation = a.getClass().getMethod("key");
        // 注解的值本质是注解里的定义的方法返回值
        //String key = (String) methodOfAnnotation.invoke(a);
        // 到redis中获取缓存
        log.info("<====== 通过key:{}从redis中查询 ======>", key);
        String cache = handler.getCache(key);
        if (cache == null) {
            log.info("<====== Redis 中不存在该记录,从数据库查找 ======>");
            // 若不存在,则到数据库中去获取
            result = proceedingJoinPoint.proceed();
            if (result != null) {
                // 从数据库获取后存入redis, 若有指定过期时间,则设置
                long expireTime = annotation.expire();
                if (expireTime != -1) {
                    handler.saveCache(key, result, expireTime, annotation.unit());
                } else {
                    handler.saveCache(key, result);
                }
            }
            return result;
        } else {
            return deSerialize(m, cache);
        }
    }

    private Object deSerialize(Method m, String cache) {
        Class returnTypeClass = m.getReturnType();
        log.info("从缓存中获取数据:{},返回类型为:{}" , cache, returnTypeClass);
        Object object = null;
        Type returnType = m.getGenericReturnType();
        if(returnType instanceof ParameterizedType){
            ParameterizedType type = (ParameterizedType) returnType;
            Type[] typeArguments = type.getActualTypeArguments();
            for(Type typeArgument : typeArguments){
                Class typeArgClass = (Class) typeArgument;
                log.info("<======获取到泛型:{}" , typeArgClass.getName());
                object = JSON.parseArray(cache, typeArgClass);
            }
        }else {
            object = JSON.parseObject(cache, returnTypeClass);
        }
        return object;
    }

    // 在使用RedisCacheSave注解的地方织入此切点
    @Around(value = "removeCache()")
    private Object removeCache(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("<======拦截到saveCache方法:{}.{}======>" ,
                proceedingJoinPoint.getTarget().getClass().getName(), proceedingJoinPoint.getSignature().getName());
        // 获取切入的方法对象
        // 这个m是代理对象的,没有包含注解
        Method m = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
        // this()返回代理对象,target()返回目标对象,目标对象反射获取的method对象才包含注解
        Method methodWithAnnotations = proceedingJoinPoint.getTarget().getClass().getDeclaredMethod(
                proceedingJoinPoint.getSignature().getName(), m.getParameterTypes());
        Object[] args = proceedingJoinPoint.getArgs();
        Object result;
        result = proceedingJoinPoint.proceed(args);
        RedisCacheRemove annotation = methodWithAnnotations.getAnnotation(RedisCacheRemove.class);
        String key = parseKey(methodWithAnnotations, proceedingJoinPoint.getArgs(), annotation.key(),
                annotation.nameSpace());
        handler.removeCache(key);
        return result;
    }


    //解析springEL表达式
    private String parseKey(Method method, Object[] argValues, String keyEl, String nameSpace) {
        //创建解析器
        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(keyEl);
        EvaluationContext context = new StandardEvaluationContext(); // 参数
        // 添加参数
        DefaultParameterNameDiscoverer discover = new DefaultParameterNameDiscoverer();
        String[] parameterNames = discover.getParameterNames(method);
        for (int i = 0; i < parameterNames.length; i++) {
            context.setVariable(parameterNames[i], argValues[i]);
        }
        // 解析
        return /*method.getName() + ":" +*/ nameSpace + expression.getValue(context).toString();
    }


    @Component
    class RedisHandler {

        @Resource
        StringRedisTemplate cache;

        @PostConstruct
        StringRedisTemplate init() {
            GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();
            cache.setDefaultSerializer(serializer);
            return cache;
        }

         void saveCache(String key, T t, long expireTime, TimeUnit unit) {
            String value = JSON.toJSONString(t);
            log.info("<====== 存入Redis 数据:{}", value);
            cache.opsForValue().set(key, value, expireTime, unit);
        }

         void saveCache(String key, T t) {
            String value = JSON.toJSONString(t, SerializerFeature.WRITE_MAP_NULL_FEATURES);
            cache.opsForValue().set(key, value);
        }

        void removeCache(String key) {
            cache.delete(key);
        }

        String getCache(String key) {
            return cache.opsForValue().get(key);
        }

    }


}

不足与补充

此注解还可以使用Guava包中的布隆过滤器,对数据库和缓存中都不存在的查询放进过滤器,防止缓存击穿攻击;

总结
其实重复造轮子是没有必要的,但是以学习或特定业务为目的造个小轮子是值得的,这次的学习也让我体会到AOP和注解的强大之处,站在伟人的肩膀上看得更远。
 

 

 

你可能感兴趣的:(缓存中间件)