Spring中spel表达式是解析的原理

文章详细介绍了SpringExpressionLanguage(SpEL)中的SpelExpressionParser类,它扩展了TemplateAwareExpressionParser,专门解析包含模板和EL表达式的字符串,通过递归解析和表达式组合确保正确执行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

// 解析表达式
public class SpelExpressionParser extends TemplateAwareExpressionParser {
    public SpelExpressionParser() {
        this.configuration = new SpelParserConfiguration();
    }

    public Expression parseExpression(String expressionString, @Nullable ParserContext context) throws ParseException {
        // 是否存在模板解析
        if (context != null && context.isTemplate()) {
            // 解析模板
            return parseTemplate(expressionString, context) {
                // 如果没有表达式
                if (expressionString.isEmpty()) {
                    // 返回一个直接的表达式,不含El表达式
                    return new LiteralExpression("");
                }
                // 解析表达式
                Expression[] expressions = parseExpressions(expressionString, context) {
                    // 解析出来的表达式
                    List<Expression> expressions = new ArrayList<>();
                    // 获取设置的模板前缀
                    String prefix = context.getExpressionPrefix();
                    // 获取设置的模板后缀
                    String suffix = context.getExpressionSuffix();
                    // 解析字符串
                    int startIdx = 0;
                    while (startIdx < expressionString.length()) {
                        // 获取#{}的位置
                        int prefixIndex = expressionString.indexOf(prefix, startIdx);
                        // 找到了#{,并且的位置在最前面或者后面都行
                        if (prefixIndex >= startIdx) {
                            // #{前面还有字符串,表示前面还有表达式,表示一个具体的值,例如: 100 - #{100-200}
                            if (prefixIndex > startIdx) {
                                // 将 100- 这个表达式保存成LiteralExpression表达式对象
                                // LiteralExpression表示这一个直接的表达式,不需要解析变量啥的
                                expressions.add(new LiteralExpression(expressionString.substring(startIdx, prefixIndex)));
                            }
                            // 获取#{后面的表达式
                            int afterPrefixIndex = prefixIndex + prefix.length();
                            // 解析后缀的}的索引它的索引用来截取就是所有#{}内部的表达式
                            int suffixIndex = skipToCorrectEndSuffix(suffix, expressionString, afterPrefixIndex) {
                                // #{的位置
                                int pos = afterPrefixIndex;
                                // # 需要解析字符串的长度
                                int maxlen = expressionString.length();
                                // 解析}的位置
                                int nextSuffix = expressionString.indexOf(suffix, afterPrefixIndex);
                                // 如果不存在},表示}缺失,表达式异常
                                if (nextSuffix == -1) {
                                    return -1;
                                }
                                // 创建一个栈
                                Deque<Bracket> stack = new ArrayDeque<>();
                                while (pos < maxlen) {
                                    // 如果是}结束位置,结束
                                    if (isSuffixHere(expressionString, pos, suffix) && stack.isEmpty()) {
                                        break;
                                    }
                                    // 一一获取字符串
                                    char ch = expressionString.charAt(pos);
                                    switch (ch) {
                                        case '{':
                                        case '[':
                                        case '(':
                                            // 起始括号,入栈,为了校验括号对是否合法
                                            stack.push(new Bracket(ch, pos));
                                            break;
                                        case '}':
                                        case ']':
                                        case ')':
                                            // 栈为空,表示不存在起始括号,现又有结束括号,表示括号对不匹配
                                            if (stack.isEmpty()) {
                                                throw new ParseException(expressionString, pos);
                                            }
                                            // 出栈起始括号,因为括号对是栈,先进后出的,例如: { [ ( } ] )
                                            Bracket p = stack.pop();
                                            // 判断是不是与起始括号匹配匹配的括号对
                                            if (!p.compatibleWithCloseBracket(ch)) {
                                                throw new ParseException(expressionString, pos, "Found closing '" + ch);
                                            }
                                            break;
                                        case '\'':
                                        case '"':
                                            // 如果是字符串,直接解析"后面的字符串,看是否还存在"
                                            int endLiteral = expressionString.indexOf(ch, pos + 1);
                                            // 如果后面不存在",表示""对不匹配
                                            if (endLiteral == -1) {
                                                throw new ParseException(expressionString, pos);
                                            }
                                            // 将最后的"位置重新赋值,因为""内部的位置没必要解析
                                            // 我们现在只是为了获取最后一个}的位置,然后同时校验
                                            pos = endLiteral;
                                            break;
                                    }
                                    // 如果不是上面的符号,直接跳过
                                    pos++;
                                }
                                // 如果字符串解析完毕,栈中还存在未出站的符号,表示括号对异常
                                if (!stack.isEmpty()) {
                                    Bracket p = stack.pop();
                                    throw new ParseException(expressionString, p.pos, "Missing closing");
                                    ;
                                }
                                // 所有字符串都处理完了,还没有找到最后一个}的位置,表示有问题
                                if (!isSuffixHere(expressionString, pos, suffix)) {
                                    return -1;
                                }
                                // 返回找到的最后一个{的位置
                                return pos;
                            }
                            // -1表示解析出现异常
                            if (suffixIndex == -1) {
                                throw new ParseException(expressionString);
                            }
                            // 如果最后一个}的位置和#{的位置是一样的,表示#{}空的,抛出异常
                            if (suffixIndex == afterPrefixIndex) {
                                throw new ParseException(expressionString);
                            }
                            // 解析#{到}中间的表达式
                            String expr = expressionString.substring(prefixIndex + prefix.length(), suffixIndex);
                            // 去除空格
                            expr = expr.trim();
                            // 如果#{}之间全是空格,也报错
                            if (expr.isEmpty()) {
                                throw new ParseException(expressionString);
                            }
                            // 解析#{}内部的表达式,并保存
                            expressions.add(doParseExpression(expr, context) {
                                return new InternalSpelExpressionParser(this.configuration).doParseExpression(expressionString, context)
                                {
                                    this.expressionString = expressionString;
                                    // 创建表达式分割器
                                    Tokenizer tokenizer = new Tokenizer(expressionString);
                                    // 使用表达式进行分割
                                    this.tokenStream = tokenizer.process() {
                                    // 将字符串一一判断
                                    while (this.pos < this.max) {
                                        // 获取正在解析的字符串
                                        char ch = this.charsToProcess[this.pos];
                                        // 是否是字符符号
                                        // ('a'..'z'|'A'..'Z'|'_'|'$') ('a'..'z'|'A'..'Z'|'_'|'$'|'0'..'9'|DOT_ESCAPED)*
                                        if (isAlphabetic(ch)) {
                                            // 记录当前字符为标识符
                                            lexIdentifier() {
                                                // 从当前位置开始解析
                                                int start = this.pos;
                                                do {
                                                    this.pos++;
                                                }
                                                // ('a'..'z'|'A'..'Z'|'_'|'$') ('a'..'z'|'A'..'Z'|'_'|'$'|'0'..'9'|DOT_ESCAPED)*
                                                // 这个if就是判断字母(变量名的)
                                                while (isIdentifier(this.charsToProcess[this.pos]));
                                                // 符合字母的数组,也就是单词的数组
                                                char[] subarray = subarray(start, this.pos);
                                                // 两个字母,处理gt,lt,eq,div,not ... 特殊的两三个字母
                                                if ((this.pos - start) == 2 || (this.pos - start) == 3) {
                                                    // 全转换为小写
                                                    String asString = new String(subarray).toUpperCase();
                                                    // 二分查找{"DIV", "EQ", "GE", "GT", "LE", "LT", "MOD", "NE", "NOT"};
                                                    // 是不是这些标识符
                                                    int idx = Arrays.binarySearch(ALTERNATIVE_OPERATOR_NAMES, asString);
                                                    // 如果是
                                                    if (idx >= 0) {
                                                        // 保存这个单词是一个操作符
                                                        pushOneCharOrTwoCharToken(TokenKind.valueOf(asString), start, subarray)
                                                        {
                                                            this.tokens.add(new Token(kind, data, pos, pos + kind.getLength()));
                                                        }
                                                        return;
                                                    }
                                                }
                                                // 如果不是上面的两三个特殊字符,标记这是个标识符(变量)
                                                this.tokens.add(new Token(TokenKind.IDENTIFIER, subarray, start, this.pos));
                                            }
                                        }
                                        // 如果不是字符符号,那就是操作符
                                        else {
                                            switch (ch) {
                                                // 下面只贴部分操作符,实际上还有很多
                                                // 详见org.springframework.expression.spel.standard.Tokenizer
                                                // 下面方法都是只做一件事,将表达式封装成Token对象,描述了该表达式是字符还是操作符
                                                // 以及字符的长度,起始位置和结束位置,方便获取这个单词或者操作符的长度
                                                // this.tokens.add(new Token(TokenKind.LITERAL_INT, data, start, end));
                                                case '+':
                                                    // 可能出现++,或者+
                                                    if (isTwoCharToken(TokenKind.INC)) {
                                                        pushPairToken(TokenKind.INC);
                                                    } else {
                                                        pushCharToken(TokenKind.PLUS);
                                                    }
                                                    break;
                                                case ':':
                                                    pushCharToken(TokenKind.COLON);
                                                    break;
                                                case '*':
                                                    pushCharToken(TokenKind.STAR);
                                                    break;
                                                case '/':
                                                    pushCharToken(TokenKind.DIV);
                                                    break;
                                                case '%':
                                                    pushCharToken(TokenKind.MOD);
                                                    break;
                                                pushCharToken(TokenKind.LSQUARE);
                                                break;
                                                case '#':
                                                    pushCharToken(TokenKind.HASH);
                                                    break;
                                                // 如果是\,直接抛异常
                                                case '\\':
                                                    raiseParseException(this.pos, SpelMessage.UNEXPECTED_ESCAPE_CHAR);
                                                    break;
                                                default:
                                                    throw new IllegalStateException("Cannot handle (" + (int) ch + ") '" + ch + "'");
                                            }
                                        }
                                    }
                                    // 返回所有分词后的对象
                                    return this.tokens;
                                }
                                    // 递归解析所有的表达式,封装成一个完整的SpelNodeImpl表达式
                                    // 而SpelNodeImpl是对应了多种类型实现,例如+,-,*,对应的实现有OpPlus,OpAnd,OpOr
                                    // 还有对应的值,1,2 false,"dd"对应的实现LongLiteral,XXxLiteral
                                    // 以及赋值符号 = Assign
                                    // 内部通过children来表示所有的子表达式,并且还包含对应的操作符,或者对应的实际值...
                                    SpelNodeImpl ast = eatExpression();
                                    // 封装成一个Spel表达式对象
                                    return new SpelExpression(expressionString, ast, this.configuration);
                                }
                            });
                            startIdx = suffixIndex + suffix.length();
                        } else {
                            // 到这里表示所有含EL表达式的的部分解析完成
                            // 如果进到这里,表示除了EL表达式之外,后面还有一点表达式,就将后面的整个表达式作为一个整体
                            // 包装成一个直接的LiteralExpression表达式
                            expressions.add(new LiteralExpression(expressionString.substring(startIdx)));
                            // 终止while循环
                            startIdx = expressionString.length();
                        }
                    }
                    // 返回解析到的所有表达式
                    return expressions.toArray(new Expression[0]);
                }
                // 只有一个,返回
                if (expressions.length == 1) {
                    return expressions[0];
                } else {
                    // 如果有多个,需要将表达式进行合并
                    return new CompositeStringExpression(expressionString, expressions);
                }
            }
        }
        // 如果没有设置解析的模版上下文,默认不处理EL表达式模版,只能解析变量,不能使用#{表达式},因为不会解析
        else {
            return doParseExpression(expressionString, context);
        }
    }
}

