第七章 Spring之假如让你来写IOC容器——属性填充特别篇:SpEL表达式

Spring源码阅读目录

第一章 Spring之最熟悉的陌生人——IOC
第二章 Spring之假如让你来写IOC容器——加载资源篇
第三章 Spring之假如让你来写IOC容器——解析配置文件篇
第四章 Spring之假如让你来写IOC容器——XML配置文件篇
第五章 Spring之假如让你来写IOC容器——BeanFactory和FactoryBean
第六章 Spring之假如让你来写IOC容器——Scope和属性填充
第七章 Spring之假如让你来写IOC容器——属性填充特别篇:SpEL表达式
第八章 Spring之假如让你来写IOC容器——拓展篇
第九章 Spring之源码阅读——环境搭建篇
第十章 Spring之源码阅读——IOC篇

第二部分——AOP篇

第十一章 Spring之不太熟的熟人——AOP
第十二章 Spring之不得不了解的内容——概念篇
第十三章 Spring之假如让你来写AOP——AOP联盟篇
第十四章 Spring之假如让你来写AOP——雏形篇
第十五章 Spring之假如让你来写AOP——Joinpoint(连接点)篇
第十六章 Spring之假如让你来写AOP——Pointcut(切点)篇
第十七章 Spring之假如让你来写AOP——Advice(通知)上篇
第十八章 Spring之假如让你来写AOP——Advice(通知)下篇
第十九章 Spring之假如让你来写AOP——番外篇:Spring早期设计
第二十章 Spring之假如让你来写AOP——Aspect(切面)篇
第二十一章 Spring之假如让你来写AOP——Weaver(织入器)篇
第二十二章 Spring之假如让你来写AOP——Target Object(目标对象)篇
第二十三章 Spring之假如让你来写AOP——融入IOC容器篇
第二十四章 Spring之源码阅读——AOP篇

第三部分——事务篇

第二十五章 Spring之曾经的老朋友——事务
第二十六章 Spring之假如让你来写事务——初稿篇
第二十七章 Spring之假如让你来写事务——铁三角篇
第二十八章 Spring之假如让你来写事务——属性篇
第二十九章 Spring之假如让你来写事务——状态篇
第三十章 Spring之假如让你来写事务——管理篇
第三十一章 Spring之假如让你来写事务——融入IOC容器篇
第三十二章 Spring之源码阅读——事务篇

第四部分——MVC篇

第三十三章 Spring之梦开始的地方——MVC
第三十四章 Spring之假如让你来写MVC——草图篇
第三十五章 Spring之假如让你来写MVC——映射器篇
第三十六章 Spring之假如让你来写MVC——拦截器篇
第三十七章 Spring之假如让你来写MVC——控制器篇
第三十八章 Spring之假如让你来写MVC——适配器篇
第三十九章 Spring之假如让你来写MVC——番外篇:类型转换
第四十章 Spring之假如让你来写MVC——ModelAndView篇
第四十一章 Spring之假如让你来写MVC——番外篇:数据绑定
第四十二章 Spring之假如让你来写MVC——视图篇
第四十三章 Spring之假如让你来写MVC——上传文件篇
第四十四章 Spring之假如让你来写MVC——异常处理器篇
第四十五章 Spring之假如让你来写MVC——国际化篇
第四十六章 Spring之假如让你来写MVC——主题解析器篇
第四十七章 Spring之假如让你来写MVC——闪存管理器篇
第四十八章 Spring之假如让你来写MVC——请求映射视图篇
第四十九章 Spring之假如让你来写MVC——番外篇:属性操作
第五十章 Spring之假如让你来写MVC——融入IOC容器篇
第五十一章 Spring之源码阅读——MVC篇

第五部分——Boot篇

第五十二章 Spring之再进一步——Boot
第五十三章 Spring之假如让你来写Boot——环境篇
第五十四章 Spring之假如让你来写Boot——注解篇(上)
第五十五章 Spring之假如让你来写Boot——注解篇(下)
第五十六章 Spring之假如让你来写Boot——SPI篇
第五十七章 Spring之假如让你来写Boot——配置文件篇(上)
第五十八章 Spring之假如让你来写Boot——配置文件篇(下)
第五十九章 Spring之假如让你来写Boot——番外篇:再谈Bean定义
第六十章 Spring之假如让你来写Boot——自动装配篇
第六十一章 Spring之假如让你来写Boot——番外篇:杂谈Starter
第六十二章 Spring之假如让你来写Boot——番外篇:重构BeanFactory
第六十三章 Spring之假如让你来写Boot——番外篇:再谈ApplicationContext
第六十四章 Spring之假如让你来写Boot——内嵌Web容器篇
第六十五章 Spring之假如让你来写Boot——Main方法启动篇
第六十六章 Spring之最终章——结语篇



前言

    对于Spring一直都是既熟悉又陌生,说对它熟悉吧,平时用用没啥问题,但面试的时候被问的一脸懵逼,就很尴尬,都不好意思在简历上写着熟悉Spring了
在这里插入图片描述

    所以决定花点时间研究研究Spring的源码。主要参考的书籍是:《Spring源码深度解析(第2版)》、《Spring揭秘》、《Spring技术内幕:深入解析Spring架构与设计原理(第2版)》


    书接上回,在上篇 第六章 Spring之假如让你来写IOC容器——Scope和属性填充 已经实现了Scope作用域和自定义类型转换器。接下来作为拓展内容,新增了spel表达式相关内容作为 第六章 的补充内容,不关心这部分内容的小伙伴也可以直接跳过,感兴趣的小伙伴就接着往下看吧

尝试动手写IOC容器

    出场人物:A君(苦逼的开发)、老大(项目经理)

    背景:老大要求A君在一周内开发个简单的IOC容器

    前情提要:A君 不满足于现状,希望自己写的ioc容器可以支持spel表达式。。。

属性填充——特别篇:SpEL表达式

    随着版本的落下,A君的IOC容器已经可以填充各式各样的属性值,但是A君并不满足于此。因为spring还有一个强大的功能——Spel表达式。这玩意,A君平时工作中也没少用过,像取配置文件中的值:${xxx.xx},执行方法:#{xxx.getXx()},计算表达式:#{1+2+3} 等等

    虽然A君心潮澎湃,不过还是需要冷静下来思考如何实现?通常对于这种提取字符串内容的情况,A君的第一反应是用正则匹配,单一的表达式还好,如果像:#{xxx.getXx().xxx + 1 + 2 / 2 + ‘dddd’} 这种混合表达式,就算能写出正则来,怕是也快哭了。既然正则行不通,那只剩一条路了——编译原理

    编译原理 涉及到的内容比较多,比如:词法分析、语法分析、语义分析、中间代码生成、代码生成等,具体可以参考:龙书、虎书、鲸书 等相关专业书籍。不过 A君 并不打算直接去看相关原理——容易睡着。A君 打算做的是丐版的,只需要词法分析、语法分析即可,先不用管他们是什么,按照自己的理解做就行了

    要实现一个简单的Spel,A君 首先得知道哪些是他需要的内容,比如:#{a.b} + 1+2,真正需要的只有a.b,其他内容就不关 A君 的事了,所以需要定义一个接口,用来规定Spel表达式的前缀、后缀,方便 A君 提取Spel表达式。ParserContext接口代码如下:


/**
 * 表达式转换模板
 */
public interface ParserContext {
    /**
     * 是否启用模板
     * true:使用模板解析,例如:#{xxx.xx}
     * false:直接解析
     *
     * @return
     */
    boolean isTemplate();

    /**
     * Spel的前缀
     *
     * @return
     */
    String getExpressionPrefix();

    /**
     * Spel的后缀
     *
     * @return
     */
    String getExpressionSuffix();
}

A君 提供了个默认实现,以#{}包裹的内容就是Spel表达式,默认实现TemplateParserContext,如下:


import com.hqd.ch03.v11.spel.ParserContext;

/**
 * Spel表达式默认模板,以#{}包裹的内容
 */
public class TemplateParserContext implements ParserContext {
    private final String expressionPrefix;

