开发中多多少少会使用spel,spel是Spring3引入了Spring表达式语言(Spring Expression Language,SpEL),在一些配置中,注解中经常用到,可谓是神器。比如说spring中的@Cacheable注解,其中key、unless等属性都支持Spel。举个例子:
1 2 3 4 @Cacheable(key ="#user.name + '_' + #user_phone" , unless ="#user.age > 18" ) public Product getProduct(User user ) { }
上面spel表达的意思就是,缓存使用的key由入参属性name和phone组成,当用户的年纪大于18岁时,不进入缓存。 但是@Cacheable其中cacheNames这个属性不支持Spel,很痛苦。所以我准备“重复造一次轮子”(解决cacheNames的问题还是有别的方法解决的,所以本文纯属技术交流)
自定义注解 目的很明确,定义一个支持spel的注解
1 2 3 4 5 6 7 8 9 10 11 @Target ({ElementType.METHOD, ElementType.TYPE})@Retention (RetentionPolicy.RUNTIME)public @interface Interest{ String key (); String unless (); } @Interest (key = "#user.name + #user.phone" , unless = "#user.age > 18" ) public void interest (User user){ }
实现Spel 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 package com.yzker.interest.core.aop;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.reflect.MethodSignature;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.core.LocalVariableTableParameterNameDiscoverer;import org.springframework.expression.EvaluationContext;import org.springframework.expression.Expression;import org.springframework.expression.ExpressionParser;import org.springframework.expression.spel.standard.SpelExpressionParser;import org.springframework.expression.spel.support.StandardEvaluationContext;import org.springframework.stereotype.Component;import java.lang.reflect.Method;@Component @Aspect public class InterestResolveELAspect { private static final Logger logger = LoggerFactory.getLogger(InterestResolveELAspect.class ) ; ExpressionParser parser = new SpelExpressionParser(); LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer(); @Around ("@annotation(anno)" ) public Object invoked (ProceedingJoinPoint pjp, Interest anno) throws Throwable { Object[] args = pjp.getArgs(); Method method = ((MethodSignature) pjp.getSignature()).getMethod(); String[] params = discoverer.getParameterNames(method); EvaluationContext context = new StandardEvaluationContext(); for (int len = 0 ; len < params.length; len++) { context.setVariable(params[len], args[len]); } String keySpel = anno.key(); Expression keyExpression = parser.parseExpression(keySpel); String key = keyExpression.getValue(context, String.class ) ; String unlessSpel = anno.unless(); Expression unlessExpression = parser.parseExpression(unlessSpel); Boolean unless = unlessExpression.getValue(context, Boolean.class ) ; logger.info("call InterestResolveELAspect.invoked, keySpel:[{}], resolvedKey:[{}], unlessSpel:[{}], resolvedUnless:[{}]" , keySpel, key, unlessSpel, unless); return pjp.proceed(); } }
以上代码是由aop解析spel,缓存处理逻辑未实现,本文重点在解析spel,不在于造轮子,如果不到万不得已不要尝试自己造轮子。 说一下关于cacheNames不支持spel。首先得明白cacheNames是干嘛的,在这个注解中,它主要用来配置每一类型的缓存数据过期时间。其实你可以定义一个全局默认的cacheName,所有缓存都使用它,而过期时间你在别的地方修改。不管你是用redis缓存,还是guava缓存,它总的有一个put进缓存的方法,其实你重写它本身的put方法,这里面一般会有过期时间的,你可以在这里根据你的业务场景修改过期时间。