原文:https://cloud.tencent.com/developer/article/1497676
前言
Spring Expression Language
(簡稱 SpEL)是一個支持查詢和操作運行時對象導航圖功能
的強大的表達式語言。它的語法類似於傳統 EL,但提供額外的功能,最出色的就是函數調用和簡單字符串的模板函數。
這不得不介紹的SpEL的概念。Sp:Spring,EL:Expression Language
。我們熟悉的還有比如JSP中的EL表達式、Struts中的OGNL
等等。那那那既然有了它們,為何還要SpEL呢?
SpEL 創建的初衷是給 Spring 社區提供一種簡單而高效的表達式語言,一種可貫穿整個 Spring 產品組的語言。這種語言的特性基於 Spring 產品的需求而設計,這是它出現的一大特色。
在我們離不開Spring框架的同時,其實我們也已經離不開SpEL了,因為它太好用、太強大了。此處我貼出官網的這張圖:

從圖中可以看出SpEL
的重要,它在Spring家族中如同基石一般的存在。 SpEL是spring-expression
這個jar提供給我們的功能,它從Spring3.x版本開始提供~
備注:
SpEL
並不依附於Spring
容器,它也可以獨立於容器解析。因此,我們在書寫自己的邏輯、框架的時候,也可以借助SpEL
定義支持一些高級表達式~ 需注意一點若看到這么用:#{ systemProperties['user.dir'] }
,我們知道systemProperties
是Spring容器就內置的,至於為何?之前在分析容器原理的的時候有介紹過~ 還有systemEnvironment
等等等等都是可以直接使用的~
關於systemProperties
和systemEnvironment
具體取值可參考:【小家Java】Java環境變量(Env)和系統屬性(Property)詳解—工具文章
閱讀前准備
需要說明是:本文着眼於SpEL原理、源碼層面的剖析,因此閱讀本文之前,我默認小伙伴已經是掌握和可以熟練使用SpEL的的,這里貼出兩個文檔型兄弟博文供以參考: Spring學習總結(四)——表達式語言 Spring Expression Language Spring Expression Language(SpEL) 4 學習筆記
SpEL的使用基本總結如下:
- SpEL 字面量: - 整數:#{8} - 小數:#{8.8} - 科學計數法:#{1e4} - String:可以使用單引號或者雙引號作為字符串的定界符號。 - Boolean:#{true}
- SpEL引用bean , 屬性和方法: - 引用其他對象:#{car} - 引用其他對象的屬性:#{car.brand} - 調用其它方法 , 還可以鏈式操作:#{car.toString()} - 調用靜態方法靜態屬性:#{T(java.lang.Math).PI}
- SpEL支持的運算符號: - 算術運算符:+,-,*,/,%,^(加號還可以用作字符串連接) - 比較運算符:< , > , == , >= , <= , lt , gt , eg , le , ge - 邏輯運算符:and , or , not , | - if-else 運算符(類似三目運算符):?:(temary),
?:(Elvis)
- 正則表達式:#{admin.email matches ‘[a-zA-Z0-9._%±]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}’}
基本原理
為了更好的敘述,以一個簡單例子作為參照:
public static void main(String[] args) { String expressionStr = "1 + 2"; ExpressionParser parpser = new SpelExpressionParser(); //SpelExpressionParser是Spring內部對ExpressionParser的唯一最終實現類 Expression exp = parpser.parseExpression(expressionStr); //把該表達式,解析成一個Expression對象:SpelExpression // 方式一:直接計算 Object value = exp.getValue(); System.out.println(value.toString()); //3 // 若你在@Value中或者xml使用此表達式,請使用#{}包裹~~~~~~~~~~~~~~~~~ System.out.println(parpser.parseExpression("T(System).getProperty('user.dir')").getValue()); //E:\work\remotegitcheckoutproject\myprojects\java\demo-war System.out.println(parpser.parseExpression("T(java.lang.Math).random() * 100.0").getValue()); //27.38227555400853 // 方式二:定義環境變量,在環境內計算拿值 // 環境變量可設置多個值:比如BeanFactoryResolver、PropertyAccessor、TypeLocator等~~~ // 有環境變量,就有能力處理里面的占位符 ${} EvaluationContext context = new StandardEvaluationContext(); System.out.println(exp.getValue(context)); //3 }
任何語言都需要有自己的語法,SpEL當然也不例外。所以我們應該能夠想到,給一個字符串最終解析成一個值,這中間至少得經歷: 字符串 -> 語法分析 -> 生成表達式對象 -> (添加執行上下文) -> 執行此表達式對象 -> 返回結果
關於SpEL
的幾個概念:
- 表達式(“干什么”):
SpEL
的核心,所以表達式語言都是圍繞表達式進行的 - 解析器(“誰來干”):用於將字符串表達式解析為表達式對象
- 上下文(“在哪干”):表達式對象執行的環境,該環境可能定義變量、定義自定義函數、提供類型轉換等等
- root根對象及活動上下文對象(“對誰干”):root根對象是默認的活動上下文對象,活動上下文對象表示了當前表達式操作的對象
這是對於解析一個語言表達式比較基本的一個處理步驟,為了更形象的表達出意思,繪制一幅圖友好展示如下:

步驟解釋:
- 按照SpEL支持的語法結構,寫出一個
expressionStr
- 准備一個表達式解析器
ExpressionParser
,調用方法parseExpression()
對它進行解析。這一步至少完成了如下三件事:- 使用一個專門的斷詞器
Tokenizer
,將給定的表達式字符串拆分為Spring可以認可的數據格式 - 根據斷詞器處理的操作結果生成相應的語法結構
- 在這處理過程之中就需要進行表達式的對錯檢查(語法格式不對要精准報錯出來)
- 使用一個專門的斷詞器
- 將已經處理好后的表達式定義到一個專門的對象
Expression
里,等待結果 - 由於表達式內可能存在占位符變量
${}
,所以還不太適合馬上直接getValue()
(若不需要解析占位符那就直接getValue()也是可以拿到值的)。所以在計算之前還得設置一個表達式上下文對象`EvaluationContext`(這一步步不是必須的) - 替換好占位符內容后,利用表達式對象計算出最終的結果~~~~
相信從這個Demo可以了解到SpEL處理的一個過程邏輯,有處理流程有一個整體的認識了。那么接下來就是要拆分到各個核心組件的內部,一探究竟~
ExpressionParser:表達式解析器
將表達式字符串解析為可計算的已編譯表達式。支持分析模板(Template)
和標准表達式字符串
。 它是一個抽象,並沒有要求具體的語法規則,Spring實現的語法規則是:SpEL語法。
// @since 3.0
public interface ExpressionParser { // 他倆都是把字符串解析成一個Expression對象~~~~ 備注expressionString都是可以被repeated evaluation的 Expression parseExpression(String expressionString) throws ParseException; Expression parseExpression(String expressionString, ParserContext context) throws ParseException; }
此處,ParserContext
:提供給表達式分析器的輸入,它可能影響表達式分析/編譯例程。它會對我們解析表達式字符串的行為影響
ParserContext
public interface ParserContext { // 是否是模版表達式。 比如:#{3 + 4} boolean isTemplate(); // 模版的前綴、后綴 子類是可以定制化的~~~ String getExpressionPrefix(); String getExpressionSuffix(); // 默認提供的實例支持:#{} 的形式 顯然我們可以改變它但我們一般並不需要這么去做~ ParserContext TEMPLATE_EXPRESSION = new ParserContext() { @Override public boolean isTemplate() { return true; } @Override public String getExpressionPrefix() { return "#{"; } @Override public String getExpressionSuffix() { return "}"; } }; }
它只有一個實現類:TemplateParserContext
。(ParserContext.TEMPLATE_EXPRESSION也是該接口的一個內部實現,我們可以直接引用)
關於
StandardBeanExpressionResolver
的內部類實現,也是一個非常基礎的實現。關於@Value的原理文章有提到
public class TemplateParserContext implements ParserContext { private final String expressionPrefix; private final String expressionSuffix; // 默認就是它了~~~ public TemplateParserContext() { this("#{", "}"); } @Override public final boolean isTemplate() { return true; } @Override public final String getExpressionPrefix() { return this.expressionPrefix; } @Override public final String getExpressionSuffix() { return this.expressionSuffix; } }
~ExpressionParser
的繼承樹如下

TemplateAwareExpressionParser
它是一個支持解析模版Template的解析器。
// @since 3.0 它是一個抽象類
public abstract class TemplateAwareExpressionParser implements ExpressionParser { @Override public Expression parseExpression(String expressionString) throws ParseException { return parseExpression(expressionString, null); } @Override public Expression parseExpression(String expressionString, @Nullable ParserContext context) throws ParseException { // 若指定了上下文,並且是模版 就走parseTemplate if (context != null && context.isTemplate()) { return parseTemplate(expressionString, context); } else { // 抽象方法 子類去實現~~~ return doParseExpression(expressionString, context); } } private Expression parseTemplate(String expressionString, ParserContext context) throws ParseException { // 若解析字符串是空串~~~~~ if (expressionString.isEmpty()) { return new LiteralExpression(""); } // 若只有一個模版表達式,直接返回。否則會返回一個CompositeStringExpression,聚合起來的表達式~ Expression[] expressions = parseExpressions(expressionString, context); if (expressions.length == 1) { return expressions[0]; } else { return new CompositeStringExpression(expressionString, expressions); } } // ... parseExpressions的實現邏輯 還是稍顯復雜的~ 因為支持的case太多了~~~ }
它的子類實現有:InternalSpelExpressionParser
和SpelExpressionParser
SpelExpressionParser
SpEL parser
。該實例是可重用的和線程安全的(原因?此處賣個關子,小伙伴可自行想想)
public class SpelExpressionParser extends TemplateAwareExpressionParser { private final SpelParserConfiguration configuration; public SpelExpressionParser() { this.configuration = new SpelParserConfiguration(); } public SpelExpressionParser(SpelParserConfiguration configuration) { Assert.notNull(configuration, "SpelParserConfiguration must not be null"); this.configuration = configuration; } // 最終都是委托給了Spring的內部使用的類:InternalSpelExpressionParser--> 內部的SpEL表達式解析器~~~ public SpelExpression parseRaw(String expressionString) throws ParseException { return doParseExpression(expressionString, null); } // 這里需要注意:因為是new的,所以每次都是一個新對象,所以它是線程安全的~ @Override protected SpelExpression doParseExpression(String expressionString, @Nullable ParserContext context) throws ParseException { return new InternalSpelExpressionParser(this.configuration).doParseExpression(expressionString, context); } }
這里的SpelParserConfiguration
表示:顧名思義它表示SpEL的配置類。在構建SpelExpressionParser時我們可以給其傳遞一個SpelParserConfiguration對象以對SpelExpressionParser進行配置。其可以用於指定在遇到List或Array為null時是否自動new一個對應的實例(一般不建議修改此值~以保持語義統一)
// 它是個public類,因為`StandardBeanExpressionResolver`也使用到了它~~~
public class SpelParserConfiguration { // OFF IMMEDIATE(expressions are compiled as soon as possible) MIXED private static final SpelCompilerMode defaultCompilerMode; static { // 它的值可由`spring.properties`里面的配置改變~~~~ 所以你可以在你的類路徑下放置一個文件,通過`spring.expression.compiler.mode=IMMEDIATE`來控制編譯行為 String compilerMode = SpringProperties.getProperty("spring.expression.compiler.mode"); defaultCompilerMode = (compilerMode != null ? SpelCompilerMode.valueOf(compilerMode.toUpperCase()) : SpelCompilerMode.OFF); } // 調用者若沒指定,會使用上面的默認的~ private final SpelCompilerMode compilerMode; @Nullable private final ClassLoader compilerClassLoader; // 碰到為null的,是否給自動new一個對象,比如new String(),new ArrayList()等等~ private final boolean autoGrowNullReferences; // 專門針對於集合是否new private final boolean autoGrowCollections; // 集合能夠自動增長到的最大值~~~~ private final int maximumAutoGrowSize; // 省略get/set方法~~~后面會給一個自定義配置的示例~~~ }
InternalSpelExpressionParser
上面知道SpelExpressionParser
最終都是委托它里做的,並且configuration也交給它,然后調用doParseExpression
方法處理~
// 它是Spring內部使用的類~
class InternalSpelExpressionParser extends TemplateAwareExpressionParser { private static final Pattern VALID_QUALIFIED_ID_PATTERN = Pattern.compile("[\\p{L}\\p{N}_$]+"); private final SpelParserConfiguration configuration; //SpEL的配置 // 此處用一個雙端隊列 來保存表達式的每一個節點,每個節點都是一個SpelNode 該對象記錄着位置、子節點、父節點等等~~~ private final Deque<SpelNodeImpl> constructedNodes = new ArrayDeque<>(); private String expressionString = ""; // 帶解析的表達式字符串~ // Token流:token保存着符號類型(如int(,]+=?>=等等各種符號 非常之多) 然后記錄着它startPos和endPos private List<Token> tokenStream = Collections.emptyList(); // length of a populated token stream private int tokenStreamLength; // Current location in the token stream when processing tokens private int tokenStreamPointer; // 唯一的一個構造函數~ public InternalSpelExpressionParser(SpelParserConfiguration configuration) { this.configuration = configuration; } @Override protected SpelExpression doParseExpression(String expressionString, @Nullable ParserContext context) throws ParseException { try { this.expressionString = expressionString; // Tokenizer就是分詞器。把待解析的表達式交給它分詞~~~ Tokenizer tokenizer = new Tokenizer(expressionString); // process處理,得到tokenStream 並且記錄上它的總長度 並且標記當前處理點為0 this.tokenStream = tokenizer.process(); this.tokenStreamLength = this.tokenStream.size(); this.tokenStreamPointer = 0; this.constructedNodes.clear(); // 顯然把當前節點清空~~ SpelNodeImpl ast = eatExpression(); Assert.state(ast != null, "No node"); Token t = peekToken(); if (t != null) { throw new SpelParseException(t.startPos, SpelMessage.MORE_INPUT, toString(nextToken())); } Assert.isTrue(this.constructedNodes.isEmpty(), "At least one node expected"); // 最終:每一個SpelNodeImpl 它就是一個SpelExpression表達式,但會出去。\ // 此時:只是把我們的字符串解析成為一個SpelExpression,還沒有參與賦值、計算哦~~~~ return new SpelExpression(expressionString, ast, this.configuration); } catch (InternalParseException ex) { throw ex.getCause(); } } ... // 解析表達式的邏輯非常的復雜,Spring團隊老牛逼了,竟然支持到了這么多的功能~~~~ }
這么一來,我們的ExpressionParser
就算解釋完成了。絕大部分情況下我們最終都是使用了SpelExpressionParser
去解析標准的語言表達式。
但是,但是,但是我們上面也說了,它還支持Template
模式,下面以一個Demo加深了解:
SpEL對Template模式支持
public static void main(String[] args) { String greetingExp = "Hello, #{#user} ---> #{T(System).getProperty('user.home')}"; ExpressionParser parser = new SpelExpressionParser(); EvaluationContext context = new StandardEvaluationContext(); context.setVariable("user", "fsx"); Expression expression = parser.parseExpression(greetingExp, new TemplateParserContext()); System.out.println(expression.getValue(context, String.class)); //Hello, fsx ---> C:\Users\fangshixiang }
這個功能就有點像加強版的字符串格式化了。它的執行步驟描述如下:
- 創建一個模板表達式,所謂模板就是帶字面量和表達式的字符串。其中#{}表示表達式的起止。上面的
#user
是表達式字符串,表示引用一個變量(注意這個寫法,有兩個#號) - 解析字符串。其實SpEL框架的抽象是與具體實現無關的,只是我們這里使用的都是
SpelExpressionParser
- 通過
evaluationContext.setVariable
可以在上下文中設定變量。 - 使用
Expression.getValue()
獲取表達式的值,這里傳入了Evalution上下文,第二個參數是類型參數,表示返回值的類型。
只有
Template
模式的時候,才需要#{},不然SpEL就是里面的內容即可,如1+2就是一個SpEL 至於@Value
為何需要#{spel表示是內容}
這樣包裹着,是因為它是這樣的expr = this.expressionParser.parseExpression(value, this.beanExpressionParserContext);
,也就是說它最終是parseTemplate()
這個去解析的~~~~ 如果parse的時候傳的context是null啥的,就不會解析外層#{}了
Expression
表示的是表達式對象。能夠根據上下文對象對自身進行計算的表達式。
封裝以前分析的表達式字符串的詳細信息。
// @since 3.0 表達式計算的通用抽象。 該接口提供的方法非常非常之多~~~ 但不要怕大部分都是重載的~~~
public interface Expression { // 返回原始表達式的字符串~~~ String getExpressionString(); // 使用一個默認的標准的context執行計算~~~ @Nullable Object getValue() throws EvaluationException; // SpEL內部幫你轉換~~~ 使用的是默認的context @Nullable <T> T getValue(@Nullable Class<T> desiredResultType) throws EvaluationException; // 根據指定的根對象計算此表達式 @Nullable Object getValue(Object rootObject) throws EvaluationException; @Nullable <T> T getValue(Object rootObject, @Nullable Class<T> desiredResultType) throws EvaluationException; // 根據指定的上下文:EvaluationContext來計算值~~~ rootObject:跟對象 @Nullable Object getValue(EvaluationContext context) throws EvaluationException; // 以rootObject作為表達式的root對象來計算表達式的值。 // root對象:比如parser.parseExpression("name").getValue(person);相當於去person里拿到name屬性的值。這個person就叫root對象 @Nullable Object getValue(EvaluationContext context, Object rootObject) throws EvaluationException; @Nullable <T> T getValue(EvaluationContext context, @Nullable Class<T> desiredResultType) throws EvaluationException; @Nullable <T> T getValue(EvaluationContext context, Object rootObject, @Nullable Class<T> desiredResultType) throws EvaluationException; // 返回可傳遞給@link setvalue的最一般類型 @Nullable Class<?> getValueType() throws EvaluationException; @Nullable Class<?> getValueType(Object rootObject) throws EvaluationException; @Nullable Class<?> getValueType(EvaluationContext context) throws EvaluationException; @Nullable Class<?> getValueType(EvaluationContext context, Object rootObject) throws EvaluationException; @Nullable TypeDescriptor getValueTypeDescriptor() throws EvaluationException; @Nullable TypeDescriptor getValueTypeDescriptor(Object rootObject) throws EvaluationException; @Nullable TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws EvaluationException; @Nullable TypeDescriptor getValueTypeDescriptor(EvaluationContext context, Object rootObject) throws EvaluationException; // 確定是否可以寫入表達式,即可以調用setValue() boolean isWritable(Object rootObject) throws EvaluationException; boolean isWritable(EvaluationContext context) throws EvaluationException; boolean isWritable(EvaluationContext context, Object rootObject) throws EvaluationException; // 在提供的上下文中將此表達式設置為提供的值。 void setValue(Object rootObject, @Nullable Object value) throws EvaluationException; void setValue(EvaluationContext context, @Nullable Object value) throws EvaluationException; void setValue(EvaluationContext context, Object rootObject, @Nullable Object value) throws EvaluationException; }
它的繼承樹如下:

SpelExpression
這個是我們核心,甚至也是目前SpEL的唯一實現。 表達式可以獨立計算,也可以在指定的上下文中計算。
在表達式計算期間,可能會要求上下文解析:對類型、bean、屬性和方法的引用。
public class SpelExpression implements Expression { // 在編譯表達式之前解釋該表達式的次數。 private static final int INTERPRETED_COUNT_THRESHOLD = 100; // 放棄前嘗試編譯表達式的次數 private static final int FAILED_ATTEMPTS_THRESHOLD = 100; private final String expression; // AST:抽象語法樹~ private final SpelNodeImpl ast; // SpelNodeImpl的實現類非常非常之多~~~ private final SpelParserConfiguration configuration; @Nullable private EvaluationContext evaluationContext; // 如果沒有指定,就會用默認的上下文 new StandardEvaluationContext() // 如果該表達式已經被編譯了,就會放在這里 @since 4.1 Spring內部並沒有它的實現類 尷尬~~~編譯是要交給我們自己實現??? @Nullable private CompiledExpression compiledAst; // 表達式被解釋的次數-達到某個限制時可以觸發編譯 private volatile int interpretedCount = 0; // 編譯嘗試和失敗的次數——使我們最終放棄了在似乎不可能編譯時嘗試編譯它的嘗試。 private volatile int failedAttempts = 0; // 唯一構造函數~ public SpelExpression(String expression, SpelNodeImpl ast, SpelParserConfiguration configuration) { this.expression = expression; this.ast = ast; this.configuration = configuration; } ... // 若沒有指定,這里會使用默認的StandardEvaluationContext上下文~ public EvaluationContext getEvaluationContext() { if (this.evaluationContext == null) { this.evaluationContext = new StandardEvaluationContext(); } return this.evaluationContext; } ... @Override @Nullable public Object getValue() throws EvaluationException { // 如果已經被編譯過,就直接從編譯后的里getValue即可~~~~ if (this.compiledAst != null) { try { EvaluationContext context = getEvaluationContext(); return this.compiledAst.getValue(context.getRootObject().getValue(), context); } catch (Throwable ex) { // If running in mixed mode, revert to interpreted if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) { this.interpretedCount = 0; this.compiledAst = null; } else { throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION); } } } ExpressionState expressionState = new ExpressionState(getEvaluationContext(), this.configuration); // 比如此處SeEl是加法+,所以ast為:OpPlus語法樹去處理的~~~ Object result = this.ast.getValue(expressionState); // 檢查是否需要編譯它~~~ checkCompile(expressionState); return result; } ... // 備注:數據轉換都是EvaluationContext.getTypeConverter() 來進行轉換 // 注意:此處的TypeConverter為`org.springframework.expression`的 只有一個實現類:StandardTypeConverter // 它內部都是委托給ConversionService去做的,具體是`DefaultConversionService`去做的~ @Override @Nullable public Class<?> getValueType() throws EvaluationException { return getValueType(getEvaluationContext()); } @Override @Nullable public Class<?> getValueType(Object rootObject) throws EvaluationException { return getValueType(getEvaluationContext(), rootObject); } @Override @Nullable public Class<?> getValueType(EvaluationContext context