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();
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章