第七章 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容器——Scope和属性填充
第九章 Spring之源码阅读——环境搭建篇
第十章 Spring之源码阅读——IOC篇



前言

    对于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、三目运算等,这些就看各位看官发挥了
在这里插入图片描述

总结

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

穷儒公羊

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

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

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

打赏作者

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

抵扣说明:

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

余额充值