java 实现的数据查询缓存通用模型——SPEL表达式支持(6)
接续上文(java 实现的数据查询缓存通用模型——那些我在缓存模型中用到的工具类(5))
7、SPEL表达式支持
spel表达式,SpEL(Spring Expression Language),即spring 表达式语言,它是一种轻量的、灵活的语言,通过spring的解析工具进行解析。
在前文中,我们搭建实现的数据查询缓存通用模型,是通过cacheKeyCreater的机制来生成key的,这种方式可以实现很高的定制化,但是也有个缺点,那就是要写的代码更多,也不够直观。例如,先前的构造缓存按ID查询产品的Key,它的cacheKeyCreater代码需要写这么多:
public class ProductIdCacheKeyCreator implements DawnCacheKeyCreator {
public final static String CACHE_PER = "DAWN-CACHE-PRODUCT-ID";
/**
* 它的规则是:如果从参数中直接取到了id,那么直接使用该ID;如果参数中不包含ID,而是包含product,那么取product的id;之后使用前缀DAWN-CACHE-PRODUCT-ID加上`.`加上ID生成了KEY。
*
* @param request 请求
* @param target 注解的方法的所在对象
* @param method 注解的方法
* @param params 方法携带的参数Map<String,Object>
* @return Key 字符串
*/
@Override
public String createCacheKey(HttpServletRequest request, Object target, Method method, Map<String, Object> params) {
Object id = params.get("id");
if (id == null) {
Object product = params.get("product");
if (product instanceof Product p) {
id = p.getId();
}
}
if ((!(id instanceof Long))) {
return null;
}
return String.format("%s.%d", CACHE_PER, id);
}
@Override
public String createCacheEvictKey(String cacheMethodName, HttpServletRequest request, Object target, Method method, Map<String, Object> params) {
return createCacheKey(request, target, method, params);
}
}
spel表达式可以很好的弥补这个缺点。改成spel表达式之后:
设置缓存:
@DawnCacheable(sync = false, cacheTimeUnit = TimeUnit.MILLISECONDS, expire = 50000,
spel = "'DAWN-CACHE-PRODUCT-ID.' + #params['id']")
清除缓存:
@DawnCacheEvict(name = "evictGetProductById", spel = "'DAWN-CACHE-PRODUCT-ID.' + #params['id']"),
一行就可以了,代码写的很少。
为了添加对spel表达式的支持,增加了一个spel handler类:
package cn.hengyumo.dawn.core.cache.core;
import cn.hengyumo.dawn.utils.SpelUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.expression.EvaluationContext;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Map;
@Slf4j
public class DawnCacheSpelHandler {
public static EvaluationContext buildContext(DawnCacheable dawnCacheable,
DawnCacheEvict dawnCacheEvict,
HttpServletRequest request,
Object target,
Method method,
Map<String, Object> params) {
EvaluationContext context = SpelUtil.buildBaseContext(request, target, method, params);
context.setVariable("dawnCacheable", dawnCacheable);
context.setVariable("dawnCacheEvict", dawnCacheEvict);
context.setVariable("user", null);
context.setVariable("userName", null);
context.setVariable("userId", null);
return context;
}
public static String parseLogSpel(EvaluationContext context, String spel) {
return SpelUtil.parseLogSpel(context, spel);
}
}
然后在AOP 拦截方法中做了一些改造:
/**
* 解析spel表达式生成key
*
* @param dawnCacheable 注解
* @param target 目标对象
* @param method 目标方法
* @param params 参数
* @param spel spel表达式
* @return key
*/
private String createKeyBySpel(DawnCacheable dawnCacheable, DawnCacheEvict dawnCacheEvict,
Object target, Method method,
Map<String, Object> params, String spel) {
EvaluationContext context = DawnCacheSpelHandler.buildContext(
dawnCacheable, dawnCacheEvict, AspectUtil.getRequest(), target, method, params);
return DawnCacheSpelHandler.parseLogSpel(context, spel);
}
/**
* 使用 DawnCacheKeyCreator 生成key
*
* @param dawnCacheable DawnCacheable注解
* @param dawnCacheEvict DawnCacheEvict注解
* @param target 目标对象
* @param method 目标方法
* @param params 参数
*
* @return key
*/
private String createKeyByDawnCacheKeyCreator(DawnCacheable dawnCacheable, DawnCacheEvict dawnCacheEvict,
Object target, Method method,
Map<String, Object> params) {
if (dawnCacheable != null) {
return getDawnCacheKeyCreator(dawnCacheable.cacheKeyCreator())
.createCacheKey(AspectUtil.getRequest(), target, method, params);
} else if (dawnCacheEvict != null) {
return getDawnCacheKeyCreator(dawnCacheEvict.cacheKeyCreator())
.createCacheEvictKey(dawnCacheEvict.method(), AspectUtil.getRequest(), target, method, params);
}
return null;
}
/**
* 生成缓存 key
*
* @param dawnCacheable 注解
* @param proceedingJoinPoint 切点
* @return key字符串
*/
private String createKey(DawnCacheable dawnCacheable, DawnCacheEvict dawnCacheEvict,
ProceedingJoinPoint proceedingJoinPoint) {
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
String key = null;
boolean created = false;
Class<? extends DawnCacheKeyCreator> cacheKeyCreator;
String spel;
if (dawnCacheable != null) {
cacheKeyCreator = dawnCacheable.cacheKeyCreator();
spel = dawnCacheable.spel();
} else if (dawnCacheEvict != null) {
cacheKeyCreator = dawnCacheEvict.cacheKeyCreator();
spel = dawnCacheEvict.spel();
} else {
return null;
}
// 如果自定义设置了cacheKeyCreator,那么其优先级高于 spel表达式
if (cacheKeyCreator == DawnDefaultCacheKeyCreator.class) {
if (StringUtil.isNotEmpty(spel)) {
created = true;
key = createKeyBySpel(dawnCacheable, dawnCacheEvict, proceedingJoinPoint.getTarget(),
signature.getMethod(), AspectUtil.getParams(proceedingJoinPoint), spel);
}
}
// 如果未用spel表达式创建,则使用cacheKeyCreator创建
if (!created) {
key = createKeyByDawnCacheKeyCreator(dawnCacheable, dawnCacheEvict,
proceedingJoinPoint.getTarget(), signature.getMethod(),
AspectUtil.getParams(proceedingJoinPoint));
}
return key;
}
原有的createKey被拆分成两个,根据传入的注解参数动态变化,如果自定义设置了cacheKeyCreator,那么其优先级高于 spel表达式。
这一点是考虑到 cacheKeyCreator的灵活性是优于spel表达式的,毕竟spel表达式不好写的太长,并且也不好复用逻辑。对于一些复杂的key的生成,还是需要使用cacheKeyCreator。
然而大部分情况我相信spel都足以满足key的生成。
这篇文章算是一篇番外篇了,起因也是在另一个系列的文章——基于Redis的日志缓存工具中使用了spel表达式,发现还挺不错的,后边就决定在Dawn的缓存模型中试了一下。发现还不错,没有改太多的代码。
END
版权声明
本文章由作者“衡于墨”创作,转载请注明出处,未经允许禁止用于商业用途
发布时间:2021年07月13日 11:37:54
备案号:
闽ICP备19015193号-1
关闭特效
评论区#
还没有评论哦,期待您的评论!
引用发言