    private final String expressionSuffix;

    public TemplateParserContext() {
        this("#{", "}");
    }

    public TemplateParserContext(String expressionPrefix, String expressionSuffix) {
        this.expressionPrefix = expressionPrefix;
        this.expressionSuffix = expressionSuffix;
    }

    @Override
    public boolean isTemplate() {
        return true;
    }

    @Override
    public String getExpressionPrefix() {
        return expressionPrefix;
    }

    @Override
    public String getExpressionSuffix() {
        return expressionSuffix;
    }
}

    有了模板接口之后, A君 就知道字符串中哪些是Spel表达式了。接下来A君 又开始挠头:怎么处理提出来的Spel表达式,对于计算机来说,啥Spel表达式,它可不认识,对它来说都是字符串。当务之急,需要先给表达式进行分类,再进行对应的操作。比如:类型是+就进行加法运算、是&&就进行且判断等等。于是乎,A君 定义个TokenKind枚举类规定Spel表达式支持的类型。代码如下:


/**
 * token类型
 */
public enum TokenKind {
    LITERAL_INT,

    LITERAL_REAL,

    LITERAL_STRING,

    LPAREN("("),

    RPAREN(")"),

    COMMA(","),

    DOT("."),

    IDENTIFIER,

    PLUS("+"),

    STAR("*"),

    MINUS("-"),

    DIV("/"),

    GE(">="),

    GT(">"),

    LE("<="),

    LT("<"),

    EQ("=="),

    NE("!="),

    MOD("%"),

    NOT("!"),

    ASSIGN("="),

    FACTORY_BEAN_REF("&"),

    SYMBOLIC_OR("||"),

    SYMBOLIC_AND("&&"),

    INC("++"),

    DEC("--");
    final char[] tokenChars;

    private TokenKind(String tokenString) {
        this.tokenChars = tokenString.toCharArray();
    }

    private TokenKind() {
        this("");
    }

    public int getLength() {
        return this.tokenChars.length;
    }
}

再定一个实体类——Token,用来包装解析出来的内容,代码如下:

/**
 *
 */
public class Token {
    private int endPos;
    private int startPos;
    private String data;
    private TokenKind kind;

    public Token(TokenKind tokenKind, int startPos, int endPos) {
        this.kind = tokenKind;
        this.startPos = startPos;
        this.endPos = endPos;
    }

    public Token(TokenKind tokenKind, char[] tokenData, int startPos, int endPos) {
        this(tokenKind, startPos, endPos);
        this.data = new String(tokenData);
    }

    public boolean isIdentifier() {
        return (this.kind == TokenKind.IDENTIFIER);
    }

    public TokenKind getKind() {
        return kind;
    }

    public int getEndPos() {
        return endPos;
    }

    public int getStartPos() {
        return startPos;
    }

    public boolean isNumericRelationalOperator() {
        return (this.kind == TokenKind.GT || this.kind == TokenKind.GE || this.kind == TokenKind.LT ||
                this.kind == TokenKind.LE || this.kind == TokenKind.EQ || this.kind == TokenKind.NE);
    }

    public String getData() {
        return data;
    }
}

TokenKindToken 都准备好之后,接着就是把获取到的表达式进行归类了,例如:0-9就是数字,+就是加号等(就是一堆switch…case)。Tokenizer 代码如下:


import com.hqd.ch03.v11.spel.exception.InternalParseException;
import com.hqd.ch03.v11.spel.exception.SpelMessage;
import com.hqd.ch03.v11.spel.exception.SpelParseException;

import java.util.ArrayList;
import java.util.List;

/**
 * 词法解析:将获取到的表达式解析成Token
 */
public class Tokenizer {
    private String expressionString;
    private char[] charsToProcess;
    private int pos;
    private int max;
    private List<Token> tokens = new ArrayList<>();

    public Tokenizer(String expressionString) {
        this.expressionString = expressionString;
        this.charsToProcess = this.expressionString.toCharArray();
        this.pos = 0;
        this.max = this.expressionString.length();
    }

	/**
     * 词法解析
     *
     * @return
     */
    public List<Token> process() {
        while (this.pos < this.max) {
            char ch = this.charsToProcess[pos++];
            if (Character.isLetter(ch)) {
                lexIdentifier(ch);
            } else {
                switch (ch) {
                    case '0':
                    case '1':
                    case '2':
                    case '3':
                    case '4':
                    case '5':
                    case '6':
                    case '7':
                    case '8':
                    case '9':
                        pushNumberToken(ch);
                        break;
                    case '+':
                        pushToken(TokenKind.PLUS);
                        break;
                    case '-':
                        pushToken(TokenKind.MINUS);
                        break;
                    case '*':
                        pushToken(TokenKind.STAR);
                        break;
                    case '/':
                        pushToken(TokenKind.DIV);
                        break;
                    case '%':
                        pushToken(TokenKind.MOD);
                        break;
                    case '(':
                        pushToken(TokenKind.LPAREN);
                        break;
                    case ')':
                        pushToken(TokenKind.RPAREN);
                        break;
                    case '.':
                        pushToken(TokenKind.DOT);
                        break;
                    case ',':
                        pushToken(TokenKind.COMMA);
                        break;
                    case '\'':
                        lexQuotedStringLiteral();
                        break;
                    case '=':
                        if (isTwoCharToken(TokenKind.EQ)) {
                            pushPairToken(TokenKind.EQ);
                        } else {
                            pushToken(TokenKind.ASSIGN);
                        }
                        break;
                    case '<':
                        if (isTwoCharToken(TokenKind.LE)) {
                            pushPairToken(TokenKind.LE);
                        } else {
                            pushToken(TokenKind.LT);
                        }
                        break;
                    case '>':
                        if (isTwoCharToken(TokenKind.GE)) {
                            pushPairToken(TokenKind.GE);
                        } else {
                            pushToken(TokenKind.GT);
                        }
                        break;
                    case '&':
                        if (isTwoCharToken(TokenKind.SYMBOLIC_AND)) {
                            pushPairToken(TokenKind.SYMBOLIC_AND);
                        } else {
                            raiseParseException(pos, SpelMessage.MISSING_CHARACTER, "&");
                        }
                        break;
                    case '|':
                        if (!isTwoCharToken(TokenKind.SYMBOLIC_OR)) {
                            raiseParseException(this.pos, SpelMessage.MISSING_CHARACTER, "|");
                        }
                        pushPairToken(TokenKind.SYMBOLIC_OR);
                        break;
                    case '!':
                        if (isTwoCharToken(TokenKind.NE)) {
                            pushPairToken(TokenKind.NE);
                        } else {
                            pushToken(TokenKind.NOT);
                        }
                        break;
                    default:
                        break;
                }
            }
        }
        return tokens;
    }

    private void lexIdentifier(char ch) {
        int start = this.pos - 1;
        StringBuilder sb = new StringBuilder();
        sb.append(ch);
        while (pos < max) {
            ch = this.charsToProcess[this.pos++];
            if (isIdentifier(ch)) {
                sb.append(ch);
            } else {
                this.pos--;
                break;
            }
        }
        this.tokens.add(new Token(TokenKind.IDENTIFIER, sb.toString().toCharArray(), start, this.pos));
    }

    private boolean isIdentifier(char ch) {
        return Character.isLetter(ch) || Character.isDigit(ch) || ch == '_' || ch == '$';
    }

    private void pushPairToken(TokenKind kind) {
        this.tokens.add(new Token(kind, this.pos - 1, this.pos + 1));
        this.pos++;
    }

    private void pushToken(TokenKind kind) {
        pushToken(kind, this.pos - 1, this.pos);
    }

    private void pushToken(TokenKind kind, int startPos, int endPos) {
        tokens.add(new Token(kind, startPos, endPos));
    }

    private void lexQuotedStringLiteral() {
        int start = pos - 1;
        StringBuilder sb = new StringBuilder();
        while (pos < max) {
            char ch = charsToProcess[pos++];
            if (ch != '\'') {
                sb.append(ch);
            } else {
                break;
            }
        }
        int end = this.pos - 1;
        tokens.add(new Token(TokenKind.LITERAL_STRING, sb.toString().toCharArray(), start, end));
    }

