25. Spring源码篇之SpEL表达式

简介

Spring(Spring Expression
Language)表达式简称SpEL表达式,该功能在Spring中实现还是比较复杂,在Spring中单独有一个模块spring-expression来实现,所以本文主要看一小部分的源码,大概知道怎么使用就行了

源码分析

在AbstractBeanFactory中就有一个属性 beanExpressionResolver 会设置默认的表达式

private BeanExpressionResolver beanExpressionResolver;  

在准备BeanFactory的时候设置默认值 org.springframework.context.support.AbstractApplicationContext#prepareBeanFactory

private static final boolean shouldIgnoreSpel = SpringProperties.getFlag("spring.spel.ignore");

// 设置默认 StandardBeanExpressionResolver
if (!shouldIgnoreSpel) {
    beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
}

// StandardBeanExpressionResolver构造器
public StandardBeanExpressionResolver(@Nullable ClassLoader beanClassLoader) {
    this.expressionParser = new SpelExpressionParser(new SpelParserConfiguration(null, beanClassLoader));
}

shouldIgnoreSpel意思是还可以通过配置关闭表达式 spring.spel.ignore=false,默认开启

BeanExpressionResolver

只有一个evaluate方法 传入表达式,返回解析后的值

public interface BeanExpressionResolver {
	Object evaluate(@Nullable String value, BeanExpressionContext evalContext) throws BeansException;

}

StandardBeanExpressionResolver

StandardBeanExpressionResolver作为BeanExpressionResolver的实现类,具体实现evaluate方法

省略了部分代码

private final ParserContext beanExpressionParserContext = new ParserContext() {
    @Override
    public boolean isTemplate() {
        return true;
    }
    @Override
    public String getExpressionPrefix() {
        return "#{";
    }
    @Override
    public String getExpressionSuffix() {
        return "}";
    }
};

public Object evaluate(@Nullable String value, BeanExpressionContext evalContext) throws BeansException {
    
    //  缓存找一下
    Expression expr = this.expressionCache.get(value);
    if (expr == null) {
        // 解析表达式 value表达式
        expr = this.expressionParser.parseExpression(value, this.beanExpressionParserContext);
        // 缓存起来
        this.expressionCache.put(value, expr);
    }
    StandardEvaluationContext sec = this.evaluationCache.get(evalContext);
    
    // 获取解析的值
    return expr.getValue(sec);
}

解析表达是的接口为 ExpressionParser

public interface ExpressionParser {

	Expression parseExpression(String expressionString) throws ParseException;

	Expression parseExpression(String expressionString, ParserContext context) throws ParseException;
}

就是传入一个表达式,ParserContext给定表达式的规则模版,例如上面的beanExpressionParserContext就可以解析了,其中下面的三个子类都比较重要

SpelExpressionParser、InternalSpelExpressionParser、TemplateAwareExpressionParser

上面的this.expressionParser.parseExpression会进入到TemplateAwareExpressionParser#parseExpressions

TemplateAwareExpressionParser#parseExpressions

TemplateAwareExpressionParser为SpelExpressionParser的父类


// 假设 expressionString 为 #{'127.0.0.1,::1'.split(',')}
private Expression[] parseExpressions(String expressionString, ParserContext context) throws ParseException {
    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 = skipToCorrectEndSuffix(suffix, expressionString, afterPrefixIndex);
            
            // '127.0.0.1,::1'.split(',')
            String expr = expressionString.substring(prefixIndex + prefix.length(), suffixIndex);
            expr = expr.trim();
            
            // 解析表达式 InternalSpelExpressionParser#doParseExpression
            expressions.add(doParseExpression(expr, context));
            startIdx = suffixIndex + suffix.length();
        }
        else {
            expressions.add(new LiteralExpression(expressionString.substring(startIdx)));
            startIdx = expressionString.length();
        }
    }

    return expressions.toArray(new Expression[0]);
}

进入InternalSpelExpressionParser#doParseExpression

InternalSpelExpressionParser#doParseExpression

protected SpelExpression doParseExpression(String expressionString, @Nullable ParserContext context)
			throws ParseException {
    this.expressionString = expressionString;
    Tokenizer tokenizer = new Tokenizer(expressionString);
    // 这一步很关键,真正解析
    this.tokenStream = tokenizer.process();
    this.tokenStreamLength = this.tokenStream.size();
    this.tokenStreamPointer = 0;
    this.constructedNodes.clear();
    // 将表达式解析成SpelNodeImpl对象
    SpelNodeImpl ast = eatExpression();
    Assert.state(ast != null, "No node");
    Token t = peekToken();
    
    return new SpelExpression(expressionString, ast, this.configuration);
    
}

