SpringEl + aop + jedis實現緩存管理

SpringEl + aop + jedis實現緩存管理

目的:使用 aop 配合 jedis 和 spring expression 實現緩存管理

切點

其它的切點,如刪除緩存等操作代碼實現類似.

import java.lang.annotation.*;

/**
 * 配合{@link CacheAspect},該註解是切入點
 * @author fengxuechao
 * @version 0.1
 * @date 2019/8/26
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Cacheable {

    /**
     * redis key 的前綴
     * @return
     */
    String prefix() default "";

    /**
     * redis key
     * @return
     */
    String key();
}

切面

切點就是上文的 Cacheable ,實現也非常簡單,主要運用了反射方面的知識。

/**
 * @author fengxuechao
 * @version 0.1
 * @date 2019/9/2
 */
@Slf4j
@Aspect
@Component
public class CacheAspect {

    @Autowired
    private JedisCluster jedisCluster;

    @Autowired
    private AthenaIotProperties properties;

    /**
     * 模仿spring cache
     *
     * @param pjp
     * @return
     * @throws Throwable
     */
    @Around("@annotation(Cacheable)")
    public Object cacheable(ProceedingJoinPoint pjp) throws Throwable {
        boolean info = log.isInfoEnabled();
        Method method = AopUtils.getMethod(pjp);
        Cacheable cacheable = method.getAnnotation(Cacheable.class);
        String key = getELString(cacheable, method, pjp.getArgs());
        int expireIn = properties.getTokenExpireIn();
        Boolean existKey = jedisCluster.exists(key);
        if (!existKey) {
            if (info) {
                log.info("緩存中沒有 key:{}", key);
            }
            Object obj = pjp.proceed();
            String value = JSON.toJSONString(obj);
            jedisCluster.setex(key, expireIn, value);
            if (info) {
                log.info("緩存token:key={}, expireIn={}, value={}", key, expireIn, value);
            }
            return obj;
        }
        String athenaIotTokenString = jedisCluster.get(key);
        if (info) {
            log.info("緩存token:key={}, expireIn={}, value={}", key, expireIn, athenaIotTokenString);
        }
        return JSON.parseObject(athenaIotTokenString, method.getReturnType());
    }

    private String getELString(Cacheable cacheable, Method method, Object[] args) {
        ExpressionParser parser = new SpelExpressionParser();
        EvaluationContext context = new StandardEvaluationContext();
        //獲取被攔截方法參數名列表(使用Spring支持類庫)
        LocalVariableTableParameterNameDiscoverer nameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
        String[] paraNameArr = nameDiscoverer.getParameterNames(method);
        //把方法參數放入SPEL上下文中
        for (int i = 0; i < paraNameArr.length; i++) {
            context.setVariable(paraNameArr[i], args[i]);
        }
        String keyPrefix = parser.parseExpression(cacheable.prefix()).getValue(context, String.class);
        String key = parser.parseExpression(cacheable.key()).getValue(context, String.class);
        return MessageFormat.format("{0}{1}", keyPrefix, key);
    }

}

AopUtils

/**
 * @author fengxuechao
 * @version 0.1
 * @date 2019/8/26
 */
public class AopUtils {

    /**
     * 獲取被攔截方法對象
     * MethodSignature.getMethod() 獲取的是頂層接口或者父類的方法對象
     * 而緩存的註解在實現類的方法上
     * 所以應該使用反射獲取當前對象的方法對象
     *
     * @param pjp
     * @return
     * @throws NoSuchMethodException
     */
    public static Method getMethod(ProceedingJoinPoint pjp) throws NoSuchMethodException {
        //--------------------------------------------------------------------------
        // 獲取參數的類型
        //--------------------------------------------------------------------------
        Object[] args = pjp.getArgs();
        Class[] argTypes = new Class[pjp.getArgs().length];
        for (int i = 0; i < args.length; i++) {
            argTypes[i] = args[i].getClass();
        }

        String methodName = pjp.getSignature().getName();
        Class<?> targetClass = pjp.getTarget().getClass();
        Method[] methods = targetClass.getMethods();

        //--------------------------------------------------------------------------
        // 查找Class<?>裏函數名稱、參數數量、參數類型(相同或子類)都和攔截的method相同的Method
        //--------------------------------------------------------------------------
        Method method = null;
        for (int i = 0; i < methods.length; i++) {
            if (methods[i].getName() == methodName) {
                Class<?>[] parameterTypes = methods[i].getParameterTypes();
                boolean isSameMethod = true;

                // 如果相比較的兩個method的參數長度不一樣,則結束本次循環,與下一個method比較
                if (args.length != parameterTypes.length) {
                    continue;
                }

                //--------------------------------------------------------------------------
                // 比較兩個method的每個參數,是不是同一類型或者傳入對象的類型是形參的子類
                //--------------------------------------------------------------------------
                for (int j = 0; parameterTypes != null && j < parameterTypes.length; j++) {
                    if (parameterTypes[j] != argTypes[j] && !parameterTypes[j].isAssignableFrom(argTypes[j])) {
                        isSameMethod = false;
                        break;
                    }
                }
                if (isSameMethod) {
                    method = methods[i];
                    break;
                }
            }
        }
        return method;
    }
}

實現

@Cacheable(prefix = "'token:'", key = "#authRequest.appId+':'+#authRequest.secret")
//@Cacheable(value = "token", key = "#p0.appId+':'+#p0.secret")
@Override
public AccessToken getAccessToken(AuthenticationRequest authRequest) {
    String tokenUrl = authRequest.getTokenUrl();
    ParameterizedTypeReference typeReference = new ParameterizedTypeReference<ResultBean<AccessToken>>(){};
    ResponseEntity<ResultBean<AccessToken>> entity = restTemplate.exchange(tokenUrl, HttpMethod.GET, null, typeReference);
    return entity.getBody().getData();
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章