    private void pushNumberToken(char ch) {
        StringBuilder sb = new StringBuilder();
        sb.append(ch);
        TokenKind kind = TokenKind.LITERAL_INT;
        int start = this.pos - 1;
        sb.append(getNumber());
        if (pos < max) {
            ch = charsToProcess[pos];
            if (ch == '.') {
                sb.append(ch);
                pos++;
                sb.append(getNumber());
                kind = TokenKind.LITERAL_REAL;
            }
        }
        int end = this.pos - 1;
        tokens.add(new Token(kind, sb.toString().toCharArray(), start, end));
    }

    private String getNumber() {
        StringBuilder sb = new StringBuilder();
        while (pos < max) {
            char ch = charsToProcess[this.pos++];
            if (ch >= '0' && ch <= '9') {
                sb.append(ch);
            } else {
                this.pos--;
                break;
            }
        }
        return sb.toString();
    }

    private boolean isTwoCharToken(TokenKind kind) {
        return (kind.tokenChars.length == 2 &&
                this.charsToProcess[this.pos - 1] == kind.tokenChars[0] &&
                this.charsToProcess[this.pos] == kind.tokenChars[1]);
    }

    private void raiseParseException(int start, SpelMessage msg, Object... inserts) {
        throw new InternalParseException(new SpelParseException(this.expressionString, start, msg, inserts));
    }

}

    将表达式解析成一个个Token之后,按道理说接下来就是解析并计算相应的值了。但是新的问题又出现了:如何计算这些值,先算哪个,后算哪个。比如:3+4/2-2*3,应该先算乘除,再算加减,表达式存在优先级问题。这不经让 A君 一阵犯难

    所幸,前辈们已经给出对应的解决方法,那就是抽象语法树(AST)。简单的说,就是把解析出来的Token构建成一棵树形结构,以上边:3 + 4 / 2 - 2 * 3 为例,语法树如下:

在这里插入图片描述
A君 依稀记得大学时候的知识:二叉树的遍历可以根据根节点的位置分为前序、中序、后序。多叉树遍历方式主要分为前序、后序、层序。A君 认真观察了下,发现对上边的AST树进行前序遍历就是前缀表达式(波兰表达式):- + 3 / 4 2 * 2 3,中序遍历就是中缀表达式(平时用的表达式):3 + 4 / 2 - 2 * 3 ,后序遍历就是后缀表达式(逆波兰表达式):3 4 2 / + 2 3 * - 。有了以上的铺垫,A君 大概知道怎么实现了,先把Token构建成一棵AST树,而后再进行后序遍历即可

注:AST树不一定是二叉树,也有可能是多叉树。取决于语法规则。可能有些小伙伴对于为什么AST树可以保证优先级有所疑惑,其实跟构建节点时候的顺序有关,构建AST树的时候高优先级先构建,在构建低优先级,这样就保证高优先级是低优先级的子节点。而遍历的时候用的是后序遍历,先子后父,所以高优先级的运算符就会先执行,从而保证了优先级

    要想构建一个AST树,先要有对应的节点才行。A君 定义了一个 SpelNode 接口作为树的节点接口,代码如下:

import com.hqd.ch03.v11.spel.standard.ExpressionState;

/**
 * ast节点接口
 */
public interface SpelNode {
    /**
     * 获取节点值
     *
     * @param expressionState
     * @return
     */
    Object getValue(ExpressionState expressionState);

    /**
     * 获取子节点
     *
     * @param index
     * @return
     */
    SpelNode getChild(int index);

    /**
     * 获取起始下标
     *
     * @return
     */
    int getStartPos();

    /**
     * 获取结束下标
     *
     * @return
     */
    int getEndPos();
}

在接口里看到一个不认识的类——ExpressionState ,这个涉及到取值问题,先按下不表,A君 后边会一并说到。照例,A君 提取一个抽象类,用以封装公共实现。SpelNodeImpl 代码如下:

import com.hqd.ch03.v11.spel.SpelNode;
import com.hqd.ch03.v11.spel.context.StandardEvaluationContext;
import com.hqd.ch03.v11.spel.standard.ExpressionState;
import org.apache.commons.lang3.ObjectUtils;

/**
 * ast节点抽象类
 */
public abstract class SpelNodeImpl implements SpelNode {
    private final int startPos;

    private final int endPos;
    protected SpelNodeImpl[] children = new SpelNodeImpl[0];
    protected SpelNodeImpl parent;

    public SpelNodeImpl(int startPos, int endPos, SpelNodeImpl... operands) {
        this.startPos = startPos;
        this.endPos = endPos;
        if (!ObjectUtils.isEmpty(operands)) {
            this.children = operands;
            for (SpelNodeImpl operand : operands) {
                operand.parent = this;
            }
        }
    }

    @Override
    public SpelNode getChild(int index) {
        return children[index];
    }

    public Object getValue(StandardEvaluationContext context) {
        return getValue(new ExpressionState(context, context.getRootObj()));
    }

    @Override
    public abstract Object getValue(ExpressionState state);

    @Override
    public int getStartPos() {
        return startPos;
    }

    @Override
    public int getEndPos() {
        return endPos;
    }
}

封装好抽象类之后,接着还需要实现各个具体的运算,而节点类型大致可以分为:运算符、变量/常量。这里A君 打算先做常量/变量,因为相对比较简单。首先A君 定义了个 IntLiteral 节点,用以支持Int类型的常量,代码如下:

import com.hqd.ch03.v11.spel.standard.ExpressionState;

/**
 * int类型节点
 */
public class IntLiteral extends SpelNodeImpl {
    private Integer val;

    public IntLiteral(int startPos, int endPos, String strVal) {
        super(startPos, endPos);
        this.val = Integer.parseInt(strVal);
    }

    @Override
    public Object getValue(ExpressionState state) {
        return val;
    }
}

还有String类型,这个也不少了。StringLiteral 代码如下:

import com.hqd.ch03.v11.spel.standard.ExpressionState;

/**
 * 字符串节点
 */
public class StringLiteral extends SpelNodeImpl {
    private String val;


    public StringLiteral(int startPos, int endPos, String strVal) {
        super(startPos, endPos);
        this.val = strVal;
    }

    @Override
    public Object getValue(ExpressionState state) {
        return val;
    }
}

其他常量类型与之类似,就不一一列举了。接着 A君 把目光转到了运算符上面,运算符也是多种,大致可分为:一元运算、二元运算、三元运算。比如:!、++、-- 这些就属于一元运算,+、-、&&、|| 这些属于二元运算,三元运算最经典的当属三目运算了。抛去那些晦涩的原理,其实可以根据操作数判断,需要一个操作数就是一元运算,两个就是二元运算,以此类推。A君 先从二元运算入手,由于二元运算都需要两个操作数,分别获取左右节点后在进行运算,也存在着共同点,所以老样子,提取一个抽象类——Operator。其代码如下:

import com.hqd.ch03.v11.spel.standard.ExpressionState;

/**
 * 二元运算符抽象类
 */
public abstract class Operator extends SpelNodeImpl {
    /**
     * 运算符名称
     */
    private final String operatorName;

    public Operator(String payload, int startPos, int endPos, SpelNodeImpl... operands) {
        super(startPos, endPos, operands);
        this.operatorName = payload;
    }

    @Override
    public Object getValue(ExpressionState state) {
        Object left = getLeftOperand().getValue(state);
        Object right = getRightOperand().getValue(state);
        return getValueInternal(left, right);
    }

    protected abstract Object getValueInternal(Object left, Object right);

    public SpelNodeImpl getLeftOperand() {
        return this.children[0];
    }

    public SpelNodeImpl getRightOperand() {
        return this.children[1];
    }
}

A君 先以简单的加减乘除入手,加法节点需要获取左右节点后进行加法运算,如果是数字就直接相加,但是遇到字符串的时候比较特殊,需要进行字符的拼接。所以OpPlus代码如下:

