彻底搞懂 Spring spel 表达式(上)


彻底搞懂 Spring spel 表达式(上)


在学习Spring spel表达式之前我们先实现这样一个需求:

定义一个 字符串 "(HelloWorld).concat(#end)"end 是一个任意字符串的变量,如果 end = " 我是拼接值",那么这个字符串的最终执行结果就是 "HelloWorld 我是拼接值"。

当然也可以直接使用String.concat,那么我们不想直接使用的这个方式,我们想动态的执行这个字符串完成拼接,而不是直接运行String的concat方法。

现在屡一下思路:

  • 这里原始值:HelloWorld 。
  • 要执行的方法:concat
  • 要拼接的值得变量 : end

这个三个结构要素,如果想完成动态执行需要如下步骤:

  • 解析字符串,获取到三个结构要素。
  • 将concat变成真正可执行的方法,也就是String.concat()。
  • 定义一个上下文context,用于存储end变量的值。

有了思路就来实现

1.定义存储三个结构要素的类:

public class Token {


    public int getStartPos() {
        return startPos;
    }

    public void setStartPos(int startPos) {
        this.startPos = startPos;
    }

    public int getEndPos() {
        return endPos;
    }

    public void setEndPos(int endPos) {
        this.endPos = endPos;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    private int startPos;

    private int endPos;

    private String value;

    private String type;

}

2.定义解析字符串的接口和实现类:

public interface ExpressionParser {

    Map parseExpression(Token token);

}

public class SpelExpressionParser implements ExpressionParser {

    private String expressionString;

    private Map token= new HashMap();

    public SpelExpressionParser(String expressionString){
        this.expressionString = expressionString;
    }


    int s = 0;
    // "('HelloWorld').concat(#end)"
    @Override
    public Map parseExpression(Token targ) {


        char[] chars = expressionString.toCharArray();

        int length = chars.length;



        if(null == targ){
            for(int t=s; t < length; t++){
                if('(' == (chars[t])){
                    Token token = new Token();
                    token.setStartPos(t+1);
                    token.setType("value");
                    parseExpression(token);
                    break;
                }


            }
        }else if("value".equals(targ.getType())){
            for(int t=s; t < length; t++){
                if(')' == (chars[t])){
                    targ.setEndPos(t);
                    token.put("value",targ);
                }
                if('.' == (chars[t])){
                    s = t+1;
                    Token token = new Token();
                    token.setStartPos(t+1);
                    token.setType("method");
                    parseExpression(token);
                    break;
                }
            }

        }else if("method".equals(targ.getType())){
            for(int t=s; t < length; t++){
                if('(' == (chars[t])){
                    targ.setEndPos(t);
                    token.put("method",targ);
                    Token token = new Token();
                    s = t+1;
                    token.setType("variable");
                    parseExpression(token);
                    break;
                }
            }
        }else if("variable".equals(targ.getType())){
            for(int t=s; t < length; t++){
                if('#' == (chars[t])){
                    targ.setStartPos(t+1);
                }
                if(')' == (chars[t])){
                    targ.setEndPos(t);
                    token.put("variable",targ);
                    break;
                }
            }
        }

        return token;

    }
}

3.定义存储变量的上下文context:

public class EvaluationContext {


    private final static ThreadLocal> variables = new ThreadLocal<>();


    public static void  put(String key, String value){

        if(null == variables.get()){
            Map map = new HashMap<>();
            variables.set(map);
        }

        variables.get().put(key,value);

    }


    public static  String get(String key){

        if(null != key && !"".equals(key) && null != variables.get()){
            String s = variables.get().get(key);
            return (null != s ) ? s :"";

        }
        return "";
    }

    public static void clear(){
        variables.remove();
    }

}

4.定义将concat变成真正可执行的方法:

 /**
     * 匹配方法
     * @param methods
     * @param name
     * @return
     */
    public Method getMethod(Set methods, String name){

        for (Method v : methods) {
            if(v.getName().equals(name)){
                //System.out.println(v.getName());
                return v;
            }
        }
        throw new IllegalArgumentException("方法不合法");

    }


    /**
     * 获取类型
     * @param arg
     * @return
     */
    public Class<?> getArgType(Object arg){
        Class<?> type = (arg instanceof Class ? (Class<?>) arg : arg.getClass());
        return type;
    }


    /**
     * 转换
     * @param argType
     * @param targetObject
     * @return
     */
    public Set getMethods(Class<?> argType, Object targetObject){
        Set result = new LinkedHashSet<>();
        Method[] methods = getMethods(argType);
        for (Method method : methods) {
            if (isCandidateForInvocation(method, argType)) {
                result.add(method);
            }
        }
        return result;
    }

    /**
     * 根据类型获取可用方法
     * @param type
     * @return
     */
    Method[] getMethods(Class<?> type) {
        return type.getMethods();
    }

    boolean isCandidateForInvocation(Method method, Class<?> targetClass) {
        return true;
    }

5. 执行测试

String a = "(HelloWorld).concat(#end)";

        ltd.newbee.mall.test.spel.speltest.EvaluationContext.put("end"," 我是拼接值");

        ltd.newbee.mall.test.spel.speltest.ExpressionParser parser = new ltd.newbee.mall.test.spel.speltest.SpelExpressionParser(a);

        Map tokens = parser.parseExpression(null);

        Token value = tokens.get("value");

        Token method = tokens.get("method");

        Token vbs = tokens.get("variable");

        String es = a.substring(value.getStartPos(),value.getEndPos());

        String me = a.substring(method.getStartPos(),method.getEndPos());

        String key = a.substring(vbs.getStartPos(),vbs.getEndPos());

        Class<?> argType = getArgType(a);

        Set methods = getMethods(argType, es);

        methodToInvoke = getMethod(methods, me);

        Object result = methodToInvoke.invoke(es, ltd.newbee.mall.test.spel.speltest.EvaluationContext.get(key));

        System.out.println(result);

看看执行结果:

HelloWorld 我是拼接值

Process finished with exit code 0

是不是很简单,理解了这部分,在了解Spring spel表达式就容易了。