tokenizer.process()这一步很关键,允许出现的特殊字符对应的解析都在这

Tokenizer#process

public List<Token> process() {
    while (this.pos < this.max) {
        char ch = this.charsToProcess[this.pos];
        if (isAlphabetic(ch)) {
            lexIdentifier();
        }
        else {
            switch (ch) {
                case '+':
                    if (isTwoCharToken(TokenKind.INC)) {
                        pushPairToken(TokenKind.INC);
                    }
                    else {
                        pushCharToken(TokenKind.PLUS);
                    }
                    break;
                case '_': // the other way to start an identifier
                    lexIdentifier();
                    break;
                case '-':
                    if (isTwoCharToken(TokenKind.DEC)) {
                        pushPairToken(TokenKind.DEC);
                    }
                    else {
                        pushCharToken(TokenKind.MINUS);
                    }
                    break;
                case ':':
                    pushCharToken(TokenKind.COLON);
                    break;
                case '.':
                    pushCharToken(TokenKind.DOT);
                    break;
                case ',':
                    pushCharToken(TokenKind.COMMA);
                    break;
                case '*':
                    pushCharToken(TokenKind.STAR);
                    break;
                case '/':
                    pushCharToken(TokenKind.DIV);
                    break;
                case '%':
                    pushCharToken(TokenKind.MOD);
                    break;
                case '(':
                    pushCharToken(TokenKind.LPAREN);
                    break;
                case ')':
                    pushCharToken(TokenKind.RPAREN);
                    break;
                case '[':
                    pushCharToken(TokenKind.LSQUARE);
                    break;
                case '#':
                    pushCharToken(TokenKind.HASH);
                    break;
                case ']':
                    pushCharToken(TokenKind.RSQUARE);
                    break;
                case '{':
                    pushCharToken(TokenKind.LCURLY);
                    break;
                case '}':
                    pushCharToken(TokenKind.RCURLY);
                    break;
                case '@':
                    pushCharToken(TokenKind.BEAN_REF);
                    break;
                case '^':
                    if (isTwoCharToken(TokenKind.SELECT_FIRST)) {
                        pushPairToken(TokenKind.SELECT_FIRST);
                    }
                    else {
                        pushCharToken(TokenKind.POWER);
                    }
                    break;
                case '!':
                    if (isTwoCharToken(TokenKind.NE)) {
                        pushPairToken(TokenKind.NE);
                    }
                    else if (isTwoCharToken(TokenKind.PROJECT)) {
                        pushPairToken(TokenKind.PROJECT);
                    }
                    else {
                        pushCharToken(TokenKind.NOT);
                    }
                    break;
                case '=':
                    if (isTwoCharToken(TokenKind.EQ)) {
                        pushPairToken(TokenKind.EQ);
                    }
                    else {
                        pushCharToken(TokenKind.ASSIGN);
                    }
                    break;
                case '&':
                    if (isTwoCharToken(TokenKind.SYMBOLIC_AND)) {
                        pushPairToken(TokenKind.SYMBOLIC_AND);
                    }
                    else {
                        pushCharToken(TokenKind.FACTORY_BEAN_REF);
                    }
                    break;
                case '|':
                    if (!isTwoCharToken(TokenKind.SYMBOLIC_OR)) {
                        raiseParseException(this.pos, SpelMessage.MISSING_CHARACTER, "|");
                    }
                    pushPairToken(TokenKind.SYMBOLIC_OR);
                    break;
                case '?':
                    if (isTwoCharToken(TokenKind.SELECT)) {
                        pushPairToken(TokenKind.SELECT);
                    }
                    else if (isTwoCharToken(TokenKind.ELVIS)) {
                        pushPairToken(TokenKind.ELVIS);
                    }
                    else if (isTwoCharToken(TokenKind.SAFE_NAVI)) {
                        pushPairToken(TokenKind.SAFE_NAVI);
                    }
                    else {
                        pushCharToken(TokenKind.QMARK);
                    }
                    break;
                case '$':
                    if (isTwoCharToken(TokenKind.SELECT_LAST)) {
                        pushPairToken(TokenKind.SELECT_LAST);
                    }
                    else {
                        lexIdentifier();
                    }
                    break;
                case '>':
                    if (isTwoCharToken(TokenKind.GE)) {
                        pushPairToken(TokenKind.GE);
                    }
                    else {
                        pushCharToken(TokenKind.GT);
                    }
                    break;
                case '<':
                    if (isTwoCharToken(TokenKind.LE)) {
                        pushPairToken(TokenKind.LE);
                    }
                    else {
                        pushCharToken(TokenKind.LT);
                    }
                    break;
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                    lexNumericLiteral(ch == '0');
                    break;
                case ' ':
                case '\t':
                case '\r':
                case '\n':
                    // drift over white space
                    this.pos++;
                    break;
                case '\'':
                    lexQuotedStringLiteral();
                    break;
                case '"':
                    lexDoubleQuotedStringLiteral();
                    break;
                case 0:
                    // hit sentinel at end of value
                    this.pos++;  // will take us to the end
                    break;
                case '\\':
                    raiseParseException(this.pos, SpelMessage.UNEXPECTED_ESCAPE_CHAR);
                    break;
                default:
                    throw new IllegalStateException("Cannot handle (" + (int) ch + ") '" + ch + "'");
            }
        }
    }
    return this.tokens;
}