/**
 * 加法运算符
 */
public class OpPlus extends Operator {
    public OpPlus(int startPos, int endPos, SpelNodeImpl... operands) {
        super("+", startPos, endPos, operands);
    }

    @Override
    protected Object getValueInternal(Object left, Object right) {
        if (left instanceof Number && right instanceof Number) {
            Number leftNumber = (Number) left;
            Number rightNumber = (Number) right;
            if (leftNumber instanceof Double || rightNumber instanceof Double) {
                return leftNumber.doubleValue() + rightNumber.doubleValue();
            } else if (leftNumber instanceof Integer || rightNumber instanceof Integer) {
                return leftNumber.intValue() + rightNumber.intValue();
            }
        }
        if (left instanceof String && right instanceof String) {
            return left + (String) right;
        }
        if (left instanceof String) {
            return left + String.valueOf(right);
        }
        if (right instanceof String) {
            return String.valueOf(left) + right;
        }
        return null;
    }
}

其他的二元运算都是类似的,篇幅有限,A君 这里也不一一列举了。运算时候需要注意下顺序,先左后右

    简单的运算符 A君 已经弄得七七八八了,还有个比较蛋疼的问题:如果遇到方法调用,或者属性访问,这可要怎么办?要知道 A君 最终的目的是要整合进IOC容器之中的,必须是要支持像:#{user.name}、#{user.getName()} 这种类型的表达式。 这时候会涉及到对象变量以及操作对象属性的内容,A君 可不能提前预知要传啥对象,所以,依旧定义个接口,用来保存运算时候的变量。EvaluationContext 接口代码如下:


import java.util.List;

/**
 * spel表达式上下文接口
 * 有可能涉及到变量计算,比如获取a对象里边的b属性:#{a.b}
 */
public interface EvaluationContext {
    /**
     * 获取根对象
     *
     * @return
     */
    Object getRootObj();

    /**
     * 获取属性解析器
     *
     * @return
     */
    List<PropertyAccessor> getPropertyAccessors();
}

至于操作对象属性,A君PropertyAccessor接口进行规范,遇到一些特殊的对象操作,用户可以实现该接口用以定制化的实现。代码如下:

/**
 * 对象访问接口
 */
public interface PropertyAccessor {
    /**
     * 返回可以解析的类型数组
     *
     * @return
     */
    Class<?>[] getSpecificTargetClasses();

    /**
     * 检查是否能读取该属性
     *
     * @param context
     * @param target
     * @param name
     * @return
     */
    boolean canRead(EvaluationContext context, Object target, String name);

    /**
     * 读取属性值
     *
     * @param context
     * @param target
     * @param name
     * @return
     */
    Object read(EvaluationContext context, Object target, String name);

    /**
     * 检查该属性是否可以写入
     *
     * @param context
     * @param target
     * @param name
     * @return
     */
    boolean canWrite(EvaluationContext context, Object target, String name);

    /**
     * 写入该属性
     *
     * @param context
     * @param target
     * @param name
     * @param newValue
     */
    void write(EvaluationContext context, Object target, String name, Object newValue);
}

EvaluationContext 的实现类就比较简单了,只需要提供添加对象访问器(PropertyAccessor)和传入操作对象即可,StandardEvaluationContext 代码如下:


import com.hqd.ch03.v11.spel.EvaluationContext;
import com.hqd.ch03.v11.spel.PropertyAccessor;

import java.util.ArrayList;
import java.util.List;

/**
 * 运算是上下文默认实现
 */
public class StandardEvaluationContext implements EvaluationContext {
    private Object rootObj;
    private List<PropertyAccessor> propertyAccessors = new ArrayList<>();

    public StandardEvaluationContext() {

    }

    public StandardEvaluationContext(Object rootObj) {
        this.rootObj = rootObj;
    }

    public void addPropertyAccessor(PropertyAccessor accessor) {
        this.propertyAccessors.add(accessor);
    }

    @Override
    public Object getRootObj() {
        return rootObj;
    }

    @Override
    public List<PropertyAccessor> getPropertyAccessors() {
        return propertyAccessors;
    }
}

    用了以上内容就足够了吗?A君 不经陷入了沉思:不,还不够。如果只是一级访问,比如:#{a.b} 那上边的内容就够了,但是如果是N级访问,如:#{a.b.c.d},那就芭比Q了。为了能够支持多级访问,还需要用个栈结构来保持运行状态,也就是类 ExpressionState。这也是A君在定义SpelNode接口是为什么会多出来的一个ExpressionState 类的原因了。ExpressionState 的实现也比较简单,包含:压栈,出栈等操作,当遇到多级访问时候,先把用逗号分割成一个个节点,再循环压栈出栈,大致如下图:

在这里插入图片描述
ExpressionState 实现代码如下:


import com.hqd.ch03.v11.spel.EvaluationContext;
import org.apache.commons.collections4.CollectionUtils;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.NoSuchElementException;

/**
 * 表达式运行时状态
 */
public class ExpressionState {
    private final EvaluationContext evaluationContext;
    private final Object rootObject;
    private Deque<Object> contextObjects = new ArrayDeque<>();

    public ExpressionState(EvaluationContext context) {
        this(context, context.getRootObj());
    }

    public ExpressionState(EvaluationContext context, Object rootObject) {
        this.evaluationContext = context;
        this.rootObject = rootObject;
    }

    public EvaluationContext getEvaluationContext() {
        return evaluationContext;
    }

    /**
     * 获取栈顶元素
     *
     * @return
     */
    public Object getActiveContextObject() {
        if (CollectionUtils.isEmpty(this.contextObjects)) {
            return this.rootObject;
        }
        return this.contextObjects.element();
    }

    /**
     * 压栈
     *
     * @param obj
     */
    public void pushActiveContextObject(Object obj) {
        this.contextObjects.push(obj);
    }

    /**
     * 出栈
     *
     * @return
     */
    public Object popActiveContextObject() {
        try {
            return this.contextObjects.pop();
        } catch (NoSuchElementException ex) {
            throw new IllegalStateException("Cannot pop active context object: stack is empty");
        }
    }
}

    有了上下文(EvaluationContext)和运行时状态(ExpressionState)之后,A君 终于可以添加属性和方法节点了,属性节点由于存在对象访问器(PropertyAccessor),所以获取值时候先要看看对象访问器(PropertyAccessor)是否支持该属性,如果支持则用对象访问器(PropertyAccessor),不支持则用反射直接获取。PropertyOrFieldReference 代码如下:


import com.hqd.ch03.v11.spel.EvaluationContext;
import com.hqd.ch03.v11.spel.PropertyAccessor;
import com.hqd.ch03.v11.spel.standard.ExpressionState;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang3.ArrayUtils;

import java.util.List;

/**
 * 获取类属性
 */
public class PropertyOrFieldReference extends SpelNodeImpl {
    private final String name;

    public PropertyOrFieldReference(String propertyOrFieldName, int startPos, int endPos) {
        super(startPos, endPos);
        this.name = propertyOrFieldName;
    }

