利用递归,加强spel表达式中的三元操作符

关于spel表达式参照我另一篇文章:SPEL 表达式在自定义注解中的应用

package com.haoyu.sip.utils.spring;

import com.haoyu.sip.utils.StringUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.expression.*;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.regex.Pattern;

/**
 * SPEL表达式解析工具类
 *
 * @author trumans
 */
@Component
public class SpelPaserUtil {
    
    private static Logger logger = LoggerFactory.getLogger(SpelPaserUtil.class);
    
    /**
     * 确保#{}整个捕获
     */
    private static final Pattern ELVIS_PATTERN = Pattern.compile("(#\\{(.*?)\\?(.*?):(.*?[\\u4e00-\\u9fa5>\\w!@#$%^&*_+=])})");
    
    private static final Pattern IS_ELVIS_PATTERN = Pattern.compile(".*?\\?.*?:.*");
    
    private static final String PREFIX = "#{";
    
    private static final String SUFFIX = "}";
    
    private static ExpressionParser parser = new SpelExpressionParser();
    
    /**
     * 标准表达式环境上下文,字符串只能是纯spel表达式,不能是夹杂spel表达式的字符串
     */
    private static StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
    
    /**
     * 字符串模板的解析器上下文
     */
    private static TemplateParserContext templateParserContext = new TemplateParserContext();
    
    private static ApplicationContext applicationContext;
    
    /**
     * spel表达式获取spring容器中的bean的关键
     */
    private static BeanFactoryResolver beanFactoryResolver;
    
    @Autowired(required = false)
    public void setApplicationContext(ApplicationContext applicationContext) {
        if (SpelPaserUtil.applicationContext == null && null != applicationContext) {
            SpelPaserUtil.applicationContext = applicationContext;
        }
        if (null == SpelPaserUtil.beanFactoryResolver && SpelPaserUtil.applicationContext != null) {
            /*如果执行了,就可以使用@beanName方式获取容器中的bean。详见spel语法*/
            SpelPaserUtil.beanFactoryResolver = new BeanFactoryResolver(SpelPaserUtil.applicationContext);
            SpelPaserUtil.evaluationContext.setBeanResolver(SpelPaserUtil.beanFactoryResolver);
        }
    }
    
    /**
     * 基本方法
     *
     * @param expressionStr
     * @param root
     * @param desiredResultType
     * @param parserContext
     * @param <T>
     * @return
     */
    public static <T> T parseExpression(String expressionStr, Object root, Class<T> desiredResultType, EvaluationContext evaluationContext, ParserContext parserContext) {
        T value = null;
        try {
            Expression expression = SpelPaserUtil.parser.parseExpression(expressionStr, parserContext);
            if (evaluationContext instanceof StandardEvaluationContext && null == evaluationContext.getBeanResolver()) {
//          保证非内置StandardEvaluationContext的evaluationContext可以使用spring容器中的bean
                ((StandardEvaluationContext) evaluationContext).setBeanResolver(SpelPaserUtil.beanFactoryResolver);
            }
            value = expression.getValue(evaluationContext, root, desiredResultType);
        }catch (Exception e){
//          logger.error("{} {} {}",e.getClass().getSimpleName(),e.getMessage(),e.getStackTrace());
        }
        return value;
    }
    
    public static <T> T parseExpression(String expressionStr, Map<String, Object> rootMap, Class<T> valueType) {
        T parserResult = null;
        if (MapUtils.isNotEmpty(rootMap)) {
            StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
            evaluationContext.setVariables(rootMap);
            parserResult = parseExpression(expressionStr, rootMap, valueType, evaluationContext, null);
        } else {
            parserResult = parseExpression(expressionStr, valueType);
        }
        return parserResult;
    }
    
    public static String parseExpression(String expressionStr, Map<String, Object> rootMap) {
        return parseExpression(expressionStr, rootMap, String.class);
    }
    
    public static <T> T parseExpression(String expressionStr, Object root, Class<T> desiredResultType) {
        return parseExpression(expressionStr, root, desiredResultType, SpelPaserUtil.evaluationContext, null);
    }
    
    public static <T> T parseExpression(String expressionStr, Class<T> desiredResultType) {
        return parseExpression(expressionStr, TypedValue.NULL.getValue(), desiredResultType);
    }
    
    public static String parseExpression(String expressionStr, Object root) {
        return parseExpression(expressionStr, root, String.class);
    }
    
    public static String parseExpression(String expressionStr) {
        return parseExpression(expressionStr, TypedValue.NULL.getValue());
    }
    
    //-------------------template parser context method--------------------
    
    public static String parseTemplateExpression(String expressionStr, Object root, EvaluationContext evaluationContext) {
        return parseExpression(expressionStr, root, String.class, evaluationContext, SpelPaserUtil.templateParserContext);
    }
    
    public static String parseTemplateExpression(String expressionStr, Object root) {
        return parseTemplateExpression(expressionStr, root, SpelPaserUtil.evaluationContext);
    }
    
    public static String parseTemplateExpression(String expressionStr) {
        return parseTemplateExpression(expressionStr, TypedValue.NULL.getValue());
    }
    
    /**
     * 处理spel模板字符串。例如: this is #{#name} 。#{和}定界符将spel表达式嵌套在了字符串中
     *
     * @param expressionStr
     * @param rootMap
     * @return
     * @throws Exception
     */
    public static String parseTemplateExpression(String expressionStr, Map<String, Object> rootMap) {
        String parserResult = expressionStr;
        if (MapUtils.isNotEmpty(rootMap)) {
            StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
            evaluationContext.setVariables(rootMap);
            parserResult = parseTemplateExpression(expressionStr, rootMap, evaluationContext);
        } else {
            parserResult = parseTemplateExpression(expressionStr);
        }
        return parserResult;
    }
    
