Redis(4):カスタム注釈によるredisキャッシュ操作



一、注釈の基礎1.注記の定義:JavaファイルをAnnotationといい、@interfaceで表します.
2.メタ注釈:@interfaceの上に必要に応じて@Retention、@Target、@Document、@Inheritedの4種類を含むものを注釈します.
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.CONTRUCTOR)/コンストラクション関数
@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と注釈の強さを体得させ、偉人の肩に立ってもっと遠くを見ることができました.