spring boot上支持@cacheable的緩存孔祸,其中的“key” 和“cacheNames”支持spel表達式隆敢,效果如下
spel表達式的提示
關(guān)于spel表達式是什么,大家可以自助查詢崔慧,這個是相關(guān)文檔地址:Spel
spel表達式不僅支持調(diào)用方法拂蝎,還支持調(diào)用對象里面的參數(shù),這個正是我的需求惶室,平時傳給annotation的參數(shù)都是固定的温自,但是通過Spel表達式我們可以傳一個變量值,甚至是執(zhí)行一個方法皇钞。
下面我嘗試在aop里面注入Spel表達式的實現(xiàn):
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test{
public String id() default"";
public String text() default"";
}
使用方法:
@Test(id="#id",text="#userService.test()")
public void test(UserBase userBase, int id){
}
通過這樣的注解我是可以正常調(diào)用到userService的test方法的悼泌,這里有個值得注意的地方,test方法一定要是public的夹界,可是#id馆里,卻一直報著null的錯誤,或者是類似于這樣的錯誤
EL1008E:(pos 0): Property or field 'id' cannot be found on object of type 'java.lang.Integer' - maybe not public?
我一度懷疑是context沒有把這個id這個常量給注進去可柿,又或者我的contex獲取有誤
帶著這樣的疑惑鸠踪,一開始我找到了一個@ConditionalOnExpression的注解,它允許在Spring的EL表達式中寫一個調(diào)節(jié)复斥,在IDEA里同樣是有代碼提示的
@ConditionalOnExpression注解
但是很可惜营密,繼續(xù)源著@ConditionalOnExpression這個注解查下去并沒有太多的發(fā)現(xiàn)
這個時候,我只好查@Cacheable的實現(xiàn)目锭,看一下它的key值是如何拿出來的
因為通過調(diào)試沒辦法看出@Cacheable的是如何實現(xiàn)的评汰,我只好把@Cacheable下面的所有相關(guān)代碼大概過了一下
@Cacheable的相關(guān)代碼
結(jié)果在CacheAspectSupport的里發(fā)現(xiàn)了這個
privateObjectexecute(finalCacheOperationInvoker invoker,Method method,CacheOperationContexts contexts) {
// Special handling of synchronized invocation
if(contexts.isSynchronized()) {
CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next();
if(isConditionPassing(context,CacheOperationExpressionEvaluator.NO_RESULT)) {
Object key = generateKey(context,CacheOperationExpressionEvaluator.NO_RESULT);
Cache cache = context.getCaches().iterator().next();
try{
returncache.get(key, newCallable() {
@Override
publicObjectcall()throwsException {
returninvokeOperation(invoker);
}
如上述代碼里有一個Object key = generateKey(context,CacheOperationExpressionEvaluator.NO_RESULT);
然后發(fā)現(xiàn)了這個
protectedObjectgenerateKey(Object result) {
if(StringUtils.hasText(this.metadata.operation.getKey())) {
EvaluationContext evaluationContext = createEvaluationContext(result);
return evaluator.key(this.metadata.operation.getKey(), this.methodCacheKey,evaluationContext);
}
其中evaluationContext是用來構(gòu)造context的,而evaluator.key(this.metadata.operation.getKey(), this.methodCacheKey,evaluationContext);方法是正常的從context中獲取spel表達式里的值侣集,那么我的重點就在他的context是如何獲取到的键俱,是不是和我們寫的context有什么不一樣的地方
后來,我在MethodBasedEvaluationContext的代碼里發(fā)現(xiàn)這樣一段
key的最終實現(xiàn)
目瞪口呆
世分。编振。。臭埋。踪央。。瓢阴。畅蹂。。荣恐。液斜。累贤。。少漆。臼膏。。示损。渗磅。。检访。。。。密似。。蟆盹。逾滥。寨昙。舔哪。
好吧抬驴,我之前真的以為一定是Spel提供了什么神奇的方法布持,然后我們調(diào)用的就可以了,萬萬沒想到居然是自己挨個遍歷方法的參數(shù)把數(shù)據(jù)set進context的芙委。。。。哪审。。
如果我一開始就發(fā)現(xiàn)StandardEvaluationContext有個private final Map?variables=new HashMap();我覺得我沒必要走那么多彎路,粗心了朝氓。恋日。岂膳。
好了筷屡,真相大白了毙死,提供一下具體的代碼吧
aop的實現(xiàn)
@Around("execution(@com.didi.km.commons.annotation.SendEvent * *(..)) && @annotation(test)")
public voidaround(ProceedingJoinPoint joinPoint,Test test)throwsThrowable {
StandardEvaluationContext standardEvaluationContext =new StandardEvaluationContext(joinPoint.getArgs());
standardEvaluationContext = setContextVariables(standardEvaluationContext,joinPoint);
String key = ExplUtils.generateKey(test.id(),standardEvaluationContext);
.........................
}
把方法的參數(shù)set到context中去
privateStandardEvaluationContextsetContextVariables(StandardEvaluationContext standardEvaluationContext,ProceedingJoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method targetMethod = methodSignature.getMethod();
String[] parametersName =parameterNameDiscoverer.getParameterNames(targetMethod);
if(args ==null|| args.length<=0) {
return standardEvaluationContext;
}
for(int i =0;i < args.length;i++) {
standardEvaluationContext.setVariable(parametersName[i],args[i]);
}
return standardEvaluationContext;
}
最后收尾工作
public class ExplUtils {
public staticStringgenerateKey(String key,StandardEvaluationContext context) {
BeanFactory beanFactory = SpringBeanUtils.getBeanFactory();
context.setBeanResolver(newBeanFactoryResolver(beanFactory));
ExpressionParser parser =newSpelExpressionParser();
Expression exp = parser.parseExpression(key);
String value = exp.getValue(context,String.class);
returnvalue;
}
}
這樣就可以實現(xiàn)和@Cacheable一樣支持Spel表達式的效果了
最后想吐槽一下再菊,簡書這個代碼輸入感覺有點難用秉剑,有些格式給我去掉了,還是我哪里姿勢不對??盖文?