// 获取表达式的值
public class SpelExpression implements Expression {
    // 解析出来的el表达式
    private final String expression;
    // 解析出来的所有表达式的节点
    private final SpelNodeImpl ast;
    // 解析器配置
    private final SpelParserConfiguration configuration;
    // 整个计算过程的上下文对象
    private EvaluationContext evaluationContext;

    // 获取值
    public Object getValue(EvaluationContext context) throws EvaluationException {
        // 如果该表达式已经被编译了
        if (this.compiledAst != null) {
            // 这个提高效率的,就是jvm编译指令
            return this.compiledAst.getValue(context.getRootObject().getValue(), context);
        }
        // 创建表达式的状态对象
        ExpressionState expressionState = new ExpressionState(context, this.configuration);
        // 解析表达节点中的值
        Object result = this.ast.getValue(expressionState) {
            // 如果变量名上this
            if (this.name.equals(THIS)) {
                return state.getActiveContextObject() {
                    // 如果不存在contextObjects,直接返回传递的rootObject
                    if (CollectionUtils.isEmpty(this.contextObjects)) {
                        return this.rootObject;
                    }
                    return this.contextObjects.element();
                }
            }
            // 如果变量名是root
            if (this.name.equals(ROOT)) {
                // 直接返回传递的rootObject
                TypedValue result = state.getRootContextObject() {
                    public TypedValue getRootContextObject () {
                        return this.rootObject;
                    }
                }
                return result;
            }
            // 其他变量,从上下文中的变量中找
            TypedValue result = state.lookupVariable(this.name) {
                // final EvaluationContext relatedContext;
                // 从计算上下文中获取属性的值
                Object value = this.relatedContext.lookupVariable(name) {
                    return this.variables.get(name);
                }
                return (value != null ? new TypedValue(value) : TypedValue.NULL);
            }
            // 返回获取到的变量值
            Object value = result.getValue();
            return result;
        }
        // 判断是否需要缓存编译指令
        checkCompile(expressionState);
        return result;
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值