    @Override
    public Object getValue(ExpressionState state) {
        try {
            return readProperty(state.getActiveContextObject(), state.getEvaluationContext(), name);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 读取类属性
     *
     * @param contextObject
     * @param evalContext
     * @param name
     * @return
     */
    private Object readProperty(Object contextObject, EvaluationContext evalContext, String name) {
        try {
            /**
             * 获取对象访问器
             */
            List<PropertyAccessor> propertyAccessors = evalContext.getPropertyAccessors();
            for (PropertyAccessor accessor : propertyAccessors) {
                Class<?>[] specificTargetClasses = accessor.getSpecificTargetClasses();
                /**
                 * 如果遇到可以解析的类型,用对象访问器获取相应对象
                 * 否则,直接获取
                 */
                if (ArrayUtils.contains(specificTargetClasses, contextObject.getClass())) {
                    if (accessor.canRead(evalContext, contextObject, name)) {
                        return accessor.read(evalContext, contextObject, name);
                    }
                }
            }
            return PropertyUtils.getProperty(contextObject, name);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public String getName() {
        return name;
    }
}

方法节点也差不多,不过需要注意的是:方法存在参数,所以方法需要额外的子节点存储参数,MethodReference 代码如下:


import com.hqd.ch03.v11.spel.standard.ExpressionState;
import org.apache.commons.beanutils.MethodUtils;
import org.apache.commons.lang3.ArrayUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 方法节点
 */
public class MethodReference extends SpelNodeImpl {
    private final String name;

    public MethodReference(String methodName, int startPos, int endPos, SpelNodeImpl... args) {
        super(startPos, endPos, args);
        this.name = methodName;
    }

    @Override
    public Object getValue(ExpressionState state) {
        Object activeContextObject = state.getActiveContextObject();
        try {
            List<Object> params = new ArrayList<>();
            if (ArrayUtils.isNotEmpty(children)) {
                Arrays.stream(children).forEach(spelNode -> {
                    params.add(spelNode.getValue(state));
                });
            }
            return MethodUtils.invokeMethod(activeContextObject, name, params.toArray());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public String getName() {
        return name;
    }
}

MethodReferencePropertyOrFieldReference 只支持一级访问,所以还需要定义个父节点,用以支持多级访问。CompoundExpression 代码如下:

import com.hqd.ch03.v11.spel.ast.SpelNodeImpl;
import com.hqd.ch03.v11.spel.standard.ExpressionState;

/**
 * 属性、方法父节点,支持多级访问
 */
public class CompoundExpression extends SpelNodeImpl {
    public CompoundExpression(int startPos, int endPos, SpelNodeImpl... expressionComponents) {
        super(startPos, endPos, expressionComponents);
    }

    @Override
    public Object getValue(ExpressionState state) {
        if (children.length == 1) {
            return children[0].getValue(state);
        }
        int count = this.children.length - 1;
        SpelNodeImpl nextNode = this.children[0];
        Object result = nextNode.getValue(state);
        for (int i = 1; i < count; i++) {
            state.pushActiveContextObject(result);
            nextNode = this.children[i];
            result = nextNode.getValue(state);
            state.popActiveContextObject();

        }
        state.pushActiveContextObject(result);
        nextNode = this.children[count];
        result = nextNode.getValue(state);
        state.popActiveContextObject();
        return result;
    }
}

    树节点定义完成之后,就可以构建AST树了。不过在此之前,为了方便给别人使用,也为了区别不同的表达式,比如1+2+3 这时候就是个字符串、#{a.b} 就一个spel表达式、#{a.b} 123 #{c.d} 则是混合表达式。故而存在多种实现,需要定义个Expression 接口。代码如下:


/**
 * 表达式获取值接口
 */
public interface Expression {
    /**
     * 获取Spel表达式
     *
     * @return
     */
    String getExpressionString();

    /**
     * 获取Spel表达式结果
     *
     * @return
     */
    Object getValue();

    /**
     * 根据传入的上下文,获取spel表达式的值
     *
     * @param context 上下文
     * @return
     */
    Object getValue(EvaluationContext context);

    /**
     * 设置上下文的值
     *
     * @param context
     * @param value
     */
    void setValue(EvaluationContext context, Object value);
}

A君 先是实现最简单的常规表达式,不需要任何转换,直接返回就行了。LiteralExpression 代码如下:

import com.hqd.ch03.v11.spel.EvaluationContext;
import com.hqd.ch03.v11.spel.Expression;

/**
 * 普通字符串
 */
public class LiteralExpression implements Expression {
    private final String literalValue;


    public LiteralExpression(String literalValue) {
        this.literalValue = literalValue;
    }

    @Override
    public String getExpressionString() {
        return literalValue;
    }

    @Override
    public Object getValue() {
        return literalValue;
    }

    @Override
    public Object getValue(EvaluationContext context) {
        return literalValue;
    }

    @Override
    public void setValue(EvaluationContext context, Object value) {

    }
}

接着是单一的Spel表达式,这个只需要给个AST的根节点,获取根节点的值就行了。SpelExpression 代码如下:


import com.hqd.ch03.v11.spel.EvaluationContext;
import com.hqd.ch03.v11.spel.Expression;
import com.hqd.ch03.v11.spel.ast.SpelNodeImpl;
import com.hqd.ch03.v11.spel.context.StandardEvaluationContext;


/**
 * 单一的spel表达式
 */
public class SpelExpression implements Expression {
    private final String expression;

    /**
     * 根节点
     */
    private final SpelNodeImpl ast;
    private EvaluationContext evaluationContext = new StandardEvaluationContext();

    public SpelExpression(String expressionString, SpelNodeImpl ast) {
        this.expression = expressionString;
        this.ast = ast;
    }

    @Override
    public String getExpressionString() {
        return expression;
    }

    @Override
    public Object getValue() {
        return getValue(getEvaluationContext());
    }

    @Override
    public Object getValue(EvaluationContext context) {
        ExpressionState expressionState = new ExpressionState(context);
        return this.ast.getValue(expressionState);
    }

    @Override
    public void setValue(EvaluationContext context, Object value) {

    }

    public EvaluationContext getEvaluationContext() {
        return evaluationContext;
    }
}

混合表达式实现也简单,用个数组保存各个表达式,循环获取值,而后将结果拼接即可。CompositeStringExpression 代码如下:


import com.hqd.ch03.v11.spel.EvaluationContext;
import com.hqd.ch03.v11.spel.Expression;
import org.apache.commons.lang3.ArrayUtils;

import java.util.Arrays;

/**
 * 混合表达式
 */
public class CompositeStringExpression implements Expression {
    private final String expressionString;

    private final Expression[] expressions;


    public CompositeStringExpression(String expressionString, Expression[] expressions) {
        this.expressionString = expressionString;
        this.expressions = expressions;
    }

    @Override
    public String getExpressionString() {
        return expressionString;
    }

    @Override
    public Object getValue() {
        return getValue(null);
    }

    @Override
    public Object getValue(EvaluationContext context) {
        if (ArrayUtils.isEmpty(expressions)) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        Arrays.stream(expressions).forEach(expr -> {
            sb.append(expr.getValue(context));
        });
        return sb.toString();
    }

    @Override
    public void setValue(EvaluationContext context, Object value) {

    }
}

    经过前边的一系类铺垫,终于可以构建AST树了,A君 先定义个 ExpressionParser 接口作为构建AST树的规范。代码如下:


/**
 * 解析表达式接口
 */
public interface ExpressionParser {
    /**
     * 解析表达式
     *
     * @param expressionString
     * @return
     */
    Expression parseExpression(String expressionString);

    /**
     * 根据前后缀解析表达式
     *
     * @param expressionString
     * @param context
     * @return
     */
    Expression parseExpression(String expressionString, ParserContext context);
}

接着就是老调重弹了,定义一个抽象类,提取出公共代码:根据模板提取相应表达式。TemplateAwareExpressionParser 代码如下:


import com.hqd.ch03.v11.spel.Expression;
import com.hqd.ch03.v11.spel.ExpressionParser;
import com.hqd.ch03.v11.spel.ParserContext;
import com.hqd.ch03.v11.spel.express.CompositeStringExpression;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * Spel表达式抽象类
 */
public abstract class TemplateAwareExpressionParser implements ExpressionParser {
    @Override
    public Expression parseExpression(String expressionString) {
        return parseExpression(expressionString, null);
    }

    @Override
    public Expression parseExpression(String expressionString, ParserContext context) {
        /**
         * 按模板解析
         */
        if (context != null && context.isTemplate()) {
            return parseTemplate(expressionString, context);
        } else {
            //不用模板,直接解析
            return doParseExpression(expressionString, context);
        }
    }

    protected abstract Expression doParseExpression(String expressionString, ParserContext context);

    protected Expression parseTemplate(String expressionString, ParserContext 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) {
                //说明中间有其他字符,直接解析
                if (prefixIndex > startIdx) {
                    expressions.add(new LiteralExpression(expressionString.substring(startIdx, prefixIndex)));
                }
                int afterPrefixIndex = prefixIndex + prefix.length();
                int suffixIndex = expressionString.indexOf(suffix, afterPrefixIndex);
                if (suffixIndex == -1) {
                    throw new RuntimeException(String.format("缺失:%s", suffix));
                }
                /**
                 * 截取子串
                 */
                String expr = expressionString.substring(prefixIndex + prefix.length(), suffixIndex);
                expr = expr.trim();
                if (StringUtils.isBlank(expr)) {
                    throw new RuntimeException("expr 表达式为空");
                }
                expressions.add(doParseExpression(expr, context));
                startIdx = suffixIndex + suffix.length();
            } else {
                expressions.add(new LiteralExpression(expressionString.substring(startIdx)));
                startIdx = expressionString.length();
            }
        }
        if (CollectionUtils.isNotEmpty(expressions)) {
            if (expressions.size() == 1) {
                return expressions.get(0);
            } else {
                return new CompositeStringExpression(expressionString, expressions.toArray(new Expression[0]));
            }
        }
        return null;
    }
}

接下来就是最终实现了,代码比较多些,但是一个个节点拆开来看,其实并不难,但是核心思想就只有一个,优先级高的运算符节点先生成。InternalSpelExpressionParser 代码如下:


import com.hqd.ch03.v11.spel.Expression;
import com.hqd.ch03.v11.spel.ParserContext;
import com.hqd.ch03.v11.spel.ast.*;
import com.hqd.ch03.v11.spel.express.CompoundExpression;
import com.hqd.ch03.v11.spel.standard.SpelExpression;
import com.hqd.ch03.v11.spel.standard.Token;
import com.hqd.ch03.v11.spel.standard.TokenKind;
import com.hqd.ch03.v11.spel.standard.Tokenizer;
import org.apache.commons.lang3.ArrayUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * spel表达式解析
 */
public class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
    private String expressionString = "";
    private int pos = 0;
    private List<Token> tokenStream;

    @Override
    protected Expression doParseExpression(String expressionString, ParserContext context) {
        this.expressionString = expressionString;
        Tokenizer tokenizer = new Tokenizer(expressionString);
        tokenStream = tokenizer.process();
        this.pos = 0;
        SpelNodeImpl ast = eatExpression();
        return new SpelExpression(expressionString, ast);
    }

    /**
     * 运算符优先级,乘除先,加减后
     *
     * @return
     */
    private SpelNodeImpl eatExpression() {
        return eatLogicalOrExpression();
    }

    /**
     * 生成or节点
     *
     * @return
     */
    private SpelNodeImpl eatLogicalOrExpression() {
        SpelNodeImpl expr = eatLogicalAndExpression();
        while (peekToken(TokenKind.SYMBOLIC_OR)) {
            Token token = nextToken();
            SpelNodeImpl rhExpr = eatLogicalAndExpression();
            expr = new OpOr(token.getStartPos(), token.getEndPos(), expr, rhExpr);
        }
        return expr;
    }

    /**
     * 生成and节点
     *
     * @return
     */
    private SpelNodeImpl eatLogicalAndExpression() {
        SpelNodeImpl expr = eatRelationalExpression();
        while (peekToken(TokenKind.SYMBOLIC_AND)) {
            Token token = nextToken();
            SpelNodeImpl rhExpr = eatRelationalExpression();
            expr = new OpAnd(token.getStartPos(), token.getEndPos(), expr, rhExpr);
        }
        return expr;
    }

    /**
     * 生成 <、>、!=、<=、>=等节点
     *
     * @return
     */
    private SpelNodeImpl eatRelationalExpression() {
        SpelNodeImpl expr = eatSumExpression();
        Token t = peekToken();
        if (t == null) {
            return expr;
        }
        if (t.isNumericRelationalOperator()) {
            t = nextToken();
            TokenKind tk = t.getKind();
            SpelNodeImpl rhExpr = eatSumExpression();
            if (tk == TokenKind.GT) {
                return new OpGt(t.getStartPos(), t.getEndPos(), expr, rhExpr);
            }
            if (tk == TokenKind.LT) {
                return new OpLt(t.getStartPos(), t.getEndPos(), expr, rhExpr);
            }
            if (tk == TokenKind.LE) {
                return new OpLe(t.getStartPos(), t.getEndPos(), expr, rhExpr);
            }
            if (tk == TokenKind.GE) {
                return new OpGe(t.getStartPos(), t.getEndPos(), expr, rhExpr);
            }
            if (tk == TokenKind.EQ) {
                return new OpEQ(t.getStartPos(), t.getEndPos(), expr, rhExpr);
            }
            if (tk == TokenKind.NE) {
                return new OpNE(t.getStartPos(), t.getEndPos(), expr, rhExpr);
            }
        }
        return expr;
    }

    /**
     * 生成+、-节点
     *
     * @return
     */
    private SpelNodeImpl eatSumExpression() {
        SpelNodeImpl expr = eatProductExpression();
        while (peekToken(TokenKind.MINUS, TokenKind.PLUS)) {
            Token token = nextToken();
            SpelNodeImpl rgNode = eatProductExpression();
            if (token.getKind() == TokenKind.MINUS) {
                expr = new OpMinus(token.getStartPos(), token.getEndPos(), expr, rgNode);
            } else if (token.getKind() == TokenKind.PLUS) {
                expr = new OpPlus(token.getStartPos(), token.getEndPos(), expr, rgNode);
            }
        }
        return expr;
    }

    /**
     * 生成*、/、%节点
     *
     * @return
     */
    private SpelNodeImpl eatProductExpression() {
        SpelNodeImpl expr = eatUnaryExpression();
        if (expr == null) {
            throw new RuntimeException("表达式左边必须为数字");
        }
        while (peekToken(TokenKind.DIV, TokenKind.STAR, TokenKind.MOD)) {
            Token token = nextToken();
            SpelNodeImpl rgLiteralNode = eatLiteralNode();
            if (token.getKind() == TokenKind.DIV) {
                expr = new OpDiv(token.getStartPos(), token.getEndPos(), expr, rgLiteralNode);
            } else if (token.getKind() == TokenKind.STAR) {
                expr = new OpStar(token.getStartPos(), token.getEndPos(), expr, rgLiteralNode);
            } else if (token.getKind() == TokenKind.MOD) {
                expr = new OpMod(token.getStartPos(), token.getEndPos(), expr, rgLiteralNode);
            }
        }
        return expr;
    }

    /**
     * 生成一元表达式节点
     *
     * @return
     */
    private SpelNodeImpl eatUnaryExpression() {
        if (peekToken(TokenKind.NOT)) {
            Token t = nextToken();
            SpelNodeImpl expr = eatUnaryExpression();
            if (t.getKind() == TokenKind.NOT) {
                return new OpNOT(t.getStartPos(), t.getEndPos(), expr);
            }
        }
        return eatLiteralNode();
    }

    /**
     * 生成常量、变量节点
     *
     * @return
     */
    private SpelNodeImpl eatLiteralNode() {
        Token token = nextToken();
        if (token.getKind() == TokenKind.LITERAL_INT) {
            return new IntLiteral(token.getStartPos(), token.getEndPos(), token.getData());
        } else if (token.getKind() == TokenKind.LPAREN) {
            SpelNodeImpl spelNode = eatExpression();
            token = nextToken();
            if (token != null && token.getKind() == TokenKind.RPAREN) {
                return spelNode;
            }
            throw new RuntimeException("没有匹配到')'");
        } else if (token.getKind() == TokenKind.LITERAL_REAL) {
            return new RealLiteral(token.getStartPos(), token.getEndPos(), token.getData());
        } else if (token.getKind() == TokenKind.LITERAL_STRING) {
            return new StringLiteral(token.getStartPos(), token.getEndPos(), token.getData());
        } else if (token.getKind() == TokenKind.IDENTIFIER) {
            return eatIdentifier(token);
        }
        return null;
    }

    /**
     * 生成变量节点
     *
     * @param token
     * @return
     */
    private SpelNodeImpl eatIdentifier(Token token) {
        /**
         * 有可能是方法
         */
        SpelNodeImpl[] args = maybeEatMethodArgs();
        SpelNodeImpl startNode;
        List<SpelNodeImpl> nodes = new ArrayList<>();
        if (args == null) {
            startNode = new PropertyOrFieldReference(token.getData(), token.getStartPos(), token.getEndPos());
        } else {
            startNode = new MethodReference(token.getData(), token.getStartPos(), token.getEndPos(), args);
        }
        nodes.add(startNode);
        eatMethodOrProperty(nodes, token);
        return new CompoundExpression(startNode.getStartPos(), nodes.get(nodes.size() - 1).getEndPos(),
                nodes.toArray(new SpelNodeImpl[0]));
    }

    /**
     * 生成方法节点
     *
     * @param nodes
     * @param token
     */
    private void eatMethodOrProperty(List<SpelNodeImpl> nodes, Token token) {
        if (peekToken(TokenKind.DOT)) {
            nextToken();
            if (peekToken(TokenKind.IDENTIFIER)) {
                token = nextToken();
            }
            SpelNodeImpl[] args = maybeEatMethodArgs();
            SpelNodeImpl node;
            if (args == null) {
                node = new PropertyOrFieldReference(token.getData(), token.getStartPos(), token.getEndPos());
            } else {
                node = new MethodReference(token.getData(), token.getStartPos(), token.getEndPos(), args);
            }
            nodes.add(node);
            eatMethodOrProperty(nodes, token);
        }
    }

    private SpelNodeImpl[] maybeEatMethodArgs() {
        if (!peekToken(TokenKind.LPAREN)) {
            return null;
        }
        nextToken();
        List<SpelNodeImpl> accumulatedArguments = new ArrayList<>();
        while (!peekToken(TokenKind.RPAREN)) {
            accumulatedArguments.add(eatExpression());
            if (!peekToken(TokenKind.COMMA)) {
                break;
            }
            nextToken();
        }
        if (!peekToken(TokenKind.RPAREN)) {
            throw new RuntimeException("方法无法匹配')'");
        }
        nextToken();
        return accumulatedArguments.toArray(new SpelNodeImpl[0]);
    }

    private boolean peekToken(TokenKind... possibles) {
        Token t = peekToken();
        if (t == null) {
            return false;
        }
        if (ArrayUtils.isEmpty(possibles)) {
            return false;
        }

        return ArrayUtils.contains(possibles, t.getKind());
    }

    private Token peekToken() {
        if (pos >= tokenStream.size()) {
            return null;
        }
        return tokenStream.get(pos);
    }

    private Token nextToken() {
        if (pos >= tokenStream.size()) {
            return null;
        }
        return tokenStream.get(pos++);
    }


}

    A君 现在已经实现了一个简单的Spel表达式,不过还无法无IOC容器整合到一起。当输入变量时,如:#{user.getName()},应该先从容器中获取user对象,再进行相应的操作。想要从IOC容器中获取对象,无疑需要拿到Bean工厂。为此,A君 先定义一个 BeanExpressionContext 类用来对 Bean工厂 做一个简单的包装。代码如下:

import com.hqd.ch03.v11.factory.ConfigurableBeanFactory;

/**
 * 提供BeanFactory上下文,用以获取Bean对象
 */
public class BeanExpressionContext {
    private final ConfigurableBeanFactory beanFactory;

    public BeanExpressionContext(ConfigurableBeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    public boolean containsObject(String key) {
        return this.beanFactory.containsBean(key);
    }

    public Object getObject(String key) {
        return beanFactory.getBean(key);
    }
}

    有了BeanExpressionContext对象后,A君 可以在运行Spel时指定到Spel的上下文中。但是光指定还不够,由于Spel在获取属性节点时,会根据对象访问器(PropertyAccessor)所支持的对象进行操作,所以还需要实现一个属于IOC容器的对象访问器——BeanExpressionContextAccessor,他仅支持操作BeanExpressionContext 对象。代码如下:


import com.hqd.ch03.v11.spel.EvaluationContext;
import com.hqd.ch03.v11.spel.PropertyAccessor;
import com.hqd.ch03.v11.spel.express.BeanExpressionContext;

/**
 * 获取bean对象的属性读取器
 */
public class BeanExpressionContextAccessor implements PropertyAccessor {
    @Override
    public boolean canRead(EvaluationContext context, Object target, String name) {
        return (target instanceof BeanExpressionContext && ((BeanExpressionContext) target).containsObject(name));
    }

    @Override
    public Object read(EvaluationContext context, Object target, String name) {
        return ((BeanExpressionContext) target).getObject(name);
    }

    @Override
    public boolean canWrite(EvaluationContext context, Object target, String name) {
        return false;
    }

    @Override
    public void write(EvaluationContext context, Object target, String name, Object newValue) {
        throw new RuntimeException("Beans in a BeanFactory are read-only");
    }

    @Override
    public Class<?>[] getSpecificTargetClasses() {
        return new Class<?>[]{BeanExpressionContext.class};
    }
}

    现在 A君 一切都准备好了,只剩下最后一步。定义一个总的入口,用来给IOC容器执行Spel表达式,屏蔽掉Spel内部细节。BeanExpressionResolver 代码如下:

import com.hqd.ch03.v11.spel.express.BeanExpressionContext;

/**
 * bean中的spel表达式解析接口
 */
public interface BeanExpressionResolver {
    /**
     * 计算spel表达式
     *
     * @param value
     * @param beanExpressionContext
     * @return
     */
    Object evaluate(String value, BeanExpressionContext beanExpressionContext);
}

在给BeanExpressionResolver 接口提供个默认实现,他只需要把 BeanExpressionContext 添加到Spel上下文中,并且添加 BeanExpressionContextAccessor 对象访问器即可。StandardBeanExpressionResolver 代码如下:


import com.hqd.ch03.v11.spel.BeanExpressionResolver;
import com.hqd.ch03.v11.spel.Expression;
import com.hqd.ch03.v11.spel.ExpressionParser;
import com.hqd.ch03.v11.spel.ParserContext;
import com.hqd.ch03.v11.spel.accessor.BeanExpressionContextAccessor;
import com.hqd.ch03.v11.spel.context.StandardEvaluationContext;
import com.hqd.ch03.v11.spel.parse.InternalSpelExpressionParser;
import com.hqd.ch03.v11.spel.parse.TemplateParserContext;
import org.apache.commons.lang3.StringUtils;

public class StandardBeanExpressionResolver implements BeanExpressionResolver {
    private final ParserContext beanExpressionParserContext = new TemplateParserContext();
    private ExpressionParser expressionParser;

    public StandardBeanExpressionResolver() {
        this.expressionParser = new InternalSpelExpressionParser();
    }

    @Override
    public Object evaluate(String value, BeanExpressionContext beanExpressionContext) {
        if (StringUtils.isBlank(value)) {
            return value;
        }
        Expression expr = this.expressionParser.parseExpression(value, this.beanExpressionParserContext);
        StandardEvaluationContext sec = new StandardEvaluationContext(beanExpressionContext);
        sec.addPropertyAccessor(new BeanExpressionContextAccessor());
        return expr.getValue(sec);
    }
}

    最后,A君 进行最后一步的修改,当设置bean属性时,如果是字符串,就使用 StandardBeanExpressionResolver 进行解析,SpringImitationV11 改造如下:

在这里插入图片描述

完整代码:


import com.hqd.ch03.v11.beans.BeanWrapper;
import com.hqd.ch03.v11.beans.BeanWrapperImpl;
import com.hqd.ch03.v11.config.BeanDefinition;
import com.hqd.ch03.v11.config.MutablePropertyValues;
import com.hqd.ch03.v11.config.Scope;
import com.hqd.ch03.v11.factory.BeanFactory;
import com.hqd.ch03.v11.factory.ConfigurableBeanFactory;
import com.hqd.ch03.v11.factory.FactoryBean;
import com.hqd.ch03.v11.io.ResourceLoader;
import com.hqd.ch03.v11.io.support.DefaultResourceLoader;
import com.hqd.ch03.v11.registry.BeanDefinitionRegistry;
import com.hqd.ch03.v11.registry.support.DefaultSingletonBeanRegistry;
import com.hqd.ch03.v11.registry.support.SimpleBeanDefinitionRegistry;
import com.hqd.ch03.v11.spel.BeanExpressionResolver;
import com.hqd.ch03.v11.spel.express.BeanExpressionContext;
import com.hqd.ch03.v11.spel.express.StandardBeanExpressionResolver;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;

import java.beans.PropertyEditor;
import java.util.HashMap;
import java.util.Map;

public abstract class SpringImitationV11 extends DefaultSingletonBeanRegistry implements ConfigurableBeanFactory {
    private final Map<Class<?>, Class<? extends PropertyEditor>> customEditors = new HashMap<>();
    protected BeanDefinitionRegistry beanDefinitionRegistry = new SimpleBeanDefinitionRegistry();
    protected BeanExpressionResolver beanExpressionResolver = new StandardBeanExpressionResolver();
    /**
     * 单例缓存
     */
    protected Map<String, Scope> scopeCache = new HashMap<>();
    /**
     * 资源加载器
     */
    protected ResourceLoader resourceLoader = new DefaultResourceLoader();

    public static boolean isFactoryDereference(String name) {
        return (name != null && name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));
    }

    @Override
    public void registerCustomEditor(Class<?> requiredType, Class<? extends PropertyEditor> propertyEditorClass) {
        customEditors.put(requiredType, propertyEditorClass);
    }

    /**
     * 处理传入的name,去除&开头
     *
     * @param name
     * @return
     */
    protected String transformedBeanName(String name) {
        if (!name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
            return name;
        }
        String beanName = name;
        do {
            beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
        }
        while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));
        return beanName;
    }

    /**
     * 区分正常bean还是FactoryBean
     *
     * @param obj
     * @param name
     * @param beanName
     * @param bd
     * @return
     */
    protected Object getObjectForBeanInstance(Object obj, String name, String beanName, BeanDefinition bd) {
        if (isFactoryDereference(name)) {
            if (!(obj instanceof FactoryBean)) {
                throw new RuntimeException(String.format("%s 未实现FactoryBean接口", beanName));
            }
            return obj;
        }
        if (obj instanceof FactoryBean) {
            return ((FactoryBean) obj).getObject();
        }
        return obj;
    }

    private void initBean(BeanWrapper beanWrapper, BeanDefinition bd) {
        try {
            MutablePropertyValues properties = bd.getProperties();
            properties.getProperties().forEach(propertyValue -> {
                boolean isRef = propertyValue.isRef();
                Object value = propertyValue.getValue();
                if (isRef) {
                    value = getBean((String) value);
                }
                try {
                    if (value instanceof String) {
                        /**
                         * 使用Spel进行解析
                         */
                        value = beanExpressionResolver.evaluate((String) value, new BeanExpressionContext(this));
                    }
                    beanWrapper.setPropertyValue(propertyValue.getName(), value, isRef);
                    //BeanUtils.setProperty(bean, propertyValue.getName(), value);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            });
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    private Object createBean(BeanDefinition bd, String beanName) {
        try {
            Object instance = null;
            if (bd.isSingleton()) {//单例
                instance = getSingleton(beanName);
                if (instance == null) {
                    instance = doCreate(bd, beanName);
                }
                addSingleton(beanName, instance);
            } else if (bd.isPrototype()) {//多例
                instance = doCreate(bd, beanName);
            } else {//自定义作用域
                String scopeName = bd.getScope();
                Scope scope = scopeCache.get(scopeName);
                if (scope == null) {
                    throw new RuntimeException(String.format("[%s]未注册scope", scopeName));
                }
                instance = scope.get(beanName, () -> doCreate(bd, beanName));
            }
            return instance;

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    protected Object doCreate(BeanDefinition bd, String beanName) {
        String beanClass = bd.getBeanClass();
        try {
            Object instance = Class.forName(beanClass).getConstructor().newInstance();
            BeanWrapper beanWrapper = new BeanWrapperImpl(instance);
            //初始化BeanWrapper
            initBeanWrapper(beanWrapper);
            addSingletonFactory(beanName, () -> beanWrapper.getWrappedInstance());
            //doSomething
            initBean(beanWrapper, bd);
            return instance;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    protected void initBeanWrapper(BeanWrapper bw) {
        /**
         * 注册自定义类型转换器
         *
         */
        registerCustomEditors(bw);
    }

    @Override
    public boolean containsBean(String name) {
        String beanName = transformedBeanName(name);
        if (containsSingleton(beanName) || containsBeanDefinition(beanName)) {
            return true;
        }
        return false;
    }

    public boolean containsBeanDefinition(String beanName) {
        return this.beanDefinitionRegistry.containsBeanDefinition(beanName);
    }

    /**
     * 是否应该区分全局转换器和局部转换器
     * 全局为Factory级别,局部为Bean级别
     *
     * @param bw
     */
    protected void registerCustomEditors(BeanWrapper bw) {
        if (MapUtils.isNotEmpty(customEditors)) {
            customEditors.forEach((key, val) -> {
                try {
                    bw.registerCustomEditor(key, val.getConstructor().newInstance());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
    }

    @Override
    public void registerScope(String scopeName, Scope scope) {
        if (StringUtils.isNotBlank(scopeName) && scope != null) {
            scopeCache.put(scopeName, scope);
        }
    }

    @Override
    public <T> T getBean(String name, Class<T> clazz) {
        return (T) doGetBean(name);
    }

    @Override
    public Object getBean(String name) {
        return doGetBean(name);
    }

    protected Object doGetBean(String name) {
        String beanName = transformedBeanName(name);
        BeanDefinition bd = beanDefinitionRegistry.getBeanDefinition(beanName);
        Object bean = createBean(bd, beanName);
        bean = getObjectForBeanInstance(bean, name, beanName, bd);
        return bean;
    }


    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
}

    折腾了这么久,总算完成了。A君 按捺不住激动的心、颤抖的手。对努力的成果进行测试。先对IKun对象添加几个属性,方便测试,代码如下:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class IKun extends User {
    private String aliaName;
    private boolean handsome;
    private double deposit;
    private int scope;
    private String[] hobbies;
}

接下来就是这次的重头戏了,A君 在xml文件中配置了各式各样的Spel表达式。bean.xml 配置如下:

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="idCard" class="com.hqd.ch03.bean.IdCard">
        <property name="id" value="123456"/>
        <property name="user" ref="ikun"/>
    </bean>
    <bean id="test" class="com.hqd.ch03.bean.IdCard">
        <property name="id" value="法外狂徒-张三"/>
    </bean>
    <bean id="ikun" class="com.hqd.ch03.bean.IKun" scope="singleton">
        <property name="idCard" ref="idCard"/>
        <property name="name" value="良民"/>
        <property name="scope" value="#{3+4/2-2*3}"/>
        <property name="aliaName" value="#{test.id} (#{idCard.getUser().getName() + 1 + 2 / 2 + 'dddd'}-1)"/>
        <property name="age" value="14"/>
        <property name="handsome" value="#{1+2 &lt; 3+4}"/>
        <property name="deposit" value="6.66"/>
        <property name="hobbies" value="唱,跳,rap"/>
    </bean>
</beans>

A君 已经将测试案例准备完毕,接下来是骡子是马,跑跑就知道了。运行结果如下:

在这里插入图片描述

结果并没有让 A君 失望,努力几天的成果并没有付之东流。A君 又Get到了一个小技能呢。当然,A君 做的还差很多功能,比如:获取配置属性、instanceof、matches、三目运算等,这些就看各位看官发挥了
在这里插入图片描述

总结

    正所谓树欲静而风不止,欲知后事如何,请看下回分解(✪ω✪)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

穷儒公羊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值