上面是解析可以出现的每一个字符,另外还有一个关键地方就是eatExpression(); 它会进一步的进行处理

private SpelNodeImpl eatExpression() {
    // 处理运算符 or and || && + - * / ++ 等等
    SpelNodeImpl expr = eatLogicalOrExpression();
    return expr;
}

其中eatLogicalOrExpression();非常关键,里面一步步处理

eatLogicalOrExpression  or ||
eatLogicalAndExpression and &&
eatRelationalExpression > < >= <= == 等
eatSumExpression + - ++ --
eatProductExpression  * /
eatPowerIncDecExpression ^
eatUnaryExpression
eatPrimaryExpression()

eatStartNode()
maybeEatLiteral()基本数据类型开头
maybeEatParenExpression( 开头
maybeEatTypeReference T 开头 T]
maybeEatBeanReference @打头 例如 @'bean.name' &打头(FactoryBean)
maybeEatProjection ![ 集合投影 后面例子介绍
maybeEatSelection 匹配 选择所有匹配上的 ?[ 选择第一个匹配上的^[,选择最后一个匹配上的$[ 
maybeEatNullReference null 直接返回null
maybeEatConstructorReference new NEW]

上面源码我们看了一点点,具体SpEL表达式其实也是一个语法树,比较复杂,我们只要知道它能做什么,该怎么写就行了

通过上面我们也知道,可以运算,可以获取Bean,只可以执行方法等等,字符规范也有()or and > < . ? $等等

下面我们就简单使用一下

使用

案例一

public class ExpressionTest {
    // 解析模版
	private static final ParserContext parserContext = new ParserContext() {
		@Override
		public boolean isTemplate() {
			return true;
		}

		@NotNull
		@Override
		public String getExpressionPrefix() {
			return "#{";
		}

		@NotNull
		@Override
		public String getExpressionSuffix() {
			return "}";
		}
	};


	private final static ExpressionParser parser = new SpelExpressionParser();

	private static void evaluate(String text) {
		evaluate(text, null);
	}

	private static void evaluate(String text, Object object) {
		Expression expression = parser.parseExpression(text, parserContext);
		System.out.println(expression.getValue(object));
	}

	public static void main(String[] args) {
		// map
		evaluate("#{{'a':1, 'b':2, 'c':3, 'd':4, 'e':5}}");
		// list
		evaluate("#{{1,2,3,4,5}}");
		// new 一个int数组
		evaluate("#{new int[]{1,2,3}}");
		// String
		evaluate("#{T(java.lang.String)}");
		// 逻辑运行
		evaluate("#{3==5}");
		// lt > < 
		evaluate("#{3 lt 5}");
		// null
		evaluate("#{null}");
	}

}

输出

{a=1, b=2, c=3, d=4, e=5}
[1, 2, 3, 4, 5]
[I@37f8bb67
class java.lang.String
false
true
null

案例二

定义一组数据

public class ExpressionData {

	private List<Integer> emptyData;
	private final List<Integer> listData = new ArrayList<>();
	private final Map<String, Object> MapData = new HashMap<>();

	public ExpressionData() {
		for (int i = 0; i < 10; i++) {
			listData.add(i);
			MapData.put(i + "", i);
		}
	}

	public List<Integer> getEmptyData() {
		return emptyData;
	}

	public List<Integer> getListData() {
		return listData;
	}

	public Map<String, Object> getMapData() {
		return MapData;
	}
}

对以上数据处理

public class ExpressionTest {
    // 解析模版
	private static final ParserContext parserContext = new ParserContext() {
		@Override
		public boolean isTemplate() {
			return true;
		}

		@NotNull
		@Override
		public String getExpressionPrefix() {
			return "#{";
		}

		@NotNull
		@Override
		public String getExpressionSuffix() {
			return "}";
		}
	};


	private final static ExpressionParser parser = new SpelExpressionParser();

	private static void evaluate(String text) {
		evaluate(text, null);
	}

	private static void evaluate(String text, Object object) {
		Expression expression = parser.parseExpression(text, parserContext);
		System.out.println(expression.getValue(object));
	}

	public static void main(String[] args) {

		ExpressionData data = new ExpressionData();
		// 构造器实例一个ExpressionData
		evaluate("#{new com.shura.expression.ExpressionData()}");
		// 输出listData属性的值
		evaluate("#{(listData)}", data);
		evaluate("#{(listData.size())}", data);
		//输出mapData属性的值
		evaluate("#{mapData}", data);
		// 输出listData的第一个元素
		evaluate("#{listData[0]}", data);
		// 输出mapData的key为4的值
		evaluate("#{mapData['4']}", data);
		// 输出listData所有小于5的数据
		evaluate("#{listData.?[#this<5]}", data);
		// 输出listData小于5的第一条数据
		evaluate("#{listData.^[#this<5]}", data);
		// 输出listData小于5的最后一条数据
		evaluate("#{listData.$[#this<5]}", data);
		// 输出mapData中value小于5的所有数据
		evaluate("#{mapData.?[value<5]}", data);
		// 将mapData的value全部*2之后组成一个list
		evaluate("#{mapData.![value * 2]}", data);
		// ?. 安全导航,如果emptyData为null,那么就返回null,在一些语言比如ts里面就支持这种语法
		evaluate("#{emptyData?.size()}", data);
		
	}

}

输出

com.shura.expression.ExpressionData@533ddba
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
10
{0=0, 1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9}
0
4
[0, 1, 2, 3, 4]
0
4
{0=0, 1=1, 2=2, 3=3, 4=4}
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
null

案例三

执行方法

定义一个工具类,又一个静态方法

如果传入 8000-9000,那么将这个区间的值全部返回

public class Utils {

	public static List<String> parseRange(String ports) {
		String[] split = ports.split("-");


		if (split.length == 2) {
			List<String> list = new ArrayList<>();
			for (int i = Integer.parseInt(split[0]); i <= Integer.parseInt(split[1]); i++) {
				list.add(i + "");
			}
			return list;
		}
		return Collections.singletonList(ports);
	}
}

调用Utils.parseRange

public class ExpressionTest {
    // ... 省略部分 从案例一拷贝
	public static void main(String[] args) {

		evaluate("#{'127.0.0.1,::1'.split(',')}");
		evaluate("#{'127.0.0.1,::1'.split(',').length}");
		evaluate("#{T(com.shura.util.Utils).parseRange('8080-8090')}");
	}
}

输出

[Ljava.lang.String;@67424e82
2
[8080, 8081, 8082, 8083, 8084, 8085, 8086, 8087, 8088, 8089, 8090]

案例四

获取spring中的Bean

准备两个Bean,一个是UserBean一个是UserFactoryBean


@Component
public class UserBean implements UserGenerics {

}

@Component
public class UserFactoryBean implements SmartFactoryBean<UserBean> {
	@Override
	public UserBean getObject() throws Exception {
		return new UserBean();
	}

	@Override
	public Class<?> getObjectType() {
		return UserBean.class;
	}

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

@获取Bean、&获取FactoryBean

测试

public static void main(String[] args) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
    Object evaluate1 = context.getBeanFactory().getBeanExpressionResolver().evaluate("#{@userBean}", new BeanExpressionContext(context.getBeanFactory(), null));
    Object evaluate2 = context.getBeanFactory().getBeanExpressionResolver().evaluate("#{&userFactoryBean}", new BeanExpressionContext(context.getBeanFactory(), null));
    System.out.println(evaluate1);
    System.out.println(evaluate2);
}

输出
com.shura.beans.UserBean@56f4468b
com.shura.beans.UserFactoryBean@6cc4c815

spring的表达式就介绍到这里,大概知道有哪些关键类,表达式大概有哪些可以使用的字符,代表的含义功能是什么等等就说明已经没问题了


欢迎关注,学习不迷路!

你可能感兴趣的:(spring,framework,spring,java,后端)