实战Spring之解释器模式对应的Expression表达式

推荐语雀观看视角,让文章体验更好

推荐语雀观看视角,让文章体验更好

推荐语雀观看视角,让文章体验更好

应对及其复杂的表达式计算,善于对语法进行分析拆解重组。

当我们需要实现一个加减乘除的计算方法之后,你会如何去做?
比如 : **3+2*6+5-2*2-1-1-1-1-1-1-1-1+2/2=?**

  • 乘法和除法是需要先计算结果的。
  • 加法和减法需要从左到右依次处理
  • 最后得到结果

我们先来看下Spring中的解释器使用方式:

import org.junit.Test;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;

public class ExpressionTest {

    @Test
    public void test(){
        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression("3+2*6+5-2*2-1-1-1-1-1-1-1-1+2/2");
        int result = (Integer) expression.getValue();
        System.out.println("计算结果是:" + result);
        // 计算结果是:9
    }
}

如何设计?

  • +、-、*、/ 都是计算符号,需要独立识别并且计算。
  • 数字值库理解为字面值,需要和计算符号拆分开。

得到上面的信息我们可以这么去设计:

package com.lkx.code.utils;

import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

public class JieShiQi {

    /**
     * 最上层表达式
     */
    public interface Expression {
        int interpreter(Context context);//一定会有解释方法
    }

    /**
     * 非终结符 , 可以理解为定义的符号比如 + 、 - 、 * 、/
     */
    public static abstract class NonTerminalExpression implements Expression {
        Expression e1, e2;

        public NonTerminalExpression(Expression e1, Expression e2) {
            this.e1 = e1;
            this.e2 = e2;
        }
    }

    /**
     * 减法
     */
    public static class MinusOperation extends NonTerminalExpression {

        public MinusOperation(Expression e1, Expression e2) {
            super(e1, e2);
        }

        //将两个表达式相减
        @Override
        public int interpreter(Context context) {
            int left = this.e1.interpreter(context);
            int right = this.e2.interpreter(context);
            int i = left - right;
            System.out.println("- : " + left + " - " + right + " = " + i);
            return i;
        }
    }

    /**
     * 乘法操作器
     */
    public static class MultiplyOperation extends NonTerminalExpression {

        public MultiplyOperation(Expression e1, Expression e2) {
            super(e1, e2);
        }

        //将两个表达式相减
        @Override
        public int interpreter(Context context) {
            int left = this.e1.interpreter(context);
            int right = this.e2.interpreter(context);
            int i = left * right;
            System.out.println("* : " + left + " * " + right + " = " + i);
            return i;
        }
    }
    /**
     * 终结符号 : 可以理解为字面量 比如 1 + 2 其中1和2代表的就是字面量
     */
    public static class TerminalExpression implements Expression {

        String variable;

        public TerminalExpression(String variable) {
            this.variable = variable;
        }

        @Override
        public int interpreter(Context context) {
            //因为要兼容之前的版本
            Integer lookup = context.lookup(variable);
            if (lookup == null)
                //若在map中能找到对应的数则返回
                return Integer.valueOf(variable);
            //找不到则直接返回(认为输入的就是数字)
            return lookup;
        }
    }

    public static class PlusOperation extends NonTerminalExpression {

        public PlusOperation(Expression e1, Expression e2) {
            super(e1, e2);
        }

        //将两个表达式相加
        @Override
        public int interpreter(Context context) {
            int left = this.e1.interpreter(context);
            int right = this.e2.interpreter(context);
            int i = left + right;
            System.out.println("+ : " + left + " + " + right + " = " + i);
            return i;
        }
    }

    public static class DivisionOperation extends NonTerminalExpression {

        public DivisionOperation(Expression e1, Expression e2) {
            super(e1, e2);
        }

        //将两个表达式相加
        @Override
        public int interpreter(Context context) {
            int left = this.e1.interpreter(context);
            int right = this.e2.interpreter(context);
            int i = left / right;
            System.out.println("/ : " + left + " / " + right + " = " + i);
            return i;
        }
    }

    /**
     * 上下文操作  涵盖 可见变量、操作关系、还新增解析器等等
     */
    public static class Context {
        private Map<String, Integer> visibleValueMap = new HashMap<>();

        private Map<Character, Class> operationMap = new HashMap<>();

        public void registerOperation(Character character, Class<? extends NonTerminalExpression> clazz) {
            operationMap.put(character, clazz);
        }

        public boolean isOperation(Character character) {
            return operationMap.containsKey(character);
        }

        //定义变量
        public void add(String key, Integer value) {
            visibleValueMap.put(key, value);
        }

        //将变量转换成数字
        public Integer lookup(String key) {
            return visibleValueMap.get(key);
        }


