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();
}