    /**
     * 用来解决spel表达式在三元操作符的局限性(即无法在三元操作符中的true or false 部分继续书写spel template模板表达式)
     * 三元操作的logic expression部分,使用非TemplateExpression,true or false 部分可以继续使用TemplateExpression一直递归。但必须保证,整个三元操作符是TemplateExpression。
     * 已无效:注意:#{ 与 } 前后千万别有空格 因为正则表达式没有进行配对匹配。后期可能修正,未知;
     * 可供参考,加以修改
     * @param expressionStr
     * @param rootMap
     * @return
     * @throws Exception
     */
    public static String elvisRecursive(String expressionStr, Map<String, Object> rootMap) {
        try {
            Stack<Integer> logicSymbol = new Stack<Integer>();
            List<Integer> coordinate = new ArrayList<>();
            int ti = expressionStr.indexOf("?", 0);
            if (ti != -1) {
                logicSymbol.push(ti);
                coordinate.add(ti);
            }
            for (int i = ti + 1; i < expressionStr.length(); i++) {
                /*确定正确的?:位置*/
                if (!logicSymbol.empty()) {
                    if (StringUtils.equals("?", String.valueOf(expressionStr.charAt(i)))) {
                        /*如果logicSymbol栈中没有了元素,就表明当前coordinate栈中存放的就是?和:的位置*/
                        logicSymbol.push(i);
                        coordinate.add(i);
                    } else if (StringUtils.equals(":", String.valueOf(expressionStr.charAt(i)))) {
                        coordinate.add(i);
                        logicSymbol.pop();
                    }
                } else {
                    break;
                }
            }
            if (CollectionUtils.isNotEmpty(coordinate) && coordinate.size() > 1) {
                coordinate.set(1, coordinate.get(coordinate.size() - 1));
                /*保证表达式有递归Elvis表达式,因为从?号之前的字符串中最末尾的 #{ 标志就是整个模板开始*/
                int prefixIndex = expressionStr.lastIndexOf(PREFIX, coordinate.get(0));
                /*prefixStack 保存 #{ 或者 { 的位置 */
                Stack<Integer> prefixStack = new Stack<Integer>();
                /*suffixStack保存 } 的位置*/
                Stack<Integer> suffixStack = new Stack<Integer>();
                for (int j = coordinate.get(1); j < expressionStr.length(); ) {
                    /*整体算法是,每找到一个#{ 或者 { ,向prefixStack其中加入一个值;每找到一个suffixStack,则向suffixStack加入,同时将prefixStack弹出一个;如果prefixStack为空时,就是匹配的 } 位置*/
                    if (StringUtils.equals(String.valueOf(expressionStr.charAt(j)), SUFFIX)) {
                        if (prefixStack.empty()) {
                            suffixStack.push(j);
                            break;
                        } else {
                            prefixStack.pop();
                        }
                        j = j + SUFFIX.length();
                    } else if (j < expressionStr.length() - 1 && StringUtils.equals(String.valueOf(new char[]{expressionStr.charAt(j), expressionStr.charAt(j + 1)}), PREFIX)) {
                        /*确保可以在表达式中使用内建map表达式*/
                        prefixStack.push(j);
                        j = j + PREFIX.length();
                    } else if (StringUtils.equals(String.valueOf(expressionStr.charAt(j)), "{")) {
                        /*确保可以在表达式中使用内建map表达式*/
                        prefixStack.push(j);
                        j += 1;
                    } else {
                        j += 1;
                    }
                }
                int suffixIndex = suffixStack.pop();
                /*分离出 logic ? true : false 三元表达式中 logic,true,false 三部分*/
                String elvisExp = StringUtils.subNoException(expressionStr, prefixIndex, suffixIndex + 1);
                String logicExp = StringUtils.subNoException(expressionStr, prefixIndex + PREFIX.length(), coordinate.get(0));
                String trueExp = StringUtils.subNoException(expressionStr, coordinate.get(0) + 1, coordinate.get(1));
                String falseExp = StringUtils.subNoException(expressionStr, coordinate.get(1) + 1, suffixIndex);
                String subElvisResult = "";
                String logicRs = SpelPaserUtil.parseExpression(logicExp, rootMap);
                logicRs = logicRs == null ? "true" : logicRs;
                if (Boolean.valueOf(logicRs)) {
                    subElvisResult = elvisRecursive(trueExp, rootMap);
                } else {
                    subElvisResult = elvisRecursive(falseExp, rootMap);
                }
                int elvisIndex = expressionStr.indexOf(elvisExp, 0);
                expressionStr = expressionStr.replace(elvisExp, subElvisResult);
                if (IS_ELVIS_PATTERN.matcher(expressionStr).find(elvisIndex)) {
                    /*如果还有三元表达式,继续解析*/
                    subElvisResult = elvisRecursive(expressionStr, rootMap);
                    expressionStr = subElvisResult;/*如果已经到了这条代码,就证明在整个表达式中Elvis已经没有了,并且全部的SPEL解析完成了*/
                }
            }
            expressionStr = SpelPaserUtil.parseTemplateExpression(expressionStr, rootMap);
            return expressionStr;
        } catch (Throwable e) {
//          logger.info("elvis happend error . current expression is {}", expressionStr);
//          e.printStackTrace();
        }
        return "";
    }
    
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352