笔记簿
ᴄᴏᴅɪɴɢ ɪs ᴀʀᴛ
首页
关于
搜索
登录
注册
Spring Aop中解析SpEl表达式 - SpEl表达式
##### 前言 在spring中aop是常用的,但是有时候为了能灵活使用切面,可以组合SPEL表达式,使得使用切面的场景更加方便,典型的有`org.springframework.cache.annotation.Cacheable` ```java @Cacheable(value = "optionsCache", key = "'option' + #id", condition = "#id lt 12") @Override public String getOption(int id) { return optionsMapper.getOption(id); } ``` ```java @Caching(put = { @CachePut(value = "postsCache", key = "#postsVO.id"), @CachePut(value = "postsCache", key = "#postsVO.postTitle"), @CachePut(value = "postsCache", key = "#postsVO.postExcerpt") }) @Transactional(rollbackFor = Exception.class) @Override public Posts saveArticle(PostsVO postsVO) {...} ``` #### 下面介绍的是用切面做个业务接口上的访问限制 ##### 准备切面 - 切面标识 ```java @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface AccessLimit { /** * 锁的时长 */ long time() default 5L; /** * 是否需要加锁 */ boolean need() default true; /** * 锁的时间单位 */ TimeUnit timeUnit() default TimeUnit.SECONDS; /** * key的SpEl表达书 eg: 'DemoBankAccessServiceImpl:bankAccess:' + #creditApply.orderId */ String spEl() default ""; } ``` - 切面拦截 ```java @Resource private StringRedisTemplate stringRedisTemplate; @Around(value = "@annotation(accessLimit)") public Object around(ProceedingJoinPoint joinPoint, AccessLimit accessLimit) throws Throwable { if (!accessLimit.need()) { return joinPoint.proceed(); } MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); Method method = methodSignature.getMethod(); String spEl = accessLimit.spEl(); long time = accessLimit.time(); TimeUnit timeUnit = accessLimit.timeUnit(); String key = this.parse(spEl, method, joinPoint.getArgs()); String value = stringRedisTemplate.opsForValue().get(key); if (StringUtils.isNotEmpty(value)) { throw new ApiException(ErrorCode.REQUEST_AGAIN, String.format("%s%s内不允许多次请求", time, timeUnit.toString())); } try { stringRedisTemplate.opsForValue().set(key, Boolean.TRUE.toString(), time, timeUnit); return joinPoint.proceed(); } finally { stringRedisTemplate.delete(key); } } ``` - 解析spEl表达式 ```java // 使用spEl进行key的解析 private final SpelExpressionParser spelExpressionParser = new SpelExpressionParser(); // 获取被拦截方法参数名列表(使用Spring支持类库) private final LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer(); private String parse(String spEl, Method method, Object[] args) { String[] paraNameArr = discoverer.getParameterNames(method); if (StringUtils.isEmpty(spEl) || Objects.isNull(paraNameArr)) { return method.getName(); } //上下文 StandardEvaluationContext context = new StandardEvaluationContext(); //把方法参数放入上下文中 for (int i = 0; i < paraNameArr.length; i++) { context.setVariable(paraNameArr[i], args[i]); } return spelExpressionParser.parseExpression(spEl).getValue(context, String.class); } public
T parse(Object rootObject, String spEl, Method method, Object[] args, Class
clazz) { String[] paraNameArr = discoverer.getParameterNames(method); //spEl StandardEvaluationContext context = new MethodBasedEvaluationContext(rootObject, method, args, discoverer); //把方法参数放入spEl上下文中 for (int i = 0; i < paraNameArr.length; i++) { context.setVariable(paraNameArr[i], args[i]); } return spelExpressionParser.parseExpression(spEl).getValue(context, clazz); } ``` #### 总结 针对切面中解析spEl表达式,大致实现如此,可以概述为,通过切面的形参列表组合spEl表达式,解析出需要的数值,通过解析后不同的数值进而进行相应的逻辑处理。当然可拓展的地方还很多,包括锁的释放等等。