        private Object getExpressionObject(char c, Expression left, Expression right) {
            try {
                return operationMap.get(c).getConstructor(Expression.class, Expression.class).newInstance(left, right);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    }

    /**
     * 解释器组件,负责解释字面量并构建成一个树叶节点的链表对象
     */
    public static class InterpreterComponents {
        private String sourceString;
        private int currentIndex;
        private char[] charArray;
        private int sourceLength;
        Stack<Expression> objectStack = new Stack<>();

        public InterpreterComponents(String str) {
            this.sourceString = str;
            this.charArray = str.toCharArray();
            this.sourceLength = str.length();
            this.currentIndex = 0;
        }

        public Expression builder(Context context) {

            Expression left = sum(context);

            return left;
        }


        public Expression sum(Context context) {
            Expression left = cheng(context);
            while (isPeekToken('+', '-')) {
                Character character = isPeekToken();
                nextToken();
                Expression regiht = cheng(context);
                left = (Expression) context.getExpressionObject(character, left, regiht);
            }
            return left;
        }

        public Expression cheng(Context context) {
            Expression left = text(context);
            while (isPeekToken('*', '/')) {
                Character character = isPeekToken();
                nextToken();
                Expression regiht = text(context);
                left = (Expression) context.getExpressionObject(character, left, regiht);
            }
            return left;
        }

        /**
         * 获取文本值,也就是字面值,而非符号。
         *
         * @param context
         * @return
         */
        public Expression text(Context context) {
            Character text = isPeekToken();
            StringBuilder textBuilder = new StringBuilder();
            while (text != null && !context.isOperation(text)) {
                textBuilder.append(text);

                if (nextToken() != null) {
                    text = isPeekToken();
                } else {
                    break;
                }

            }
            return new TerminalExpression(textBuilder.toString());
        }

        /**
         * 判断当前下标是否涵盖特定符号
         *
         * @param ch
         * @return
         */
        private boolean isPeekToken(Character... ch) {
            Character t = isPeekToken();
            if (t == null) {
                return false;
            }

            for (int i = ch.length - 1; i >= 0; i--) {
                if (t == ch[i]) {
                    return true;
                }
            }
            return false;
        }

        /**
         * 获取当前下标的值
         *
         * @return
         */
        public Character isPeekToken() {
            if (currentIndex == sourceLength) {
                return null;
            }
            return charArray[currentIndex];
        }

        /**
         * 指针向下移动
         *
         * @return
         */
        public Character nextToken() {
            if (currentIndex == sourceLength) {
                return null;
            }
            return charArray[currentIndex++];
        }

        public boolean isEnd() {
            return currentIndex == sourceLength;
        }

    }


    public static void main(String[] args) {

        String str = "3+2*6+5-2*2-1-1-1-1-1-1-1-1+20/2+${abc}";

        Context context = new Context();

        // 注册非终结符号的操作类
        context.registerOperation('+', PlusOperation.class);
        context.registerOperation('-', MinusOperation.class);
        context.registerOperation('*', MultiplyOperation.class);
        context.registerOperation('/', DivisionOperation.class);

        // 可见值,也可以理解为变量字段名称。
        context.add("${abc}", 110);

        // 解释器,将str的每一个符号进行解释并计算得到一个链表节点,里面涵盖了执行顺序
        InterpreterComponents interpreterComponents = new InterpreterComponents(str);

        Expression builder = interpreterComponents.builder(context);

        // 这里会进行递归获取结果
        int interpreter = builder.interpreter(context);
        System.out.println(interpreter);
    }

}

结果:

3+2*6+5-2*2-1-1-1-1-1-1-1-1+20/2+${abc} = ?

* : 2 * 6 = 12
+ : 3 + 12 = 15
+ : 15 + 5 = 20
* : 2 * 2 = 4
- : 20 - 4 = 16
- : 16 - 1 = 15
- : 15 - 1 = 14
- : 14 - 1 = 13
- : 13 - 1 = 12
- : 12 - 1 = 11
- : 11 - 1 = 10
- : 10 - 1 = 9
- : 9 - 1 = 8
/ : 20 / 2 = 10
+ : 8 + 10 = 18
+ : 18 + 110 = 128

--> 最终结果: 128

大体类图:

上面的实现思路都是基于Spring中的SpelExpressionParser来做的,不过没有全部实现,仅仅实现部分,更为复杂的比如()等等,不过基于以上思路可以继续延伸。

Spring中的解释器执行顺序:
由上往下优先级从低到高

org.springframework.expression.spel.standard.InternalSpelExpressionParser#eatExpression
方法名称处理符号描述
eatExpression=、?:、?
eatLogicalOrExpressionor、**
eatLogicalAndExpressionand、&&
eatRelationalExpressionGT、LT、LE、GE、EQ、>、<、<=、>=、!=、instanceof、matches、between
eatSumExpression+、-、++
eatProductExpression*、/、%
eatPowerIncDecExpression^、++、–
eatUnaryExpression+、-、!、++、–
eatPrimaryExpressioneatNode.、?.、[、]
eatStartNode> 这里面的方法可以理解为将一些特殊符号进行替换,或者提前解释比如()、[]、{}等等,都是要先将括号内的表达式先处理。例如@代表Spring中的标记,先提前转换。
  • PropertyOrFieldReference
  • MethodReference
  • VariableReference
  • FunctionReference
  • Projection
  • Selection
  • Indexer

相关Spring的Expression的备忘录


版权声明:本文为lkx444368875原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。